@scalepad/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai/rules/date-handling.md +39 -0
- package/.ai/rules/figma-design-system.md +372 -0
- package/.ai/rules/figma-lm-design-system-keys.md +680 -0
- package/.ai/rules/file-extensions.md +13 -0
- package/.ai/rules/modal-confirmation-mutation.md +56 -0
- package/.ai/rules/react-hooks.md +29 -0
- package/.ai/rules/styling.md +83 -0
- package/AGENTS.md +37 -0
- package/README.md +125 -0
- package/figma.config.json +9 -0
- package/package.json +127 -0
- package/scripts/install-ai-rules.mjs +136 -0
- package/src/ThemeProvider.tsx +57 -0
- package/src/charts.ts +32 -0
- package/src/components/ActionCard/ActionCard.css.ts +60 -0
- package/src/components/ActionCard/ActionCard.tsx +154 -0
- package/src/components/ActionCard/index.ts +2 -0
- package/src/components/Anchor/Anchor.tsx +47 -0
- package/src/components/Anchor/index.ts +2 -0
- package/src/components/AppliedFiltersManagerBar/AppliedFiltersManagerBar.tsx +105 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.css.ts +23 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.tsx +50 -0
- package/src/components/AppliedFiltersManagerBar/index.ts +5 -0
- package/src/components/Badge/Badge.css.ts +72 -0
- package/src/components/Badge/Badge.figma.tsx +43 -0
- package/src/components/Badge/Badge.tsx +159 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/BreadCrumb/BreadCrumb.tsx +62 -0
- package/src/components/BreadCrumb/index.ts +2 -0
- package/src/components/BulkActionBar/BulkActionBar.css.ts +26 -0
- package/src/components/BulkActionBar/BulkActionBar.tsx +164 -0
- package/src/components/BulkActionBar/index.ts +2 -0
- package/src/components/Button/Button.css.ts +272 -0
- package/src/components/Button/Button.figma.tsx +74 -0
- package/src/components/Button/Button.tsx +84 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Charts/ChartTooltip.figma.tsx +33 -0
- package/src/components/Charts/ChartTooltip.tsx +101 -0
- package/src/components/Charts/MiniBarSparkline.tsx +75 -0
- package/src/components/Charts/StackedPatternBarChart.tsx +494 -0
- package/src/components/Charts/TrendAreaChart.css.ts +23 -0
- package/src/components/Charts/TrendAreaChart.tsx +210 -0
- package/src/components/Charts/index.ts +12 -0
- package/src/components/CodePanel/CodePanel.css.ts +113 -0
- package/src/components/CodePanel/CodePanel.tsx +121 -0
- package/src/components/CodePanel/index.ts +2 -0
- package/src/components/CommentComposer/CommentComposer.css.ts +60 -0
- package/src/components/CommentComposer/CommentComposer.tsx +181 -0
- package/src/components/CommentComposer/index.ts +2 -0
- package/src/components/ConfirmationModal/ConfirmationModal.tsx +149 -0
- package/src/components/ConfirmationModal/index.ts +2 -0
- package/src/components/ConfirmationTooltip/ConfirmationTooltip.tsx +132 -0
- package/src/components/ConfirmationTooltip/index.ts +2 -0
- package/src/components/DataDialog.figma.tsx +33 -0
- package/src/components/DataDialog.tsx +46 -0
- package/src/components/DataTable/DataTable.tsx +1042 -0
- package/src/components/DataTable/RowExpandToggle.tsx +105 -0
- package/src/components/DataTable/RowGroupHeader.tsx +190 -0
- package/src/components/DataTable/createActionsColumn.tsx +86 -0
- package/src/components/DataTable/index.ts +25 -0
- package/src/components/DatePicker/CustomRangePicker.tsx +59 -0
- package/src/components/DatePicker/DateInput.tsx +329 -0
- package/src/components/DatePicker/DateNavigator.tsx +486 -0
- package/src/components/DatePicker/DatePicker.tsx +242 -0
- package/src/components/DatePicker/MonthlyRangePicker.tsx +231 -0
- package/src/components/DatePicker/QuarterlyRangePicker.tsx +224 -0
- package/src/components/DatePicker/QuickPicksSidebar.tsx +242 -0
- package/src/components/DatePicker/YearlyRangePicker.tsx +171 -0
- package/src/components/DatePicker/index.ts +7 -0
- package/src/components/DatePicker/types.ts +12 -0
- package/src/components/DesignSystemPrimitives/FluidGrid.tsx +44 -0
- package/src/components/DesignSystemPrimitives/InteractivePrimitives.tsx +177 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.tsx +220 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.types.tsx +15 -0
- package/src/components/DesignSystemPrimitives/SurfacePrimitives.tsx +46 -0
- package/src/components/DesignSystemPrimitives/index.ts +55 -0
- package/src/components/Details/Details.css.ts +74 -0
- package/src/components/Details/Details.tsx +140 -0
- package/src/components/Details/index.ts +2 -0
- package/src/components/DownloadCard/DownloadCard.css.ts +22 -0
- package/src/components/DownloadCard/DownloadCard.tsx +63 -0
- package/src/components/DownloadCard/index.ts +2 -0
- package/src/components/Drawer/Drawer.css.ts +32 -0
- package/src/components/Drawer/Drawer.tsx +236 -0
- package/src/components/Drawer/hooks/useDetailDrawer.ts +61 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigation.ts +125 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigationContext.ts +66 -0
- package/src/components/EditableRichText/EditableRichText.css.ts +72 -0
- package/src/components/EditableRichText/EditableRichText.tsx +324 -0
- package/src/components/EditableRichText/index.ts +2 -0
- package/src/components/EditableSelect/EditableSelect.css.ts +62 -0
- package/src/components/EditableSelect/EditableSelect.tsx +224 -0
- package/src/components/EditableSelect/index.ts +2 -0
- package/src/components/EditableText/EditableText.tsx +377 -0
- package/src/components/EditableText/index.ts +2 -0
- package/src/components/EmptyState/EmptyState.figma.tsx +33 -0
- package/src/components/EmptyState/EmptyState.tsx +230 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/ErrorBoundary.tsx +135 -0
- package/src/components/ErrorState/ErrorState.tsx +197 -0
- package/src/components/ErrorState/index.ts +2 -0
- package/src/components/FeatureCard.tsx +42 -0
- package/src/components/FilterMenu/FilterMenu.figma.tsx +30 -0
- package/src/components/FilterMenu/FilterMenu.tsx +198 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/BooleanFilterSubmenu.tsx +46 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/SearchableFilterSubmenu.tsx +239 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/index.ts +8 -0
- package/src/components/FilterMenu/defaultFilterSchemas.ts +63 -0
- package/src/components/FilterMenu/helpers.ts +115 -0
- package/src/components/FilterMenu/index.ts +35 -0
- package/src/components/FilterMenu/types.ts +101 -0
- package/src/components/IconButton/IconButton.css.ts +272 -0
- package/src/components/IconButton/IconButton.figma.tsx +47 -0
- package/src/components/IconButton/IconButton.tsx +72 -0
- package/src/components/IconButton/README.md +230 -0
- package/src/components/IconButton/index.ts +2 -0
- package/src/components/InfiniteScrollSentinel.tsx +86 -0
- package/src/components/InfiniteScrollTrigger.tsx +78 -0
- package/src/components/InfoCard.figma.tsx +47 -0
- package/src/components/InfoCard.tsx +216 -0
- package/src/components/KbdHint/KbdHint.tsx +23 -0
- package/src/components/KbdHint/index.ts +2 -0
- package/src/components/LabeledField/LabeledField.tsx +21 -0
- package/src/components/LabeledField/index.ts +2 -0
- package/src/components/LookupSelect/LookupSelect.css.ts +149 -0
- package/src/components/LookupSelect/LookupSelect.tsx +325 -0
- package/src/components/LookupSelect/index.ts +2 -0
- package/src/components/Menu/Menu.css.ts +89 -0
- package/src/components/Menu/Menu.tsx +105 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/MessageBox/MessageBox.tsx +168 -0
- package/src/components/MessageBox/index.ts +2 -0
- package/src/components/MetricDisplay/MetricDisplay.tsx +55 -0
- package/src/components/MetricDisplay/index.ts +1 -0
- package/src/components/MultiSelect/MultiSelect.tsx +278 -0
- package/src/components/MultiSelect/index.ts +2 -0
- package/src/components/Notifications/Notifications.tsx +12 -0
- package/src/components/Notifications/README.md +93 -0
- package/src/components/Notifications/index.ts +4 -0
- package/src/components/Notifications/showToast.tsx +100 -0
- package/src/components/PropertyRow/PropertyRow.tsx +96 -0
- package/src/components/PropertyRow/index.ts +2 -0
- package/src/components/RadioTile/RadioTile.tsx +253 -0
- package/src/components/RadioTile/index.ts +2 -0
- package/src/components/RichText/FormattingToolbar.css.ts +69 -0
- package/src/components/RichText/FormattingToolbar.tsx +112 -0
- package/src/components/RichText/RichTextInline.css.ts +54 -0
- package/src/components/RichText/RichTextInline.tsx +318 -0
- package/src/components/RichText/formattingCommands.ts +181 -0
- package/src/components/RichText/formattingTypes.ts +34 -0
- package/src/components/RichText/index.ts +49 -0
- package/src/components/RichText/richTextExtensions.ts +111 -0
- package/src/components/RichText/richTextHelpers.ts +65 -0
- package/src/components/RichText/richTextImage.ts +253 -0
- package/src/components/RichText/richTextImageHandlers.ts +244 -0
- package/src/components/RichText/richTextProse.css.ts +261 -0
- package/src/components/RichTextEditor/RichTextEditor.css.ts +82 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +204 -0
- package/src/components/RichTextEditor/index.ts +2 -0
- package/src/components/RichTextView/RichTextView.css.ts +11 -0
- package/src/components/RichTextView/RichTextView.tsx +114 -0
- package/src/components/RichTextView/index.ts +2 -0
- package/src/components/Schedule/Schedule.tsx +35 -0
- package/src/components/SchedulePicker/SchedulePicker.css.ts +42 -0
- package/src/components/SchedulePicker/SchedulePicker.tsx +130 -0
- package/src/components/SchedulePicker/index.ts +2 -0
- package/src/components/SearchableList/types.ts +30 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.css.ts +25 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.tsx +139 -0
- package/src/components/SearchableSubMenu/index.ts +2 -0
- package/src/components/Select/README.md +114 -0
- package/src/components/Select/Select.css.ts +110 -0
- package/src/components/Select/Select.tsx +133 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/SelectCreatable/SelectCreatable.css.ts +16 -0
- package/src/components/SelectCreatable/SelectCreatable.tsx +203 -0
- package/src/components/SelectCreatable/index.ts +2 -0
- package/src/components/SettingsCard/SettingsCard.tsx +98 -0
- package/src/components/SettingsCard/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.css.ts +91 -0
- package/src/components/Sidebar/Sidebar.tsx +129 -0
- package/src/components/Sidebar/index.ts +5 -0
- package/src/components/SimpleList/SimpleList.css.ts +12 -0
- package/src/components/SimpleList/SimpleList.tsx +44 -0
- package/src/components/SimpleList/index.ts +2 -0
- package/src/components/SimpleTable/SimpleTable.tsx +296 -0
- package/src/components/SimpleTable/index.ts +2 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.css.ts +62 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.tsx +85 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.css.ts +124 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.tsx +168 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.css.ts +81 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.tsx +538 -0
- package/src/components/SlashRichTextEditor/SlashSuggestionExtension.ts +48 -0
- package/src/components/SlashRichTextEditor/index.ts +13 -0
- package/src/components/SlashRichTextEditor/types.ts +48 -0
- package/src/components/StatCard/StatCard.css.ts +70 -0
- package/src/components/StatCard/StatCard.tsx +201 -0
- package/src/components/StatCard/index.ts +1 -0
- package/src/components/StatusBadge/StatusBadge.tsx +70 -0
- package/src/components/StatusBadge/index.ts +2 -0
- package/src/components/StatusIndicator/StatusIndicator.tsx +67 -0
- package/src/components/StatusIndicator/index.ts +6 -0
- package/src/components/SubNavigation/SubNavigation.css.ts +72 -0
- package/src/components/SubNavigation/SubNavigation.tsx +104 -0
- package/src/components/SubNavigation/index.ts +2 -0
- package/src/components/SuspenseLoader.tsx +22 -0
- package/src/components/Table/SortableColumnHeader.tsx +99 -0
- package/src/components/Table/TableSkeletonRows.figma.tsx +22 -0
- package/src/components/Table/TableSkeletonRows.tsx +113 -0
- package/src/components/Table/index.ts +9 -0
- package/src/components/TableActionsMenu.tsx +58 -0
- package/src/components/TableCard.tsx +29 -0
- package/src/components/TableContainer/TableContainer.tsx +86 -0
- package/src/components/TableContainer/index.ts +2 -0
- package/src/components/TableControlBar/TableControlBar.tsx +156 -0
- package/src/components/TableControlBar/TableSelectionButton.tsx +57 -0
- package/src/components/TableControlBar/index.ts +13 -0
- package/src/components/TableControlBar/useTableControlBar.tsx +314 -0
- package/src/components/TableSelection/TableSelection.tsx +43 -0
- package/src/components/TableSelection/index.ts +5 -0
- package/src/components/Tabs/README.md +76 -0
- package/src/components/Tabs/Tabs.css.ts +54 -0
- package/src/components/Tabs/Tabs.figma.tsx +47 -0
- package/src/components/Tabs/Tabs.tsx +96 -0
- package/src/components/Tabs/index.ts +8 -0
- package/src/components/TextInput/README.md +98 -0
- package/src/components/TextInput/SearchTextInput.figma.tsx +22 -0
- package/src/components/TextInput/SearchTextInput.tsx +150 -0
- package/src/components/TextInput/TextInput.figma.tsx +44 -0
- package/src/components/TextInput/TextInput.tsx +42 -0
- package/src/components/TextInput/index.ts +4 -0
- package/src/components/ThemeSwitcher.figma.tsx +28 -0
- package/src/components/ThemeSwitcher.tsx +69 -0
- package/src/components/TrendBadge/TrendBadge.tsx +76 -0
- package/src/components/TrendBadge/index.ts +2 -0
- package/src/components/TruncatedText.tsx +115 -0
- package/src/components/Typography/Text.tsx +74 -0
- package/src/components/Typography/Title.tsx +100 -0
- package/src/components/Typography/index.ts +4 -0
- package/src/geist-fonts.ts +48 -0
- package/src/hooks/index.ts +31 -0
- package/src/hooks/useFilters.ts +152 -0
- package/src/hooks/useInfiniteScroll.ts +62 -0
- package/src/hooks/usePlatform.ts +33 -0
- package/src/hooks/useServerTable.ts +495 -0
- package/src/hooks/useTableSelection.ts +102 -0
- package/src/hooks/useTableSort.ts +259 -0
- package/src/index.ts +483 -0
- package/src/mantine.ts +25 -0
- package/src/theme/mantineVars.ts +12 -0
- package/src/theme/themeContract.css.ts +131 -0
- package/src/theme/themeVars.ts +31 -0
- package/src/theme.ts +168 -0
- package/src/tokens/color-types.ts +107 -0
- package/src/tokens/colors.ts +243 -0
- package/src/tokens/index.ts +14 -0
- package/src/tokens/radius.ts +17 -0
- package/src/tokens/semantic-colors.ts +224 -0
- package/src/tokens/semantic-tokens-css.ts +53 -0
- package/src/tokens/shadows.ts +11 -0
- package/src/tokens/spacing.ts +20 -0
- package/src/tokens/text-styles.ts +179 -0
- package/src/tokens/typography.ts +40 -0
- package/src/tokens/zIndex.ts +27 -0
- package/src/types/mantine-theme.d.ts +17 -0
- package/src/types/tanstack-table.d.ts +22 -0
- package/src/utils/avatar.ts +150 -0
- package/src/utils/chartHelpers.ts +53 -0
- package/src/utils/color-props.ts +77 -0
- package/src/utils/createDesignComponent.tsx +104 -0
- package/src/utils/nestFlatRows.ts +111 -0
- package/src/utils/withStaticComponents.ts +6 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconButton component styles – vanilla-extract with semantic design tokens
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { style } from '@vanilla-extract/css';
|
|
6
|
+
|
|
7
|
+
import { mantineVars } from '../../theme/mantineVars';
|
|
8
|
+
import { tokens } from '../../theme/themeContract.css';
|
|
9
|
+
|
|
10
|
+
const focusRing = {
|
|
11
|
+
outline: `2px solid ${tokens.color.stroke.focusStrong}`,
|
|
12
|
+
outlineOffset: 3,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const outlineBase = style({
|
|
16
|
+
backgroundColor: tokens.color.background.default,
|
|
17
|
+
border: `1px solid ${tokens.color.stroke.default}`,
|
|
18
|
+
color: tokens.color.text.default,
|
|
19
|
+
transition: 'all 150ms ease',
|
|
20
|
+
selectors: {
|
|
21
|
+
'&:focus-visible': focusRing,
|
|
22
|
+
'&:hover': {
|
|
23
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
24
|
+
borderColor: tokens.color.stroke.default,
|
|
25
|
+
color: tokens.color.text.primaryDefault,
|
|
26
|
+
},
|
|
27
|
+
'&:active': {
|
|
28
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
29
|
+
color: tokens.color.text.primaryDefault,
|
|
30
|
+
},
|
|
31
|
+
'&:disabled': {
|
|
32
|
+
backgroundColor: tokens.color.background.default,
|
|
33
|
+
borderColor: tokens.color.stroke.default,
|
|
34
|
+
color: tokens.color.text.disabledDefault,
|
|
35
|
+
opacity: 0.6,
|
|
36
|
+
cursor: 'not-allowed',
|
|
37
|
+
boxShadow: 'none',
|
|
38
|
+
},
|
|
39
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
40
|
+
backgroundColor: tokens.color.background.default,
|
|
41
|
+
borderColor: tokens.color.stroke.default,
|
|
42
|
+
color: tokens.color.text.default,
|
|
43
|
+
},
|
|
44
|
+
[`${mantineVars.darkSelector} &:hover`]: {
|
|
45
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
46
|
+
borderColor: tokens.color.stroke.default,
|
|
47
|
+
},
|
|
48
|
+
[`${mantineVars.darkSelector} &:active`]: {
|
|
49
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
50
|
+
},
|
|
51
|
+
[`${mantineVars.darkSelector} &:disabled`]: {
|
|
52
|
+
backgroundColor: tokens.color.background.default,
|
|
53
|
+
borderColor: tokens.color.stroke.default,
|
|
54
|
+
color: tokens.color.text.disabledDefault,
|
|
55
|
+
opacity: 0.6,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const outlineIconButton = outlineBase;
|
|
61
|
+
|
|
62
|
+
export const outlineInverseIconButton = style({
|
|
63
|
+
backgroundColor: tokens.color.background.transparent,
|
|
64
|
+
borderColor: tokens.color.stroke.inverse,
|
|
65
|
+
color: tokens.color.text.inverse,
|
|
66
|
+
transition: 'all 150ms ease',
|
|
67
|
+
selectors: {
|
|
68
|
+
'&:focus-visible': focusRing,
|
|
69
|
+
'&:hover': {
|
|
70
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
71
|
+
borderColor: tokens.color.stroke.inverse,
|
|
72
|
+
color: tokens.color.text.primaryDefault,
|
|
73
|
+
},
|
|
74
|
+
'&:disabled': {
|
|
75
|
+
borderColor: tokens.color.stroke.inverse,
|
|
76
|
+
color: tokens.color.text.disabledDefault,
|
|
77
|
+
opacity: 0.6,
|
|
78
|
+
cursor: 'not-allowed',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const secondaryIconButton = style({
|
|
84
|
+
backgroundColor: tokens.color.background.subduedLight,
|
|
85
|
+
color: tokens.color.text.default,
|
|
86
|
+
transition: 'all 150ms ease',
|
|
87
|
+
border: `1px solid ${tokens.color.stroke.default}`,
|
|
88
|
+
selectors: {
|
|
89
|
+
'&:focus-visible': focusRing,
|
|
90
|
+
'&:hover': {
|
|
91
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
92
|
+
color: tokens.color.text.primaryDefault,
|
|
93
|
+
},
|
|
94
|
+
'&:disabled': {
|
|
95
|
+
backgroundColor: tokens.color.background.subduedLight,
|
|
96
|
+
color: tokens.color.text.disabledDefault,
|
|
97
|
+
borderColor: tokens.color.stroke.default,
|
|
98
|
+
opacity: 0.6,
|
|
99
|
+
cursor: 'not-allowed',
|
|
100
|
+
},
|
|
101
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
102
|
+
backgroundColor: tokens.color.background.subduedLight,
|
|
103
|
+
color: tokens.color.text.default,
|
|
104
|
+
borderColor: tokens.color.stroke.default,
|
|
105
|
+
},
|
|
106
|
+
[`${mantineVars.darkSelector} &:hover`]: {
|
|
107
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
108
|
+
color: tokens.color.text.primaryDefault,
|
|
109
|
+
},
|
|
110
|
+
[`${mantineVars.darkSelector} &:disabled`]: {
|
|
111
|
+
backgroundColor: tokens.color.background.subduedLight,
|
|
112
|
+
color: tokens.color.text.disabledDefault,
|
|
113
|
+
borderColor: tokens.color.stroke.default,
|
|
114
|
+
opacity: 0.6,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export const tertiaryIconButton = style({
|
|
120
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
121
|
+
color: tokens.color.text.primaryDefault,
|
|
122
|
+
transition: 'all 150ms ease',
|
|
123
|
+
border: 'none',
|
|
124
|
+
selectors: {
|
|
125
|
+
'&:focus-visible': focusRing,
|
|
126
|
+
'&:hover': {
|
|
127
|
+
backgroundColor: tokens.color.background.primaryLightHover,
|
|
128
|
+
},
|
|
129
|
+
'&:disabled': {
|
|
130
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
131
|
+
color: tokens.color.text.disabledDefault,
|
|
132
|
+
opacity: 0.6,
|
|
133
|
+
cursor: 'not-allowed',
|
|
134
|
+
},
|
|
135
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
136
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
137
|
+
color: tokens.color.text.primaryDefault,
|
|
138
|
+
},
|
|
139
|
+
[`${mantineVars.darkSelector} &:hover`]: {
|
|
140
|
+
backgroundColor: tokens.color.background.primaryLightHover,
|
|
141
|
+
},
|
|
142
|
+
[`${mantineVars.darkSelector} &:disabled`]: {
|
|
143
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
144
|
+
color: tokens.color.text.disabledDefault,
|
|
145
|
+
opacity: 0.6,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export const destructiveIconButton = style({
|
|
151
|
+
backgroundColor: tokens.color.background.dangerFilled,
|
|
152
|
+
color: tokens.color.text.inverse,
|
|
153
|
+
transition: 'all 150ms ease',
|
|
154
|
+
border: 'none',
|
|
155
|
+
selectors: {
|
|
156
|
+
'&:focus-visible': focusRing,
|
|
157
|
+
'&:hover': {
|
|
158
|
+
backgroundColor: tokens.color.background.dangerFilledHover,
|
|
159
|
+
},
|
|
160
|
+
'&:disabled': {
|
|
161
|
+
backgroundColor: tokens.color.background.disabledDefault,
|
|
162
|
+
color: tokens.color.text.disabledDefault,
|
|
163
|
+
opacity: 0.6,
|
|
164
|
+
cursor: 'not-allowed',
|
|
165
|
+
},
|
|
166
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
167
|
+
backgroundColor: tokens.color.background.dangerFilled,
|
|
168
|
+
color: tokens.color.text.inverse,
|
|
169
|
+
},
|
|
170
|
+
[`${mantineVars.darkSelector} &:hover`]: {
|
|
171
|
+
backgroundColor: tokens.color.background.dangerFilledHover,
|
|
172
|
+
},
|
|
173
|
+
[`${mantineVars.darkSelector} &:disabled`]: {
|
|
174
|
+
backgroundColor: tokens.color.background.disabledDefault,
|
|
175
|
+
color: tokens.color.text.disabledDefault,
|
|
176
|
+
opacity: 0.6,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export const ghostIconButton = style({
|
|
182
|
+
backgroundColor: 'transparent',
|
|
183
|
+
color: tokens.color.text.default,
|
|
184
|
+
transition: 'all 150ms ease',
|
|
185
|
+
border: 'none',
|
|
186
|
+
selectors: {
|
|
187
|
+
'&:focus-visible': focusRing,
|
|
188
|
+
'&:hover': {
|
|
189
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
190
|
+
color: tokens.color.text.primaryDefault,
|
|
191
|
+
},
|
|
192
|
+
'&:disabled': {
|
|
193
|
+
backgroundColor: 'transparent',
|
|
194
|
+
color: tokens.color.text.disabledDefault,
|
|
195
|
+
opacity: 0.6,
|
|
196
|
+
cursor: 'not-allowed',
|
|
197
|
+
},
|
|
198
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
199
|
+
color: tokens.color.text.default,
|
|
200
|
+
},
|
|
201
|
+
[`${mantineVars.darkSelector} &:hover`]: {
|
|
202
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
203
|
+
color: tokens.color.text.primaryDefault,
|
|
204
|
+
},
|
|
205
|
+
[`${mantineVars.darkSelector} &:disabled`]: {
|
|
206
|
+
color: tokens.color.text.disabledDefault,
|
|
207
|
+
opacity: 0.6,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export const filledIconButton = style({
|
|
213
|
+
backgroundColor: tokens.color.background.primaryFilled,
|
|
214
|
+
color: tokens.color.text.inverse,
|
|
215
|
+
transition: 'all 150ms ease',
|
|
216
|
+
border: 'none',
|
|
217
|
+
selectors: {
|
|
218
|
+
'&:focus-visible': focusRing,
|
|
219
|
+
'&:hover': {
|
|
220
|
+
backgroundColor: tokens.color.background.primaryFilledHover,
|
|
221
|
+
},
|
|
222
|
+
'&:disabled': {
|
|
223
|
+
backgroundColor: tokens.color.background.disabledDefault,
|
|
224
|
+
color: tokens.color.text.disabledDefault,
|
|
225
|
+
opacity: 0.6,
|
|
226
|
+
cursor: 'not-allowed',
|
|
227
|
+
},
|
|
228
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
229
|
+
backgroundColor: tokens.color.background.primaryFilled,
|
|
230
|
+
color: tokens.color.text.inverse,
|
|
231
|
+
},
|
|
232
|
+
[`${mantineVars.darkSelector} &:hover`]: {
|
|
233
|
+
backgroundColor: tokens.color.background.primaryFilledHover,
|
|
234
|
+
},
|
|
235
|
+
[`${mantineVars.darkSelector} &:disabled`]: {
|
|
236
|
+
backgroundColor: tokens.color.background.disabledDefault,
|
|
237
|
+
color: tokens.color.text.disabledDefault,
|
|
238
|
+
opacity: 0.6,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
export const ghostMutedIconButton = style({
|
|
244
|
+
backgroundColor: tokens.color.background.transparent,
|
|
245
|
+
color: tokens.color.text.subduedStrong,
|
|
246
|
+
transition: 'all 150ms ease',
|
|
247
|
+
border: 'none',
|
|
248
|
+
selectors: {
|
|
249
|
+
'&:focus-visible': focusRing,
|
|
250
|
+
'&:hover': {
|
|
251
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
252
|
+
color: tokens.color.text.primaryDefault,
|
|
253
|
+
},
|
|
254
|
+
'&:disabled': {
|
|
255
|
+
backgroundColor: tokens.color.background.transparent,
|
|
256
|
+
color: tokens.color.text.disabledDefault,
|
|
257
|
+
opacity: 0.4,
|
|
258
|
+
cursor: 'not-allowed',
|
|
259
|
+
},
|
|
260
|
+
[`${mantineVars.darkSelector} &`]: {
|
|
261
|
+
color: tokens.color.text.subduedStrong,
|
|
262
|
+
},
|
|
263
|
+
[`${mantineVars.darkSelector} &:hover`]: {
|
|
264
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
265
|
+
color: tokens.color.text.primaryDefault,
|
|
266
|
+
},
|
|
267
|
+
[`${mantineVars.darkSelector} &:disabled`]: {
|
|
268
|
+
color: tokens.color.text.disabledDefault,
|
|
269
|
+
opacity: 0.4,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import figma from '@figma/code-connect';
|
|
2
|
+
|
|
3
|
+
import { IconButton } from './IconButton';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* -- This file was auto-generated by Code Connect --
|
|
7
|
+
* None of your props could be automatically mapped to Figma properties.
|
|
8
|
+
* You should update the `props` object to include a mapping from your
|
|
9
|
+
* code props to Figma properties, and update the `example` function to
|
|
10
|
+
* return the code example you'd like to see in Figma
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
figma.connect(
|
|
14
|
+
IconButton,
|
|
15
|
+
'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=9%3A775',
|
|
16
|
+
{
|
|
17
|
+
props: {
|
|
18
|
+
// No matching props could be found for these Figma properties:
|
|
19
|
+
// "leftIcon": figma.instance('⮑ Left icon'),
|
|
20
|
+
// "roundness": figma.enum('Roundness', {
|
|
21
|
+
// "Default": "default",
|
|
22
|
+
// "Round": "round"
|
|
23
|
+
// }),
|
|
24
|
+
// "variant": figma.enum('Variant', {
|
|
25
|
+
// "Primary": "primary",
|
|
26
|
+
// "Secondary": "secondary",
|
|
27
|
+
// "Outline": "outline",
|
|
28
|
+
// "Ghost": "ghost",
|
|
29
|
+
// "Ghost Muted": "ghost-muted",
|
|
30
|
+
// "Destructive": "destructive"
|
|
31
|
+
// }),
|
|
32
|
+
// "size": figma.enum('Size', {
|
|
33
|
+
// "Regular": "regular",
|
|
34
|
+
// "Large": "large",
|
|
35
|
+
// "Small": "small",
|
|
36
|
+
// "Mini": "mini"
|
|
37
|
+
// }),
|
|
38
|
+
// "state": figma.enum('State', {
|
|
39
|
+
// "Default": "default",
|
|
40
|
+
// "Hover & Active": "hover---active",
|
|
41
|
+
// "Focus": "focus",
|
|
42
|
+
// "Disabled": "disabled"
|
|
43
|
+
// })
|
|
44
|
+
},
|
|
45
|
+
example: _props => <IconButton aria-label="Add item">+</IconButton>,
|
|
46
|
+
},
|
|
47
|
+
);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createPolymorphicComponent,
|
|
5
|
+
ActionIcon as MantineActionIcon,
|
|
6
|
+
type ActionIconProps as MantineActionIconProps,
|
|
7
|
+
} from '@mantine/core';
|
|
8
|
+
|
|
9
|
+
import * as iconButtonStyles from './IconButton.css';
|
|
10
|
+
|
|
11
|
+
import type { ButtonProps } from '../Button';
|
|
12
|
+
|
|
13
|
+
/** IconButton variant: design system (matches Button) or legacy 'outline'/'filled' for backward compat */
|
|
14
|
+
export type IconButtonVariant = ButtonProps['variant'];
|
|
15
|
+
|
|
16
|
+
export interface IconButtonProps extends MantineActionIconProps {
|
|
17
|
+
/** IconButton variant from Figma design system */
|
|
18
|
+
variant?: IconButtonVariant;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const IconButtonBase = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
22
|
+
({ variant = 'outline', className, ...props }, ref) => {
|
|
23
|
+
let mantineVariant: 'outline' | 'subtle' | 'filled' = 'outline';
|
|
24
|
+
let mantineColor = props.color;
|
|
25
|
+
let customClassName = className;
|
|
26
|
+
|
|
27
|
+
if (variant === 'primary') {
|
|
28
|
+
mantineVariant = 'filled';
|
|
29
|
+
mantineColor = 'dark';
|
|
30
|
+
customClassName = `${iconButtonStyles.filledIconButton}${className ? ` ${className}` : ''}`;
|
|
31
|
+
} else if (variant === 'secondary') {
|
|
32
|
+
mantineVariant = 'filled';
|
|
33
|
+
customClassName = `${iconButtonStyles.secondaryIconButton}${className ? ` ${className}` : ''}`;
|
|
34
|
+
} else if (variant === 'tertiary') {
|
|
35
|
+
mantineVariant = 'filled';
|
|
36
|
+
customClassName = `${iconButtonStyles.tertiaryIconButton}${className ? ` ${className}` : ''}`;
|
|
37
|
+
} else if (variant === 'destructive') {
|
|
38
|
+
mantineVariant = 'filled';
|
|
39
|
+
customClassName = `${iconButtonStyles.destructiveIconButton}${className ? ` ${className}` : ''}`;
|
|
40
|
+
} else if (variant === 'outline') {
|
|
41
|
+
mantineVariant = 'outline';
|
|
42
|
+
customClassName = `${iconButtonStyles.outlineIconButton}${className ? ` ${className}` : ''}`;
|
|
43
|
+
} else if (variant === 'outline-inverse') {
|
|
44
|
+
mantineVariant = 'outline';
|
|
45
|
+
mantineColor = 'white';
|
|
46
|
+
customClassName = `${iconButtonStyles.outlineInverseIconButton}${className ? ` ${className}` : ''}`;
|
|
47
|
+
} else if (variant === 'ghost') {
|
|
48
|
+
mantineVariant = 'subtle';
|
|
49
|
+
customClassName = `${iconButtonStyles.ghostIconButton}${className ? ` ${className}` : ''}`;
|
|
50
|
+
} else if (variant === 'ghost-muted') {
|
|
51
|
+
mantineVariant = 'subtle';
|
|
52
|
+
customClassName = `${iconButtonStyles.ghostMutedIconButton}${className ? ` ${className}` : ''}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<MantineActionIcon
|
|
57
|
+
ref={ref}
|
|
58
|
+
variant={mantineVariant}
|
|
59
|
+
color={mantineColor}
|
|
60
|
+
className={customClassName}
|
|
61
|
+
{...props}
|
|
62
|
+
radius={props.radius ?? 'lg'}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
IconButtonBase.displayName = 'IconButton';
|
|
69
|
+
|
|
70
|
+
export const IconButton = createPolymorphicComponent<'button', IconButtonProps>(
|
|
71
|
+
IconButtonBase,
|
|
72
|
+
);
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# IconButton Component
|
|
2
|
+
|
|
3
|
+
A reusable icon button component that matches the Figma design system specifications. This component wraps Mantine's `ActionIcon` with custom styling that includes shadows, proper colors, hover states, and focus rings.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Shadow Support**: Includes box-shadow on outline variant (matches Figma design)
|
|
8
|
+
- ✅ **Design System Colors**: Uses semantic color tokens from Figma
|
|
9
|
+
- ✅ **Three Variants**: outline (default), ghost, and filled
|
|
10
|
+
- ✅ **Hover States**: Proper hover and active states
|
|
11
|
+
- ✅ **Focus Visible**: Keyboard navigation support with focus ring
|
|
12
|
+
- ✅ **Disabled State**: Proper disabled styling
|
|
13
|
+
- ✅ **Dark Mode**: Full dark mode support
|
|
14
|
+
- ✅ **Polymorphic**: Can render as different HTML elements
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { IconButton } from '@acme/ui';
|
|
20
|
+
import { Copy } from 'lucide-react';
|
|
21
|
+
|
|
22
|
+
// Outline variant (default) - has shadow
|
|
23
|
+
<IconButton variant="outline" size="lg" radius="lg" onClick={handleCopy}>
|
|
24
|
+
<Copy size={16} />
|
|
25
|
+
</IconButton>
|
|
26
|
+
|
|
27
|
+
// Ghost variant - transparent background
|
|
28
|
+
<IconButton variant="ghost" size="lg" radius="lg" onClick={handleSettings}>
|
|
29
|
+
<Settings size={16} />
|
|
30
|
+
</IconButton>
|
|
31
|
+
|
|
32
|
+
// Filled variant - solid background
|
|
33
|
+
<IconButton variant="filled" size="lg" radius="lg" onClick={handleDelete}>
|
|
34
|
+
<Trash2 size={16} />
|
|
35
|
+
</IconButton>
|
|
36
|
+
|
|
37
|
+
// Disabled state
|
|
38
|
+
<IconButton variant="outline" size="lg" radius="lg" disabled>
|
|
39
|
+
<Copy size={16} />
|
|
40
|
+
</IconButton>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
Inherits all props from Mantine's `ActionIconProps` plus:
|
|
46
|
+
|
|
47
|
+
| Prop | Type | Default | Description |
|
|
48
|
+
|------|------|---------|-------------|
|
|
49
|
+
| `variant` | `'outline' \| 'ghost' \| 'filled'` | `'outline'` | Visual style variant |
|
|
50
|
+
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | - | Button size |
|
|
51
|
+
| `radius` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | - | Border radius |
|
|
52
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
53
|
+
|
|
54
|
+
## Variants
|
|
55
|
+
|
|
56
|
+
### Outline (Default)
|
|
57
|
+
|
|
58
|
+
The outline variant is designed to match the Figma specifications exactly:
|
|
59
|
+
|
|
60
|
+
- White background (`--color-unofficial-outline`)
|
|
61
|
+
- Border with design system color (`--color-general-border`)
|
|
62
|
+
- **Box shadow** (`var(--mantine-shadow-sm)`) - this is what was missing before!
|
|
63
|
+
- Hover: Dark background with white text
|
|
64
|
+
- Active: Darker background
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<IconButton variant="outline" size="lg" radius="lg">
|
|
68
|
+
<Copy size={16} />
|
|
69
|
+
</IconButton>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Ghost
|
|
73
|
+
|
|
74
|
+
Transparent background with subtle hover state:
|
|
75
|
+
|
|
76
|
+
- Transparent background
|
|
77
|
+
- No border
|
|
78
|
+
- No shadow
|
|
79
|
+
- Hover: Subtle background color
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<IconButton variant="ghost" size="lg" radius="lg">
|
|
83
|
+
<Settings size={16} />
|
|
84
|
+
</IconButton>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Filled
|
|
88
|
+
|
|
89
|
+
Solid background for primary actions:
|
|
90
|
+
|
|
91
|
+
- Dark background (`--color-general-primary`)
|
|
92
|
+
- White text
|
|
93
|
+
- No border
|
|
94
|
+
- Hover: Slightly darker background
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<IconButton variant="filled" size="lg" radius="lg">
|
|
98
|
+
<Heart size={16} />
|
|
99
|
+
</IconButton>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Icon Sizing
|
|
103
|
+
|
|
104
|
+
Icon size should typically match the button size:
|
|
105
|
+
|
|
106
|
+
| Button Size | Icon Size |
|
|
107
|
+
|-------------|-----------|
|
|
108
|
+
| `xs` | `12px` |
|
|
109
|
+
| `sm` | `14px` |
|
|
110
|
+
| `md` | `16px` |
|
|
111
|
+
| `lg` | `18px` |
|
|
112
|
+
| `xl` | `20px` |
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
<IconButton variant="outline" size="lg" radius="lg">
|
|
116
|
+
<Copy size={18} /> {/* 18px for lg size */}
|
|
117
|
+
</IconButton>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Common Use Cases
|
|
121
|
+
|
|
122
|
+
### Copy to Clipboard
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
<IconButton
|
|
126
|
+
variant="outline"
|
|
127
|
+
size="lg"
|
|
128
|
+
radius="lg"
|
|
129
|
+
onClick={() => navigator.clipboard.writeText(text)}
|
|
130
|
+
>
|
|
131
|
+
<Copy size={16} />
|
|
132
|
+
</IconButton>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Settings Menu
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
<IconButton
|
|
139
|
+
variant="ghost"
|
|
140
|
+
size="lg"
|
|
141
|
+
radius="lg"
|
|
142
|
+
onClick={openSettings}
|
|
143
|
+
>
|
|
144
|
+
<Settings size={16} />
|
|
145
|
+
</IconButton>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Delete Action
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
<IconButton
|
|
152
|
+
variant="filled"
|
|
153
|
+
size="lg"
|
|
154
|
+
radius="lg"
|
|
155
|
+
onClick={handleDelete}
|
|
156
|
+
>
|
|
157
|
+
<Trash2 size={16} />
|
|
158
|
+
</IconButton>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Design System Integration
|
|
162
|
+
|
|
163
|
+
This component uses the following Figma design tokens:
|
|
164
|
+
|
|
165
|
+
### Colors
|
|
166
|
+
|
|
167
|
+
- `--color-unofficial-outline` - Background color for outline variant
|
|
168
|
+
- `--color-general-border` - Border color
|
|
169
|
+
- `--color-general-foreground` - Text/icon color
|
|
170
|
+
- `--color-unofficial-outline-hover` - Hover background
|
|
171
|
+
- `--color-unofficial-outline-active` - Active background
|
|
172
|
+
- `--color-general-primary` - Filled variant background
|
|
173
|
+
- `--color-general-primary-foreground` - Filled variant text
|
|
174
|
+
|
|
175
|
+
### Shadows
|
|
176
|
+
|
|
177
|
+
- `var(--mantine-shadow-sm)` - Box shadow for outline variant (from Figma shadow tokens)
|
|
178
|
+
|
|
179
|
+
### Focus Ring
|
|
180
|
+
|
|
181
|
+
- `--color-focus-ring` - Focus outline color
|
|
182
|
+
|
|
183
|
+
## Migration Guide
|
|
184
|
+
|
|
185
|
+
If you're currently using `ActionIcon` directly, you should migrate to `IconButton`:
|
|
186
|
+
|
|
187
|
+
### Before
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { ActionIcon } from '@acme/ui';
|
|
191
|
+
|
|
192
|
+
<ActionIcon variant="outline" size="lg" radius="lg">
|
|
193
|
+
<Copy size={16} />
|
|
194
|
+
</ActionIcon>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### After
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { IconButton } from '@acme/ui';
|
|
201
|
+
|
|
202
|
+
<IconButton variant="outline" size="lg" radius="lg">
|
|
203
|
+
<Copy size={16} />
|
|
204
|
+
</IconButton>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The main differences:
|
|
208
|
+
1. ✅ Shadow is now applied automatically to outline variant
|
|
209
|
+
2. ✅ Colors match the Figma design exactly
|
|
210
|
+
3. ✅ Hover states are more refined
|
|
211
|
+
4. ✅ Focus states include proper focus ring
|
|
212
|
+
|
|
213
|
+
## Accessibility
|
|
214
|
+
|
|
215
|
+
- Supports keyboard navigation with Tab/Shift+Tab
|
|
216
|
+
- Shows focus ring when focused via keyboard (`:focus-visible`)
|
|
217
|
+
- Can be disabled with proper visual feedback
|
|
218
|
+
- Works with screen readers (use `aria-label` for icon-only buttons)
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<IconButton
|
|
222
|
+
variant="outline"
|
|
223
|
+
size="lg"
|
|
224
|
+
radius="lg"
|
|
225
|
+
aria-label="Copy to clipboard"
|
|
226
|
+
onClick={handleCopy}
|
|
227
|
+
>
|
|
228
|
+
<Copy size={16} />
|
|
229
|
+
</IconButton>
|
|
230
|
+
```
|