mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: button storybook
This commit is contained in:
@@ -117,6 +117,18 @@ module.exports = runESMImports().then(() => defineConfig([
|
|||||||
'no-undef': 'error'
|
'no-undef': 'error'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Storybook config files use CommonJS with __dirname and module.exports
|
||||||
|
files: ['packages/bruno-app/storybook/**/*.js'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-undef': 'error'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
files: ['packages/bruno-cli/**/*.js'],
|
files: ['packages/bruno-cli/**/*.js'],
|
||||||
ignores: ['**/*.config.js'],
|
ignores: ['**/*.config.js'],
|
||||||
|
|||||||
2211
package-lock.json
generated
2211
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,11 @@
|
|||||||
],
|
],
|
||||||
"homepage": "https://usebruno.com",
|
"homepage": "https://usebruno.com",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@storybook/addon-webpack5-compiler-babel": "^4.0.0",
|
||||||
|
"@storybook/builder-webpack5": "^10.1.10",
|
||||||
|
"@storybook/react": "^10.1.10",
|
||||||
|
"@storybook/react-webpack5": "^10.1.10",
|
||||||
|
"storybook": "^10.1.10",
|
||||||
"@eslint/compat": "^1.3.2",
|
"@eslint/compat": "^1.3.2",
|
||||||
"@faker-js/faker": "^7.6.0",
|
"@faker-js/faker": "^7.6.0",
|
||||||
"@jest/globals": "^29.2.0",
|
"@jest/globals": "^29.2.0",
|
||||||
|
|||||||
1
packages/bruno-app/.gitignore
vendored
1
packages/bruno-app/.gitignore
vendored
@@ -22,6 +22,7 @@ build
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
*.log
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
"preview": "rsbuild preview",
|
"preview": "rsbuild preview",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
|
"test:prettier": "prettier --check \"./src/**/*.{js,jsx,json,ts,tsx}\"",
|
||||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
|
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\"",
|
||||||
|
"storybook": "storybook dev -p 6006 --config-dir storybook",
|
||||||
|
"build-storybook": "storybook build --config-dir storybook"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/inter": "^5.0.15",
|
"@fontsource/inter": "^5.0.15",
|
||||||
@@ -63,6 +65,7 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pdfjs-dist": "4.4.168",
|
"pdfjs-dist": "4.4.168",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
|
"polished": "^4.3.1",
|
||||||
"posthog-node": "4.2.1",
|
"posthog-node": "4.2.1",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
|
|||||||
@@ -74,26 +74,6 @@ const Wrapper = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add-param {
|
|
||||||
font-size: 12px;
|
|
||||||
color: ${(props) => props.theme.textLink};
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 7px 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: ${(props) => props.theme.sidebar.collection.item.indentBorder};
|
|
||||||
background: transparent;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: ${(props) => props.theme.listItem.hoverBg};
|
|
||||||
border-color: ${(props) => props.theme.textLink};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-mod {
|
.tooltip-mod {
|
||||||
font-size: 11px !important;
|
font-size: 11px !important;
|
||||||
max-width: 200px !important;
|
max-width: 200px !important;
|
||||||
@@ -123,65 +103,11 @@ const Wrapper = styled.div`
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 4px;
|
|
||||||
color: ${(props) => props.theme.colors.text.muted};
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: color 0.15s ease, background 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
padding: 12px 2px;
|
|
||||||
background: ${(props) => props.theme.bg};
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit {
|
|
||||||
padding: 6px 16px;
|
|
||||||
font-size: ${(props) => props.theme.font.size.sm};
|
|
||||||
border-radius: ${(props) => props.theme.border.radius.base};
|
|
||||||
border: none;
|
|
||||||
background: ${(props) => props.theme.brand};
|
|
||||||
color: ${(props) => props.theme.bg};
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.reset {
|
|
||||||
background: transparent;
|
|
||||||
padding: 6px 16px;
|
|
||||||
color: ${(props) => props.theme.brand};
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.discard {
|
|
||||||
padding: 6px 16px;
|
|
||||||
font-size: ${(props) => props.theme.font.size.sm};
|
|
||||||
border-radius: ${(props) => props.theme.border.radius.base};
|
|
||||||
background: transparent;
|
|
||||||
color: ${(props) => props.theme.text};
|
|
||||||
border: ${(props) => props.theme.sidebar.collection.item.indentBorder};
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
} from 'providers/ReduxStore/slices/global-environments';
|
} from 'providers/ReduxStore/slices/global-environments';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import { getGlobalEnvironmentVariables } from 'utils/collections';
|
import { getGlobalEnvironmentVariables } from 'utils/collections';
|
||||||
|
import Button from 'ui/Button';
|
||||||
|
|
||||||
const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
|
const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -425,14 +426,14 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="button-container">
|
<div className="button-container mt-5">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center gap-2">
|
||||||
<button type="button" className="submit" onClick={handleSave} data-testid="save-env">
|
<Button type="submit" size="sm" onClick={handleSave} data-testid="save-env">
|
||||||
Save
|
Save
|
||||||
</button>
|
</Button>
|
||||||
<button type="button" className="submit reset ml-2" onClick={handleReset} data-testid="reset-env">
|
<Button type="reset" size="sm" color="secondary" variant="ghost" onClick={handleReset} data-testid="reset-env">
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ const colors = {
|
|||||||
BRAND: '#d9a342',
|
BRAND: '#d9a342',
|
||||||
TEXT: '#d4d4d4',
|
TEXT: '#d4d4d4',
|
||||||
TEXT_MUTED: '#858585',
|
TEXT_MUTED: '#858585',
|
||||||
TEXT_LINK: '#569cd6',
|
TEXT_LINK: '#8BB7E0',
|
||||||
BG: '#1e1e1e',
|
BG: '#1e1e1e',
|
||||||
|
|
||||||
GREEN: '#4ec9b0',
|
GREEN: '#4ec9b0',
|
||||||
YELLOW: '#d9a342',
|
YELLOW: '#d9a342',
|
||||||
|
WHITE: '#fff',
|
||||||
|
BLACK: '#000',
|
||||||
|
|
||||||
GRAY_1: '#252526',
|
GRAY_1: '#252526',
|
||||||
GRAY_2: '#3D3D3D',
|
GRAY_2: '#3D3D3D',
|
||||||
@@ -343,6 +345,30 @@ const darkTheme = {
|
|||||||
border: '#dc3545'
|
border: '#dc3545'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
button2: {
|
||||||
|
color: {
|
||||||
|
primary: {
|
||||||
|
bg: colors.BRAND,
|
||||||
|
text: colors.BLACK
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
bg: colors.GRAY_4,
|
||||||
|
text: '#fff'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
bg: '#059669',
|
||||||
|
text: '#fff'
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
bg: '#f59e0b',
|
||||||
|
text: '#1e1e1e'
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
bg: '#f43f5e',
|
||||||
|
text: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
tabs: {
|
tabs: {
|
||||||
marginRight: '1.2rem',
|
marginRight: '1.2rem',
|
||||||
|
|||||||
@@ -348,7 +348,30 @@ const lightTheme = {
|
|||||||
border: '#dc3545'
|
border: '#dc3545'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
button2: {
|
||||||
|
color: {
|
||||||
|
primary: {
|
||||||
|
bg: colors.BRAND,
|
||||||
|
text: '#fff'
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
bg: '#e5e7eb',
|
||||||
|
text: colors.TEXT
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
bg: '#4f9a7d',
|
||||||
|
text: '#fff'
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
bg: '#c98b2b',
|
||||||
|
text: '#fff'
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
bg: '#d14f5b',
|
||||||
|
text: '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
tabs: {
|
tabs: {
|
||||||
marginRight: '1.2rem',
|
marginRight: '1.2rem',
|
||||||
active: {
|
active: {
|
||||||
|
|||||||
430
packages/bruno-app/src/ui/Button/Button.stories.jsx
Normal file
430
packages/bruno-app/src/ui/Button/Button.stories.jsx
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Button from './index';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Button',
|
||||||
|
component: Button,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered'
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['xs', 'sm', 'base', 'md', 'lg'],
|
||||||
|
description: 'The size of the button'
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['filled', 'outline', 'ghost'],
|
||||||
|
description: 'The visual style variant of the button'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['primary', 'secondary', 'success', 'warning', 'danger'],
|
||||||
|
description: 'The color of the button'
|
||||||
|
},
|
||||||
|
fontWeight: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['regular', 'medium'],
|
||||||
|
description: 'Font weight (default: regular for filled/ghost, medium for outline)'
|
||||||
|
},
|
||||||
|
rounded: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['sm', 'md', 'full'],
|
||||||
|
description: 'Border radius style'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Whether the button is disabled'
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Whether the button is in loading state'
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Whether the button takes full width'
|
||||||
|
},
|
||||||
|
iconPosition: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['left', 'right'],
|
||||||
|
description: 'Position of the icon relative to text'
|
||||||
|
},
|
||||||
|
children: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Button text content'
|
||||||
|
},
|
||||||
|
onClick: { action: 'clicked' },
|
||||||
|
onDoubleClick: { action: 'double-clicked' },
|
||||||
|
onMouseEnter: { action: 'mouse-entered' },
|
||||||
|
onMouseLeave: { action: 'mouse-left' }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sample icon component for stories
|
||||||
|
const PlusIcon = () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SendIcon = () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<line x1="22" y1="2" x2="11" y2="13"></line>
|
||||||
|
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const TrashIcon = () => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="3 6 5 6 21 6"></polyline>
|
||||||
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default story
|
||||||
|
export const Default = {
|
||||||
|
args: {
|
||||||
|
children: 'Button'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Variants
|
||||||
|
export const Filled = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
|
<Button variant="filled" color="primary">Primary</Button>
|
||||||
|
<Button variant="filled" color="secondary">Secondary</Button>
|
||||||
|
<Button variant="filled" color="success">Success</Button>
|
||||||
|
<Button variant="filled" color="warning">Warning</Button>
|
||||||
|
<Button variant="filled" color="danger">Danger</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Outline = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
|
<Button variant="outline" color="primary">Primary</Button>
|
||||||
|
<Button variant="outline" color="secondary">Secondary</Button>
|
||||||
|
<Button variant="outline" color="success">Success</Button>
|
||||||
|
<Button variant="outline" color="warning">Warning</Button>
|
||||||
|
<Button variant="outline" color="danger">Danger</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Ghost = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
|
<Button variant="ghost" color="primary">Primary</Button>
|
||||||
|
<Button variant="ghost" color="secondary">Secondary</Button>
|
||||||
|
<Button variant="ghost" color="success">Success</Button>
|
||||||
|
<Button variant="ghost" color="warning">Warning</Button>
|
||||||
|
<Button variant="ghost" color="danger">Danger</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// With Icons
|
||||||
|
export const WithIconLeft = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Filled</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="filled" size="xs" icon={<PlusIcon />} iconPosition="left">Add</Button>
|
||||||
|
<Button variant="filled" size="sm" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="filled" size="base" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="filled" size="md" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="filled" size="lg" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Outline</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="outline" size="xs" icon={<PlusIcon />} iconPosition="left">Add</Button>
|
||||||
|
<Button variant="outline" size="sm" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="outline" size="base" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="outline" size="md" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="outline" size="lg" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Ghost</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="ghost" size="xs" icon={<PlusIcon />} iconPosition="left">Add</Button>
|
||||||
|
<Button variant="ghost" size="sm" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="ghost" size="base" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="ghost" size="md" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
<Button variant="ghost" size="lg" icon={<PlusIcon />} iconPosition="left">Add Item</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithIconRight = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Filled</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="filled" size="xs" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="filled" size="sm" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="filled" size="base" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="filled" size="md" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="filled" size="lg" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Outline</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="outline" size="xs" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="outline" size="sm" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="outline" size="base" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="outline" size="md" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="outline" size="lg" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Ghost</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="ghost" size="xs" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="ghost" size="sm" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="ghost" size="base" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="ghost" size="md" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
<Button variant="ghost" size="lg" icon={<SendIcon />} iconPosition="right">Send</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IconOnly = {
|
||||||
|
args: {
|
||||||
|
icon: <PlusIcon />,
|
||||||
|
rounded: 'full',
|
||||||
|
size: 'base'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// States
|
||||||
|
export const Disabled = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: '48px' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
|
<h3 style={{ marginBottom: '4px', fontSize: '14px', fontWeight: 600 }}>Enabled</h3>
|
||||||
|
<Button variant="filled" color="primary">Primary</Button>
|
||||||
|
<Button variant="filled" color="secondary">Secondary</Button>
|
||||||
|
<Button variant="filled" color="success">Success</Button>
|
||||||
|
<Button variant="filled" color="warning">Warning</Button>
|
||||||
|
<Button variant="filled" color="danger">Danger</Button>
|
||||||
|
<Button variant="outline" color="primary">Outline</Button>
|
||||||
|
<Button variant="ghost" color="primary">Ghost</Button>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
|
<h3 style={{ marginBottom: '4px', fontSize: '14px', fontWeight: 600 }}>Disabled</h3>
|
||||||
|
<Button variant="filled" color="primary" disabled>Primary</Button>
|
||||||
|
<Button variant="filled" color="secondary" disabled>Secondary</Button>
|
||||||
|
<Button variant="filled" color="success" disabled>Success</Button>
|
||||||
|
<Button variant="filled" color="warning" disabled>Warning</Button>
|
||||||
|
<Button variant="filled" color="danger" disabled>Danger</Button>
|
||||||
|
<Button variant="outline" color="primary" disabled>Outline</Button>
|
||||||
|
<Button variant="ghost" color="primary" disabled>Ghost</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Filled</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="filled" size="xs" loading>Loading</Button>
|
||||||
|
<Button variant="filled" size="sm" loading>Loading</Button>
|
||||||
|
<Button variant="filled" size="base" loading>Loading</Button>
|
||||||
|
<Button variant="filled" size="md" loading>Loading</Button>
|
||||||
|
<Button variant="filled" size="lg" loading>Loading</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Outline</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="outline" size="xs" loading>Loading</Button>
|
||||||
|
<Button variant="outline" size="sm" loading>Loading</Button>
|
||||||
|
<Button variant="outline" size="base" loading>Loading</Button>
|
||||||
|
<Button variant="outline" size="md" loading>Loading</Button>
|
||||||
|
<Button variant="outline" size="lg" loading>Loading</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Ghost</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="ghost" size="xs" loading>Loading</Button>
|
||||||
|
<Button variant="ghost" size="sm" loading>Loading</Button>
|
||||||
|
<Button variant="ghost" size="base" loading>Loading</Button>
|
||||||
|
<Button variant="ghost" size="md" loading>Loading</Button>
|
||||||
|
<Button variant="ghost" size="lg" loading>Loading</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rounded variants
|
||||||
|
export const RoundedSmall = {
|
||||||
|
args: {
|
||||||
|
children: 'Small Radius',
|
||||||
|
rounded: 'sm'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RoundedMedium = {
|
||||||
|
args: {
|
||||||
|
children: 'Medium Radius',
|
||||||
|
rounded: 'md'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RoundedFull = {
|
||||||
|
args: {
|
||||||
|
children: 'Pill Button',
|
||||||
|
rounded: 'full'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Full Width
|
||||||
|
export const FullWidth = {
|
||||||
|
args: {
|
||||||
|
children: 'Full Width Button',
|
||||||
|
fullWidth: true
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div style={{ width: '300px' }}>
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combined Examples
|
||||||
|
export const DangerWithIcon = {
|
||||||
|
args: {
|
||||||
|
children: 'Delete',
|
||||||
|
variant: 'filled',
|
||||||
|
color: 'danger',
|
||||||
|
icon: <TrashIcon />
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// All Colors Showcase
|
||||||
|
export const AllColors = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Filled Variant</h3>
|
||||||
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
|
<Button variant="filled" color="primary">Primary</Button>
|
||||||
|
<Button variant="filled" color="secondary">Secondary</Button>
|
||||||
|
<Button variant="filled" color="success">Success</Button>
|
||||||
|
<Button variant="filled" color="warning">Warning</Button>
|
||||||
|
<Button variant="filled" color="danger">Danger</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Outline Variant</h3>
|
||||||
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
|
<Button variant="outline" color="primary">Primary</Button>
|
||||||
|
<Button variant="outline" color="secondary">Secondary</Button>
|
||||||
|
<Button variant="outline" color="success">Success</Button>
|
||||||
|
<Button variant="outline" color="warning">Warning</Button>
|
||||||
|
<Button variant="outline" color="danger">Danger</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Ghost Variant</h3>
|
||||||
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap' }}>
|
||||||
|
<Button variant="ghost" color="primary">Primary</Button>
|
||||||
|
<Button variant="ghost" color="secondary">Secondary</Button>
|
||||||
|
<Button variant="ghost" color="success">Success</Button>
|
||||||
|
<Button variant="ghost" color="warning">Warning</Button>
|
||||||
|
<Button variant="ghost" color="danger">Danger</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// All Sizes Showcase
|
||||||
|
export const AllSizes = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Filled</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="filled" size="xs">Extra Small</Button>
|
||||||
|
<Button variant="filled" size="sm">Small</Button>
|
||||||
|
<Button variant="filled" size="base">Base</Button>
|
||||||
|
<Button variant="filled" size="md">Medium</Button>
|
||||||
|
<Button variant="filled" size="lg">Large</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Outline</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="outline" size="xs">Extra Small</Button>
|
||||||
|
<Button variant="outline" size="sm">Small</Button>
|
||||||
|
<Button variant="outline" size="base">Base</Button>
|
||||||
|
<Button variant="outline" size="md">Medium</Button>
|
||||||
|
<Button variant="outline" size="lg">Large</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 style={{ marginBottom: '12px', fontSize: '14px', fontWeight: 600 }}>Ghost</h3>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||||||
|
<Button variant="ghost" size="xs">Extra Small</Button>
|
||||||
|
<Button variant="ghost" size="sm">Small</Button>
|
||||||
|
<Button variant="ghost" size="base">Base</Button>
|
||||||
|
<Button variant="ghost" size="md">Medium</Button>
|
||||||
|
<Button variant="ghost" size="lg">Large</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
274
packages/bruno-app/src/ui/Button/StyledWrapper.js
Normal file
274
packages/bruno-app/src/ui/Button/StyledWrapper.js
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
import styled, { css, keyframes } from 'styled-components';
|
||||||
|
import { darken, rgba } from 'polished';
|
||||||
|
|
||||||
|
const spin = keyframes`
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const sizeStyles = {
|
||||||
|
xs: css`
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: ${(props) => props.theme.font.size.xs};
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-icon {
|
||||||
|
width: 0.75rem;
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
sm: css`
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: ${(props) => props.theme.font.size.sm};
|
||||||
|
gap: 0.375rem;
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
width: 0.875rem;
|
||||||
|
height: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-icon {
|
||||||
|
width: 0.875rem;
|
||||||
|
height: 0.875rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
base: css`
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: ${(props) => props.theme.font.size.base};
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-icon {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
md: css`
|
||||||
|
padding: 0.625rem 1.125rem;
|
||||||
|
font-size: ${(props) => props.theme.font.size.md};
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-icon {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
lg: css`
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: ${(props) => props.theme.font.size.md};
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
width: 1.125rem;
|
||||||
|
height: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-icon {
|
||||||
|
width: 1.125rem;
|
||||||
|
height: 1.125rem;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
const roundedStyles = {
|
||||||
|
sm: css`
|
||||||
|
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||||
|
`,
|
||||||
|
base: css`
|
||||||
|
border-radius: ${(props) => props.theme.border.radius.base};
|
||||||
|
`,
|
||||||
|
md: css`
|
||||||
|
border-radius: ${(props) => props.theme.border.radius.md};
|
||||||
|
`,
|
||||||
|
lg: css`
|
||||||
|
border-radius: ${(props) => props.theme.border.radius.lg};
|
||||||
|
`,
|
||||||
|
full: css`
|
||||||
|
border-radius: 9999px;
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
const fontWeightStyles = {
|
||||||
|
regular: 400,
|
||||||
|
medium: 500
|
||||||
|
};
|
||||||
|
|
||||||
|
// For secondary, use text color for outline/ghost; for others, use bg
|
||||||
|
const getDisplayColor = (colorConfig, colorName) => {
|
||||||
|
return colorName === 'secondary' ? colorConfig.text : colorConfig.bg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVariantStyles = (variant, color) => {
|
||||||
|
if (variant === 'filled') {
|
||||||
|
return css`
|
||||||
|
background-color: ${(props) => props.theme.button2.color[color].bg};
|
||||||
|
color: ${(props) => props.theme.button2.color[color].text};
|
||||||
|
border: 1px solid ${(props) => props.theme.button2.color[color].bg};
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: ${(props) => props.theme.button2.color[color].text} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
${(props) => {
|
||||||
|
const bg = props.theme.button2.color[color].bg;
|
||||||
|
return css`
|
||||||
|
background-color: ${darken(0.03, bg)};
|
||||||
|
border-color: ${darken(0.03, bg)};
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
${(props) => {
|
||||||
|
const bg = props.theme.button2.color[color].bg;
|
||||||
|
return css`
|
||||||
|
background-color: ${darken(0.07, bg)};
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'outline') {
|
||||||
|
return css`
|
||||||
|
background-color: transparent;
|
||||||
|
color: ${(props) => getDisplayColor(props.theme.button2.color[color], color)};
|
||||||
|
border: 1px solid ${(props) => getDisplayColor(props.theme.button2.color[color], color)};
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
${(props) => {
|
||||||
|
const displayColor = getDisplayColor(props.theme.button2.color[color], color);
|
||||||
|
return css`
|
||||||
|
background-color: ${rgba(displayColor, 0.05)};
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
${(props) => {
|
||||||
|
const displayColor = getDisplayColor(props.theme.button2.color[color], color);
|
||||||
|
return css`
|
||||||
|
background-color: ${rgba(displayColor, 0.1)};
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'ghost') {
|
||||||
|
return css`
|
||||||
|
background-color: transparent;
|
||||||
|
color: ${(props) => getDisplayColor(props.theme.button2.color[color], color)};
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
${(props) => {
|
||||||
|
const displayColor = getDisplayColor(props.theme.button2.color[color], color);
|
||||||
|
return css`
|
||||||
|
background-color: ${rgba(displayColor, 0.1)};
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
${(props) => {
|
||||||
|
const displayColor = getDisplayColor(props.theme.button2.color[color], color);
|
||||||
|
return css`
|
||||||
|
background-color: ${rgba(displayColor, 0.15)};
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return css``;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
display: ${(props) => (props.$fullWidth ? 'block' : 'inline-block')};
|
||||||
|
width: ${(props) => (props.$fullWidth ? '100%' : 'auto')};
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: ${(props) => (props.$fullWidth ? '100%' : 'auto')};
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: ${(props) => fontWeightStyles[props.$fontWeight] || 400};
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
outline: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
${(props) => sizeStyles[props.$size]}
|
||||||
|
${(props) => roundedStyles[props.$rounded]}
|
||||||
|
${(props) => getVariantStyles(props.$variant, props.$color)}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
${(props) => {
|
||||||
|
const colorConfig = props.theme.button2.color[props.$color];
|
||||||
|
const focusColor = props.$color === 'secondary' ? colorConfig.text : colorConfig.bg;
|
||||||
|
return css`
|
||||||
|
box-shadow: 0 0 0 2px ${rgba(focusColor, 0.4)};
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-content {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-spinner {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.spinner-icon {
|
||||||
|
animation: ${spin} 0.75s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
||||||
81
packages/bruno-app/src/ui/Button/index.js
Normal file
81
packages/bruno-app/src/ui/Button/index.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Button = ({
|
||||||
|
children,
|
||||||
|
size = 'base',
|
||||||
|
variant = 'filled',
|
||||||
|
color = 'primary',
|
||||||
|
disabled = false,
|
||||||
|
loading = false,
|
||||||
|
icon,
|
||||||
|
iconPosition = 'left',
|
||||||
|
fullWidth = false,
|
||||||
|
type = 'button',
|
||||||
|
rounded = 'sm',
|
||||||
|
fontWeight,
|
||||||
|
onClick,
|
||||||
|
onDoubleClick,
|
||||||
|
className = '',
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const handleClick = (e) => {
|
||||||
|
if (disabled || loading) return;
|
||||||
|
onClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDoubleClick = (e) => {
|
||||||
|
if (disabled || loading) return;
|
||||||
|
onDoubleClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper
|
||||||
|
$size={size}
|
||||||
|
$variant={variant}
|
||||||
|
$color={color}
|
||||||
|
$disabled={disabled}
|
||||||
|
$loading={loading}
|
||||||
|
$fullWidth={fullWidth}
|
||||||
|
$rounded={rounded}
|
||||||
|
$fontWeight={fontWeight}
|
||||||
|
$hasIcon={!!icon}
|
||||||
|
$iconPosition={iconPosition}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type={type}
|
||||||
|
disabled={disabled || loading}
|
||||||
|
onClick={handleClick}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{loading && (
|
||||||
|
<span className="button-spinner">
|
||||||
|
<svg className="spinner-icon" viewBox="0 0 24 24">
|
||||||
|
<circle
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="3"
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray="31.4 31.4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{icon && iconPosition === 'left' && !loading && (
|
||||||
|
<span className="button-icon button-icon-left">{icon}</span>
|
||||||
|
)}
|
||||||
|
{children && <span className="button-content">{children}</span>}
|
||||||
|
{icon && iconPosition === 'right' && !loading && (
|
||||||
|
<span className="button-icon button-icon-right">{icon}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
||||||
35
packages/bruno-app/storybook/main.js
Normal file
35
packages/bruno-app/storybook/main.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/** @type { import('@storybook/react-webpack5').StorybookConfig } */
|
||||||
|
const config = {
|
||||||
|
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-webpack5-compiler-babel'
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-webpack5',
|
||||||
|
options: {}
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
autodocs: true
|
||||||
|
},
|
||||||
|
webpackFinal: async (config) => {
|
||||||
|
// Add path aliases to match jsconfig.json
|
||||||
|
config.resolve.alias = {
|
||||||
|
...config.resolve.alias,
|
||||||
|
assets: path.resolve(__dirname, '../src/assets'),
|
||||||
|
ui: path.resolve(__dirname, '../src/ui'),
|
||||||
|
components: path.resolve(__dirname, '../src/components'),
|
||||||
|
hooks: path.resolve(__dirname, '../src/hooks'),
|
||||||
|
themes: path.resolve(__dirname, '../src/themes'),
|
||||||
|
api: path.resolve(__dirname, '../src/api'),
|
||||||
|
pageComponents: path.resolve(__dirname, '../src/pageComponents'),
|
||||||
|
providers: path.resolve(__dirname, '../src/providers'),
|
||||||
|
utils: path.resolve(__dirname, '../src/utils')
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
73
packages/bruno-app/storybook/preview.jsx
Normal file
73
packages/bruno-app/storybook/preview.jsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ThemeProvider as SCThemeProvider, createGlobalStyle } from 'styled-components';
|
||||||
|
import themes from 'themes/index';
|
||||||
|
import '@fontsource/inter/400.css';
|
||||||
|
import '@fontsource/inter/500.css';
|
||||||
|
import '@fontsource/inter/600.css';
|
||||||
|
import '@fontsource/inter/700.css';
|
||||||
|
|
||||||
|
const GlobalStyle = createGlobalStyle`
|
||||||
|
* {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/** @type { import('@storybook/react').Preview } */
|
||||||
|
const preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgrounds: {
|
||||||
|
default: 'light',
|
||||||
|
values: [
|
||||||
|
{ name: 'light', value: '#ffffff' },
|
||||||
|
{ name: 'dark', value: '#1e1e1e' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
globalTypes: {
|
||||||
|
theme: {
|
||||||
|
description: 'Global theme for components',
|
||||||
|
toolbar: {
|
||||||
|
title: 'Theme',
|
||||||
|
icon: 'paintbrush',
|
||||||
|
items: [
|
||||||
|
{ value: 'light', title: 'Light' },
|
||||||
|
{ value: 'dark', title: 'Dark' }
|
||||||
|
],
|
||||||
|
dynamicTitle: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initialGlobals: {
|
||||||
|
theme: 'light'
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story, context) => {
|
||||||
|
const themeName = context.globals.theme || 'light';
|
||||||
|
const theme = themes[themeName];
|
||||||
|
|
||||||
|
// Update background and text color based on theme
|
||||||
|
const isDark = themeName === 'dark';
|
||||||
|
const backgroundColor = isDark ? '#1e1e1e' : '#ffffff';
|
||||||
|
const textColor = isDark ? '#d4d4d4' : '#333333';
|
||||||
|
document.body.style.backgroundColor = backgroundColor;
|
||||||
|
document.body.style.color = textColor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SCThemeProvider theme={theme}>
|
||||||
|
<GlobalStyle />
|
||||||
|
<div style={{ padding: '1rem', color: textColor }}>
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
</SCThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
Reference in New Issue
Block a user