ui-lab-registry 0.3.1 → 0.3.2
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/dist/component-dependencies.d.ts +0 -11
- package/dist/component-dependencies.d.ts.map +1 -1
- package/dist/component-dependencies.js +4 -4
- package/dist/component-dependencies.js.map +1 -1
- package/dist/components/Color/examples/03-format-switching.d.ts +0 -4
- package/dist/components/Color/examples/03-format-switching.d.ts.map +1 -1
- package/dist/components/Color/examples/03-format-switching.js +2 -2
- package/dist/components/Color/examples/03-format-switching.js.map +1 -1
- package/dist/components/Color/examples.json +0 -5
- package/dist/components/Command/examples/01-basic-command.js +1 -1
- package/dist/components/Command/examples/01-basic-command.js.map +1 -1
- package/dist/components/Command/examples.json +1 -1
- package/dist/components/Command/index.js +3 -3
- package/dist/components/Command/index.js.map +1 -1
- package/dist/components/Divider/examples/02-pattern-variants.js +1 -1
- package/dist/components/Divider/examples.json +1 -1
- package/dist/components/Grid/examples/01-basic-grid.js +1 -1
- package/dist/components/Grid/examples/01-basic-grid.js.map +1 -1
- package/dist/components/Grid/examples.json +1 -1
- package/dist/components/Grid/index.js +2 -2
- package/dist/components/Grid/index.js.map +1 -1
- package/dist/components/Input/examples/02-validation.js +1 -1
- package/dist/components/Input/examples/02-validation.js.map +1 -1
- package/dist/components/Input/examples.json +1 -1
- package/dist/components/Path/examples.json +0 -5
- package/dist/components/Radio/index.js +1 -1
- package/dist/components/Select/examples/02-searchable-select.js +1 -1
- package/dist/components/Select/examples/02-searchable-select.js.map +1 -1
- package/dist/components/Select/examples.json +1 -1
- package/dist/components/Tabs/examples/01-basic-tabs.js +2 -2
- package/dist/components/Tabs/examples/01-basic-tabs.js.map +1 -1
- package/dist/components/Tabs/examples/02-vertical-tabs.js +2 -2
- package/dist/components/Tabs/examples/02-vertical-tabs.js.map +1 -1
- package/dist/components/Tabs/examples.json +2 -2
- package/dist/components/Tabs/index.js +11 -11
- package/dist/components/Tabs/index.js.map +1 -1
- package/dist/elements/AI/AIChatInput/index.js +1 -1
- package/dist/elements/AI/AIChatInput/variations/02-with-suggestions/index.js +1 -1
- package/dist/elements/AI/AIChatInput/variations/02-with-suggestions/layout/PromptInputWithSuggestions.js +1 -1
- package/dist/elements/AI/AIChatInput/variations.json +2 -2
- package/dist/elements/AI/ChainOfThought/index.js +1 -1
- package/dist/elements/AI/Chat/index.js +1 -1
- package/dist/elements/AI/Chat/variations/02-with-actions/index.js +1 -1
- package/dist/elements/AI/Chat/variations.json +1 -1
- package/dist/elements/Documentation/CopyPage/index.js +1 -1
- package/dist/elements/Documentation/NextArticle/index.js +1 -1
- package/dist/elements/Documentation/NextArticle/variations/01-basic/layout/NextArticleCard.js +1 -1
- package/dist/elements/Documentation/NextArticle/variations/02-with-icon/layout/NextArticleWithIconCard.js +1 -1
- package/dist/elements/Documentation/NextArticle/variations.json +2 -2
- package/dist/elements/Documentation/TOC/index.js +1 -1
- package/dist/generated-data.d.ts.map +1 -1
- package/dist/generated-data.js +213 -947
- package/dist/generated-data.js.map +1 -1
- package/dist/generated-styles.d.ts.map +1 -1
- package/dist/generated-styles.js +171 -63
- package/dist/generated-styles.js.map +1 -1
- package/dist/generated-styles.json +171 -63
- package/dist/patterns/data/data-table-row/metadata.json +3 -3
- package/dist/patterns/data/data-table-row/variations/expandable/index.js +1 -1
- package/dist/patterns/data/data-table-row/variations/main/index.js +1 -1
- package/dist/patterns/data/data-table-row/variations/selectable/index.js +1 -1
- package/dist/patterns/data/progress-metric/metadata.json +3 -3
- package/dist/patterns/data/progress-metric/variations/colored/index.js +1 -1
- package/dist/patterns/data/progress-metric/variations/main/index.js +1 -1
- package/dist/patterns/data/progress-metric/variations/stacked/index.js +1 -1
- package/dist/patterns/form/labeled-field/metadata.json +4 -4
- package/dist/patterns/form/labeled-field/variations/main/index.js +1 -1
- package/dist/patterns/form/labeled-field/variations/with-error/index.js +1 -1
- package/dist/patterns/form/labeled-field/variations/with-hint/index.js +1 -1
- package/dist/patterns/form/select-row/metadata.json +2 -2
- package/dist/patterns/form/select-row/variations/main/index.js +1 -1
- package/dist/patterns/form/select-row/variations/with-icon/index.js +1 -1
- package/dist/patterns/form/toggle-setting-row/metadata.json +2 -2
- package/dist/patterns/form/toggle-setting-row/variations/destructive/index.js +1 -1
- package/dist/patterns/form/toggle-setting-row/variations/main/index.js +1 -1
- package/dist/patterns/interaction/icon-action-bar/variations/compact/index.js +1 -1
- package/dist/patterns/layout/media-object/metadata.json +1 -1
- package/dist/patterns/layout/media-object/variations/sm/index.js +1 -1
- package/dist/patterns/layout/split-row/metadata.json +3 -3
- package/dist/patterns/layout/split-row/variations/actions/index.js +1 -1
- package/dist/patterns/layout/split-row/variations/main/index.js +1 -1
- package/dist/patterns/layout/split-row/variations/meta/index.js +1 -1
- package/dist/patterns/layout/stat-block/metadata.json +3 -3
- package/dist/patterns/layout/stat-block/variations/compact/index.js +1 -1
- package/dist/patterns/layout/stat-block/variations/main/index.js +1 -1
- package/dist/patterns/layout/stat-block/variations/with-icon/index.js +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +17 -27
- package/dist/registry.js.map +1 -1
- package/dist/sections/CTA/variations/02-split/index.js +1 -1
- package/dist/sections/CTA/variations.json +1 -1
- package/dist/sections/Pricing/variations/01-cards/index.js +1 -1
- package/dist/sections/Pricing/variations/02-comparison/index.js +1 -1
- package/dist/sections/Pricing/variations.json +2 -2
- package/dist/sections/categories.d.ts +0 -11
- package/dist/sections/categories.d.ts.map +1 -1
- package/dist/sections/categories.js +2 -2
- package/dist/sections/categories.js.map +1 -1
- package/dist/starters/NextJS/index.js +1 -1
- package/dist/types.d.ts +0 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/load-component-examples.d.ts +3 -2
- package/dist/utils/load-component-examples.d.ts.map +1 -1
- package/package.json +2 -6
- package/src/component-dependencies.ts +4 -4
- package/src/components/Anchor/metadata.json +1 -1
- package/src/components/Banner/metadata.json +1 -1
- package/src/components/Code/metadata.json +1 -1
- package/src/components/Color/examples/03-format-switching.tsx +3 -3
- package/src/components/Color/examples.json +0 -5
- package/src/components/Command/examples/01-basic-command.tsx +1 -1
- package/src/components/Command/examples.json +1 -1
- package/src/components/Command/index.tsx +3 -3
- package/src/components/Date/metadata.json +1 -1
- package/src/components/Divider/examples/02-pattern-variants.tsx +3 -3
- package/src/components/Divider/examples.json +1 -1
- package/src/components/Flex/metadata.json +1 -1
- package/src/components/Gallery/metadata.json +1 -1
- package/src/components/Grid/examples/01-basic-grid.tsx +1 -1
- package/src/components/Grid/examples.json +1 -1
- package/src/components/Grid/index.tsx +2 -2
- package/src/components/Group/metadata.json +1 -1
- package/src/components/Input/examples/02-validation.tsx +2 -2
- package/src/components/Input/examples.json +1 -1
- package/src/components/List/metadata.json +1 -1
- package/src/components/Page/metadata.json +1 -1
- package/src/components/Panel/metadata.json +1 -1
- package/src/components/Path/examples.json +0 -5
- package/src/components/Radio/index.tsx +1 -1
- package/src/components/Select/examples/02-searchable-select.tsx +1 -1
- package/src/components/Select/examples.json +1 -1
- package/src/components/Tabs/examples/01-basic-tabs.tsx +12 -12
- package/src/components/Tabs/examples/02-vertical-tabs.tsx +19 -19
- package/src/components/Tabs/examples.json +2 -2
- package/src/components/Tabs/index.tsx +45 -45
- package/src/elements/AI/AIChatInput/index.tsx +2 -2
- package/src/elements/AI/AIChatInput/variations/02-with-suggestions/index.tsx +1 -1
- package/src/elements/AI/AIChatInput/variations/02-with-suggestions/layout/PromptInputWithSuggestions.tsx +1 -1
- package/src/elements/AI/AIChatInput/variations.json +2 -2
- package/src/elements/AI/ChainOfThought/index.tsx +1 -1
- package/src/elements/AI/Chat/index.tsx +4 -4
- package/src/elements/AI/Chat/variations/02-with-actions/index.tsx +3 -3
- package/src/elements/AI/Chat/variations.json +1 -1
- package/src/elements/Documentation/CopyPage/index.tsx +1 -1
- package/src/elements/Documentation/NextArticle/index.tsx +1 -1
- package/src/elements/Documentation/NextArticle/variations/01-basic/layout/NextArticleCard.tsx +1 -1
- package/src/elements/Documentation/NextArticle/variations/02-with-icon/layout/NextArticleWithIconCard.tsx +1 -1
- package/src/elements/Documentation/NextArticle/variations.json +2 -2
- package/src/elements/Documentation/TOC/index.tsx +4 -4
- package/src/generated-data.ts +213 -947
- package/src/generated-styles.ts +171 -63
- package/src/patterns/data/data-table-row/metadata.json +4 -4
- package/src/patterns/data/data-table-row/variations/expandable/index.tsx +3 -3
- package/src/patterns/data/data-table-row/variations/main/index.tsx +2 -2
- package/src/patterns/data/data-table-row/variations/selectable/index.tsx +2 -2
- package/src/patterns/data/progress-metric/metadata.json +4 -4
- package/src/patterns/data/progress-metric/variations/colored/index.tsx +2 -2
- package/src/patterns/data/progress-metric/variations/main/index.tsx +2 -2
- package/src/patterns/data/progress-metric/variations/stacked/index.tsx +2 -2
- package/src/patterns/form/labeled-field/metadata.json +5 -5
- package/src/patterns/form/labeled-field/variations/main/index.tsx +1 -1
- package/src/patterns/form/labeled-field/variations/with-error/index.tsx +1 -1
- package/src/patterns/form/labeled-field/variations/with-hint/index.tsx +1 -1
- package/src/patterns/form/select-row/metadata.json +3 -3
- package/src/patterns/form/select-row/variations/main/index.tsx +1 -1
- package/src/patterns/form/select-row/variations/with-icon/index.tsx +1 -1
- package/src/patterns/form/toggle-setting-row/metadata.json +3 -3
- package/src/patterns/form/toggle-setting-row/variations/destructive/index.tsx +1 -1
- package/src/patterns/form/toggle-setting-row/variations/main/index.tsx +1 -1
- package/src/patterns/interaction/icon-action-bar/variations/compact/index.tsx +1 -1
- package/src/patterns/layout/media-object/metadata.json +2 -2
- package/src/patterns/layout/media-object/variations/sm/index.tsx +2 -2
- package/src/patterns/layout/split-row/metadata.json +4 -4
- package/src/patterns/layout/split-row/variations/actions/index.tsx +1 -1
- package/src/patterns/layout/split-row/variations/main/index.tsx +2 -2
- package/src/patterns/layout/split-row/variations/meta/index.tsx +3 -3
- package/src/patterns/layout/stat-block/metadata.json +4 -4
- package/src/patterns/layout/stat-block/variations/compact/index.tsx +1 -1
- package/src/patterns/layout/stat-block/variations/main/index.tsx +1 -1
- package/src/patterns/layout/stat-block/variations/with-icon/index.tsx +1 -1
- package/src/registry.ts +17 -27
- package/src/sections/CTA/variations/02-split/index.tsx +1 -1
- package/src/sections/CTA/variations.json +1 -1
- package/src/sections/Pricing/variations/01-cards/index.tsx +1 -1
- package/src/sections/Pricing/variations/02-comparison/index.tsx +1 -1
- package/src/sections/Pricing/variations.json +2 -2
- package/src/sections/categories.ts +2 -2
- package/src/starters/NextJS/index.tsx +2 -2
- package/src/types.ts +1 -1
- package/src/utils/load-component-examples.ts +2 -2
- package/src/components/Path/examples/01-basic-breadcrumbs.tsx +0 -17
- package/src/providers/Theme/examples/01-provider-setup.tsx +0 -37
- package/src/providers/Theme/examples/02-toggle-component.tsx +0 -52
- package/src/providers/Theme/examples/03-advanced-patterns.tsx +0 -138
- package/src/providers/Theme/examples/index.ts +0 -3
- package/src/src/generated-styles.ts +0 -1359
package/dist/generated-data.js
CHANGED
|
@@ -108,79 +108,6 @@ export const generatedAPI = {
|
|
|
108
108
|
}
|
|
109
109
|
]
|
|
110
110
|
},
|
|
111
|
-
"banner": {
|
|
112
|
-
"props": [
|
|
113
|
-
{
|
|
114
|
-
"name": "variant",
|
|
115
|
-
"type": "string",
|
|
116
|
-
"required": false,
|
|
117
|
-
"defaultValue": "note",
|
|
118
|
-
"description": "Variant class appended to the root element. Accepts any string."
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
"name": "size",
|
|
122
|
-
"type": "sm | md | lg",
|
|
123
|
-
"required": false,
|
|
124
|
-
"defaultValue": "md",
|
|
125
|
-
"description": "Controls the padding and font size of the banner",
|
|
126
|
-
"enumValues": [
|
|
127
|
-
"sm",
|
|
128
|
-
"md",
|
|
129
|
-
"lg"
|
|
130
|
-
]
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
"name": "isDismissible",
|
|
134
|
-
"type": "boolean",
|
|
135
|
-
"required": false,
|
|
136
|
-
"defaultValue": "false",
|
|
137
|
-
"description": "When true, renders a dismiss button that hides the banner on click"
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
"name": "onDismiss",
|
|
141
|
-
"type": "(() => void)",
|
|
142
|
-
"required": false,
|
|
143
|
-
"description": "Called when the dismiss button is clicked"
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
"name": "styles",
|
|
147
|
-
"type": "BannerStylesProp",
|
|
148
|
-
"required": false,
|
|
149
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
150
|
-
}
|
|
151
|
-
],
|
|
152
|
-
"subComponents": {
|
|
153
|
-
"Banner.Title": {
|
|
154
|
-
"description": "Heading text for the banner message",
|
|
155
|
-
"props": [
|
|
156
|
-
{
|
|
157
|
-
"name": "styles",
|
|
158
|
-
"type": "StyleValue",
|
|
159
|
-
"required": false,
|
|
160
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
161
|
-
}
|
|
162
|
-
]
|
|
163
|
-
},
|
|
164
|
-
"Banner.Body": {
|
|
165
|
-
"description": "Body text content of the banner",
|
|
166
|
-
"props": [
|
|
167
|
-
{
|
|
168
|
-
"name": "styles",
|
|
169
|
-
"type": "StyleValue",
|
|
170
|
-
"required": false,
|
|
171
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
"examples": [
|
|
177
|
-
{
|
|
178
|
-
"title": "Basic Banner",
|
|
179
|
-
"description": "A neutral note banner using background shades instead of semantic colors. The default banner variant for general-purpose messaging.",
|
|
180
|
-
"code": "import React from 'react';\nimport { Banner } from 'ui-lab-components';\n\nexport default function Example() {\n return (\n <Banner variant=\"note\" size=\"md\">\n This is a note banner. Use it for general-purpose messages and information without semantic intent.\n </Banner>\n );\n}"
|
|
181
|
-
}
|
|
182
|
-
]
|
|
183
|
-
},
|
|
184
111
|
"button": {
|
|
185
112
|
"props": [
|
|
186
113
|
{
|
|
@@ -192,15 +119,10 @@ export const generatedAPI = {
|
|
|
192
119
|
},
|
|
193
120
|
{
|
|
194
121
|
"name": "size",
|
|
195
|
-
"type": "
|
|
122
|
+
"type": "ButtonSize",
|
|
196
123
|
"required": false,
|
|
197
124
|
"defaultValue": "md",
|
|
198
|
-
"description": "Size
|
|
199
|
-
"enumValues": [
|
|
200
|
-
"sm",
|
|
201
|
-
"md",
|
|
202
|
-
"lg"
|
|
203
|
-
]
|
|
125
|
+
"description": "Size class appended to the root element. Accepts any string."
|
|
204
126
|
},
|
|
205
127
|
{
|
|
206
128
|
"name": "isDisabled",
|
|
@@ -216,7 +138,7 @@ export const generatedAPI = {
|
|
|
216
138
|
},
|
|
217
139
|
{
|
|
218
140
|
"name": "icon",
|
|
219
|
-
"type": "
|
|
141
|
+
"type": "ReactNode | ButtonIconSlots",
|
|
220
142
|
"required": false,
|
|
221
143
|
"description": "Icon slots rendered before (left) or after (right) the button label"
|
|
222
144
|
},
|
|
@@ -703,130 +625,6 @@ export const generatedAPI = {
|
|
|
703
625
|
"title": "Opacity Slider",
|
|
704
626
|
"description": "Color picker with opacity/alpha slider enabled for transparent color selection.",
|
|
705
627
|
"code": "import React, { useState } from 'react';\nimport { Color } from 'ui-lab-components';\n\nexport default function Example() {\n const [color, setColor] = useState('rgba(106, 90, 205, 0.75)');\n\n return (\n <div className=\"p-4 space-y-4\">\n <div>\n <p className=\"text-sm text-foreground-300 mb-3\">Selected color: <code className=\"text-accent-500 font-mono\">{color}</code></p>\n <Color\n value={color}\n onChange={setColor}\n showOpacity\n format=\"rgb\"\n defaultValue=\"rgba(106, 90, 205, 0.75)\"\n />\n </div>\n </div>\n );\n}"
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
"title": "Format Switching",
|
|
709
|
-
"description": "Color picker with format toggle between hex and RGB to copy colors in different formats.",
|
|
710
|
-
"code": "import React, { useState } from 'react';\nimport { Color } from 'ui-lab-components';\n\nexport default function Example() {\n const [color, setColor] = useState('#3B82F6');\n const [format, setFormat] = useState<'hex' | 'rgb'>('hex');\n\n const handleFormatChange = () => {\n setFormat(format === 'hex' ? 'rgb' : 'hex');\n };\n\n return (\n <div className=\"p-4 space-y-4\">\n <div>\n <div className=\"flex items-center justify-between mb-3\">\n <p className=\"text-sm text-foreground-300\">\n Selected color: <code className=\"text-accent-500 font-mono\">{color}</code>\n </p>\n <button\n onClick={handleFormatChange}\n className=\"px-3 py-1 text-xs bg-foreground-400 hover:bg-foreground-400 text-foreground-100 rounded transition-colors\"\n >\n Format: {format.toUpperCase()}\n </button>\n </div>\n <Color\n value={color}\n onChange={setColor}\n format={format}\n defaultValue=\"#3B82F6\"\n />\n </div>\n <div className=\"mt-4 p-3 bg-foreground-400 rounded border border-foreground-400\">\n <p className=\"text-xs text-foreground-300\">\n <strong>Tip:</strong> Click the format button to switch between hex and RGB output formats.\n This is useful when you need to copy colors in different formats for different contexts.\n </p>\n </div>\n </div>\n );\n}"
|
|
711
|
-
}
|
|
712
|
-
]
|
|
713
|
-
},
|
|
714
|
-
"command": {
|
|
715
|
-
"props": [
|
|
716
|
-
{
|
|
717
|
-
"name": "value",
|
|
718
|
-
"type": "string",
|
|
719
|
-
"required": false,
|
|
720
|
-
"description": "Controlled search text value"
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
"name": "onChange",
|
|
724
|
-
"type": "((value: string) => void)",
|
|
725
|
-
"required": false,
|
|
726
|
-
"description": "Called when the search text changes"
|
|
727
|
-
},
|
|
728
|
-
{
|
|
729
|
-
"name": "placeholder",
|
|
730
|
-
"type": "string",
|
|
731
|
-
"required": false,
|
|
732
|
-
"defaultValue": "Search...",
|
|
733
|
-
"description": "Placeholder text for the search input"
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
"name": "className",
|
|
737
|
-
"type": "string",
|
|
738
|
-
"required": false,
|
|
739
|
-
"description": "Additional CSS class for the search input"
|
|
740
|
-
}
|
|
741
|
-
],
|
|
742
|
-
"subComponents": {
|
|
743
|
-
"Command.List": {
|
|
744
|
-
"description": "Scrollable container that renders the filtered command items",
|
|
745
|
-
"props": [
|
|
746
|
-
{
|
|
747
|
-
"name": "children",
|
|
748
|
-
"type": "ReactNode",
|
|
749
|
-
"required": false,
|
|
750
|
-
"description": "Child elements rendered inside the list"
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
"name": "emptyMessage",
|
|
754
|
-
"type": "string",
|
|
755
|
-
"required": false,
|
|
756
|
-
"defaultValue": "No items found.",
|
|
757
|
-
"description": "Message shown when no items match the search"
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
"name": "className",
|
|
761
|
-
"type": "string",
|
|
762
|
-
"required": false,
|
|
763
|
-
"description": "Additional CSS class for the list container"
|
|
764
|
-
}
|
|
765
|
-
]
|
|
766
|
-
},
|
|
767
|
-
"Command.Category": {
|
|
768
|
-
"description": "Labeled section grouping related commands",
|
|
769
|
-
"props": [
|
|
770
|
-
{
|
|
771
|
-
"name": "children",
|
|
772
|
-
"type": "ReactNode",
|
|
773
|
-
"required": false,
|
|
774
|
-
"description": "Child elements rendered inside the category header"
|
|
775
|
-
},
|
|
776
|
-
{
|
|
777
|
-
"name": "className",
|
|
778
|
-
"type": "string",
|
|
779
|
-
"required": false,
|
|
780
|
-
"description": "Additional CSS class for the category"
|
|
781
|
-
}
|
|
782
|
-
]
|
|
783
|
-
},
|
|
784
|
-
"Command.Footer": {
|
|
785
|
-
"description": "Fixed bottom bar in the command palette for hints or actions",
|
|
786
|
-
"props": [
|
|
787
|
-
{
|
|
788
|
-
"name": "children",
|
|
789
|
-
"type": "ReactNode",
|
|
790
|
-
"required": false,
|
|
791
|
-
"description": "Child elements rendered inside the footer"
|
|
792
|
-
},
|
|
793
|
-
{
|
|
794
|
-
"name": "className",
|
|
795
|
-
"type": "string",
|
|
796
|
-
"required": false,
|
|
797
|
-
"description": "Additional CSS class applied to the footer"
|
|
798
|
-
}
|
|
799
|
-
]
|
|
800
|
-
},
|
|
801
|
-
"Command.Groups": {
|
|
802
|
-
"description": "Wrapper that renders multiple Command.Category sections",
|
|
803
|
-
"props": [
|
|
804
|
-
{
|
|
805
|
-
"name": "renderCategory",
|
|
806
|
-
"type": "((category: string) => ReactNode)",
|
|
807
|
-
"required": false,
|
|
808
|
-
"description": "Renders a category header for the given category name"
|
|
809
|
-
},
|
|
810
|
-
{
|
|
811
|
-
"name": "renderItem",
|
|
812
|
-
"type": "(command: CommandItem, hint?: string ) => ReactNode",
|
|
813
|
-
"required": true,
|
|
814
|
-
"description": "Renders a single command item row"
|
|
815
|
-
},
|
|
816
|
-
{
|
|
817
|
-
"name": "className",
|
|
818
|
-
"type": "string",
|
|
819
|
-
"required": false,
|
|
820
|
-
"description": "Additional CSS class for the groups container"
|
|
821
|
-
}
|
|
822
|
-
]
|
|
823
|
-
}
|
|
824
|
-
},
|
|
825
|
-
"examples": [
|
|
826
|
-
{
|
|
827
|
-
"title": "Basic Command Palette",
|
|
828
|
-
"description": "A searchable command palette with keyboard shortcuts. Use Cmd+K (or Ctrl+K) to open.",
|
|
829
|
-
"code": "'use client';\n\nimport React from 'react';\nimport { Command, Button, Badge } from 'ui-lab-components';\n\nexport default function Example() {\n const [open, setOpen] = React.useState(false);\n\n const commands = [\n {\n id: 'search',\n label: 'Search',\n description: 'Search documents',\n shortcut: '⌘F',\n action: () => console.log('Search'),\n },\n {\n id: 'create',\n label: 'Create new',\n description: 'Create a new document',\n shortcut: '⌘N',\n action: () => console.log('Create'),\n },\n {\n id: 'settings',\n label: 'Settings',\n description: 'Open application settings',\n shortcut: '⌘,',\n action: () => console.log('Settings'),\n },\n ];\n\n return (\n <>\n <Button onClick={() => setOpen(true)}>\n Open Palette (⌘K)\n </Button>\n <Command\n open={open}\n onOpenChange={setOpen}\n items={commands}\n >\n <Command.SearchInput placeholder=\"Search commands...\" />\n <Command.List>\n <Command.Groups\n renderCategory={(category) =>\n category ? <Command.Category>{category}</Command.Category> : null\n }\n renderItem={(cmd) => (\n <Command.Item\n key={cmd.id}\n value={cmd.id}\n textValue={cmd.label}\n action={cmd.action}\n hint={cmd.shortcut}\n >\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n <div>\n <div style={{ fontWeight: 500 }}>{cmd.label}</div>\n {cmd.description && (\n <div style={{ fontSize: '0.875em', opacity: 0.7 }}>\n {cmd.description}\n </div>\n )}\n </div>\n </div>\n </Command.Item>\n )}\n />\n </Command.List>\n </Command>\n </>\n );\n}"
|
|
830
628
|
}
|
|
831
629
|
]
|
|
832
630
|
},
|
|
@@ -1001,17 +799,6 @@ export const generatedAPI = {
|
|
|
1001
799
|
}
|
|
1002
800
|
],
|
|
1003
801
|
"subComponents": {
|
|
1004
|
-
"Date.DayHeaders": {
|
|
1005
|
-
"description": "Row of weekday abbreviation labels above the calendar grid",
|
|
1006
|
-
"props": [
|
|
1007
|
-
{
|
|
1008
|
-
"name": "className",
|
|
1009
|
-
"type": "string",
|
|
1010
|
-
"required": false,
|
|
1011
|
-
"description": "Additional CSS class for the day headers row"
|
|
1012
|
-
}
|
|
1013
|
-
]
|
|
1014
|
-
},
|
|
1015
802
|
"Date.Header": {
|
|
1016
803
|
"description": "Navigation header with month/year display and prev/next controls",
|
|
1017
804
|
"props": [
|
|
@@ -1118,7 +905,7 @@ export const generatedAPI = {
|
|
|
1118
905
|
{
|
|
1119
906
|
"title": "Pattern Variants",
|
|
1120
907
|
"description": "Dividers support three distinct pattern styles: solid for continuous lines, dashed for rectangular segments, and dotted for circular dots.",
|
|
1121
|
-
"code": "import { Divider } from 'ui-lab-components';\n\nexport default function Example() {\n return (\n <div className=\"space-y-6 w-full\">\n <div className=\"space-y-2\">\n <span className=\"text-
|
|
908
|
+
"code": "import { Divider } from 'ui-lab-components';\n\nexport default function Example() {\n return (\n <div className=\"space-y-6 w-full\">\n <div className=\"space-y-2\">\n <span className=\"text-sm text-foreground-400\">Solid</span>\n <Divider variant=\"solid\" />\n </div>\n <div className=\"space-y-2\">\n <span className=\"text-sm text-foreground-400\">Dashed</span>\n <Divider variant=\"dashed\" />\n </div>\n <div className=\"space-y-2\">\n <span className=\"text-sm text-foreground-400\">Dotted</span>\n <Divider variant=\"dotted\" />\n </div>\n </div>\n );\n}"
|
|
1122
909
|
},
|
|
1123
910
|
{
|
|
1124
911
|
"title": "Vertical Orientation",
|
|
@@ -1192,115 +979,6 @@ export const generatedAPI = {
|
|
|
1192
979
|
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object."
|
|
1193
980
|
}
|
|
1194
981
|
],
|
|
1195
|
-
"subComponents": {
|
|
1196
|
-
"Expand.Icon": {
|
|
1197
|
-
"description": "Animated chevron icon that rotates when the section is open",
|
|
1198
|
-
"props": [
|
|
1199
|
-
{
|
|
1200
|
-
"name": "children",
|
|
1201
|
-
"type": "ReactNode",
|
|
1202
|
-
"required": false,
|
|
1203
|
-
"description": "Custom icon element; defaults to a chevron"
|
|
1204
|
-
}
|
|
1205
|
-
]
|
|
1206
|
-
},
|
|
1207
|
-
"Expand.Trigger": {
|
|
1208
|
-
"description": "Clickable button that toggles the expand/collapse state",
|
|
1209
|
-
"props": [
|
|
1210
|
-
{
|
|
1211
|
-
"name": "children",
|
|
1212
|
-
"type": "ReactNode",
|
|
1213
|
-
"required": false,
|
|
1214
|
-
"description": "Label or content of the trigger button"
|
|
1215
|
-
},
|
|
1216
|
-
{
|
|
1217
|
-
"name": "title",
|
|
1218
|
-
"type": "ReactNode",
|
|
1219
|
-
"required": false,
|
|
1220
|
-
"description": "ReactNode label rendered in the title span (overrides HTML title tooltip)"
|
|
1221
|
-
}
|
|
1222
|
-
]
|
|
1223
|
-
},
|
|
1224
|
-
"Expand.Content": {
|
|
1225
|
-
"description": "Collapsible content area revealed when expanded",
|
|
1226
|
-
"props": [
|
|
1227
|
-
{
|
|
1228
|
-
"name": "children",
|
|
1229
|
-
"type": "ReactNode",
|
|
1230
|
-
"required": true,
|
|
1231
|
-
"description": "Content shown when the expand is open"
|
|
1232
|
-
},
|
|
1233
|
-
{
|
|
1234
|
-
"name": "from",
|
|
1235
|
-
"type": "below | above | left | right",
|
|
1236
|
-
"required": false,
|
|
1237
|
-
"description": "Direction the content reveals from the trigger",
|
|
1238
|
-
"enumValues": [
|
|
1239
|
-
"below",
|
|
1240
|
-
"above",
|
|
1241
|
-
"left",
|
|
1242
|
-
"right"
|
|
1243
|
-
]
|
|
1244
|
-
}
|
|
1245
|
-
]
|
|
1246
|
-
},
|
|
1247
|
-
"Expand.Divider": {
|
|
1248
|
-
"description": "Separator line between expand sections",
|
|
1249
|
-
"props": [
|
|
1250
|
-
{
|
|
1251
|
-
"name": "variant",
|
|
1252
|
-
"type": "solid | dashed | dotted",
|
|
1253
|
-
"required": false,
|
|
1254
|
-
"description": "Controls the line style of the divider",
|
|
1255
|
-
"enumValues": [
|
|
1256
|
-
"solid",
|
|
1257
|
-
"dashed",
|
|
1258
|
-
"dotted"
|
|
1259
|
-
]
|
|
1260
|
-
},
|
|
1261
|
-
{
|
|
1262
|
-
"name": "orientation",
|
|
1263
|
-
"type": "horizontal | vertical",
|
|
1264
|
-
"required": false,
|
|
1265
|
-
"description": "Controls the axis the divider spans",
|
|
1266
|
-
"enumValues": [
|
|
1267
|
-
"horizontal",
|
|
1268
|
-
"vertical"
|
|
1269
|
-
]
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
"name": "size",
|
|
1273
|
-
"type": "sm | md | lg",
|
|
1274
|
-
"required": false,
|
|
1275
|
-
"description": "Size of the divider thickness",
|
|
1276
|
-
"enumValues": [
|
|
1277
|
-
"sm",
|
|
1278
|
-
"md",
|
|
1279
|
-
"lg"
|
|
1280
|
-
]
|
|
1281
|
-
},
|
|
1282
|
-
{
|
|
1283
|
-
"name": "spacing",
|
|
1284
|
-
"type": "sm | md | lg | none",
|
|
1285
|
-
"required": false,
|
|
1286
|
-
"defaultValue": "none",
|
|
1287
|
-
"description": "Controls the margin around the divider",
|
|
1288
|
-
"enumValues": [
|
|
1289
|
-
"sm",
|
|
1290
|
-
"md",
|
|
1291
|
-
"lg",
|
|
1292
|
-
"none"
|
|
1293
|
-
]
|
|
1294
|
-
},
|
|
1295
|
-
{
|
|
1296
|
-
"name": "styles",
|
|
1297
|
-
"type": "DividerStylesProp",
|
|
1298
|
-
"required": false,
|
|
1299
|
-
"description": "Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object."
|
|
1300
|
-
}
|
|
1301
|
-
]
|
|
1302
|
-
}
|
|
1303
|
-
},
|
|
1304
982
|
"examples": [
|
|
1305
983
|
{
|
|
1306
984
|
"title": "Basic Expand",
|
|
@@ -1503,7 +1181,7 @@ export const generatedAPI = {
|
|
|
1503
1181
|
"props": [
|
|
1504
1182
|
{
|
|
1505
1183
|
"name": "columns",
|
|
1506
|
-
"type": "number |
|
|
1184
|
+
"type": "number | ResponsiveColumns",
|
|
1507
1185
|
"required": false,
|
|
1508
1186
|
"defaultValue": "3",
|
|
1509
1187
|
"description": "Number of columns in the gallery grid"
|
|
@@ -1531,7 +1209,7 @@ export const generatedAPI = {
|
|
|
1531
1209
|
]
|
|
1532
1210
|
},
|
|
1533
1211
|
{
|
|
1534
|
-
"name": "
|
|
1212
|
+
"name": "responsive",
|
|
1535
1213
|
"type": "boolean",
|
|
1536
1214
|
"required": false,
|
|
1537
1215
|
"description": "Whether to enable container-query-based responsive columns"
|
|
@@ -1737,7 +1415,7 @@ export const generatedAPI = {
|
|
|
1737
1415
|
]
|
|
1738
1416
|
},
|
|
1739
1417
|
{
|
|
1740
|
-
"name": "
|
|
1418
|
+
"name": "responsive",
|
|
1741
1419
|
"type": "boolean",
|
|
1742
1420
|
"required": false,
|
|
1743
1421
|
"defaultValue": "false",
|
|
@@ -1754,314 +1432,68 @@ export const generatedAPI = {
|
|
|
1754
1432
|
{
|
|
1755
1433
|
"title": "Basic Grid",
|
|
1756
1434
|
"description": "A simple grid layout with multiple cells. Use this for organizing content in a responsive grid structure.",
|
|
1757
|
-
"code": "import React from 'react';\nimport { Grid } from 'ui-lab-components';\n\nexport default function Example() {\n return (\n <Grid columns
|
|
1435
|
+
"code": "import React from 'react';\nimport { Grid } from 'ui-lab-components';\n\nexport default function Example() {\n return (\n <Grid columns={3} gap=\"md\">\n <div style={{ padding: '1rem', background: '#e0e0e0' }}>Cell 1</div>\n <div style={{ padding: '1rem', background: '#d0d0d0' }}>Cell 2</div>\n <div style={{ padding: '1rem', background: '#c0c0c0' }}>Cell 3</div>\n <div style={{ padding: '1rem', background: '#b0b0b0' }}>Cell 4</div>\n <div style={{ padding: '1rem', background: '#a0a0a0' }}>Cell 5</div>\n <div style={{ padding: '1rem', background: '#909090' }}>Cell 6</div>\n </Grid>\n );\n}"
|
|
1758
1436
|
}
|
|
1759
|
-
]
|
|
1760
|
-
},
|
|
1761
|
-
"group": {
|
|
1762
|
-
"props": [
|
|
1763
|
-
{
|
|
1764
|
-
"name": "orientation",
|
|
1765
|
-
"type": "horizontal | vertical",
|
|
1766
|
-
"required": false,
|
|
1767
|
-
"defaultValue": "horizontal",
|
|
1768
|
-
"description": "Controls the axis that children are arranged along",
|
|
1769
|
-
"enumValues": [
|
|
1770
|
-
"horizontal",
|
|
1771
|
-
"vertical"
|
|
1772
|
-
]
|
|
1773
|
-
},
|
|
1774
|
-
{
|
|
1775
|
-
"name": "spacing",
|
|
1776
|
-
"type": "none | xs | sm",
|
|
1777
|
-
"required": false,
|
|
1778
|
-
"defaultValue": "none",
|
|
1779
|
-
"description": "Controls the gap between group items",
|
|
1780
|
-
"enumValues": [
|
|
1781
|
-
"none",
|
|
1782
|
-
"xs",
|
|
1783
|
-
"sm"
|
|
1784
|
-
]
|
|
1785
|
-
},
|
|
1786
|
-
{
|
|
1787
|
-
"name": "variant",
|
|
1788
|
-
"type": "primary | secondary | outline | ghost",
|
|
1789
|
-
"required": false,
|
|
1790
|
-
"defaultValue": "primary",
|
|
1791
|
-
"description": "Controls the shared visual style applied to group items",
|
|
1792
|
-
"enumValues": [
|
|
1793
|
-
"primary",
|
|
1794
|
-
"secondary",
|
|
1795
|
-
"outline",
|
|
1796
|
-
"ghost"
|
|
1797
|
-
]
|
|
1798
|
-
},
|
|
1799
|
-
{
|
|
1800
|
-
"name": "isDisabled",
|
|
1801
|
-
"type": "boolean",
|
|
1802
|
-
"required": false,
|
|
1803
|
-
"defaultValue": "false",
|
|
1804
|
-
"description": "Whether all items in the group are non-interactive"
|
|
1805
|
-
}
|
|
1806
|
-
],
|
|
1807
|
-
"subComponents": {
|
|
1808
|
-
"Group.Button": {
|
|
1809
|
-
"description": "Button styled to merge seamlessly with adjacent group items",
|
|
1810
|
-
"props": [
|
|
1811
|
-
{
|
|
1812
|
-
"name": "active",
|
|
1813
|
-
"type": "boolean",
|
|
1814
|
-
"required": false,
|
|
1815
|
-
"description": "Whether this button is in an active/pressed state"
|
|
1816
|
-
},
|
|
1817
|
-
{
|
|
1818
|
-
"name": "variant",
|
|
1819
|
-
"type": "string",
|
|
1820
|
-
"required": false,
|
|
1821
|
-
"defaultValue": "primary",
|
|
1822
|
-
"description": "Variant class appended to the root element. Accepts any string."
|
|
1823
|
-
},
|
|
1824
|
-
{
|
|
1825
|
-
"name": "size",
|
|
1826
|
-
"type": "sm | md | lg",
|
|
1827
|
-
"required": false,
|
|
1828
|
-
"description": "Size of the button",
|
|
1829
|
-
"enumValues": [
|
|
1830
|
-
"sm",
|
|
1831
|
-
"md",
|
|
1832
|
-
"lg"
|
|
1833
|
-
]
|
|
1834
|
-
},
|
|
1835
|
-
{
|
|
1836
|
-
"name": "isDisabled",
|
|
1837
|
-
"type": "boolean",
|
|
1838
|
-
"required": false,
|
|
1839
|
-
"defaultValue": "false",
|
|
1840
|
-
"description": "Disables interaction and applies disabled styling"
|
|
1841
|
-
},
|
|
1842
|
-
{
|
|
1843
|
-
"name": "onPress",
|
|
1844
|
-
"type": "((e: { target: EventTarget | null; }) => void)",
|
|
1845
|
-
"required": false,
|
|
1846
|
-
"description": "React Aria press handler — preferred over onClick for accessibility"
|
|
1847
|
-
},
|
|
1848
|
-
{
|
|
1849
|
-
"name": "icon",
|
|
1850
|
-
"type": "{ left?: ReactNode; right?: ReactNode; }",
|
|
1851
|
-
"required": false,
|
|
1852
|
-
"description": "Icon slots rendered before (left) or after (right) the button label"
|
|
1853
|
-
},
|
|
1854
|
-
{
|
|
1855
|
-
"name": "href",
|
|
1856
|
-
"type": "string",
|
|
1857
|
-
"required": false,
|
|
1858
|
-
"description": "Renders the button as an anchor element when provided"
|
|
1859
|
-
},
|
|
1860
|
-
{
|
|
1861
|
-
"name": "target",
|
|
1862
|
-
"type": "HTMLAttributeAnchorTarget",
|
|
1863
|
-
"required": false,
|
|
1864
|
-
"description": "Browsing context for the anchor variant (e.g. \"_blank\")"
|
|
1865
|
-
},
|
|
1866
|
-
{
|
|
1867
|
-
"name": "styles",
|
|
1868
|
-
"type": "ButtonStylesProp",
|
|
1869
|
-
"required": false,
|
|
1870
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1871
|
-
}
|
|
1872
|
-
]
|
|
1873
|
-
},
|
|
1874
|
-
"Group.Input": {
|
|
1875
|
-
"description": "Input field integrated into the button group",
|
|
1876
|
-
"props": [
|
|
1877
|
-
{
|
|
1878
|
-
"name": "variant",
|
|
1879
|
-
"type": "ghost | default",
|
|
1880
|
-
"required": false,
|
|
1881
|
-
"defaultValue": "primary",
|
|
1882
|
-
"description": "Controls the visual style of the input",
|
|
1883
|
-
"enumValues": [
|
|
1884
|
-
"ghost",
|
|
1885
|
-
"default"
|
|
1886
|
-
]
|
|
1887
|
-
},
|
|
1888
|
-
{
|
|
1889
|
-
"name": "error",
|
|
1890
|
-
"type": "boolean",
|
|
1891
|
-
"required": false,
|
|
1892
|
-
"description": "Whether the input is in an error state"
|
|
1893
|
-
},
|
|
1894
|
-
{
|
|
1895
|
-
"name": "prefixIcon",
|
|
1896
|
-
"type": "ReactNode",
|
|
1897
|
-
"required": false,
|
|
1898
|
-
"description": "Icon displayed before the input value"
|
|
1899
|
-
},
|
|
1900
|
-
{
|
|
1901
|
-
"name": "suffixIcon",
|
|
1902
|
-
"type": "ReactNode",
|
|
1903
|
-
"required": false,
|
|
1904
|
-
"description": "Icon displayed after the input value"
|
|
1905
|
-
},
|
|
1906
|
-
{
|
|
1907
|
-
"name": "styles",
|
|
1908
|
-
"type": "InputStylesProp",
|
|
1909
|
-
"required": false,
|
|
1910
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1911
|
-
}
|
|
1912
|
-
]
|
|
1913
|
-
},
|
|
1914
|
-
"Group.InputWrapper": {
|
|
1915
|
-
"description": "Input variant that preserves Input styling within the group",
|
|
1916
|
-
"props": [
|
|
1917
|
-
{
|
|
1918
|
-
"name": "variant",
|
|
1919
|
-
"type": "ghost | default",
|
|
1920
|
-
"required": false,
|
|
1921
|
-
"defaultValue": "primary",
|
|
1922
|
-
"description": "Controls the visual style of the input",
|
|
1923
|
-
"enumValues": [
|
|
1924
|
-
"ghost",
|
|
1925
|
-
"default"
|
|
1926
|
-
]
|
|
1927
|
-
},
|
|
1928
|
-
{
|
|
1929
|
-
"name": "error",
|
|
1930
|
-
"type": "boolean",
|
|
1931
|
-
"required": false,
|
|
1932
|
-
"description": "Whether the input is in an error state"
|
|
1933
|
-
},
|
|
1934
|
-
{
|
|
1935
|
-
"name": "prefixIcon",
|
|
1936
|
-
"type": "ReactNode",
|
|
1937
|
-
"required": false,
|
|
1938
|
-
"description": "Icon displayed before the input value"
|
|
1939
|
-
},
|
|
1940
|
-
{
|
|
1941
|
-
"name": "suffixIcon",
|
|
1942
|
-
"type": "ReactNode",
|
|
1943
|
-
"required": false,
|
|
1944
|
-
"description": "Icon displayed after the input value"
|
|
1945
|
-
},
|
|
1946
|
-
{
|
|
1947
|
-
"name": "styles",
|
|
1948
|
-
"type": "InputStylesProp",
|
|
1949
|
-
"required": false,
|
|
1950
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1951
|
-
}
|
|
1952
|
-
]
|
|
1953
|
-
},
|
|
1954
|
-
"Group.Select": {
|
|
1955
|
-
"description": "Select dropdown integrated into the button group",
|
|
1956
|
-
"props": [
|
|
1957
|
-
{
|
|
1958
|
-
"name": "mode",
|
|
1959
|
-
"type": "multiple | single",
|
|
1960
|
-
"required": false,
|
|
1961
|
-
"description": "Selection mode: \"single\" for one item, \"multiple\" for multi-item selection",
|
|
1962
|
-
"enumValues": [
|
|
1963
|
-
"multiple",
|
|
1964
|
-
"single"
|
|
1965
|
-
]
|
|
1966
|
-
},
|
|
1967
|
-
{
|
|
1968
|
-
"name": "items",
|
|
1969
|
-
"type": "any[]",
|
|
1970
|
-
"required": false,
|
|
1971
|
-
"description": "External items array — used when items are provided as data rather than JSX"
|
|
1972
|
-
},
|
|
1973
|
-
{
|
|
1974
|
-
"name": "selectedKey",
|
|
1975
|
-
"type": "Key | null",
|
|
1976
|
-
"required": false,
|
|
1977
|
-
"description": "Controlled selected key for single-select mode"
|
|
1978
|
-
},
|
|
1979
|
-
{
|
|
1980
|
-
"name": "defaultSelectedKey",
|
|
1981
|
-
"type": "Key | null",
|
|
1982
|
-
"required": false,
|
|
1983
|
-
"description": "Default selected key for uncontrolled single-select"
|
|
1984
|
-
},
|
|
1985
|
-
{
|
|
1986
|
-
"name": "selectedKeys",
|
|
1987
|
-
"type": "Key[]",
|
|
1988
|
-
"required": false,
|
|
1989
|
-
"description": "Controlled selected keys for multi-select mode"
|
|
1990
|
-
},
|
|
1991
|
-
{
|
|
1992
|
-
"name": "defaultSelectedKeys",
|
|
1993
|
-
"type": "Key[]",
|
|
1994
|
-
"required": false,
|
|
1995
|
-
"description": "Default selected keys for uncontrolled multi-select"
|
|
1996
|
-
},
|
|
1997
|
-
{
|
|
1998
|
-
"name": "defaultValue",
|
|
1999
|
-
"type": "string | null",
|
|
2000
|
-
"required": false,
|
|
2001
|
-
"description": "Default display text shown in the trigger when nothing is selected"
|
|
2002
|
-
},
|
|
2003
|
-
{
|
|
2004
|
-
"name": "valueLabel",
|
|
2005
|
-
"type": "string",
|
|
2006
|
-
"required": false,
|
|
2007
|
-
"description": "Display text for the currently selected value — used for SSR/SSG to avoid\nflash of placeholder before items register. Provide alongside selectedKey or\ndefaultSelectedKey so the correct label renders on the first pass."
|
|
2008
|
-
},
|
|
2009
|
-
{
|
|
2010
|
-
"name": "onSelectionChange",
|
|
2011
|
-
"type": "((value: any) => void)",
|
|
2012
|
-
"required": false,
|
|
2013
|
-
"description": "Called when selection changes; receives a single key (single) or key array (multiple)"
|
|
2014
|
-
},
|
|
2015
|
-
{
|
|
2016
|
-
"name": "isDisabled",
|
|
2017
|
-
"type": "boolean",
|
|
2018
|
-
"required": false,
|
|
2019
|
-
"defaultValue": "false",
|
|
2020
|
-
"description": "Disables the entire select and prevents interaction"
|
|
2021
|
-
},
|
|
2022
|
-
{
|
|
2023
|
-
"name": "autoFocus",
|
|
2024
|
-
"type": "boolean",
|
|
2025
|
-
"required": false,
|
|
2026
|
-
"description": "Focuses the trigger automatically on mount"
|
|
2027
|
-
},
|
|
2028
|
-
{
|
|
2029
|
-
"name": "maxItems",
|
|
2030
|
-
"type": "number",
|
|
2031
|
-
"required": false,
|
|
2032
|
-
"description": "Maximum number of items visible before the dropdown scrolls"
|
|
2033
|
-
},
|
|
2034
|
-
{
|
|
2035
|
-
"name": "className",
|
|
2036
|
-
"type": "string",
|
|
2037
|
-
"required": false,
|
|
2038
|
-
"description": "Additional CSS class for the root wrapper"
|
|
2039
|
-
},
|
|
2040
|
-
{
|
|
2041
|
-
"name": "trigger",
|
|
2042
|
-
"type": "click | hover",
|
|
2043
|
-
"required": false,
|
|
2044
|
-
"description": "How the dropdown opens: \"click\" (default) or \"hover\"",
|
|
2045
|
-
"enumValues": [
|
|
2046
|
-
"click",
|
|
2047
|
-
"hover"
|
|
2048
|
-
]
|
|
2049
|
-
},
|
|
2050
|
-
{
|
|
2051
|
-
"name": "filter",
|
|
2052
|
-
"type": "((item: any) => boolean)",
|
|
2053
|
-
"required": false,
|
|
2054
|
-
"description": "Custom filter predicate applied to the items array"
|
|
2055
|
-
},
|
|
2056
|
-
{
|
|
2057
|
-
"name": "styles",
|
|
2058
|
-
"type": "SelectStylesProp",
|
|
2059
|
-
"required": false,
|
|
2060
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
2061
|
-
}
|
|
1437
|
+
]
|
|
1438
|
+
},
|
|
1439
|
+
"group": {
|
|
1440
|
+
"props": [
|
|
1441
|
+
{
|
|
1442
|
+
"name": "orientation",
|
|
1443
|
+
"type": "horizontal | vertical",
|
|
1444
|
+
"required": false,
|
|
1445
|
+
"defaultValue": "horizontal",
|
|
1446
|
+
"description": "Controls the axis that children are arranged along",
|
|
1447
|
+
"enumValues": [
|
|
1448
|
+
"horizontal",
|
|
1449
|
+
"vertical"
|
|
1450
|
+
]
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
"name": "spacing",
|
|
1454
|
+
"type": "none | xs | sm",
|
|
1455
|
+
"required": false,
|
|
1456
|
+
"defaultValue": "none",
|
|
1457
|
+
"description": "Controls the gap between group items",
|
|
1458
|
+
"enumValues": [
|
|
1459
|
+
"none",
|
|
1460
|
+
"xs",
|
|
1461
|
+
"sm"
|
|
1462
|
+
]
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
"name": "variant",
|
|
1466
|
+
"type": "primary | secondary | outline | ghost",
|
|
1467
|
+
"required": false,
|
|
1468
|
+
"defaultValue": "primary",
|
|
1469
|
+
"description": "Controls the shared visual style applied to group items",
|
|
1470
|
+
"enumValues": [
|
|
1471
|
+
"primary",
|
|
1472
|
+
"secondary",
|
|
1473
|
+
"outline",
|
|
1474
|
+
"ghost"
|
|
2062
1475
|
]
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
"name": "isDisabled",
|
|
1479
|
+
"type": "boolean",
|
|
1480
|
+
"required": false,
|
|
1481
|
+
"defaultValue": "false",
|
|
1482
|
+
"description": "Whether all items in the group are non-interactive"
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
"name": "value",
|
|
1486
|
+
"type": "string",
|
|
1487
|
+
"required": false,
|
|
1488
|
+
"description": "The currently active button value for toggle group behavior"
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
"name": "onChange",
|
|
1492
|
+
"type": "((value: string) => void)",
|
|
1493
|
+
"required": false,
|
|
1494
|
+
"description": "Called when a button with a value prop is pressed"
|
|
2063
1495
|
}
|
|
2064
|
-
|
|
1496
|
+
],
|
|
2065
1497
|
"examples": [
|
|
2066
1498
|
{
|
|
2067
1499
|
"title": "Basic Group",
|
|
@@ -2091,16 +1523,22 @@ export const generatedAPI = {
|
|
|
2091
1523
|
"description": "Whether the input is in an error state"
|
|
2092
1524
|
},
|
|
2093
1525
|
{
|
|
2094
|
-
"name": "
|
|
2095
|
-
"type": "ReactNode",
|
|
1526
|
+
"name": "icon",
|
|
1527
|
+
"type": "ReactNode | InputIconSlots",
|
|
1528
|
+
"required": false,
|
|
1529
|
+
"description": "Icon displayed before the input value by default, or in named prefix/suffix slots"
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
"name": "actions",
|
|
1533
|
+
"type": "InputAction[] | InputActionSlots",
|
|
2096
1534
|
"required": false,
|
|
2097
|
-
"description": "
|
|
1535
|
+
"description": "Inline actions rendered on the left or right side of the input. Passing an array keeps the existing right-side behavior."
|
|
2098
1536
|
},
|
|
2099
1537
|
{
|
|
2100
|
-
"name": "
|
|
1538
|
+
"name": "hint",
|
|
2101
1539
|
"type": "ReactNode",
|
|
2102
1540
|
"required": false,
|
|
2103
|
-
"description": "
|
|
1541
|
+
"description": "Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts."
|
|
2104
1542
|
},
|
|
2105
1543
|
{
|
|
2106
1544
|
"name": "styles",
|
|
@@ -2118,7 +1556,7 @@ export const generatedAPI = {
|
|
|
2118
1556
|
{
|
|
2119
1557
|
"title": "Validation States",
|
|
2120
1558
|
"description": "Input fields with error and success validation states, including helper text for user feedback.",
|
|
2121
|
-
"code": "import React from 'react';\nimport { Input, Label } from 'ui-lab-components';\nimport { FaCircleExclamation, FaCircleCheck } from 'react-icons/fa6';\n\nexport default function Example() {\n return (\n <div className=\"flex flex-col gap-6 w-full max-w-sm\">\n {/* Error State */}\n <div className=\"flex flex-col gap-1.5\">\n <Label error helperText=\"Please enter a valid email address\" helperTextError>\n Email\n </Label>\n <Input\n type=\"email\"\n placeholder=\"Enter your email\"\n error\n defaultValue=\"invalid-email\"\n
|
|
1559
|
+
"code": "import React from 'react';\nimport { Input, Label } from 'ui-lab-components';\nimport { FaCircleExclamation, FaCircleCheck } from 'react-icons/fa6';\n\nexport default function Example() {\n return (\n <div className=\"flex flex-col gap-6 w-full max-w-sm\">\n {/* Error State */}\n <div className=\"flex flex-col gap-1.5\">\n <Label error helperText=\"Please enter a valid email address\" helperTextError>\n Email\n </Label>\n <Input\n type=\"email\"\n placeholder=\"Enter your email\"\n error\n defaultValue=\"invalid-email\"\n icon={{ suffix: <FaCircleExclamation className=\"text-danger-600\" size={14} /> }}\n />\n </div>\n\n {/* Success State */}\n <div className=\"flex flex-col gap-1.5\">\n <Label helperText=\"Email address is available\">\n Email\n </Label>\n <Input\n type=\"email\"\n placeholder=\"Enter your email\"\n defaultValue=\"user@example.com\"\n icon={{ suffix: <FaCircleCheck className=\"text-success-600\" size={14} /> }}\n className=\"border-success-600 focus:border-success-600\"\n />\n </div>\n\n {/* Default State with Helper Text */}\n <div className=\"flex flex-col gap-1.5\">\n <Label required helperText=\"We'll never share your email with anyone else.\">\n Email\n </Label>\n <Input\n type=\"email\"\n placeholder=\"Enter your email\"\n />\n </div>\n </div>\n );\n}"
|
|
2122
1560
|
}
|
|
2123
1561
|
]
|
|
2124
1562
|
},
|
|
@@ -2445,16 +1883,22 @@ export const generatedAPI = {
|
|
|
2445
1883
|
"description": "Whether the input is in an error state"
|
|
2446
1884
|
},
|
|
2447
1885
|
{
|
|
2448
|
-
"name": "
|
|
2449
|
-
"type": "ReactNode",
|
|
1886
|
+
"name": "icon",
|
|
1887
|
+
"type": "ReactNode | InputIconSlots",
|
|
1888
|
+
"required": false,
|
|
1889
|
+
"description": "Icon displayed before the input value by default, or in named prefix/suffix slots"
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
"name": "actions",
|
|
1893
|
+
"type": "InputAction[] | InputActionSlots",
|
|
2450
1894
|
"required": false,
|
|
2451
|
-
"description": "
|
|
1895
|
+
"description": "Inline actions rendered on the left or right side of the input. Passing an array keeps the existing right-side behavior."
|
|
2452
1896
|
},
|
|
2453
1897
|
{
|
|
2454
|
-
"name": "
|
|
1898
|
+
"name": "hint",
|
|
2455
1899
|
"type": "ReactNode",
|
|
2456
1900
|
"required": false,
|
|
2457
|
-
"description": "
|
|
1901
|
+
"description": "Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts."
|
|
2458
1902
|
}
|
|
2459
1903
|
]
|
|
2460
1904
|
},
|
|
@@ -2692,9 +2136,6 @@ export const generatedAPI = {
|
|
|
2692
2136
|
}
|
|
2693
2137
|
],
|
|
2694
2138
|
"subComponents": {
|
|
2695
|
-
"MenuContext": {
|
|
2696
|
-
"props": []
|
|
2697
|
-
},
|
|
2698
2139
|
"MenuSubmenuContext": {
|
|
2699
2140
|
"props": []
|
|
2700
2141
|
},
|
|
@@ -3288,110 +2729,6 @@ export const generatedAPI = {
|
|
|
3288
2729
|
"description": "Slot styles."
|
|
3289
2730
|
}
|
|
3290
2731
|
],
|
|
3291
|
-
"subComponents": {
|
|
3292
|
-
"Panel.Header": {
|
|
3293
|
-
"description": "Top bar of the panel, typically for a title and actions",
|
|
3294
|
-
"props": [
|
|
3295
|
-
{
|
|
3296
|
-
"name": "sticky",
|
|
3297
|
-
"type": "boolean",
|
|
3298
|
-
"required": false,
|
|
3299
|
-
"defaultValue": "true",
|
|
3300
|
-
"description": "Whether the header sticks to the top while scrolling"
|
|
3301
|
-
}
|
|
3302
|
-
]
|
|
3303
|
-
},
|
|
3304
|
-
"Panel.Content": {
|
|
3305
|
-
"description": "Main scrollable body area of the panel",
|
|
3306
|
-
"props": []
|
|
3307
|
-
},
|
|
3308
|
-
"Panel.Footer": {
|
|
3309
|
-
"description": "Bottom bar of the panel, typically for controls or status",
|
|
3310
|
-
"props": [
|
|
3311
|
-
{
|
|
3312
|
-
"name": "fixed",
|
|
3313
|
-
"type": "boolean",
|
|
3314
|
-
"required": false,
|
|
3315
|
-
"defaultValue": "false",
|
|
3316
|
-
"description": "Whether the footer is fixed to the bottom of the panel"
|
|
3317
|
-
}
|
|
3318
|
-
]
|
|
3319
|
-
},
|
|
3320
|
-
"Panel.Sidebar": {
|
|
3321
|
-
"description": "Collapsible side panel that slides in from left or right",
|
|
3322
|
-
"props": [
|
|
3323
|
-
{
|
|
3324
|
-
"name": "side",
|
|
3325
|
-
"type": "left | right",
|
|
3326
|
-
"required": false,
|
|
3327
|
-
"defaultValue": "left",
|
|
3328
|
-
"description": "Which side of the panel the sidebar appears on",
|
|
3329
|
-
"enumValues": [
|
|
3330
|
-
"left",
|
|
3331
|
-
"right"
|
|
3332
|
-
]
|
|
3333
|
-
},
|
|
3334
|
-
{
|
|
3335
|
-
"name": "defaultOpen",
|
|
3336
|
-
"type": "boolean",
|
|
3337
|
-
"required": false,
|
|
3338
|
-
"defaultValue": "true",
|
|
3339
|
-
"description": "Whether the sidebar is open on initial render"
|
|
3340
|
-
},
|
|
3341
|
-
{
|
|
3342
|
-
"name": "width",
|
|
3343
|
-
"type": "string | number",
|
|
3344
|
-
"required": false,
|
|
3345
|
-
"defaultValue": "240px",
|
|
3346
|
-
"description": "Width of the sidebar when open"
|
|
3347
|
-
},
|
|
3348
|
-
{
|
|
3349
|
-
"name": "collapsedWidth",
|
|
3350
|
-
"type": "string | number",
|
|
3351
|
-
"required": false,
|
|
3352
|
-
"defaultValue": "0",
|
|
3353
|
-
"description": "Width of the sidebar when collapsed"
|
|
3354
|
-
}
|
|
3355
|
-
]
|
|
3356
|
-
},
|
|
3357
|
-
"Panel.Toggle": {
|
|
3358
|
-
"description": "Button that shows/hides the Panel.Sidebar",
|
|
3359
|
-
"props": [
|
|
3360
|
-
{
|
|
3361
|
-
"name": "children",
|
|
3362
|
-
"type": "ReactElement<unknown, string | JSXElementConstructor<any>>",
|
|
3363
|
-
"required": true,
|
|
3364
|
-
"description": "Button element that triggers sidebar open/close"
|
|
3365
|
-
}
|
|
3366
|
-
]
|
|
3367
|
-
},
|
|
3368
|
-
"Panel.Group": {
|
|
3369
|
-
"description": "Container that manages side-by-side resizable panel columns",
|
|
3370
|
-
"props": [
|
|
3371
|
-
{
|
|
3372
|
-
"name": "direction",
|
|
3373
|
-
"type": "horizontal | vertical",
|
|
3374
|
-
"required": false,
|
|
3375
|
-
"defaultValue": "horizontal",
|
|
3376
|
-
"description": "Controls the axis panels are arranged along",
|
|
3377
|
-
"enumValues": [
|
|
3378
|
-
"horizontal",
|
|
3379
|
-
"vertical"
|
|
3380
|
-
]
|
|
3381
|
-
}
|
|
3382
|
-
]
|
|
3383
|
-
},
|
|
3384
|
-
"Panel.Resize": {
|
|
3385
|
-
"description": "Drag handle between Panel.Group columns for resizing",
|
|
3386
|
-
"props": [
|
|
3387
|
-
{
|
|
3388
|
-
"name": "data-resize-index",
|
|
3389
|
-
"type": "number",
|
|
3390
|
-
"required": false
|
|
3391
|
-
}
|
|
3392
|
-
]
|
|
3393
|
-
}
|
|
3394
|
-
},
|
|
3395
2732
|
"examples": []
|
|
3396
2733
|
},
|
|
3397
2734
|
"path": {
|
|
@@ -3454,11 +2791,6 @@ export const generatedAPI = {
|
|
|
3454
2791
|
}
|
|
3455
2792
|
},
|
|
3456
2793
|
"examples": [
|
|
3457
|
-
{
|
|
3458
|
-
"title": "Basic Path",
|
|
3459
|
-
"description": "A simple path navigation showing the current page location. Use this to help users understand their position in the site hierarchy.",
|
|
3460
|
-
"code": "import { PathItem, Path } from 'ui-lab-components';\n\nexport default function Example() {\n return (\n <Path>\n <PathItem href=\"/\">Home</PathItem>\n <PathItem href=\"/products\">Products</PathItem>\n <PathItem href=\"/products/electronics\">Electronics</PathItem>\n <PathItem>Laptop</PathItem>\n </Path>\n );\n}"
|
|
3461
|
-
},
|
|
3462
2794
|
{
|
|
3463
2795
|
"title": "Basic Path",
|
|
3464
2796
|
"description": "A simple path navigation showing the current page location. Use this to help users understand their position in the site hierarchy.",
|
|
@@ -3717,7 +3049,7 @@ export const generatedAPI = {
|
|
|
3717
3049
|
"description": "Padding on the top and bottom of the scrollbar track in pixels"
|
|
3718
3050
|
},
|
|
3719
3051
|
{
|
|
3720
|
-
"name": "
|
|
3052
|
+
"name": "fade-y",
|
|
3721
3053
|
"type": "boolean",
|
|
3722
3054
|
"required": false,
|
|
3723
3055
|
"defaultValue": "false",
|
|
@@ -3751,6 +3083,13 @@ export const generatedAPI = {
|
|
|
3751
3083
|
"defaultValue": "true",
|
|
3752
3084
|
"description": "Whether to hide the scrollbar when not actively scrolling"
|
|
3753
3085
|
},
|
|
3086
|
+
{
|
|
3087
|
+
"name": "inset",
|
|
3088
|
+
"type": "boolean",
|
|
3089
|
+
"required": false,
|
|
3090
|
+
"defaultValue": "false",
|
|
3091
|
+
"description": "When true, the scrollbar sits inline displacing content; when false (default), it overlays the content"
|
|
3092
|
+
},
|
|
3754
3093
|
{
|
|
3755
3094
|
"name": "styles",
|
|
3756
3095
|
"type": "ScrollStylesProp",
|
|
@@ -4211,23 +3550,44 @@ export const generatedAPI = {
|
|
|
4211
3550
|
"description": "Combobox-style input that opens the dropdown on focus and filters items as you type",
|
|
4212
3551
|
"props": [
|
|
4213
3552
|
{
|
|
4214
|
-
"name": "
|
|
4215
|
-
"type": "
|
|
3553
|
+
"name": "styles",
|
|
3554
|
+
"type": "SearchableTriggerStylesProp",
|
|
4216
3555
|
"required": false,
|
|
4217
|
-
"description": "
|
|
3556
|
+
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
4218
3557
|
},
|
|
4219
3558
|
{
|
|
4220
|
-
"name": "
|
|
4221
|
-
"type": "
|
|
3559
|
+
"name": "icon",
|
|
3560
|
+
"type": "ReactNode | InputIconSlots",
|
|
4222
3561
|
"required": false,
|
|
4223
|
-
"
|
|
4224
|
-
"description": "Placeholder text shown when the input is empty"
|
|
3562
|
+
"description": "Icon displayed before the input value by default, or in named prefix/suffix slots"
|
|
4225
3563
|
},
|
|
4226
3564
|
{
|
|
4227
|
-
"name": "
|
|
4228
|
-
"type": "
|
|
3565
|
+
"name": "variant",
|
|
3566
|
+
"type": "ghost | default",
|
|
4229
3567
|
"required": false,
|
|
4230
|
-
"description": "
|
|
3568
|
+
"description": "Controls the visual style of the input",
|
|
3569
|
+
"enumValues": [
|
|
3570
|
+
"ghost",
|
|
3571
|
+
"default"
|
|
3572
|
+
]
|
|
3573
|
+
},
|
|
3574
|
+
{
|
|
3575
|
+
"name": "error",
|
|
3576
|
+
"type": "boolean",
|
|
3577
|
+
"required": false,
|
|
3578
|
+
"description": "Whether the input is in an error state"
|
|
3579
|
+
},
|
|
3580
|
+
{
|
|
3581
|
+
"name": "actions",
|
|
3582
|
+
"type": "InputAction[] | InputActionSlots",
|
|
3583
|
+
"required": false,
|
|
3584
|
+
"description": "Inline actions rendered on the left or right side of the input. Passing an array keeps the existing right-side behavior."
|
|
3585
|
+
},
|
|
3586
|
+
{
|
|
3587
|
+
"name": "hint",
|
|
3588
|
+
"type": "ReactNode",
|
|
3589
|
+
"required": false,
|
|
3590
|
+
"description": "Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts."
|
|
4231
3591
|
}
|
|
4232
3592
|
]
|
|
4233
3593
|
}
|
|
@@ -4241,7 +3601,7 @@ export const generatedAPI = {
|
|
|
4241
3601
|
{
|
|
4242
3602
|
"title": "Searchable Select",
|
|
4243
3603
|
"description": "A filterable select component with search input. Type to filter through a large list of options.",
|
|
4244
|
-
"code": "import React from 'react';\nimport { Select, Searchable } from 'ui-lab-components';\n\nconst countries = [\n { value: 'us', label: 'United States' },\n { value: 'ca', label: 'Canada' },\n { value: 'mx', label: 'Mexico' },\n { value: 'br', label: 'Brazil' },\n { value: 'ar', label: 'Argentina' },\n { value: 'uk', label: 'United Kingdom' },\n { value: 'fr', label: 'France' },\n { value: 'de', label: 'Germany' },\n { value: 'it', label: 'Italy' },\n { value: 'es', label: 'Spain' },\n { value: 'pt', label: 'Portugal' },\n { value: 'nl', label: 'Netherlands' },\n { value: 'be', label: 'Belgium' },\n { value: 'ch', label: 'Switzerland' },\n { value: 'at', label: 'Austria' },\n { value: 'se', label: 'Sweden' },\n { value: 'no', label: 'Norway' },\n { value: 'dk', label: 'Denmark' },\n { value: 'fi', label: 'Finland' },\n { value: 'pl', label: 'Poland' },\n { value: 'jp', label: 'Japan' },\n { value: 'cn', label: 'China' },\n { value: 'kr', label: 'South Korea' },\n { value: 'in', label: 'India' },\n { value: 'au', label: 'Australia' },\n { value: 'nz', label: 'New Zealand' },\n];\n\nexport default function Example() {\n return (\n <Select>\n <Searchable.
|
|
3604
|
+
"code": "import React from 'react';\nimport { Select, Searchable } from 'ui-lab-components';\n\nconst countries = [\n { value: 'us', label: 'United States' },\n { value: 'ca', label: 'Canada' },\n { value: 'mx', label: 'Mexico' },\n { value: 'br', label: 'Brazil' },\n { value: 'ar', label: 'Argentina' },\n { value: 'uk', label: 'United Kingdom' },\n { value: 'fr', label: 'France' },\n { value: 'de', label: 'Germany' },\n { value: 'it', label: 'Italy' },\n { value: 'es', label: 'Spain' },\n { value: 'pt', label: 'Portugal' },\n { value: 'nl', label: 'Netherlands' },\n { value: 'be', label: 'Belgium' },\n { value: 'ch', label: 'Switzerland' },\n { value: 'at', label: 'Austria' },\n { value: 'se', label: 'Sweden' },\n { value: 'no', label: 'Norway' },\n { value: 'dk', label: 'Denmark' },\n { value: 'fi', label: 'Finland' },\n { value: 'pl', label: 'Poland' },\n { value: 'jp', label: 'Japan' },\n { value: 'cn', label: 'China' },\n { value: 'kr', label: 'South Korea' },\n { value: 'in', label: 'India' },\n { value: 'au', label: 'Australia' },\n { value: 'nz', label: 'New Zealand' },\n];\n\nexport default function Example() {\n return (\n <Select>\n <Searchable.Input placeholder=\"Search countries...\" />\n <Searchable.Content searchPlaceholder=\"Type to filter...\">\n {countries.map((country) => (\n <Select.Item key={country.value} value={country.value} textValue={country.label}>\n {country.label}\n </Select.Item>\n ))}\n </Searchable.Content>\n </Select>\n );\n}"
|
|
4245
3605
|
}
|
|
4246
3606
|
]
|
|
4247
3607
|
},
|
|
@@ -4502,110 +3862,16 @@ export const generatedAPI = {
|
|
|
4502
3862
|
"description": "Custom styles for the component slots"
|
|
4503
3863
|
}
|
|
4504
3864
|
],
|
|
4505
|
-
"subComponents": {
|
|
4506
|
-
"TabsList": {
|
|
4507
|
-
"description": "Container for the row of tab trigger buttons",
|
|
4508
|
-
"props": [
|
|
4509
|
-
{
|
|
4510
|
-
"name": "className",
|
|
4511
|
-
"type": "string",
|
|
4512
|
-
"required": false,
|
|
4513
|
-
"description": "Additional CSS class names"
|
|
4514
|
-
},
|
|
4515
|
-
{
|
|
4516
|
-
"name": "aria-label",
|
|
4517
|
-
"type": "string",
|
|
4518
|
-
"required": false,
|
|
4519
|
-
"description": "Accessible label for the tab list"
|
|
4520
|
-
},
|
|
4521
|
-
{
|
|
4522
|
-
"name": "styles",
|
|
4523
|
-
"type": "StylesProp<TabsListStyleSlots>",
|
|
4524
|
-
"required": false,
|
|
4525
|
-
"description": "Custom styles for the component slots"
|
|
4526
|
-
}
|
|
4527
|
-
]
|
|
4528
|
-
},
|
|
4529
|
-
"Tab": {
|
|
4530
|
-
"description": "A tab button that activates its associated content panel",
|
|
4531
|
-
"props": [
|
|
4532
|
-
{
|
|
4533
|
-
"name": "value",
|
|
4534
|
-
"type": "string",
|
|
4535
|
-
"required": true,
|
|
4536
|
-
"description": "Unique identifier matching the associated TabsContent value"
|
|
4537
|
-
},
|
|
4538
|
-
{
|
|
4539
|
-
"name": "disabled",
|
|
4540
|
-
"type": "boolean",
|
|
4541
|
-
"required": false,
|
|
4542
|
-
"defaultValue": "false",
|
|
4543
|
-
"description": "Whether the tab trigger is disabled"
|
|
4544
|
-
},
|
|
4545
|
-
{
|
|
4546
|
-
"name": "icon",
|
|
4547
|
-
"type": "ReactNode",
|
|
4548
|
-
"required": false,
|
|
4549
|
-
"description": "Icon element displayed before the tab label"
|
|
4550
|
-
},
|
|
4551
|
-
{
|
|
4552
|
-
"name": "className",
|
|
4553
|
-
"type": "string",
|
|
4554
|
-
"required": false,
|
|
4555
|
-
"description": "Additional CSS class names"
|
|
4556
|
-
},
|
|
4557
|
-
{
|
|
4558
|
-
"name": "styles",
|
|
4559
|
-
"type": "StylesProp<TabsTriggerStyleSlots>",
|
|
4560
|
-
"required": false,
|
|
4561
|
-
"description": "Custom styles for the component slots"
|
|
4562
|
-
},
|
|
4563
|
-
{
|
|
4564
|
-
"name": "_registerDisabled",
|
|
4565
|
-
"type": "((value: string) => void)",
|
|
4566
|
-
"required": false
|
|
4567
|
-
},
|
|
4568
|
-
{
|
|
4569
|
-
"name": "_unregisterDisabled",
|
|
4570
|
-
"type": "((value: string) => void)",
|
|
4571
|
-
"required": false
|
|
4572
|
-
}
|
|
4573
|
-
]
|
|
4574
|
-
},
|
|
4575
|
-
"TabsContent": {
|
|
4576
|
-
"description": "Content panel shown when its corresponding tab is active",
|
|
4577
|
-
"props": [
|
|
4578
|
-
{
|
|
4579
|
-
"name": "value",
|
|
4580
|
-
"type": "string",
|
|
4581
|
-
"required": true,
|
|
4582
|
-
"description": "Unique identifier matching the associated TabsTrigger value"
|
|
4583
|
-
},
|
|
4584
|
-
{
|
|
4585
|
-
"name": "className",
|
|
4586
|
-
"type": "string",
|
|
4587
|
-
"required": false,
|
|
4588
|
-
"description": "Additional CSS class names"
|
|
4589
|
-
},
|
|
4590
|
-
{
|
|
4591
|
-
"name": "styles",
|
|
4592
|
-
"type": "StylesProp<TabsContentStyleSlots>",
|
|
4593
|
-
"required": false,
|
|
4594
|
-
"description": "Custom styles for the component slots"
|
|
4595
|
-
}
|
|
4596
|
-
]
|
|
4597
|
-
}
|
|
4598
|
-
},
|
|
4599
3865
|
"examples": [
|
|
4600
3866
|
{
|
|
4601
3867
|
"title": "Basic Tabs",
|
|
4602
3868
|
"description": "A simple tabbed interface with content switching. Use this to organize related content into separate views.",
|
|
4603
|
-
"code": "import React from 'react';\nimport { Tabs
|
|
3869
|
+
"code": "import React from 'react';\nimport { Tabs } from 'ui-lab-components';\n\nexport default function Example() {\n return (\n <Tabs defaultValue=\"overview\">\n <Tabs.List aria-label=\"Content sections\">\n <Tabs.Trigger value=\"overview\">Overview</Tabs.Trigger>\n <Tabs.Trigger value=\"details\">Details</Tabs.Trigger>\n <Tabs.Trigger value=\"settings\">Settings</Tabs.Trigger>\n </Tabs.List>\n <Tabs.Content value=\"overview\">\n <p>Overview content goes here.</p>\n </Tabs.Content>\n <Tabs.Content value=\"details\">\n <p>Details content goes here.</p>\n </Tabs.Content>\n <Tabs.Content value=\"settings\">\n <p>Settings content goes here.</p>\n </Tabs.Content>\n </Tabs>\n );\n}"
|
|
4604
3870
|
},
|
|
4605
3871
|
{
|
|
4606
3872
|
"title": "Vertical Tabs (Sidebar)",
|
|
4607
3873
|
"description": "A vertical tab layout ideal for settings pages or sidebar navigation. Tabs are stacked vertically with content panels beside them.",
|
|
4608
|
-
"code": "import React from 'react';\nimport { Tabs,
|
|
3874
|
+
"code": "import React from 'react';\nimport { Tabs, Card } from 'ui-lab-components';\nimport { User, Settings, Bell, Shield } from 'lucide-react';\n\nexport default function Example() {\n return (\n <div className=\"flex items-center justify-center bg-background-950 p-4 min-h-[400px]\">\n <Card className=\"w-full max-w-2xl\">\n <Tabs defaultValue=\"profile\" className=\"flex flex-row\">\n {/* Vertical tab list - styled as sidebar */}\n <Tabs.List\n aria-label=\"Settings sections\"\n className=\"flex-col items-stretch justify-start h-auto w-48 border-r border-background-700 rounded-none bg-transparent p-2\"\n >\n <Tabs.Trigger value=\"profile\" icon={<User className=\"w-4 h-4\" />} className=\"justify-start\">\n Profile\n </Tabs.Trigger>\n <Tabs.Trigger value=\"notifications\" icon={<Bell className=\"w-4 h-4\" />} className=\"justify-start\">\n Notifications\n </Tabs.Trigger>\n <Tabs.Trigger value=\"security\" icon={<Shield className=\"w-4 h-4\" />} className=\"justify-start\">\n Security\n </Tabs.Trigger>\n <Tabs.Trigger value=\"preferences\" icon={<Settings className=\"w-4 h-4\" />} className=\"justify-start\">\n Preferences\n </Tabs.Trigger>\n </Tabs.List>\n\n {/* Content panels */}\n <div className=\"flex-1 p-6\">\n <Tabs.Content value=\"profile\" className=\"mt-0\">\n <h3 className=\"text-lg font-semibold text-foreground-100 mb-2\">Profile Settings</h3>\n <p className=\"text-foreground-400 text-sm mb-4\">\n Manage your personal information and how others see you on the platform.\n </p>\n <div className=\"space-y-3\">\n <div className=\"h-10 w-full bg-background-800 rounded border border-background-700\" />\n <div className=\"h-10 w-full bg-background-800 rounded border border-background-700\" />\n <div className=\"h-10 w-2/3 bg-background-800 rounded border border-background-700\" />\n </div>\n </Tabs.Content>\n\n <Tabs.Content value=\"notifications\" className=\"mt-0\">\n <h3 className=\"text-lg font-semibold text-foreground-100 mb-2\">Notification Preferences</h3>\n <p className=\"text-foreground-400 text-sm mb-4\">\n Control how and when you receive alerts and updates.\n </p>\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"h-5 w-5 bg-accent-500 rounded\" />\n <div className=\"h-4 w-32 bg-background-800 rounded\" />\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"h-5 w-5 bg-background-700 rounded\" />\n <div className=\"h-4 w-40 bg-background-800 rounded\" />\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"h-5 w-5 bg-accent-500 rounded\" />\n <div className=\"h-4 w-28 bg-background-800 rounded\" />\n </div>\n </div>\n </Tabs.Content>\n\n <Tabs.Content value=\"security\" className=\"mt-0\">\n <h3 className=\"text-lg font-semibold text-foreground-100 mb-2\">Security Settings</h3>\n <p className=\"text-foreground-400 text-sm mb-4\">\n Protect your account with passwords, two-factor authentication, and more.\n </p>\n <div className=\"space-y-3\">\n <div className=\"p-3 bg-background-800 rounded border border-background-700\">\n <div className=\"h-4 w-24 bg-background-700 rounded mb-2\" />\n <div className=\"h-3 w-48 bg-background-700/50 rounded\" />\n </div>\n <div className=\"p-3 bg-background-800 rounded border border-background-700\">\n <div className=\"h-4 w-32 bg-background-700 rounded mb-2\" />\n <div className=\"h-3 w-40 bg-background-700/50 rounded\" />\n </div>\n </div>\n </Tabs.Content>\n\n <Tabs.Content value=\"preferences\" className=\"mt-0\">\n <h3 className=\"text-lg font-semibold text-foreground-100 mb-2\">General Preferences</h3>\n <p className=\"text-foreground-400 text-sm mb-4\">\n Customize your experience with theme, language, and display options.\n </p>\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"h-20 bg-background-800 rounded border border-background-700\" />\n <div className=\"h-20 bg-background-800 rounded border border-background-700\" />\n <div className=\"h-20 bg-background-800 rounded border border-background-700\" />\n <div className=\"h-20 bg-background-800 rounded border border-background-700\" />\n </div>\n </Tabs.Content>\n </div>\n </Tabs>\n </Card>\n </div>\n );\n}"
|
|
4609
3875
|
}
|
|
4610
3876
|
]
|
|
4611
3877
|
},
|
|
@@ -4818,73 +4084,73 @@ export const generatedAPI = {
|
|
|
4818
4084
|
}
|
|
4819
4085
|
};
|
|
4820
4086
|
export const generatedStyles = {
|
|
4821
|
-
"anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n @apply inline;\n }\n\n .preview {\n @apply inline;\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background);\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n}\n",
|
|
4822
|
-
"badge": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2 px-3 py-0.5;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n }\n\n .badge.sm {\n @apply px-1.5 py-px;\n gap: 0.25rem;\n font-size: var(--text-
|
|
4823
|
-
"banner": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\n font-size: var(--text-
|
|
4824
|
-
"button": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n
|
|
4087
|
+
"anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n @apply inline;\n }\n\n .preview {\n @apply inline;\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover);\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background);\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n}\n",
|
|
4088
|
+
"badge": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2 px-3 py-0.5;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--background-border);\n border-radius: var(--radius-sm);\n }\n\n .badge.sm {\n @apply px-1.5 py-px;\n gap: 0.25rem;\n font-size: var(--text-sm);\n }\n\n .badge.dismissible {\n @apply pr-0.5;\n }\n\n .badge.md {\n @apply px-3.5 py-1;\n font-size: var(--text-sm);\n }\n\n .badge.lg {\n @apply px-4 py-2.5;\n font-size: var(--text-sm);\n }\n\n .pill { border-radius: 9999px; }\n\n .icon {\n @apply flex items-center shrink-0;\n }\n\n .dismiss {\n @apply ml-1 flex items-center justify-center p-1 cursor-pointer;\n border-radius: var(--radius-xs);\n background: transparent;\n border: none;\n color: var(--dismiss-color);\n transition: opacity 150ms var(--ease-snappy-pop), transform 150ms var(--ease-snappy-pop);\n outline: none;\n }\n\n .dismiss-button[data-hovered=\"true\"] {\n background: var(--dismiss-hover-background);\n }\n\n .dismiss-button[data-pressed=\"true\"] {\n background: var(--dismiss-pressed-background);\n transform: scale(0.95);\n }\n\n .dismiss-button[data-focus-visible=\"true\"] {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n}\n",
|
|
4089
|
+
"banner": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n line-height: var(--leading-normal);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out, border-color 0.15s ease-out;\n }\n\n .content {\n @apply flex flex-col gap-2;\n }\n\n .icon-container {\n @apply flex shrink-0 items-center justify-center self-start;\n }\n\n .icon {\n @apply mr-4 h-5 w-5;\n color: var(--icon-color, currentColor);\n }\n\n .dismiss {\n @apply flex h-8 w-8 shrink-0 items-center justify-center p-0 cursor-pointer;\n background-color: transparent;\n color: currentColor;\n border: none;\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n }\n\n .title {\n font-weight: var(--font-weight-semibold);\n font-size: inherit;\n line-height: var(--leading-tight);\n @apply my-0;\n }\n\n .body {\n font-weight: var(--font-weight-medium);\n font-size: inherit;\n line-height: var(--leading-normal);\n @apply my-0;\n }\n}\n\n\n.banner.sm {\n @apply px-3 py-2;\n font-size: var(--text-sm);\n}\n\n.banner.md {\n @apply px-4 py-3;\n font-size: var(--text-sm);\n}\n\n.banner.lg {\n @apply px-6 py-4;\n font-size: var(--text-sm);\n}\n",
|
|
4090
|
+
"button": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:active:not(:disabled) {\n filter: brightness(0.8);\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 1.5px var(--focus-visible);\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n filter: grayscale(0.5);\n }\n }\n}\n",
|
|
4825
4091
|
"card": "@reference \"tailwindcss\";\n\n@layer components {\n .card {\n @apply overflow-hidden;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n }\n\n .card[data-focused=\"true\"] {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n }\n\n .header {\n @apply p-4;\n border-bottom: var(--border-width-base) solid var(--border);\n }\n\n .body {\n @apply px-4 py-2;\n }\n\n .footer {\n @apply px-2 py-2;\n background-color: var(--background);\n border-top: var(--border-width-base) solid var(--border);\n }\n}\n",
|
|
4826
|
-
"checkbox": "@reference \"tailwindcss\";\n\n@layer components {\n /* Hidden input element positioned behind visual checkbox */\n .checkbox-input {\n @apply absolute inset-0 h-full w-full cursor-pointer;\n opacity: 0;\n }\n\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .checkbox-container {\n @apply relative inline-flex items-center justify-center;\n }\n\n /* Visual checkbox */\n .checkbox {\n --disabled-opacity: 0.6;\n\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-xs);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n /* Interactive States */\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:focus-visible {\n outline: 2px solid transparent;\n box-shadow: 0 0 0 3px var(--ring-color);\n }\n\n &[data-pressed=\"true\"] {\n transform: scale(0.92);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n /* Checkmark and Indeterminate styles - combined */\n .checkbox-checkmark,\n .checkbox-indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--checkmark-color);\n pointer-events: none;\n }\n\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .label-sm {\n font-size: var(--text-
|
|
4092
|
+
"checkbox": "@reference \"tailwindcss\";\n\n@layer components {\n /* Hidden input element positioned behind visual checkbox */\n .checkbox-input {\n @apply absolute inset-0 h-full w-full cursor-pointer;\n opacity: 0;\n }\n\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .checkbox-container {\n @apply relative inline-flex items-center justify-center;\n }\n\n /* Visual checkbox */\n .checkbox {\n --disabled-opacity: 0.6;\n\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-xs);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n /* Interactive States */\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:focus-visible {\n outline: 2px solid transparent;\n box-shadow: 0 0 0 3px var(--ring-color);\n }\n\n &[data-pressed=\"true\"] {\n transform: scale(0.92);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n /* Checkmark and Indeterminate styles - combined */\n .checkbox-checkmark,\n .checkbox-indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--checkmark-color);\n pointer-events: none;\n }\n\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .label-sm {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-md {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-lg {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-disabled {\n @apply opacity-60 cursor-not-allowed;\n }\n\n .helper-text {\n @apply text-sm ml-8;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .helper-text-normal { color: inherit; }\n\n .helper-text-error { color: var(--error-color); }\n}\n",
|
|
4827
4093
|
"code": "@reference \"tailwindcss\";\n\n@layer components {\n .code {\n --border-color: var(--background-700);\n --header-bg: mix(var(--background-900) 90%, transparent);\n --scroll-track-bg: mix(var(--background-950) 50%, transparent);\n\n max-height: 52.5rem;\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-color);\n @apply flex w-full min-w-0 flex-col overflow-hidden;\n }\n\n .header {\n flex: none;\n background-color: var(--header-bg);\n @apply flex items-center justify-between px-3 py-1.5;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-semibold);\n border-bottom: 1px solid var(--border-color);\n color: var(--foreground-400);\n }\n\n\n .body {\n @apply relative flex min-h-0 flex-1 flex-col;\n flex: 1;\n }\n\n .viewport { @apply overflow-hidden; }\n\n .viewport :global(pre) {\n background: transparent;\n @apply m-0 p-0;\n width: fit-content;\n }\n\n .viewport :global(code) {\n color: var(--foreground-300);\n white-space: pre;\n }\n\n .viewport::-webkit-scrollbar {\n width: 0.5rem;\n }\n\n .viewport::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .viewport::-webkit-scrollbar-thumb {\n background: var(--background-700);\n border-radius: 9999px;\n }\n\n .viewport::-webkit-scrollbar-thumb:hover {\n background: var(--background-600);\n }\n\n .scroll-track {\n flex: none;\n @apply w-full;\n overflow-x: auto;\n background-color: var(--scroll-track-bg);\n backdrop-filter: blur(4px);\n }\n\n .expand-button {\n @apply flex w-full items-center gap-3 px-4 py-2 cursor-pointer;\n color: var(--foreground-300);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n transition: background-color 0.15s ease-out;\n border-top: 1px solid var(--border-color);\n background: transparent;\n border-left: none;\n border-right: none;\n border-bottom: none;\n font-family: inherit;\n }\n\n .expand-button:hover { background-color: var(--background-800); }\n\n .expand-icon { @apply shrink-0; color: var(--foreground-400); }\n\n .copy-button {\n @apply absolute right-2 top-2 flex items-center justify-center p-1 cursor-pointer;\n border-radius: var(--radius-sm);\n color: var(--foreground-400);\n opacity: 0;\n transition: opacity 0.15s ease-out, background-color 0.15s ease-out, color 0.15s ease-out;\n background: transparent;\n border: none;\n z-index: 1;\n }\n\n .copy-button:hover { background-color: var(--background-800); color: var(--foreground-300); }\n\n .copy-button:focus, .body:hover .copy-button { opacity: 1; }\n}\n",
|
|
4828
4094
|
"color": "@reference \"tailwindcss\";\n\n@layer components {\n .color {\n --disabled-opacity: 0.5;\n @apply flex flex-col gap-4;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n width: 260px;\n }\n\n .color[data-disabled=\"true\"] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n .colorControls,\n .color-controls {\n @apply pb-3 px-3 space-y-3;\n }\n\n /* Input styles */\n .inputGroup,\n .input-group {\n @apply flex w-full;\n }\n\n .inputGroup > div:nth-child(1),\n .input-group > div:nth-child(1) {\n flex: 1;\n @apply min-w-0;\n }\n\n .colorInput,\n .color-input { @apply w-full; }\n\n .formatSelect,\n .format-select { @apply shrink-0; width: 85px; }\n\n .color[data-size=\"sm\"] .formatSelect,\n .color[data-size=\"sm\"] .format-select { width: 75px; }\n\n /* Canvas Styles */\n .canvasContainer,\n .canvas-container {\n @apply relative mx-auto mt-2 flex flex-col;\n width: 96%;\n cursor: crosshair;\n touch-action: none;\n min-height: 160px;\n }\n\n .canvasContainer[data-focus-visible=\"true\"],\n .canvas-container[data-focus-visible=\"true\"] {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .canvas {\n @apply relative w-full flex-1 overflow-hidden;\n flex: 1;\n border-radius: none;\n }\n\n .canvasGradientHue,\n .canvas-gradient-hue {\n @apply absolute inset-0 overflow-hidden;\n }\n\n .canvasGradientSaturation,\n .canvasGradientLightness,\n .canvas-gradient-saturation,\n .canvas-gradient-lightness {\n @apply absolute inset-0;\n border-radius: none;\n }\n\n .canvas-gradient-saturation {\n background: linear-gradient(to right, rgb(255, 255, 255), transparent);\n }\n\n .canvas-gradient-lightness {\n background: linear-gradient(to top, rgb(0, 0, 0), transparent);\n }\n\n .canvasPointer,\n .canvas-pointer {\n @apply absolute h-3 w-3;\n border-radius: var(--radius-full);\n border: 2px solid var(--pointer-border);\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n transform: translate(-50%, -50%);\n z-index: 10;\n }\n\n /* Hue Slider Styles */\n .hueSlider,\n .hue-slider {\n @apply relative flex items-center overflow-hidden;\n height: 16px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .hueTrack,\n .hue-track {\n @apply relative h-full w-full;\n border-radius: var(--radius-full);\n background: linear-gradient(\n to right,\n hsl(0, 100%, 50%),\n hsl(60, 100%, 50%),\n hsl(120, 100%, 50%),\n hsl(180, 100%, 50%),\n hsl(240, 100%, 50%),\n hsl(300, 100%, 50%),\n hsl(360, 100%, 50%)\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .hueThumb,\n .opacityThumb,\n .hue-thumb,\n .opacity-thumb {\n @apply absolute;\n border-radius: var(--radius-full);\n border: 2px solid var(--thumb-border);\n top: 50%;\n background: var(--thumb-background);\n pointer-events: none;\n }\n\n .hueThumb,\n .hue-thumb {\n @apply h-3 w-3;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .hueSlider[data-focus-visible=\"true\"] .hueThumb,\n .hue-slider[data-focus-visible=\"true\"] .hue-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .hue-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .hue-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Opacity Slider Styles */\n .opacitySlider,\n .opacity-slider {\n @apply relative flex items-center overflow-hidden;\n height: 12px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .opacityTrack,\n .opacity-track {\n @apply relative h-full w-full overflow-hidden;\n border-radius: var(--radius-full);\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-dark),\n var(--checkerboard-dark) 10px,\n var(--checkerboard-light) 10px,\n var(--checkerboard-light) 20px\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .opacityThumb,\n .opacity-thumb {\n @apply h-2.5 w-2.5;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .opacitySlider[data-focus-visible=\"true\"] .opacityThumb,\n .opacity-slider[data-focus-visible=\"true\"] .opacity-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .opacity-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .opacity-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Recent Colors Styles */\n .recentColors,\n .recent-colors {\n @apply flex gap-2 overflow-x-auto pb-1;\n }\n\n .recentColorSwatch,\n .recent-color-swatch {\n @apply h-8 w-8 shrink-0 cursor-pointer p-0;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n background: none;\n outline: none;\n }\n\n .recentColorSwatch:hover,\n .recent-color-swatch:hover {\n transform: scale(1.1);\n box-shadow: 0 0 0 2px var(--ring-color);\n }\n\n .recentColorSwatch:active,\n .recent-color-swatch:active {\n transform: scale(0.95);\n }\n\n .recentColorSwatch:focus-visible,\n .recent-color-swatch:focus-visible {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n\n /* Preview Container - deprecated, use preview-swatch instead */\n .previewContainer,\n .preview-container {\n @apply flex justify-center py-2;\n }\n\n /* Preview Swatch - inline with input */\n .previewSwatch,\n .preview-swatch {\n @apply relative h-9 w-9 shrink-0 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n\n .previewSwatch::before,\n .preview-swatch::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 6px,\n var(--checkerboard-dark) 6px,\n var(--checkerboard-dark) 12px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .preview {\n @apply relative h-16 w-16 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n }\n\n .preview::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 10px,\n var(--checkerboard-dark) 10px,\n var(--checkerboard-dark) 20px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .previewSwatch::after,\n .preview-swatch::after,\n .preview::after {\n content: \"\";\n @apply absolute inset-0;\n background-color: var(--preview-color, transparent);\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 2;\n }\n}\n",
|
|
4829
|
-
"command": "@reference \"tailwindcss\";\n\n@layer components {\n /* Overlay Container */\n .overlay {\n @apply fixed inset-0 flex items-start justify-center overflow-hidden;\n z-index: 999;\n padding-top: 20vh;\n /* Apply backdrop styles directly to avoid creating a containing block that disrupts sticky elements */\n background-color: var(--overlay);\n backdrop-filter: var(--overlay-backdrop);\n }\n\n /* Content */\n .content {\n @apply relative m-2 w-full max-w-[28rem];\n border-radius: var(--radius-sm);\n background: var(--background-default);\n margin-inline: 1rem;\n box-shadow: var(--shadow);\n animation: fade-in-zoom-in 0.2s ease-out;\n }\n\n .inner {\n border-radius: var(--radius-sm) var(--radius-sm) 0 0;\n border-top: var(--border-width-base) solid var(--border-default);\n @apply overflow-hidden;\n }\n\n /* Search Section */\n .search {\n @apply border-none flex p-
|
|
4095
|
+
"command": "@reference \"tailwindcss\";\n\n:global(.command) :global(.input) {\n --background: var(--background-input);\n}\n\n@layer components {\n /* Overlay Container */\n .overlay {\n @apply fixed inset-0 flex items-start justify-center overflow-hidden;\n z-index: 999;\n padding-top: 20vh;\n /* Apply backdrop styles directly to avoid creating a containing block that disrupts sticky elements */\n background-color: var(--overlay);\n backdrop-filter: var(--overlay-backdrop);\n }\n\n /* Content */\n .content {\n @apply relative m-2 w-full max-w-[28rem];\n border-radius: var(--radius-sm);\n background: var(--background-default);\n margin-inline: 1rem;\n box-shadow: var(--shadow);\n animation: fade-in-zoom-in 0.2s ease-out;\n }\n\n .inner {\n border-radius: var(--radius-sm) var(--radius-sm) 0 0;\n border-top: var(--border-width-base) solid var(--border-default);\n @apply overflow-hidden;\n }\n\n /* Search Section */\n .search {\n @apply border-none flex p-1.5;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .input {\n border-color: transparent;\n background: transparent;\n box-shadow: none;\n\n &[data-active],\n &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n\n /* List Section */\n .list {\n @apply py-0.5 px-2 space-y-2;\n background-color: var(--list-background);\n }\n\n .list :global([role=\"listbox\"]) {\n @apply flex w-full flex-col;\n }\n\n .item {\n @apply flex items-center justify-between rounded-sm px-2 py-0.5 cursor-pointer;\n border-radius: 0.375rem;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .item:hover {\n background-color: var(--background-hover);\n }\n\n .item[data-highlighted=\"true\"] {\n background-color: var(--background-selected);\n }\n\n .item-content {\n @apply flex min-w-0 flex-1 items-center gap-2.5;\n flex: 1;\n }\n\n .item-icon {\n @apply flex h-6 w-6 shrink-0 items-center justify-center;\n color: var(--color-icon);\n }\n\n .item-labels {\n flex: 1;\n @apply min-w-0;\n }\n\n .item-label {\n font-size: var(--text-sm);\n color: var(--color-default);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n color: var(--color-muted);\n font-size: 0.875rem;\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .hint-wrapper {\n @apply flex items-center;\n }\n\n .category-header {\n @apply px-2 py-1.5 mt-2 first:mt-0;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-semibold);\n color: var(--color-muted);\n }\n\n /* Empty State */\n .empty {\n padding: 1.5rem 1rem;\n text-align: center;\n font-size: 0.875rem;\n color: var(--color-muted);\n }\n\n /* Footer */\n .footer {\n @apply flex w-full items-center gap-2 px-1.5 py-2;\n background-color: var(--footer-background);\n border-top: 1px solid var(--border-default);\n justify-content: flex-between;\n }\n\n /* Animations */\n @keyframes fade-in-zoom-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n}\n",
|
|
4830
4096
|
"confirm": "@reference \"tailwindcss\";\n\n@layer components {\n .container {\n @apply flex flex-col;\n }\n\n .card {\n @apply max-w-[28rem];\n }\n\n .card-compact {\n @apply max-w-[21rem];\n }\n\n .dialog-overlay {\n @apply fixed inset-0 z-50 flex items-center justify-center;\n background-color: mix(var(--background-950) 50%, transparent);\n }\n\n .dialog-card {\n @apply max-w-[28rem];\n margin: 0 1rem;\n }\n\n .header {\n @apply flex items-start gap-3;\n }\n\n .header-content {\n @apply flex-1;\n }\n\n .header-title {\n @apply font-semibold;\n color: var(--foreground-100);\n }\n\n .body {\n @apply flex flex-col gap-4;\n }\n\n .body-compact {\n @apply gap-3;\n }\n\n .description {\n font-size: var(--text-sm);\n color: var(--foreground-300);\n }\n\n .error-message {\n font-size: var(--text-sm);\n color: var(--foreground-danger);\n }\n\n .warning-box {\n @apply p-3 rounded-sm;\n border: var(--border-width-base) solid;\n font-size: var(--text-sm);\n }\n\n .warning-box-low {\n background-color: mix(var(--background-info) 20%, transparent);\n border-color: mix(var(--background-info) 30%, transparent);\n color: var(--foreground-200);\n }\n\n .warning-box-medium {\n background-color: mix(var(--background-warning) 20%, transparent);\n border-color: mix(var(--background-warning) 30%, transparent);\n color: var(--foreground-200);\n }\n\n .warning-box-high {\n background-color: mix(var(--background-warning-dark) 20%, transparent);\n border-color: mix(var(--background-warning-dark) 30%, transparent);\n color: var(--foreground-200);\n }\n\n .warning-box-critical {\n background-color: mix(var(--background-danger) 20%, transparent);\n border-color: mix(var(--background-danger) 30%, transparent);\n color: var(--foreground-200);\n }\n\n .countdown-text {\n font-size: var(--text-sm);\n color: var(--foreground-400);\n }\n\n .input-label {\n font-size: var(--text-sm);\n margin-left: 0.25rem;\n color: var(--foreground-300);\n }\n\n .input {\n @apply w-full mt-2 px-3 py-2 rounded-sm transition-all duration-200;\n background-color: var(--background-800);\n border: var(--border-width-base) solid var(--background-700);\n color: var(--foreground-100);\n font-size: var(--text-sm);\n\n &:focus-visible {\n outline: 2px solid var(--accent-500);\n outline-offset: 2px;\n }\n }\n\n .actions {\n @apply flex gap-2;\n }\n\n .actions-inline {\n @apply flex-row;\n }\n\n .actions-dialog {\n @apply flex-row justify-end;\n }\n}\n",
|
|
4831
|
-
"date": "@reference \"tailwindcss\";\n\n@layer components {\n .calendar {\n --disabled-opacity: 0.5;\n\n @apply inline-flex flex-col overflow-hidden gap-0;\n border-radius: var(--radius-md);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n }\n\n .day-headers {\n @apply grid gap-2 px-4 pt-3 pb-1;\n grid-template-columns: repeat(7, 1fr);\n background: var(--day-headers-background);\n border-top: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-md) var(--radius-md) 0 0;\n }\n\n .day-header {\n @apply flex items-center justify-center;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-
|
|
4097
|
+
"date": "@reference \"tailwindcss\";\n\n@layer components {\n .calendar {\n --disabled-opacity: 0.5;\n\n @apply inline-flex flex-col overflow-hidden gap-0;\n border-radius: var(--radius-md);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n }\n\n .day-headers {\n @apply grid gap-2 px-4 pt-3 pb-1;\n grid-template-columns: repeat(7, 1fr);\n background: var(--day-headers-background);\n border-top: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-md) var(--radius-md) 0 0;\n }\n\n .day-header {\n @apply flex items-center justify-center;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n color: var(--day-header-color);\n }\n\n .header {\n @apply flex items-center justify-between gap-4 pl-2 pr-1.5 py-1.5;\n color: var(--header-color);\n }\n\n .month-year {\n @apply ml-2;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n text-align: center;\n }\n\n .nav-button {\n @apply inline-flex min-h-8 min-w-8 items-center justify-center cursor-pointer;\n border-radius: var(--radius-sm);\n background-color: transparent;\n color: var(--nav-button-color);\n border: 1px solid transparent;\n font-size: var(--text-sm);\n font-weight: 500;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .nav-button:hover { background-color: var(--nav-button-background-hover); }\n\n .nav-button:focus-visible {\n background: var(--nav-button-background-hover);\n border-radius: 0px;\n outline: 0px solid var(--accent-500);\n }\n\n .grid {\n @apply grid gap-1 px-4 pb-4;\n grid-template-columns: repeat(7, 1fr); /* 7 days only */\n background: var(--grid-background);\n border-radius: 0 0 var(--radius-sm) var(--radius-sm);\n }\n\n .day-cell {\n --cell-background: transparent;\n\n @apply flex min-h-8 items-center justify-center px-2.5 py-2 cursor-pointer;\n border-radius: var(--radius-base);\n background-color: var(--cell-background);\n color: var(--cell-text);\n border: 2px solid transparent;\n font-size: var(--text-sm);\n font-weight: 400;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .week-header {\n display: none;\n }\n\n .week-number {\n display: none;\n }\n}\n\n/* Variant states - these are outside @layer */\n.day-cell[data-selected=\"true\"] {\n font-weight: 500;\n}\n\n.day-cell[data-today=\"true\"] {\n border-color: transparent;\n}\n\n.day-cell[data-disabled=\"true\"],\n.day-cell[data-out-of-range=\"true\"] {\n opacity: var(--disabled-opacity);\n}\n\n.day-cell[data-disabled=\"true\"] { cursor: not-allowed; }\n\n.day-cell[data-focus-visible=\"true\"]:not([data-disabled=\"true\"]) { outline: 2px solid var(--focus-ring); outline-offset: 2px; }\n",
|
|
4832
4098
|
"expand": "@reference \"tailwindcss\";\n\n@layer components {\n .expand {\n --disabled-opacity: 0.6;\n\n @apply flex flex-col;\n }\n\n .expand[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .trigger {\n @apply flex w-full items-stretch justify-between p-0 text-left cursor-pointer;\n\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--trigger-foreground);\n background-color: var(--trigger-background);\n\n border: none;\n border-radius: var(--radius-sm);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &[data-disabled] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n }\n\n .icon {\n @apply flex shrink-0 items-center justify-center px-3 py-2;\n color: inherit;\n border-radius: var(--radius-sm);\n\n @media (hover: hover) {\n .trigger:not([data-disabled]):hover & {\n background-color: var(--trigger-background-hover);\n border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n }\n\n /* When the icon itself is hovered, it should be isolated and fully rounded */\n .trigger:not([data-disabled]) &:hover {\n border-radius: var(--radius-sm);\n }\n }\n }\n\n .icon > * {\n transition: transform 250ms var(--ease-smooth-settle);\n }\n\n .expand:has(.trigger[data-expanded=\"true\"]) .icon > *,\n .icon[data-expanded=\"true\"] > * {\n transform: rotate(180deg);\n }\n\n /* from=\"above\": content expands upward above the trigger */\n .expand:has(.content[data-from=\"above\"]) {\n flex-direction: column-reverse;\n\n .icon > * {\n transform: rotate(180deg);\n }\n\n &:has(.trigger[data-expanded=\"true\"]) .icon > * {\n transform: rotate(0deg);\n }\n }\n\n /* from=\"left\": content appears left of trigger */\n .expand:has(.content[data-from=\"left\"]) {\n @apply flex-row-reverse items-start;\n\n .trigger {\n @apply w-auto flex-col;\n }\n\n .icon > * {\n transform: rotate(-90deg);\n }\n\n &:has(.trigger[data-expanded=\"true\"]) .icon > * {\n transform: rotate(90deg);\n }\n }\n\n /* from=\"right\": content appears right of trigger */\n .expand:has(.content[data-from=\"right\"]) {\n @apply flex-row items-start;\n\n .trigger {\n @apply w-auto flex-col;\n }\n\n .icon > * {\n transform: rotate(90deg);\n }\n\n &:has(.trigger[data-expanded=\"true\"]) .icon > * {\n transform: rotate(-90deg);\n }\n }\n\n /* Horizontal content animation */\n .content[data-from=\"left\"],\n .content[data-from=\"right\"] {\n grid-template-rows: 1fr;\n grid-template-columns: 0fr;\n transition: grid-template-columns 300ms var(--ease-smooth-settle);\n\n &[data-expanded=\"true\"] {\n grid-template-columns: 1fr;\n }\n\n .content-inner {\n min-height: unset;\n min-width: 0;\n }\n }\n\n .title {\n @apply flex flex-1 min-w-0 items-center overflow-hidden py-2 pl-3;\n\n font-weight: var(--font-weight-medium);\n border-radius: var(--radius-sm) 0 0 var(--radius-sm);\n\n @media (hover: hover) {\n .trigger:not([data-disabled]):hover & {\n background-color: var(--trigger-background-hover);\n }\n\n /* When icon is hovered, remove background from title */\n .trigger:not([data-disabled]):has(.icon:hover) & {\n background-color: transparent;\n }\n }\n\n .trigger:not([data-disabled]) {\n background-color: transparent;\n }\n }\n\n .content {\n @apply grid overflow-hidden;\n grid-template-rows: 0fr;\n transition: grid-template-rows 300ms var(--ease-smooth-settle);\n\n &[data-expanded=\"true\"] {\n grid-template-rows: 1fr;\n }\n }\n\n .content-inner {\n @apply min-h-0 overflow-hidden;\n color: var(--content-foreground);\n background-color: var(--content-background);\n }\n\n .expand:has(.trigger[data-disabled]) {\n pointer-events: none;\n }\n}\n",
|
|
4833
4099
|
"flex": "@reference \"tailwindcss\";\n\n@layer components {\n .flex {\n @apply flex w-full flex-row;\n flex-wrap: nowrap;\n gap: var(--spacing-md);\n justify-content: flex-start;\n align-items: stretch;\n }\n\n /* Direction variants */\n .flex.row { flex-direction: row; }\n .flex.column { flex-direction: column; }\n\n /* Wrap variants */\n .flex.wrap { flex-wrap: wrap; }\n .flex.nowrap { flex-wrap: nowrap; }\n\n /* Gap variants */\n .flex.gap-xs { gap: var(--spacing-xs); }\n .flex.gap-sm { gap: var(--spacing-sm); }\n .flex.gap-md { gap: var(--spacing-md); }\n .flex.gap-lg { gap: var(--spacing-lg); }\n .flex.gap-xl { gap: var(--spacing-xl); }\n\n /* Justify-content variants */\n .flex.justify-flex-start { justify-content: flex-start; }\n .flex.justify-flex-end { justify-content: flex-end; }\n .flex.justify-center { justify-content: center; }\n .flex.justify-space-between { justify-content: space-between; }\n .flex.justify-space-around { justify-content: space-around; }\n .flex.justify-space-evenly { justify-content: space-evenly; }\n\n /* Align-items variants */\n .flex.align-flex-start { align-items: flex-start; }\n .flex.align-flex-end { align-items: flex-end; }\n .flex.align-center { align-items: center; }\n .flex.align-stretch { align-items: stretch; }\n .flex.align-baseline { align-items: baseline; }\n\n /* Container query parent - establishes containment context */\n .container-query-parent {\n container-type: inline-size;\n container-name: flex-parent;\n @apply w-full;\n }\n\n /* Container query responsive behavior - use .flex.container-responsive for specificity parity with base variants */\n @container flex-parent (width < 400px) {\n .flex.container-responsive {\n flex-direction: column;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (400px <= width < 500px) {\n .flex.container-responsive {\n flex-wrap: wrap;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (500px <= width < 900px) {\n .flex.container-responsive {\n gap: var(--spacing-md);\n }\n }\n\n @container flex-parent (width >= 900px) {\n .flex.container-responsive {\n gap: var(--spacing-lg);\n }\n }\n}\n",
|
|
4834
4100
|
"frame": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n --frame-radius: var(--radius-sm, 24px);\n --frame-stroke-width: var(--border-width-base, 1px);\n }\n\n .shape {\n rx: var(--frame-radius);\n }\n\n .stroke {\n stroke-width: var(--frame-stroke-width);\n vector-effect: non-scaling-stroke;\n }\n\n}\n",
|
|
4835
4101
|
"gallery": "@reference \"tailwindcss\";\n\n@layer components {\n .item {\n @apply flex flex-col border overflow-hidden no-underline cursor-pointer;\n\n background: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n color: inherit;\n }\n\n .item:focus {\n outline: none;\n }\n\n .item[data-focus-visible] {\n outline: 2px solid var(--border-focus);\n outline-offset: 2px;\n }\n\n .item[data-hovered] {\n border-color: var(--border-hover);\n }\n\n .item[data-pressed] {\n border-color: var(--border-focus);\n }\n\n .item[data-orientation=\"horizontal\"] {\n @apply flex-row;\n }\n\n .item[data-orientation=\"horizontal\"] .view {\n width: var(--gallery-horizontal-view-width, 200px);\n }\n\n .view {\n --aspect-ratio: var(--gallery-aspect-ratio, 16/9);\n\n @apply relative overflow-hidden;\n aspect-ratio: var(--aspect-ratio);\n background: var(--background);\n }\n\n .view > img,\n .view > video {\n @apply w-full h-full object-cover;\n }\n\n .body {\n @apply flex flex-col gap-1 p-3 self-start min-w-0;\n }\n\n .item[data-orientation=\"horizontal\"] .body {\n flex: 1;\n align-self: stretch;\n }\n\n .body > :first-child {\n font-weight: var(--font-weight-medium);\n color: var(--title-color);\n }\n\n .body > :not(:first-child) {\n font-size: var(--text-sm);\n color: var(--description-color);\n }\n}\n",
|
|
4836
4102
|
"grid": "@reference \"tailwindcss\";\n\n@layer components {\n .grid {\n @apply grid w-full;\n grid-template-columns: var(--grid-tpl, repeat(3, 1fr));\n grid-template-rows: var(--grid-rows, auto);\n gap: var(--grid-gap, calc(var(--spacing, 0.25rem) * 4));\n justify-items: var(--grid-ji, stretch);\n align-items: var(--grid-ai, stretch);\n justify-content: var(--grid-jc, start);\n align-content: var(--grid-ac, start);\n grid-auto-flow: var(--grid-flow, row);\n }\n\n .container {\n container-type: inline-size;\n container-name: grid-ctx;\n }\n\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-sm, 1fr);\n }\n\n @media (min-width: 640px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-md, var(--grid-tpl-sm, 1fr));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-lg, var(--grid-tpl-md, var(--grid-tpl-sm, 1fr)));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-xl, var(--grid-tpl-lg, var(--grid-tpl-md, var(--grid-tpl-sm, 1fr))));\n }\n }\n\n .grid.responsive-gap {\n gap: var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 2));\n }\n\n @media (min-width: 640px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4)));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-lg, var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4))));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-xl, var(--grid-gap-lg, var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4)))));\n }\n }\n\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-sm, auto);\n }\n\n @media (min-width: 640px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-md, var(--grid-rows-sm, auto));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-lg, var(--grid-rows-md, var(--grid-rows-sm, auto)));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-xl, var(--grid-rows-lg, var(--grid-rows-md, var(--grid-rows-sm, auto))));\n }\n }\n\n .grid.has-row-gap { row-gap: var(--grid-row-gap); }\n .grid.has-col-gap { column-gap: var(--grid-col-gap); }\n\n @container grid-ctx (width < 400px) {\n .container .grid {\n grid-template-columns: 1fr;\n gap: calc(var(--spacing, 0.25rem) * 2);\n }\n }\n}\n",
|
|
4837
|
-
"group": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --radius-basis: calc(var(--spacing) * 1.5);\n --padding: var(--radius-basis);\n\n @apply flex overflow-hidden;\n width: fit-content;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n
|
|
4838
|
-
"input": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n\n @apply
|
|
4839
|
-
"label": "@reference \"tailwindcss\";\n\n@layer components {\n .label {\n display: block;\n font-family: inherit;\n font-size: var(--text-sm);\n color: var(--foreground);\n transition: color 150ms ease;\n\n &[data-size=\"sm\"] { font-size: var(--text-
|
|
4840
|
-
"list": "@reference \"tailwindcss\";\n\n@layer components {\n .container {\n @apply mx-auto;\n max-width: 28rem;\n font-family: var(--font-sans, system-ui, -apple-system, sans-serif);\n color: var(--foreground);\n }\n\n .header {\n @apply flex items-center justify-between;\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n padding-top: 1rem;\n padding-bottom: 1rem;\n backdrop-filter: blur(12px);\n z-index: 10;\n }\n\n .header.sticky { position: sticky; top: 0; }\n\n .container[data-spacing=\"sm\"] .header {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n }\n\n .header > :first-child {\n font-weight: var(--font-weight-semibold);\n font-size: 1.125rem;\n color: var(--header-title-color);\n }\n\n .header > :last-child { color: var(--header-subtitle-color); }\n\n .item {\n @apply flex flex-row items-center gap-3 px-2 py-1 cursor-pointer;\n
|
|
4103
|
+
"group": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --radius-basis: calc(var(--spacing) * 1.5);\n --padding: var(--radius-basis);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply flex overflow-hidden;\n width: fit-content;\n flex-shrink: 0;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n\n border-radius: var(--radius);\n padding: var(--padding);\n }\n\n /* Orientations */\n .group.horizontal {\n @apply flex-row items-stretch;\n }\n\n .group.vertical {\n @apply flex-col;\n }\n\n /* Spacing */\n .group.none {\n --padding: 0;\n @apply gap-0;\n }\n\n .group.xs {\n --radius-basis: calc(var(--spacing) * 0.875);\n --padding: var(--radius-basis);\n @apply space-x-0.5;\n }\n\n .group.sm {\n --radius-basis: calc(var(--spacing) * 1.25);\n --padding: var(--radius-basis);\n @apply space-x-1;\n }\n\n /* Variants */\n .group.ghost {\n border: none;\n overflow: visible;\n @apply gap-1;\n }\n\n .item {\n @apply flex min-w-0 items-stretch;\n }\n\n .item.grow {\n flex: 1;\n }\n\n .group:not(.ghost) .item .group-item,\n .group:not(.ghost) .item .group-select-wrapper {\n border: none;\n }\n\n .group:not(.ghost) .group-input-wrapper {\n --input-border-color: transparent;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .group.none:not(.ghost) .item .group-item,\n .group.none:not(.ghost) .item .group-select-wrapper,\n .group.none:not(.ghost) .item .trigger {\n border-radius: 0;\n }\n\n .group.none:not(.ghost) .group-input-wrapper {\n --input-border-radius: 0;\n }\n\n .group.none:not(.ghost) .item .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n }\n\n .group.xs:not(.ghost) .item .group-item,\n .group.xs:not(.ghost) .item .trigger,\n .group.xs:not(.ghost) .group-select-wrapper .group-item,\n .group.xs:not(.ghost) .group-select-wrapper .trigger,\n .group.sm:not(.ghost) .item .group-item,\n .group.sm:not(.ghost) .item .trigger,\n .group.sm:not(.ghost) .group-select-wrapper .group-item,\n .group.sm:not(.ghost) .group-select-wrapper .trigger {\n border-radius: var(--inner-radius);\n }\n\n .group.xs:not(.ghost) .group-input-wrapper,\n .group.sm:not(.ghost) .group-input-wrapper {\n --input-border-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .group-item {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .group-item {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .item.divider {\n @apply flex items-stretch p-0;\n }\n\n .item.divider > [role=\"separator\"] {\n @apply h-full w-full;\n }\n\n .group.horizontal .item.divider {\n margin-top: calc(var(--padding) * -1);\n margin-bottom: calc(var(--padding) * -1);\n }\n\n .group.vertical .item.divider {\n margin-left: calc(var(--padding) * -1);\n margin-right: calc(var(--padding) * -1);\n }\n\n .group.ghost:not(.none) .item .group-item:not(.active) {\n border-radius: var(--inner-radius);\n border: var(--border-width-base) solid transparent;\n }\n\n /* ghost + none: flat children — no borders or radius */\n .group.ghost.none {\n @apply gap-0;\n }\n\n .group.ghost.none .item .group-item,\n .group.ghost.none .group-item.active {\n border: none;\n border-radius: 0;\n }\n\n .group.ghost.none .group-input-wrapper {\n --input-border-color: transparent;\n --input-border-radius: 0;\n }\n\n .group.ghost.none .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n border: none;\n border-radius: 0;\n }\n\n .group:not(.ghost) .item .group-item:focus-visible,\n .group:not(.ghost) .item .trigger:focus-visible,\n .group:not(.ghost) .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group.ghost .item .group-item:focus-visible,\n .group.ghost .item .trigger:focus-visible,\n .group.ghost .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group-input-wrapper {\n @apply flex h-full items-stretch;\n flex: 1;\n overflow: visible;\n }\n\n .group-input-wrapper input {\n height: 100%;\n }\n\n .item .group-item {\n @apply flex h-full;\n }\n\n .group.vertical .item .group-item {\n @apply w-full;\n }\n\n .group.xs .item button.group-item {\n padding: calc(var(--spacing) * 1.00) calc(var(--spacing) * 1.50);\n }\n\n .group.sm .item button.group-item {\n padding: calc(var(--spacing) * 1.50) calc(var(--spacing) * 2.00);\n }\n\n .group.none .item button.group-item {\n padding: calc(var(--spacing) * 2.00) calc(var(--spacing) * 2.50);\n }\n\n .group-select-wrapper {\n @apply flex items-stretch p-0;\n border: none;\n background-color: transparent;\n }\n\n .group.none:not(.ghost) .trigger {\n border-radius: 0;\n }\n\n .trigger {\n border: none;\n }\n\n .group-select-wrapper .select {\n @apply h-full w-full;\n }\n\n .group-item.active {\n @apply relative;\n }\n\n .group.ghost .group-item.active {\n border-radius: var(--inner-radius);\n }\n\n .group:not(.ghost) .group-item.active {\n background-color: var(--active-background);\n font-weight: 500;\n }\n}\n",
|
|
4104
|
+
"input": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n\n flex: 1;\n min-width: 0;\n @apply py-2 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n color: var(--icon-color);\n pointer-events: none;\n }\n\n .prefix-icon {\n @apply relative;\n }\n\n .suffix-icon {\n @apply relative;\n }\n\n .container {\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--input-border-color, var(--border));\n border-radius: var(--input-border-radius, var(--radius-sm));\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-active] {\n border-color: var(--input-active-border-color, var(--ring-color));\n box-shadow: var(--input-active-box-shadow, 0 0 0 1px mix(var(--ring-color) 20%, transparent));\n }\n\n &[data-focus-visible] {\n @apply ring-0;\n border-color: var(--input-active-border-color, var(--ring-color));\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n &[data-error] {\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n\n &[data-variant=\"ghost\"] {\n background-color: transparent;\n border-color: transparent;\n &[data-active], &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n }\n\n .start-adornments,\n .end-adornments {\n @apply flex items-center gap-1;\n flex-shrink: 0;\n pointer-events: none;\n }\n\n .start-adornments {\n @apply pl-2;\n }\n\n .end-adornments {\n @apply pr-2;\n }\n\n .actions {\n @apply flex items-center gap-1;\n pointer-events: auto;\n }\n\n .action {\n @apply flex items-center justify-center p-2;\n border-radius: 0.25rem;\n color: var(--action-color);\n }\n\n .action:hover {\n background-color: var(--background-hover);\n color: var(--action-hover-color);\n }\n\n .number-controls {\n @apply flex w-6 flex-col;\n pointer-events: auto;\n }\n\n .number-controls.disabled {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .spin-button {\n @apply flex w-full flex-1 items-center justify-center p-0 cursor-pointer;\n flex: 1;\n background-color: transparent;\n border: none;\n color: var(--spin-color);\n transition: color 150ms ease-out, background-color 150ms ease-out;\n\n &:hover:not(:disabled) {\n background-color: var(--spin-hover-background);\n color: var(--spin-hover-color);\n }\n\n &:active:not(:disabled) {\n background-color: var(--spin-active-background);\n color: var(--spin-active-color);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n }\n}\n",
|
|
4105
|
+
"label": "@reference \"tailwindcss\";\n\n@layer components {\n .label {\n display: block;\n font-family: inherit;\n font-size: var(--text-sm);\n color: var(--foreground);\n transition: color 150ms ease;\n\n &[data-size=\"sm\"] { font-size: var(--text-sm); }\n &[data-size=\"lg\"] { font-size: var(--text-md); }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n &[data-error] {\n color: var(--error-foreground);\n }\n }\n\n .required-indicator {\n margin-left: 0.25rem;\n color: var(--required-color);\n }\n\n .helper-text {\n display: block;\n font-size: var(--text-sm);\n margin-top: 0.25rem;\n transition: color 150ms ease;\n color: var(--helper-color);\n\n &[data-error] {\n color: var(--helper-error-color);\n }\n }\n}\n",
|
|
4106
|
+
"list": "@reference \"tailwindcss\";\n\n@layer components {\n .container {\n @apply mx-auto;\n max-width: 28rem;\n font-family: var(--font-sans, system-ui, -apple-system, sans-serif);\n color: var(--foreground);\n }\n\n .header {\n @apply flex items-center justify-between;\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n padding-top: 1rem;\n padding-bottom: 1rem;\n backdrop-filter: blur(12px);\n z-index: 10;\n }\n\n .header.sticky { position: sticky; top: 0; }\n\n .container[data-spacing=\"sm\"] .header {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n }\n\n .header > :first-child {\n font-weight: var(--font-weight-semibold);\n font-size: 1.125rem;\n color: var(--header-title-color);\n }\n\n .header > :last-child { color: var(--header-subtitle-color); }\n\n .item {\n @apply flex flex-row items-center gap-3 px-2 py-1 cursor-pointer;\n }\n\n .container .item:hover {\n background-color: var(--background-hover);\n }\n\n .container[data-keyboard-mode=\"true\"] .item[data-highlighted=\"true\"] {\n background-color: var(--background-highlighted);\n }\n\n .container[data-spacing=\"sm\"] .item {\n padding: 0.5rem 0.75rem;\n gap: 0.375rem;\n }\n\n .checkbox,\n .control,\n .media {\n @apply flex items-center justify-center flex-shrink-0;\n }\n\n .control { margin-left: auto; }\n\n .media {\n @apply h-8 w-8;\n }\n\n .desc {\n font-size: var(--text-sm);\n color: var(--desc-color);\n @apply truncate;\n }\n\n .action-group {\n @apply flex items-center;\n padding-left: 0.25rem;\n padding-right: 0.25rem;\n }\n\n .action-group[data-justify=\"space-between\"] { justify-content: space-between; }\n .action-group[data-justify=\"flex-start\"] { justify-content: flex-start; }\n .action-group[data-justify=\"flex-end\"] { justify-content: flex-end; }\n\n .actions {\n align-items: center;\n gap: 0.25rem;\n margin-left: auto;\n flex-shrink: 0;\n @apply p-1.5 hidden group-hover:flex;\n }\n\n .action {\n @apply flex items-center justify-center;\n border-radius: 0.25rem;\n color: var(--action-color);\n @apply p-2;\n }\n\n .action:hover {\n background-color: var(--background-hover);\n color: var(--action-hover-color);\n }\n\n .footer {\n @apply flex p-6 pb-12;\n }\n\n .footer[data-align=\"center\"] { justify-content: center; }\n .footer[data-align=\"flex-start\"] { justify-content: flex-start; }\n .footer[data-align=\"flex-end\"] { justify-content: flex-end; }\n\n .container[data-spacing=\"sm\"] .footer {\n padding: 0.375rem 0.75rem;\n padding-bottom: 0.375rem;\n }\n}\n",
|
|
4841
4107
|
"mask": "@reference \"tailwindcss\";\n\n@layer components {\n .mask {\n @apply relative h-full w-full;\n }\n}\n\n.mask[style*=\"mask-image\"],\n.mask[style*=\"-webkit-mask-image\"] {\n -webkit-mask-size: 100% 100%;\n mask-size: 100% 100%;\n}\n\n.mask[style*=\"--mask-clip-path\"] {\n clip-path: var(--mask-clip-path);\n}\n\n.mask-gradient {\n background: var(--mask-gradient);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n color: transparent;\n}\n",
|
|
4842
|
-
"menu": "@reference \"tailwindcss\";\n\n@layer components {\n .content,\n .sub-content {\n --padding: calc(var(--spacing) * 1.5);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n --menu-animation: none;\n --disabled-opacity: 0.5;\n }\n\n .trigger {\n &[data-type=\"pop-over\"][data-active] {\n opacity: 1
|
|
4843
|
-
"modal": "@reference \"tailwindcss\";\n\n@layer components {\n .overlay {\n --disabled-opacity: 0.5;\n }\n\n .backdrop {\n @apply absolute inset-0 cursor-pointer;\n background-color: var(--backdrop-background);\n backdrop-filter: blur(4px);\n }\n\n .modal {\n @apply relative flex w-full flex-col overflow-hidden;\n z-index: 1;\n max-height: 90vh;\n margin: 1rem;\n background-color: var(--modal-background);\n border: var(--border-width-base) solid var(--modal-border);\n border-radius: var(--radius-sm);\n pointer-events: auto;\n overflow: hidden;\n }\n\n .header {\n @apply flex shrink-0 items-center justify-between gap-2 px-6 py-4;\n border-bottom: var(--border-width-base) solid var(--modal-border);\n }\n\n .title {\n @apply m-0;\n font-size: 1.125rem;\n font-weight: var(--font-weight-semibold);\n color: var(--modal-title-color);\n }\n\n .spacer {\n flex: 1;\n }\n\n .close {\n @apply ml-auto flex items-center justify-center cursor-pointer;\n background: none;\n border: none;\n color: var(--modal-close-color);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .close:hover {\n color: var(--modal-close-hover);\n }\n\n .close:active {\n transform: scale(0.92);\n }\n\n .close:focus {\n outline: 2px solid var(--modal-close-hover);\n outline-offset: 2px;\n border-radius: 0.25rem;\n }\n\n .closeIcon {\n @apply h-5 w-5;\n }\n\n .content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n color: var(--modal-text-color);\n }\n\n .content::-webkit-scrollbar {\n width: 6px;\n }\n\n .content::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .content::-webkit-scrollbar-thumb {\n background: var(--modal-border);\n border-radius: 3px;\n transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .content::-webkit-scrollbar-thumb:hover {\n background: var(--modal-close-color);\n }\n\n .footer {\n @apply flex shrink-0 items-center justify-between gap-4 px-6 py-4;\n background-color: var(--footer-background);\n border-top: var(--border-width-base) solid var(--modal-border);\n }\n\n /* Size variants */\n .size-fit {\n width: fit-content;\n }\n\n .size-auto {\n max-width: min(90vw, 28rem);\n }\n\n /* Media queries for smaller screens */\n @media (max-width: 640px) {\n .modal {\n margin: 1rem;\n }\n\n .content {\n max-height: calc(100vh - 10rem);\n }\n }\n}\n",
|
|
4108
|
+
"menu": "@reference \"tailwindcss\";\n\n@layer components {\n .content,\n .sub-content {\n --padding: calc(var(--spacing) * 1.5);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n --menu-animation: none;\n --disabled-opacity: 0.5;\n }\n\n .trigger {\n &[data-type=\"pop-over\"][data-active] {\n opacity: 1;\n background-color: var(--trigger-active-background);\n border-radius: var(--radius-sm, 0.375rem);\n }\n }\n\n .content {\n @apply absolute min-w-40 max-w-80 overflow-hidden;\n z-index: 50000;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n\n &[data-state=\"open\"] {\n animation: var(--menu-animation, slide-in-from-top 0.15s var(--ease-snappy-pop));\n }\n\n &[data-state=\"closed\"] {\n animation: var(--menu-animation, slide-out-to-top 0.15s var(--ease-snappy-pop));\n }\n }\n\n .list {\n @apply space-y-1;\n max-height: 24rem;\n overflow-y: auto;\n }\n\n .item,\n .checkbox-item,\n .radio-item {\n @apply flex min-w-0 items-center gap-2;\n padding: var(--padding);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n cursor: default;\n user-select: none;\n outline: none;\n color: var(--item-foreground);\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .item,\n .sub-trigger {\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .item-indicator {\n @apply ml-auto flex h-4 w-4 shrink-0 items-center justify-center;\n color: var(--item-indicator-color);\n }\n\n .sub-trigger {\n @apply flex min-w-0 items-center gap-2;\n padding: var(--padding);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n cursor: default;\n user-select: none;\n outline: none;\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-state=\"open\"]:not([data-highlighted]) {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply ml-auto h-4 w-4 shrink-0;\n }\n\n .sub-content {\n @apply absolute min-w-40 max-w-80 overflow-hidden;\n z-index: 50000;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n\n &[data-state=\"open\"] {\n animation: var(--menu-animation, slide-in-from-top 0.15s var(--ease-snappy-pop));\n }\n\n &[data-state=\"closed\"] {\n animation: var(--menu-animation, slide-out-to-top 0.15s var(--ease-snappy-pop));\n }\n }\n\n .label {\n padding: var(--padding);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--label-foreground);\n\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .separator {\n @apply -mx-1 my-1 h-px;\n background-color: var(--separator-background);\n }\n\n .shortcut {\n margin-left: auto;\n font-size: var(--text-sm);\n letter-spacing: 0.1em;\n color: var(--shortcut-foreground);\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-to-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n}\n",
|
|
4109
|
+
"modal": "@reference \"tailwindcss\";\n\n@layer components {\n .overlay {\n --disabled-opacity: 0.5;\n }\n\n .backdrop {\n @apply absolute inset-0 cursor-pointer;\n background-color: var(--backdrop-background);\n backdrop-filter: blur(4px);\n }\n\n .modal:focus-visible {\n outline: 2px solid var(--modal-focus-ring);\n outline-offset: 2px;\n }\n\n .modal {\n @apply relative flex w-full flex-col overflow-hidden;\n z-index: 1;\n max-height: 90vh;\n margin: 1rem;\n background-color: var(--modal-background);\n border: var(--border-width-base) solid var(--modal-border);\n border-radius: var(--radius-sm);\n pointer-events: auto;\n overflow: hidden;\n }\n\n .header {\n @apply flex shrink-0 items-center justify-between gap-2 px-6 py-4;\n border-bottom: var(--border-width-base) solid var(--modal-border);\n }\n\n .title {\n @apply m-0;\n font-size: 1.125rem;\n font-weight: var(--font-weight-semibold);\n color: var(--modal-title-color);\n }\n\n .spacer {\n flex: 1;\n }\n\n .close {\n @apply ml-auto flex items-center justify-center cursor-pointer;\n background: none;\n border: none;\n color: var(--modal-close-color);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .close:hover {\n color: var(--modal-close-hover);\n }\n\n .close:active {\n transform: scale(0.92);\n }\n\n .close:focus {\n outline: 2px solid var(--modal-close-hover);\n outline-offset: 2px;\n border-radius: 0.25rem;\n }\n\n .closeIcon {\n @apply h-5 w-5;\n }\n\n .content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n color: var(--modal-text-color);\n }\n\n .content::-webkit-scrollbar {\n width: 6px;\n }\n\n .content::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .content::-webkit-scrollbar-thumb {\n background: var(--modal-border);\n border-radius: 3px;\n transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .content::-webkit-scrollbar-thumb:hover {\n background: var(--modal-close-color);\n }\n\n .footer {\n @apply flex shrink-0 items-center justify-between gap-4 px-6 py-4;\n background-color: var(--footer-background);\n border-top: var(--border-width-base) solid var(--modal-border);\n }\n\n /* Size variants */\n .size-fit {\n width: fit-content;\n }\n\n .size-auto {\n max-width: min(90vw, 28rem);\n }\n\n /* Media queries for smaller screens */\n @media (max-width: 640px) {\n .modal {\n margin: 1rem;\n }\n\n .content {\n max-height: calc(100vh - 10rem);\n }\n }\n}\n",
|
|
4844
4110
|
"page": "@reference \"tailwindcss\";\n\n@layer components {\n .page {\n @apply flex flex-col w-full relative;\n }\n\n .page[data-centered=\"true\"] {\n @apply items-center;\n }\n\n .page[data-fullscreen=\"false\"] {\n margin-left: auto;\n margin-right: auto;\n }\n\n .padding-none { padding: 0; }\n\n .padding-sm { padding: var(--spacing-sm, 0.5rem); }\n\n .padding-md { padding: var(--spacing-md, 1rem); }\n\n .padding-lg { padding: var(--spacing-lg, 1.5rem); }\n\n .padding-xl { padding: var(--spacing-xl, 2rem); }\n}\n",
|
|
4845
4111
|
"panel": "@reference \"tailwindcss\";\n\n@layer components {\n .panel {\n @apply flex h-full w-full min-h-0 min-w-0 flex-row;\n background: inherit;\n }\n\n .panel[data-stacked=\"true\"] { flex-direction: column; }\n\n .header,\n .footer {\n @apply shrink-0;\n background: inherit;\n }\n\n .sticky {\n position: sticky;\n top: 0;\n z-index: 10;\n }\n\n .content {\n @apply flex min-h-0 min-w-0;\n flex: 1;\n overflow: auto;\n }\n\n .fixed {\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 5;\n }\n\n /* Sidebar */\n .sidebar {\n @apply shrink-0 overflow-hidden;\n overflow: hidden;\n transition: width 0.2s ease;\n border-right: var(--border-width-base) solid var(--background-700);\n }\n\n .sidebar[data-side=\"right\"] {\n border-right: none;\n border-left: var(--border-width-base) solid var(--background-700);\n }\n\n /* Toggle */\n .toggle {\n @apply flex items-center;\n }\n\n /* Group */\n .group {\n @apply flex w-full h-full;\n background: inherit;\n }\n\n .group[data-direction=\"vertical\"] { flex-direction: column; }\n\n /* Resize handle */\n .resize {\n @apply relative shrink-0;\n cursor: col-resize;\n background: transparent;\n width: 10px;\n }\n\n .resize::before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n background: var(--background-700, #374151);\n transform: translateX(-50%);\n transition: width 0.15s ease;\n }\n\n .resize[data-direction=\"vertical\"] {\n cursor: row-resize;\n height: 10px;\n }\n\n .resize[data-direction=\"vertical\"]::before {\n top: 50%;\n bottom: auto;\n left: 0;\n right: 0;\n width: auto;\n height: 1px;\n transform: translateY(-50%);\n }\n\n .resize:hover::before,\n .resize[data-resizing=\"true\"]::before { width: 2px; }\n\n .resize[data-direction=\"vertical\"]:hover::before,\n .resize[data-direction=\"vertical\"][data-resizing=\"true\"]::before {\n width: auto;\n height: 2px;\n }\n\n /* Spacing variants */\n .spacingNone,\n .spacing-none { gap: 0; }\n\n .spacingSm,\n .spacing-sm { gap: var(--spacing-sm, 0.5rem); }\n\n .spacingMd,\n .spacing-md { gap: var(--spacing-md, 1rem); }\n\n .spacingLg,\n .spacing-lg { gap: var(--spacing-lg, 1.5rem); }\n\n /* Compact variant */\n .compact {\n gap: calc(var(--spacing-sm, 0.5rem) / 2);\n }\n\n /* Responsive stacking (mobile) */\n @media (max-width: 767px) {\n .stacked { flex-direction: column; }\n }\n}\n",
|
|
4846
|
-
"path": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n --foreground: var(--foreground-primary);\n --foreground-muted: var(--foreground-secondary);\n --separator-color: var(--border-secondary);\n --focus-ring-color: var(--accent-500);\n --disabled-opacity: 0.6;\n\n @apply block;\n }\n\n .path-list {\n list-style: none;\n @apply m-0 flex flex-wrap items-center gap-2 p-0;\n flex-wrap: wrap;\n }\n\n .path-list.with-custom-separator .path-item:not(:last-child)::after {\n content: none;\n }\n\n .path-item {\n @apply m-0 flex items-center gap-2 p-0;\n }\n\n /* Separator after each item except the last */\n .path-item:not(:last-child)::after {\n content: '/';\n color: var(--separator-color);\n margin-left: 0.5rem;\n user-select: none;\n pointer-events: none;\n }\n\n /* Custom separator element */\n .separator {\n list-style: none;\n @apply m-0 flex items-center p-0;\n color: var(--separator-color);\n user-select: none;\n pointer-events: none;\n }\n\n .path-item-link {\n @apply relative cursor-pointer rounded-sm px-2 py-1;\n color: var(--foreground);\n text-decoration: none;\n font-size: var(--text-
|
|
4847
|
-
"popover": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--popover-fill);\n --frame-stroke-color: var(--popover-border-color);\n opacity: 0;\n transform: scale(0.95);\n transition: opacity 0.2s ease-out, transform 0.2s ease-out;\n pointer-events: none;\n min-width: 200px;\n max-width: 400px;\n padding: 0.75rem;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n }\n\n .content[data-instant] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-
|
|
4112
|
+
"path": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n --foreground: var(--foreground-primary);\n --foreground-muted: var(--foreground-secondary);\n --separator-color: var(--border-secondary);\n --focus-ring-color: var(--accent-500);\n --disabled-opacity: 0.6;\n\n @apply block;\n }\n\n .path-list {\n list-style: none;\n @apply m-0 flex flex-wrap items-center gap-2 p-0;\n flex-wrap: wrap;\n }\n\n .path-list.with-custom-separator .path-item:not(:last-child)::after {\n content: none;\n }\n\n .path-item {\n @apply m-0 flex items-center gap-2 p-0;\n }\n\n /* Separator after each item except the last */\n .path-item:not(:last-child)::after {\n content: '/';\n color: var(--separator-color);\n margin-left: 0.5rem;\n user-select: none;\n pointer-events: none;\n }\n\n /* Custom separator element */\n .separator {\n list-style: none;\n @apply m-0 flex items-center p-0;\n color: var(--separator-color);\n user-select: none;\n pointer-events: none;\n }\n\n .path-item-link {\n @apply relative cursor-pointer rounded-sm px-2 py-1;\n color: var(--foreground);\n text-decoration: none;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n line-height: 1.5;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover:not([data-disabled='true']) {\n background-color: var(--background-hover, rgba(0, 0, 0, 0.04));\n color: var(--accent-600);\n }\n\n &:active:not([data-disabled='true']) {\n background-color: var(--background-active, rgba(0, 0, 0, 0.08));\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring-color);\n outline-offset: 2px;\n }\n\n &[data-current='true'] {\n color: var(--foreground-muted);\n cursor: default;\n font-weight: var(--font-weight-medium);\n\n &:hover {\n background-color: transparent;\n }\n }\n\n &[data-disabled='true'] {\n color: var(--foreground-muted);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n\n &:hover {\n background-color: transparent;\n }\n }\n }\n}\n",
|
|
4113
|
+
"popover": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--popover-fill);\n --frame-stroke-color: var(--popover-border-color);\n opacity: 0;\n transform: scale(0.95);\n transition: opacity 0.2s ease-out, transform 0.2s ease-out;\n pointer-events: none;\n min-width: 200px;\n max-width: 400px;\n padding: 0.75rem;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n }\n\n .content[data-instant] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n}\n",
|
|
4848
4114
|
"progress": "@reference \"tailwindcss\";\n\n@layer components {\n .progress {\n @apply relative w-full overflow-hidden;\n border-radius: var(--radius-full);\n background-color: var(--track-background);\n }\n\n .progress.sm { height: 0.25rem; }\n .progress.md { height: 0.5rem; }\n .progress.lg { height: 0.75rem; }\n\n .fill {\n @apply h-full;\n border-radius: var(--radius-full);\n background-color: var(--fill-background);\n transition: width 300ms var(--ease-snappy-pop);\n }\n\n .fill.animated {\n animation: pulse 2s var(--ease-gentle-ease) infinite;\n }\n\n .fill.indeterminate {\n width: 33.333%;\n animation: progress-indeterminate 1.5s var(--ease-gentle-ease) infinite;\n }\n\n .wrapper {\n @apply w-full;\n }\n\n .wrapper.has-label {\n @apply space-y-1;\n }\n\n .label-row {\n @apply flex items-center justify-between;\n font-size: var(--text-sm);\n color: var(--foreground);\n }\n\n .label {\n user-select: none;\n }\n\n .value {\n font-variant-numeric: tabular-nums;\n }\n\n @keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n }\n\n @keyframes progress-indeterminate {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(400%); }\n }\n}\n",
|
|
4849
4115
|
"radio": "@reference \"tailwindcss\";\n\n@layer components {\n .radio-group {\n @apply flex flex-col gap-3;\n }\n\n .radio-item {\n @apply flex items-start gap-3 cursor-pointer select-none;\n }\n\n .radio-input {\n @apply absolute inset-0 w-full h-full opacity-0 cursor-pointer\n }\n\n .radio {\n --disabled-opacity: 0.6;\n\n @apply relative flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center;\n border: var(--border-width-base) solid;\n border-radius: 9999px;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n background-color: var(--radio-background-unchecked);\n border-color: var(--radio-border-unchecked);\n }\n\n .radio-item:active .radio {\n transform: scale(0.92);\n }\n\n .radio-dot {\n border-radius: 9999px;\n background-color: var(--radio-dot-unchecked);\n transform: scale(0);\n transform-origin: center;\n transition: transform 200ms var(--ease-snappy-pop);\n }\n\n .radio[data-checked=\"true\"] {\n --radio-background-unchecked: var(--radio-background-checked);\n --radio-border-unchecked: var(--radio-border-checked);\n --radio-dot-unchecked: var(--radio-dot-checked);\n }\n\n .radio[data-checked=\"true\"] .radio-dot {\n transform: scale(1);\n }\n\n @media (hover: hover) {\n .radio-item:not([data-disabled]):hover .radio {\n --radio-background-unchecked: var(--radio-hover-background);\n --radio-border-unchecked: var(--radio-hover-border);\n opacity: 0.9;\n }\n }\n\n .radio-item[data-disabled] .radio {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n --radio-dot-unchecked: transparent;\n }\n\n .radio[data-error=\"true\"] {\n --radio-border-unchecked: var(--radio-error-border);\n }\n\n .radio[data-error=\"true\"][data-checked=\"true\"] {\n --radio-border-unchecked: var(--radio-border-checked);\n }\n\n .radio[data-focus-visible=\"true\"] {\n outline: 2px solid;\n outline-color: var(--ring-color);\n outline-offset: -2px;\n }\n\n .radio-label {\n @apply cursor-pointer;\n font-weight: var(--font-weight-medium);\n transition: color 200ms var(--ease-snappy-pop);\n color: var(--radio-foreground);\n font-size: inherit;\n line-height: inherit;\n select: none;\n }\n\n .radio-label-disabled {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n color: var(--radio-foreground-disabled);\n }\n\n .radio-description {\n font-size: 0.875rem;\n margin-top: 0.125rem;\n transition: color 200ms var(--ease-snappy-pop);\n color: var(--radio-helper);\n }\n\n .radio-description-error {\n color: var(--radio-helper-error);\n }\n /* Size variants */\n .radio.sm {\n @apply h-4 w-4;\n }\n\n .radio.sm .radio-dot {\n width: 0.375rem;\n height: 0.375rem;\n }\n\n .radio.md {\n @apply h-5 w-5;\n }\n\n .radio.md .radio-dot {\n width: 0.625rem;\n height: 0.625rem;\n }\n\n .radio.lg {\n @apply h-6 w-6;\n }\n\n .radio.lg .radio-dot {\n width: 0.75rem;\n height: 0.75rem;\n }\n}\n",
|
|
4850
|
-
"scroll": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply relative;\n }\n\n .vertical {\n --scrollbar-width: 12px;\n min-height: 0;\n }\n\n .horizontal { --scrollbar-height: 12px; }\n\n .content {\n @apply h-full w-full;\n overflow: auto;\n }\n\n .vertical .content {\n overflow-y: auto;\n overflow-x: hidden;\n
|
|
4851
|
-
"select": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n\n --padding-x: calc(var(--spacing) * 2.00);\n --padding-y: calc(var(--spacing) * 1.75);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--border-color);\n border-radius: var(--radius);\n font-size: var(--text-
|
|
4116
|
+
"scroll": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply relative;\n }\n\n .vertical {\n --scrollbar-width: 12px;\n min-height: 0;\n }\n\n .horizontal { --scrollbar-height: 12px; }\n\n .content {\n @apply h-full w-full;\n overflow: auto;\n }\n\n .vertical .content {\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .vertical[data-inset=\"true\"] .content {\n padding-right: 16px;\n }\n\n .horizontal .content {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .horizontal[data-inset=\"true\"] .content {\n padding-bottom: 16px;\n }\n\n .vertical .content::-webkit-scrollbar,\n .horizontal .content::-webkit-scrollbar { display: none; }\n\n .track {\n @apply absolute;\n z-index: 10;\n }\n\n .track[data-hide=\"true\"] {\n transition-property: opacity;\n transition-duration: 200ms;\n }\n\n .vertical .track {\n right: 4px;\n top: var(--scroll-padding-y, 0);\n width: 12px;\n height: calc(100% - 2 * var(--scroll-padding-y, 0));\n background-color: var(--track-background);\n box-sizing: border-box;\n }\n\n .horizontal .track {\n bottom: 2px;\n left: 0;\n height: 12px;\n width: 100%;\n background-color: var(--track-background);\n }\n\n .thumb {\n position: absolute;\n border-radius: calc(var(--radius-xs) * 0.80);\n background-color: var(--thumb-background);\n transition-property: background-color, width, height;\n transition-duration: 150ms;\n }\n\n .thumb:hover { background-color: var(--thumb-hover-background); }\n\n .root[data-dragging=\"true\"] .thumb {\n background-color: var(--thumb-dragging-background);\n }\n\n .vertical .thumb {\n width: 6px;\n margin-left: 6px;\n transition-property: background-color, width, margin-left;\n transition-duration: 150ms;\n }\n\n .vertical .thumb:hover,\n .vertical[data-dragging=\"true\"] .thumb {\n width: 8px;\n margin-left: 4px;\n }\n\n .horizontal .thumb {\n height: 6px;\n margin-top: 6px;\n transition-property: background-color, height, margin-top;\n transition-duration: 150ms;\n }\n\n .horizontal .thumb:hover,\n .horizontal[data-dragging=\"true\"] .thumb {\n height: 8px;\n margin-top: 4px;\n }\n}\n",
|
|
4117
|
+
"select": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n\n --padding-x: calc(var(--spacing) * 2.00);\n --padding-y: calc(var(--spacing) * 1.75);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--border-color);\n border-radius: var(--radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed]:not([data-disabled]) {\n background-color: var(--pressed-background);\n }\n\n &[aria-expanded=\"true\"] {\n background-color: var(--hover-background);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not(:disabled):hover .icon-section,\n &:not(:disabled):hover .value-section:not(:empty) {\n background-color: var(--hover-background);\n }\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 2px var(--focus-ring-background), 0 0 0 4px var(--ring-color);\n @apply outline-none;\n }\n\n :global(.group) &:focus-visible {\n @apply outline-none;\n }\n }\n\n button.trigger { @apply p-0; }\n\n .value-section {\n @apply flex items-center flex-1 min-w-0 gap-0.5;\n\n padding: var(--padding-y) var(--padding-x);\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n\n &:only-child {\n border-radius: var(--inner-radius);\n justify-content: center;\n }\n &:empty {\n flex: 0;\n padding: 0;\n min-width: auto;\n }\n }\n\n .icon-section {\n @apply flex items-center justify-center shrink-0;\n padding: var(--padding-y) var(--padding-x);\n border-radius: 0 var(--inner-radius) var(--inner-radius) 0;\n }\n\n .icon {\n @apply flex items-center justify-center w-4 h-4 opacity-70;\n }\n\n .trigger[aria-expanded=\"true\"] .icon,\n .trigger[data-open=\"true\"] .icon {\n transform: rotate(180deg);\n }\n\n .value {\n @apply flex items-center flex-1 min-w-0 gap-2 bg-transparent border-none p-0;\n cursor: inherit;\n }\n\n .value-icon {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n color: var(--foreground);\n }\n\n .value-text {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .content,\n .sub-content {\n --padding-x: calc(var(--spacing) * 1.5);\n --padding-y: var(--spacing);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n overflow: hidden;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n }\n\n .content-root,\n .sub-content-root {\n position: absolute;\n }\n\n .content {\n &[data-state=\"open\"][data-placement=\"bottom\"] { animation: slide-in-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"open\"][data-placement=\"top\"] { animation: slide-in-from-bottom 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"bottom\"] { animation: slide-out-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"top\"] { animation: slide-out-from-bottom 0.15s var(--ease-snappy-pop); }\n }\n\n .list {\n @apply space-y-1;\n }\n\n .item {\n --item-padding-x: var(--padding-x);\n --item-padding-y: calc(var(--padding-y) * 1.15);\n\n @apply flex items-center gap-2 outline-none cursor-default select-none;\n padding: var(--item-padding-y) var(--item-padding-x);\n\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n\n &[data-selected=\"true\"] {\n color: var(--item-foreground);\n }\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n }\n &[data-highlighted=\"true\"] {\n background-color: var(--item-background-hover);\n }\n }\n\n .item-content {\n @apply flex flex-col flex-1 min-w-0;\n }\n\n .item-text {\n @apply min-w-0 overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-description-color);\n @apply min-w-0 whitespace-normal break-words;\n }\n\n .item-icon, .item-indicator {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n }\n\n .item-icon { color: var(--item-icon-color); }\n .item-indicator { color: var(--item-indicator-color); margin-left: auto; }\n\n .item-with-description { @apply items-start py-2; }\n .item-icon-with-description, .item-indicator-with-description { @apply mt-0.5; }\n\n .separator {\n @apply my-1 -mx-1 h-px;\n background-color: var(--content-border); /* Reuses content border var */\n }\n\n .placeholder {\n color: var(--placeholder-color);\n }\n\n .icon-prefix {\n @apply inline-flex items-center shrink-0;\n }\n\n .select[data-mode=\"multiple\"] .item { gap: 0.5rem; }\n\n .search-trigger {\n @apply flex items-stretch relative bg-transparent cursor-text overflow-hidden;\n border-radius: var(--inner-radius);\n transition: box-shadow 150ms var(--ease-snappy-pop), border-color 150ms var(--ease-snappy-pop);\n\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n\n &:focus-within {\n @apply outline-none;\n box-shadow: 0 0 0 1px var(--search-focus-ring);\n z-index: 1;\n }\n }\n\n .search-value-section {\n @apply p-0;\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n }\n\n .input {\n padding: var(--padding-y) calc(var(--padding-x) * 1.50);\n padding-right: calc(var(--padding-x) * 2 + 1rem);\n @apply border-none rounded-none shadow-none bg-transparent;\n\n &[data-active], &[data-focus-visible] {\n @apply border-none shadow-none;\n }\n }\n\n .search-content-input {\n padding-inline: calc(var(--padding-x) * 1.50);\n @apply border-none rounded-none bg-transparent;\n }\n\n .search-icon-section {\n @apply absolute right-0 top-0 bottom-0 flex items-center justify-center bg-transparent pointer-events-none;\n padding-inline: var(--padding-x);\n }\n\n\n .search-wrapper {\n @apply overflow-hidden;\n border-bottom: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .content[data-placement=\"top\"] .search-wrapper {\n border-radius: 0;\n border-bottom: none;\n border-top: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .sub-trigger {\n --subtrigger-padding-x: var(--padding-x);\n --subtrigger-padding-y: var(--padding-y);\n\n @apply flex items-center gap-2 cursor-default select-none outline-none;\n padding: var(--subtrigger-padding-y) var(--subtrigger-padding-x);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--subtrigger-foreground);\n\n &[data-highlighted=\"true\"],\n &[data-state=\"open\"]:not([data-highlighted=\"true\"]) {\n background-color: var(--subtrigger-background-hover);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply shrink-0 ml-auto w-4 h-4 opacity-60;\n }\n\n .sub-content {\n min-width: 160px;\n max-width: 320px;\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-in-from-bottom { from { opacity: 0; translate: 0 2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-from-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n @keyframes slide-out-from-bottom { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 2px; } }\n}\n",
|
|
4852
4118
|
"slider": "@reference \"tailwindcss\";\n\n@layer components {\n .slider {\n --disabled-opacity: 0.6;\n\n @apply relative flex w-full items-center;\n touch-action: none;\n user-select: none;\n }\n\n .slider[data-size=\"sm\"] { @apply h-6; }\n .slider[data-size=\"md\"] { @apply h-8; }\n .slider[data-size=\"lg\"] { @apply h-10; }\n\n .slider[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .track {\n --track-height-sm: 0.25rem;\n --track-height-md: 0.375rem;\n --track-height-lg: 0.5rem;\n\n @apply relative flex grow items-center;\n flex-grow: 1;\n overflow: visible;\n border-radius: var(--radius-xs);\n background-color: var(--slider-track-background);\n }\n\n .slider[data-size=\"sm\"] .track { height: var(--track-height-sm); }\n .slider[data-size=\"md\"] .track { height: var(--track-height-md); }\n .slider[data-size=\"lg\"] .track { height: var(--track-height-lg); }\n\n .range {\n @apply absolute h-full;\n background-color: var(--slider-range-background-default);\n transition: background-color 200ms var(--ease-snappy-pop);\n border-radius: var(--radius-xs);\n }\n\n .slider[data-disabled] .range { background-color: var(--slider-range-background-disabled); }\n\n .thumb {\n --thumb-size-sm: 0.75rem;\n --thumb-size-md: 1rem;\n --thumb-size-lg: 1.25rem;\n\n @apply absolute block;\n background-color: var(--slider-thumb-background-default);\n border-radius: 9999px;\n cursor: grab;\n outline: none;\n top: 50%;\n transform: translate(-50%, -50%);\n }\n\n .slider[data-size=\"sm\"] .thumb {\n width: var(--thumb-size-sm);\n height: var(--thumb-size-sm);\n }\n\n .slider[data-size=\"md\"] .thumb {\n width: var(--thumb-size-md);\n height: var(--thumb-size-md);\n }\n\n .slider[data-size=\"lg\"] .thumb {\n width: var(--thumb-size-lg);\n height: var(--thumb-size-lg);\n }\n\n .slider[data-disabled] .thumb {\n background-color: var(--slider-thumb-background-disabled);\n cursor: not-allowed;\n }\n\n .thumb[data-focus-visible] {\n background-color: var(--slider-thumb-background-focus);\n box-shadow: 0 0 0 3px var(--slider-thumb-ring);\n }\n\n .thumb[data-dragging] {\n cursor: grabbing;\n transform: translate(-50%, -50%) scale(1.1);\n }\n}\n",
|
|
4853
4119
|
"switch": "@reference \"tailwindcss\";\n\n@layer components {\n .switch {\n --radius: 9999px;\n --inner-radius: calc(var(--radius) - var(--border-width-base));\n\n --width: 2.75rem;\n --height: 1.5rem;\n --thumb-size: 1rem;\n --thumb-offset: 0.25rem;\n\n --disabled-opacity: 0.6;\n\n @apply relative inline-flex cursor-pointer items-center;\n user-select: none;\n width: var(--width);\n height: var(--height);\n }\n\n .switch-track {\n @apply absolute inset-0;\n transition: background-color 180ms var(--ease-snappy-pop), border-color 180ms var(--ease-snappy-pop), transform 180ms var(--ease-snappy-pop);\n background-color: var(--track-background-unchecked);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius);\n }\n\n .switch:active:not([data-disabled]) .switch-track {\n transform: scale(0.98);\n }\n\n .switch-thumb {\n @apply absolute top-0 bottom-0 my-auto;\n left: var(--thumb-offset);\n width: var(--thumb-size);\n height: var(--thumb-size);\n transition: left 180ms var(--ease-snappy-pop), background-color 180ms var(--ease-snappy-pop);\n background-color: var(--thumb-background-unchecked);\n border-radius: var(--inner-radius);\n z-index: 1;\n pointer-events: none;\n }\n\n .switch[data-selected] .switch-track {\n background-color: var(--track-background-checked);\n border-color: var(--border-checked);\n }\n\n .switch[data-selected] .switch-thumb {\n background-color: var(--thumb-background-checked);\n left: calc(var(--width) - var(--thumb-size) - var(--thumb-offset));\n }\n\n @media (hover: hover) {\n .switch[data-selected]:not([data-disabled]):hover .switch-track {\n border-color: var(--border-hover);\n }\n }\n\n .switch[data-selected]:not([data-disabled]):active .switch-track {\n border-color: var(--border-active);\n }\n\n .switch[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n\n\n .switch[data-focus-visible] {\n box-shadow: var(--focus-ring);\n }\n\n .switch-sm {\n --width: 1.75rem;\n --height: 1rem;\n --thumb-size: 0.625rem;\n --thumb-offset: 0.1875rem;\n }\n}\n",
|
|
4854
|
-
"tabs": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n --indicator-padding: 2px;\n\n @apply absolute;\n background-color: var(--indicator-background);\n border-radius: var(--radius-xs);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n font-size: var(--text-
|
|
4855
|
-
"textarea": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n --disabled-opacity: 0.6;\n\n @apply block w-full px-3 py-2;\n font-family: inherit;\n font-size: var(--text-
|
|
4856
|
-
"toast": "@reference \"tailwindcss\";\n\n@layer components {\n .toast {\n @apply flex w-full max-w-[28rem] items-start gap-3 px-4 py-2.5 select-none;\n background: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow);\n font-family: inherit;\n font-size: var(--text-
|
|
4857
|
-
"tooltip": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-
|
|
4120
|
+
"tabs": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n --indicator-padding: 2px;\n\n @apply absolute;\n background-color: var(--indicator-background);\n border-radius: var(--radius-xs);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--trigger-color);\n outline: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]) {\n &:hover {\n color: var(--trigger-hover-color);\n }\n\n &:active {\n color: var(--trigger-active-color);\n }\n }\n\n &[data-selected=\"true\"] {\n color: var(--trigger-selected-color);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n .list & {\n background-color: var(--trigger-selected-background);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--trigger-underline-color);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--trigger-underline-color);\n }\n }\n\n &[data-focus-visible] {\n background: var(--trigger-focus-background);\n outline: none;\n }\n\n &[data-disabled=\"true\"] {\n --disabled-opacity: 0.5;\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom: none;\n border-left: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n border-left-color: var(--trigger-underline-color);\n border-bottom: none;\n }\n }\n\n .trigger-icon {\n @apply flex h-4 w-4 shrink-0 items-center justify-center;\n }\n\n .content {\n @apply w-full p-0 outline-none;\n flex: 1;\n padding-top: 1rem;\n\n &[data-orientation=\"vertical\"] {\n flex: 1;\n width: 100%;\n }\n\n &:focus-visible {\n outline: 2px solid var(--content-outline-color);\n outline-offset: 2px;\n }\n }\n\n @media (max-width: 640px) {\n .list {\n padding: 0.125rem;\n\n &[data-variant=\"underline\"] {\n padding: 0;\n }\n }\n\n .trigger {\n @apply px-1 py-1;\n font-size: var(--text-sm);\n\n .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
|
|
4121
|
+
"textarea": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n --disabled-opacity: 0.6;\n\n @apply block w-full px-3 py-2;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-sizing: border-box;\n resize: none;\n outline: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n outline: none;\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n color: var(--disabled-foreground);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n\n &[data-error] {\n border-color: var(--border);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n\n &[data-resize-axis=\"x\"],\n &[data-resize-axis=\"both\"] {\n padding-inline-end: calc(var(--textarea-padding-x) + 1rem);\n }\n\n &[data-resize-axis=\"y\"],\n &[data-resize-axis=\"both\"] {\n padding-block-end: calc(var(--textarea-padding-y) + 1rem);\n }\n\n &[data-scroll=\"true\"] {\n overflow: hidden;\n resize: none;\n background: transparent;\n border: none;\n border-radius: 0;\n box-shadow: none;\n\n &[data-active],\n &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n\n &[data-disabled] {\n background-color: transparent;\n opacity: 1;\n }\n\n &[data-error] {\n border-color: transparent;\n\n &[data-active] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n }\n }\n\n .surface {\n @apply relative w-full;\n }\n\n .resize-handle {\n position: absolute;\n z-index: 1;\n touch-action: none;\n user-select: none;\n\n &::before {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--resize-handle-color);\n transition: background-color 150ms;\n }\n\n &::after {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--resize-handle-color);\n transition: background-color 150ms;\n }\n\n &:hover::before,\n &:hover::after {\n background-color: var(--resize-handle-color-hover);\n }\n\n &[data-axis=\"both\"] {\n right: 3px;\n bottom: 3px;\n width: 1.5rem;\n height: 1.5rem;\n cursor: nwse-resize;\n\n &::before {\n bottom: 0.35rem;\n right: 0.15rem;\n width: 0.5rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n\n &::after {\n bottom: 0.6rem;\n right: 0.2rem;\n width: 1.0rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n }\n\n &[data-axis=\"x\"] {\n top: 50%;\n right: 0;\n width: 0.75rem;\n height: 2rem;\n cursor: ew-resize;\n transform: translateY(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 0.125rem;\n height: 1.5rem;\n transform: translate(-50%, -50%);\n }\n }\n\n &[data-axis=\"y\"] {\n left: 50%;\n bottom: 0;\n width: 2rem;\n height: 0.75rem;\n cursor: ns-resize;\n transform: translateX(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 1.5rem;\n height: 0.125rem;\n transform: translate(-50%, -50%);\n }\n }\n }\n\n .scroll-wrapper {\n --disabled-opacity: 0.6;\n\n @apply w-full overflow-hidden;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n outline: none;\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n opacity: var(--disabled-opacity);\n }\n\n &[data-error] {\n border-color: var(--border);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n }\n\n .textarea[data-size=\"sm\"] {\n min-height: 5rem;\n --textarea-padding-x: 0.5rem;\n --textarea-padding-y: 0.25rem;\n font-size: var(--text-sm);\n @apply px-2 py-1;\n }\n\n .textarea[data-size=\"md\"] {\n min-height: 6rem;\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n font-size: var(--text-sm);\n @apply px-3 py-2;\n }\n\n .textarea[data-size=\"lg\"] {\n min-height: 8rem;\n --textarea-padding-x: 1rem;\n --textarea-padding-y: 0.75rem;\n font-size: var(--text-md);\n @apply px-4 py-3;\n }\n\n .container {\n @apply w-full;\n }\n\n .character-count {\n font-size: var(--text-sm);\n color: var(--character-count-color);\n @apply mt-1;\n transition: color 0.15s var(--ease-snappy-pop);\n }\n\n .character-count[data-over-limit] {\n color: var(--character-count-over-limit-color);\n }\n}\n",
|
|
4122
|
+
"toast": "@reference \"tailwindcss\";\n\n@layer components {\n .toast {\n @apply flex w-full max-w-[28rem] items-start gap-3 px-4 py-2.5 select-none;\n background: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow);\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n touch-action: pan-y;\n }\n\n .icon {\n @apply mr-4 mt-2 h-5 w-5 shrink-0;\n color: var(--icon-color);\n }\n\n .content {\n @apply min-w-0 flex-1;\n }\n\n .title {\n @apply m-0;\n font-weight: var(--font-weight-semibold);\n font-size: var(--text-sm);\n line-height: var(--leading-tight);\n color: var(--title-color);\n }\n\n .description {\n @apply mt-1 mb-0;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n color: var(--description-color);\n }\n\n .close {\n @apply flex shrink-0 items-center justify-center p-2 cursor-pointer;\n background: transparent;\n border: none;\n border-radius: var(--radius-sm);\n color: var(--close-color);\n opacity: 0.6;\n transition: opacity 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n\n @media (hover: hover) {\n &:hover {\n opacity: 1;\n background: var(--close-hover-background);\n }\n }\n }\n}\n",
|
|
4123
|
+
"tooltip": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n\n .frame[data-hint] {\n @apply pr-1;\n }\n}\n"
|
|
4858
4124
|
};
|
|
4859
4125
|
export const generatedSourceCode = {
|
|
4860
4126
|
"anchor": {
|
|
4861
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Popover } from \"@/components/Popover\";\nimport css from \"./Anchor.module.css\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\";\n\
|
|
4862
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n @apply inline;\n }\n\n .preview {\n @apply inline;\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background);\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n}\n",
|
|
4127
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Popover } from \"@/components/Popover\";\nimport css from \"./Anchor.module.css\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface AnchorStyleSlots {\n root?: StyleValue;\n underline?: StyleValue;\n preview?: StyleValue;\n}\n\ntype AnchorStylesProp = StylesProp<AnchorStyleSlots>;\n\nconst resolveAnchorBaseStyles = createStylesResolver(['root', 'underline', 'preview'] as const);\n\nconst DASHED_DIMENSIONS = {\n sm: { thickness: 1, dashLength: 8, gapLength: 4 },\n md: { thickness: 2, dashLength: 8, gapLength: 4 },\n lg: { thickness: 4, dashLength: 10, gapLength: 6 },\n} as const;\n\nconst DOTTED_DIMENSIONS = {\n sm: { thickness: 1, radius: 0.5, spacing: 6 },\n md: { thickness: 2, radius: 1, spacing: 8 },\n lg: { thickness: 4, radius: 2, spacing: 12 },\n} as const;\n\nfunction getPath(orientation: Orientation, size: Size): string {\n const { thickness, dashLength, gapLength } = DASHED_DIMENSIONS[size];\n const totalLength = dashLength + gapLength;\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${totalLength}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${dashLength}' height='${thickness}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${totalLength}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${thickness}' height='${dashLength}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\nfunction getDottedMaskSvg(orientation: Orientation, size: Size): string {\n const { thickness, radius, spacing } = DOTTED_DIMENSIONS[size];\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${spacing}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${spacing}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\n// --- Sub-components ---\n\nexport interface AnchorPreviewProps\n extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nconst AnchorPreview = React.forwardRef<HTMLSpanElement, AnchorPreviewProps>(\n ({ children }, ref) => {\n return <span ref={ref as React.Ref<HTMLSpanElement>} style={{ display: \"none\" }}>{children}</span>;\n },\n);\nAnchorPreview.displayName = \"Anchor.Preview\";\n\ninterface AnchorUnderlineProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controls the line style of the underline */\n variant?: \"solid\" | \"dashed\" | \"dotted\";\n}\n\nconst AnchorUnderline = React.forwardRef<HTMLDivElement, AnchorUnderlineProps>(\n ({ className, variant = \"solid\", style, ...props }, ref) => {\n const getMaskStyles = (): React.CSSProperties => {\n if (variant === \"solid\") return {}\n\n const orientation = \"horizontal\";\n const size = \"sm\";\n\n const svgDataUri = variant === \"dashed\" ? getPath(orientation, size) : getDottedMaskSvg(orientation, size);\n const maskRepeat = \"repeat-x\";\n const encodedSvg = `url(\"data:image/svg+xml,${svgDataUri}\")`;\n\n return {\n WebkitMaskImage: encodedSvg,\n maskImage: encodedSvg,\n WebkitMaskRepeat: maskRepeat,\n maskRepeat: maskRepeat,\n } as React.CSSProperties;\n };\n\n return (\n <span\n ref={ref}\n className={cn(css.underline, className)}\n style={{ ...getMaskStyles(), ...style }}\n {...props}\n />\n );\n }\n);\nAnchorUnderline.displayName = \"Anchor.Underline\";\n\n// --- Main Anchor Component ---\n\nexport interface AnchorProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n children?: React.ReactNode;\n /** Additional CSS class for the anchor element */\n className?: string;\n /** URL the anchor navigates to */\n href?: string;\n /** Browsing context for the link (e.g. \"_blank\") */\n target?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: AnchorStylesProp;\n}\n\nconst AnchorRoot = React.forwardRef<HTMLAnchorElement | HTMLSpanElement, AnchorProps>(\n ({ className, children, href, target = \"_blank\", styles, ...props }, ref) => {\n const [isOpen, setIsOpen] = React.useState(false);\n let previewContent: React.ReactNode = null;\n let hasUnderline = false;\n\n const childrenArray = React.Children.toArray(children);\n const resolved = resolveAnchorBaseStyles(styles);\n\n let filteredChildren: React.ReactNode[] = [];\n\n // Extract preview content and filter it out from rendered children\n React.Children.forEach(childrenArray, (child) => {\n if (React.isValidElement(child)) {\n if (child.type === AnchorPreview) {\n previewContent = (child.props as any).children;\n // Don't add to filteredChildren\n } else if (child.type === AnchorUnderline) {\n hasUnderline = true;\n // Clone AnchorUnderline to inject resolved.underline\n const underlineChild = child as React.ReactElement<AnchorUnderlineProps>;\n filteredChildren.push(React.cloneElement(underlineChild, {\n className: cn(underlineChild.props.className, resolved.underline),\n }));\n } else {\n filteredChildren.push(child);\n }\n } else {\n filteredChildren.push(child);\n }\n });\n\n // Inject default underline if none provided\n if (!hasUnderline) {\n filteredChildren.push(<AnchorUnderline key=\"__default_underline\" className={resolved.underline} />);\n }\n\n const triggerElement = href ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n className={cn('anchor', 'trigger', css.trigger, resolved.root)}\n >\n {filteredChildren}\n </a>\n ) : (\n <span ref={ref as React.Ref<HTMLSpanElement>} className={cn('anchor', 'trigger', css.trigger, resolved.root)}>{filteredChildren}</span>\n );\n\n // If no preview content, render trigger directly without popover\n if (!previewContent) {\n return triggerElement;\n }\n\n return (\n <Popover\n content={previewContent}\n isOpen={isOpen}\n onOpenChange={setIsOpen}\n position=\"bottom\"\n className={cn('preview', css.preview, className, resolved.preview)}\n {...props}\n >\n {triggerElement}\n </Popover>\n );\n },\n);\nAnchorRoot.displayName = \"Anchor\";\n\n// Compound component with attached sub-components\nconst Anchor = React.forwardRef<HTMLDivElement, AnchorProps & { Preview: typeof AnchorPreview; Underline: typeof AnchorUnderline }>((props, ref) => {\n return <AnchorRoot ref={ref} {...props} />;\n}) as React.ForwardRefExoticComponent<AnchorProps & React.RefAttributes<HTMLDivElement>> & { Preview: typeof AnchorPreview; Underline: typeof AnchorUnderline };\n\nAnchor.displayName = \"Anchor\";\nAnchor.Preview = AnchorPreview;\nAnchor.Underline = AnchorUnderline;\n\nexport { Anchor };\n",
|
|
4128
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n @apply inline;\n }\n\n .preview {\n @apply inline;\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover);\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background);\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n}\n",
|
|
4863
4129
|
"cssTypes": "export const anchor: string;\nexport const preview: string;\nexport const trigger: string;\nexport const underline: string;"
|
|
4864
4130
|
},
|
|
4865
4131
|
"badge": {
|
|
4866
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Badge.module.css\";\n\nimport { X } from \"lucide-react\";\n\ntype BadgeSize = \"sm\" | \"md\" | \"lg\";\n\
|
|
4867
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2 px-3 py-0.5;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n }\n\n .badge.sm {\n @apply px-1.5 py-px;\n gap: 0.25rem;\n font-size: var(--text-
|
|
4132
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Badge.module.css\";\n\nimport { X } from \"lucide-react\";\n\ntype BadgeSize = \"sm\" | \"md\" | \"lg\";\n\ninterface BadgeStyleSlots {\n root?: StyleValue;\n icon?: StyleValue;\n dismiss?: StyleValue;\n}\n\ntype BadgeStylesProp = StylesProp<BadgeStyleSlots>;\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Visual color style of the badge */\n variant?: string;\n /** Size of the badge */\n size?: BadgeSize;\n /** Icon element displayed before the badge label */\n icon?: React.ReactNode;\n /** Whether to show a dismiss button */\n dismissible?: boolean;\n /** Called when the dismiss button is clicked */\n onDismiss?: () => void;\n /** Whether to render with a fully rounded pill shape */\n pill?: boolean;\n /** Numeric count to display; replaces children when provided */\n count?: number;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: BadgeStylesProp;\n}\n\nconst sizeMap = {\n sm: css[\"sm\"],\n md: css[\"md\"],\n lg: css[\"lg\"],\n} as const;\n\ninterface DismissButtonProps {\n onDismiss?: () => void;\n size: BadgeSize;\n className?: StyleValue;\n}\n\nfunction DismissButton({ onDismiss, size, className }: DismissButtonProps) {\n const buttonRef = React.useRef<HTMLDivElement>(null);\n\n const { buttonProps, isPressed } = useButton(\n {\n \"aria-label\": \"Dismiss\",\n onPress: onDismiss,\n },\n buttonRef\n );\n\n const { focusProps, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({});\n\n return (\n <div\n {...mergeProps(buttonProps, focusProps, hoverProps)}\n ref={buttonRef}\n role=\"button\"\n tabIndex={0}\n className={cn(css.dismiss, className)}\n data-pressed={isPressed || undefined}\n data-hovered={isHovered || undefined}\n data-focus-visible={isFocusVisible || undefined}\n >\n <X size={14} />\n </div>\n );\n}\n\nconst resolveBadgeBaseStyles = createStylesResolver(['root', 'icon', 'dismiss'] as const);\n\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n (\n {\n variant = \"default\",\n size = \"sm\",\n icon,\n dismissible = false,\n onDismiss,\n pill = false,\n count,\n children,\n className,\n styles,\n ...props\n },\n ref\n ) => {\n const resolved = resolveBadgeBaseStyles(styles);\n return (\n <span\n ref={ref}\n className={cn(\n \"badge\",\n variant,\n size,\n css.badge,\n sizeMap[size],\n pill && css.pill,\n dismissible && css.dismissible,\n className,\n resolved.root\n )}\n data-variant={variant}\n data-size={size}\n data-pill={pill ? \"true\" : undefined}\n data-dismissible={dismissible || undefined}\n {...props}\n >\n {icon && (\n <span className={cn(css.icon, resolved.icon)} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n {count !== undefined ? count : children}\n {dismissible && <DismissButton onDismiss={onDismiss} size={size} className={resolved.dismiss} />}\n </span>\n );\n }\n);\n\nBadge.displayName = \"Badge\";\n\nexport { Badge };\n",
|
|
4133
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2 px-3 py-0.5;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--background-border);\n border-radius: var(--radius-sm);\n }\n\n .badge.sm {\n @apply px-1.5 py-px;\n gap: 0.25rem;\n font-size: var(--text-sm);\n }\n\n .badge.dismissible {\n @apply pr-0.5;\n }\n\n .badge.md {\n @apply px-3.5 py-1;\n font-size: var(--text-sm);\n }\n\n .badge.lg {\n @apply px-4 py-2.5;\n font-size: var(--text-sm);\n }\n\n .pill { border-radius: 9999px; }\n\n .icon {\n @apply flex items-center shrink-0;\n }\n\n .dismiss {\n @apply ml-1 flex items-center justify-center p-1 cursor-pointer;\n border-radius: var(--radius-xs);\n background: transparent;\n border: none;\n color: var(--dismiss-color);\n transition: opacity 150ms var(--ease-snappy-pop), transform 150ms var(--ease-snappy-pop);\n outline: none;\n }\n\n .dismiss-button[data-hovered=\"true\"] {\n background: var(--dismiss-hover-background);\n }\n\n .dismiss-button[data-pressed=\"true\"] {\n background: var(--dismiss-pressed-background);\n transform: scale(0.95);\n }\n\n .dismiss-button[data-focus-visible=\"true\"] {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n}\n",
|
|
4868
4134
|
"cssTypes": "export interface Styles {\n badge: string;\n default: string;\n secondary: string;\n success: string;\n warning: string;\n danger: string;\n info: string;\n sm: string;\n md: string;\n lg: string;\n pill: string;\n dismissible: string;\n icon: string;\n dismiss: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4869
4135
|
},
|
|
4870
4136
|
"banner": {
|
|
4871
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { useHover, mergeProps } from \"react-aria\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Banner.module.css\";\nimport { Info, CircleCheck, TriangleAlert, CircleAlert } from \"lucide-react\";\n\ntype BannerSize = \"sm\" | \"md\" | \"lg\";\n\
|
|
4872
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\n font-size: var(--text-
|
|
4137
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { useHover, mergeProps } from \"react-aria\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Banner.module.css\";\nimport { Info, CircleCheck, TriangleAlert, CircleAlert } from \"lucide-react\";\n\ntype BannerSize = \"sm\" | \"md\" | \"lg\";\n\ninterface BannerStyleSlots {\n root?: StyleValue;\n \"icon-container\"?: StyleValue;\n content?: StyleValue;\n dismiss?: StyleValue;\n}\n\ntype BannerStylesProp = StylesProp<BannerStyleSlots>;\n\nexport interface BannerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Variant class appended to the root element. Accepts any string. */\n variant?: string;\n /** Controls the padding and font size of the banner */\n size?: BannerSize;\n /** When true, renders a dismiss button that hides the banner on click */\n isDismissible?: boolean;\n /** Called when the dismiss button is clicked */\n onDismiss?: () => void;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: BannerStylesProp;\n}\n\ninterface BannerTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: StyleValue;\n}\n\ninterface BannerBodyProps extends React.HTMLAttributes<HTMLParagraphElement> {\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: StyleValue;\n}\n\nconst bannerIcons = {\n note: Info,\n info: Info,\n success: CircleCheck,\n warning: TriangleAlert,\n danger: CircleAlert,\n} as const;\n\ntype PresetBannerVariant = keyof typeof bannerIcons;\n\nfunction isPresetBannerVariant(variant: string): variant is PresetBannerVariant {\n return Object.prototype.hasOwnProperty.call(bannerIcons, variant);\n}\n\nconst getBannerIcon = (variant: string) => {\n const Icon = bannerIcons[isPresetBannerVariant(variant) ? variant : \"note\"];\n return <Icon className={css.icon} />;\n};\n\nconst sizeMap = {\n sm: css[\"sm\"],\n md: css[\"md\"],\n lg: css[\"lg\"],\n} as const;\n\nconst resolveBannerBaseStyles = createStylesResolver(['root', 'icon-container', 'content', 'dismiss'] as const);\n\n/** Heading text for the banner message */\nconst BannerTitle = React.forwardRef<HTMLHeadingElement, BannerTitleProps>(\n ({ className, styles, ...props }, ref) => (\n <h3\n ref={ref}\n className={cn(\"title\", css.title, className, styles)}\n {...props}\n />\n )\n);\n\nBannerTitle.displayName = \"Banner.Title\";\n\n/** Body text content of the banner */\nconst BannerBody = React.forwardRef<HTMLParagraphElement, BannerBodyProps>(\n ({ className, styles, ...props }, ref) => (\n <p\n ref={ref}\n className={cn(\"body\", css.body, className, styles)}\n {...props}\n />\n )\n);\n\nBannerBody.displayName = \"Banner.Body\";\n\n/** Full-width notification strip for system messages and alerts */\nconst BannerRoot = React.forwardRef<HTMLDivElement, BannerProps>(\n (\n {\n className,\n styles,\n variant = \"note\",\n size = \"md\",\n isDismissible = false,\n onDismiss,\n children,\n ...props\n },\n ref\n ) => {\n const [isVisible, setIsVisible] = React.useState(true);\n const { hoverProps, isHovered } = useHover({});\n\n const handleDismiss = () => {\n setIsVisible(false);\n onDismiss?.();\n };\n\n if (!isVisible) {\n return null;\n }\n\n const icon = getBannerIcon(variant);\n const resolved = resolveBannerBaseStyles(styles);\n\n return (\n <div\n {...mergeProps(hoverProps, props)}\n ref={ref}\n className={cn(\"banner\", variant, css.banner, sizeMap[size], className, resolved.root)}\n data-variant={variant}\n data-size={size}\n data-hovered={isHovered ? \"true\" : \"false\"}\n >\n {icon && <div className={cn(\"icon\", css.icon, resolved[\"icon-container\"])}>{icon}</div>}\n <div className={cn(\"content\", css.content, resolved.content)}>\n {children}\n </div>\n {isDismissible && (\n <button\n onClick={handleDismiss}\n className={cn(\"dismiss\", css.dismiss, resolved.dismiss)}\n aria-label=\"Dismiss banner\"\n type=\"button\"\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"4\" y1=\"4\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"4\" x2=\"4\" y2=\"12\" />\n </svg>\n </button>\n )}\n </div>\n );\n }\n);\n\nBannerRoot.displayName = \"Banner\";\n\ninterface BannerComponent extends React.ForwardRefExoticComponent<BannerProps & React.RefAttributes<HTMLDivElement>> {\n Title: typeof BannerTitle;\n Body: typeof BannerBody;\n}\n\nconst Banner = Object.assign(BannerRoot, {\n Title: BannerTitle,\n Body: BannerBody,\n}) as BannerComponent;\n\nexport { Banner };\n",
|
|
4138
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n line-height: var(--leading-normal);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out, border-color 0.15s ease-out;\n }\n\n .content {\n @apply flex flex-col gap-2;\n }\n\n .icon-container {\n @apply flex shrink-0 items-center justify-center self-start;\n }\n\n .icon {\n @apply mr-4 h-5 w-5;\n color: var(--icon-color, currentColor);\n }\n\n .dismiss {\n @apply flex h-8 w-8 shrink-0 items-center justify-center p-0 cursor-pointer;\n background-color: transparent;\n color: currentColor;\n border: none;\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n }\n\n .title {\n font-weight: var(--font-weight-semibold);\n font-size: inherit;\n line-height: var(--leading-tight);\n @apply my-0;\n }\n\n .body {\n font-weight: var(--font-weight-medium);\n font-size: inherit;\n line-height: var(--leading-normal);\n @apply my-0;\n }\n}\n\n\n.banner.sm {\n @apply px-3 py-2;\n font-size: var(--text-sm);\n}\n\n.banner.md {\n @apply px-4 py-3;\n font-size: var(--text-sm);\n}\n\n.banner.lg {\n @apply px-6 py-4;\n font-size: var(--text-sm);\n}\n",
|
|
4873
4139
|
"cssTypes": "declare const styles: {\n banner: string;\n content: string;\n dismiss: string;\n note: string;\n info: string;\n success: string;\n warning: string;\n danger: string;\n sm: string;\n md: string;\n lg: string;\n icon: string;\n icon: string;\n title: string;\n body: string;\n};\n\nexport default styles;\n"
|
|
4874
4140
|
},
|
|
4875
4141
|
"button": {
|
|
4876
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Button.module.css\";\n\ntype ButtonSize = \"sm\" | \"md\" | \"lg\";\n\ninterface ButtonIconStyles {\n left?: StyleValue;\n right?: StyleValue;\n}\n\nexport interface ButtonStyleSlots {\n root?: StyleValue;\n icon?: StyleValue | ButtonIconStyles;\n}\n\nexport type ButtonStylesProp = StylesProp<ButtonStyleSlots>;\n\nconst resolveButtonBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight'] as const);\n\nfunction resolveButtonStyles(styles: ButtonStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveButtonBaseStyles(styles)\n const { root, icon } = styles;\n\n let iconLeft: StyleValue | undefined;\n let iconRight: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon;\n iconRight = icon;\n } else {\n iconLeft = icon.left;\n iconRight = icon.right;\n }\n }\n\n return resolveButtonBaseStyles({ root, iconLeft, iconRight });\n}\n\nexport interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"href\" | \"target\"> {\n /** Variant class appended to the root element. Accepts any string. */\n variant?: string;\n /** Size
|
|
4877
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n
|
|
4142
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Button.module.css\";\n\ntype ButtonSize = \"sm\" | \"md\" | \"lg\" | (string & {});\ntype ButtonIconSlots = {\n left?: React.ReactNode;\n right?: React.ReactNode;\n};\n\ninterface ButtonIconStyles {\n left?: StyleValue;\n right?: StyleValue;\n}\n\nexport interface ButtonStyleSlots {\n root?: StyleValue;\n icon?: StyleValue | ButtonIconStyles;\n}\n\nexport type ButtonStylesProp = StylesProp<ButtonStyleSlots>;\n\nconst resolveButtonBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight'] as const);\n\nfunction resolveButtonStyles(styles: ButtonStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveButtonBaseStyles(styles)\n const { root, icon } = styles;\n\n let iconLeft: StyleValue | undefined;\n let iconRight: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon;\n iconRight = icon;\n } else {\n iconLeft = icon.left;\n iconRight = icon.right;\n }\n }\n\n return resolveButtonBaseStyles({ root, iconLeft, iconRight });\n}\n\nexport interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"href\" | \"target\"> {\n /** Variant class appended to the root element. Accepts any string. */\n variant?: string;\n /** Size class appended to the root element. Accepts any string. */\n size?: ButtonSize;\n /** Disables interaction and applies disabled styling */\n isDisabled?: boolean;\n /** React Aria press handler — preferred over onClick for accessibility */\n onPress?: (e: { target: EventTarget | null }) => void;\n /** Icon slots rendered before (left) or after (right) the button label */\n icon?: React.ReactNode | ButtonIconSlots;\n /** Renders the button as an anchor element when provided */\n href?: string;\n /** Browsing context for the anchor variant (e.g. \"_blank\") */\n target?: React.HTMLAttributeAnchorTarget;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ButtonStylesProp;\n}\n\nfunction isButtonIconSlots(icon: ButtonProps[\"icon\"]): icon is ButtonIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon);\n}\n\nfunction resolveButtonIcon(icon: ButtonProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isButtonIconSlots(icon)) {\n return icon;\n }\n\n return { left: icon };\n}\n\nfunction resolveButtonIconSizeClass(size: ButtonSize | undefined) {\n if (!size) {\n return undefined;\n }\n\n return (css as unknown as Record<string, string | undefined>)[`icon-${size}`];\n}\n\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n ({ className, styles, variant = \"default\", size = \"md\", children, onClick, onPress, isDisabled, disabled, icon, href, target, rel, ...props }, ref) => {\n const buttonRef = React.useRef<HTMLButtonElement | HTMLAnchorElement>(null);\n const mergedRef = useMergedRef(ref, buttonRef);\n const isButtonDisabled = isDisabled ?? disabled ?? false;\n const [isPressed, setIsPressed] = React.useState(false);\n const isAnchor = !!href;\n\n const handlePress = React.useCallback((e: any) => {\n if (onPress) onPress({ target: e.target });\n if (onClick) onClick(e as unknown as React.MouseEvent<HTMLButtonElement>);\n }, [onPress, onClick]);\n\n const handleMouseDown = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n if (!isButtonDisabled) {\n setIsPressed(true);\n }\n props.onMouseDown?.(e as any);\n }, [isButtonDisabled, props]);\n\n const handleMouseUp = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsPressed(false);\n props.onMouseUp?.(e as any);\n }, [props]);\n\n const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsPressed(false);\n props.onMouseLeave?.(e as any);\n }, [props]);\n\n const { buttonProps } = useButton({\n isDisabled: isButtonDisabled,\n onPress: handlePress,\n }, buttonRef as React.RefObject<HTMLButtonElement>);\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing({ autoFocus: props.autoFocus });\n const { hoverProps, isHovered } = useHover({ isDisabled: isButtonDisabled });\n\n const resolved = resolveButtonStyles(styles);\n const resolvedIcon = resolveButtonIcon(icon);\n const iconSizeClassName = resolveButtonIconSizeClass(size);\n const buttonClassName = cn(\"button\", variant, size, css.button, className, resolved.root);\n\n if (isAnchor) {\n return (\n <a\n {...mergeProps(focusProps, hoverProps, props as any)}\n ref={mergedRef as unknown as React.RefObject<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={rel ?? (target === \"_blank\" ? \"noopener noreferrer\" : undefined)}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n className={buttonClassName}\n data-disabled={isButtonDisabled ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : \"false\"}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {resolvedIcon?.left && <span className={cn(iconSizeClassName, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(iconSizeClassName, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </a>\n );\n }\n\n return (\n <button\n {...mergeProps(buttonProps, focusProps, hoverProps, props)}\n ref={mergedRef as unknown as React.RefObject<HTMLButtonElement>}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n className={buttonClassName}\n data-disabled={isButtonDisabled ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : \"false\"}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {resolvedIcon?.left && <span className={cn(iconSizeClassName, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(iconSizeClassName, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </button>\n );\n }\n);\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return (value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n };\n}\n\nButton.displayName = \"Button\";\n\nexport { Button };\n",
|
|
4143
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:active:not(:disabled) {\n filter: brightness(0.8);\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 1.5px var(--focus-visible);\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n filter: grayscale(0.5);\n }\n }\n}\n",
|
|
4878
4144
|
"cssTypes": "export interface Styles {\n button: string;\n \"default\": string;\n \"primary\": string;\n \"secondary\": string;\n \"outline\": string;\n \"ghost\": string;\n \"danger\": string;\n \"sm\": string;\n \"md\": string;\n \"lg\": string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4879
4145
|
},
|
|
4880
4146
|
"card": {
|
|
4881
|
-
"tsx": "\"use client\"\n\nimport React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Card.module.css\";\n\ninterface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n styles?: CardStylesProp;\n}\n\ninterface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> { }\n\ninterface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> { }\n\ninterface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> { }\n\
|
|
4147
|
+
"tsx": "\"use client\"\n\nimport React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Card.module.css\";\n\ninterface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n styles?: CardStylesProp;\n}\n\ninterface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> { }\n\ninterface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> { }\n\ninterface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> { }\n\ninterface CardStyleSlots {\n root?: StyleValue;\n header?: StyleValue;\n body?: StyleValue;\n footer?: StyleValue;\n}\n\ntype CardStylesProp = StylesProp<CardStyleSlots>;\n\nconst resolveCardBaseStyles = createStylesResolver(['root', 'header', 'body', 'footer'] as const);\n\nconst CardStylesContext = React.createContext<Record<keyof CardStyleSlots, string>>({\n root: '',\n header: '',\n body: '',\n footer: '',\n});\n\nconst CardRoot = React.forwardRef<HTMLDivElement, CardProps>(\n ({ className, styles: stylesProp, ...props }, ref) => {\n const resolvedStyles = resolveCardBaseStyles(stylesProp);\n return (\n <CardStylesContext.Provider value={resolvedStyles}>\n <div\n ref={ref}\n className={cn('card', styles.card, resolvedStyles.root, className)}\n {...props}\n />\n </CardStylesContext.Provider>\n );\n }\n);\nCardRoot.displayName = \"Card\";\n\n/** Top section of the card, typically containing a title or toolbar */\nconst CardHeader = React.forwardRef<HTMLDivElement, CardHeaderProps>(\n ({ className, ...props }, ref) => {\n const { header } = React.useContext(CardStylesContext);\n return (\n <div\n ref={ref}\n className={cn('card', 'header', styles.header, header, className)}\n {...props}\n />\n );\n }\n);\nCardHeader.displayName = \"Card.Header\";\n\n/** Main content area of the card */\nconst CardBody = React.forwardRef<HTMLDivElement, CardBodyProps>(\n ({ className, ...props }, ref) => {\n const { body } = React.useContext(CardStylesContext);\n return (\n <div\n ref={ref}\n className={cn(styles.body, body, className)}\n {...props}\n />\n );\n }\n);\nCardBody.displayName = \"Card.Body\";\n\n/** Bottom section of the card, typically containing actions */\nconst CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>(\n ({ className, ...props }, ref) => {\n const { footer } = React.useContext(CardStylesContext);\n return (\n <div\n ref={ref}\n className={cn('card', 'footer', styles.footer, footer, className)}\n {...props}\n />\n );\n }\n);\nCardFooter.displayName = \"Card.Footer\";\n\n// Compound component\nconst Card = Object.assign(CardRoot, {\n Header: CardHeader,\n Body: CardBody,\n Footer: CardFooter,\n});\n\nexport { Card, CardHeader, CardBody, CardFooter };\nexport type { CardProps, CardHeaderProps, CardBodyProps, CardFooterProps };\n",
|
|
4882
4148
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .card {\n @apply overflow-hidden;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n }\n\n .card[data-focused=\"true\"] {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n }\n\n .header {\n @apply p-4;\n border-bottom: var(--border-width-base) solid var(--border);\n }\n\n .body {\n @apply px-4 py-2;\n }\n\n .footer {\n @apply px-2 py-2;\n background-color: var(--background);\n border-top: var(--border-width-base) solid var(--border);\n }\n}\n",
|
|
4883
4149
|
"cssTypes": "declare const styles: {\n card: string;\n header: string;\n body: string;\n footer: string;\n};\n\nexport default styles;\n"
|
|
4884
4150
|
},
|
|
4885
4151
|
"checkbox": {
|
|
4886
|
-
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Checkbox.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\
|
|
4887
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n /* Hidden input element positioned behind visual checkbox */\n .checkbox-input {\n @apply absolute inset-0 h-full w-full cursor-pointer;\n opacity: 0;\n }\n\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .checkbox-container {\n @apply relative inline-flex items-center justify-center;\n }\n\n /* Visual checkbox */\n .checkbox {\n --disabled-opacity: 0.6;\n\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-xs);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n /* Interactive States */\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:focus-visible {\n outline: 2px solid transparent;\n box-shadow: 0 0 0 3px var(--ring-color);\n }\n\n &[data-pressed=\"true\"] {\n transform: scale(0.92);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n /* Checkmark and Indeterminate styles - combined */\n .checkbox-checkmark,\n .checkbox-indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--checkmark-color);\n pointer-events: none;\n }\n\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .label-sm {\n font-size: var(--text-
|
|
4152
|
+
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Checkbox.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface CheckboxStyleSlots {\n root?: StyleValue;\n label?: StyleValue;\n helperText?: StyleValue;\n}\n\ntype CheckboxStylesProp = StylesProp<CheckboxStyleSlots>;\n\nconst resolveCheckboxBaseStyles = createStylesResolver(['root', 'label', 'helperText'] as const);\n\nexport interface CheckboxProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\"> {\n /** Size of the checkbox */\n size?: Size;\n /** Label text or element displayed next to the checkbox */\n label?: React.ReactNode;\n /** Helper text shown below the checkbox */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to show an indeterminate (partial selection) state */\n isIndeterminate?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: CheckboxStylesProp;\n}\n\nconst sizeMap: Record<Size, string> = {\n sm: css[\"size-sm\"],\n md: css[\"size-md\"],\n lg: css[\"size-lg\"],\n};\n\nconst labelSizeMap: Record<Size, string> = {\n sm: css[\"label-sm\"],\n md: css[\"label-md\"],\n lg: css[\"label-lg\"],\n};\n\nexport const Checkbox = forwardRef<HTMLDivElement, CheckboxProps>(\n (\n {\n className,\n size = \"md\",\n label,\n helperText,\n helperTextError = false,\n id,\n disabled = false,\n checked,\n defaultChecked,\n onChange,\n isIndeterminate = false,\n styles,\n ...props\n },\n ref\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null);\n const [isFocused, setIsFocused] = useState(false);\n // Track pressed state for tactile feedback animation (data-pressed attribute)\n const [isPressed, setIsPressed] = useState(false);\n const [internalChecked, setInternalChecked] = useState(() =>\n checked !== undefined ? checked : (defaultChecked ?? false)\n );\n\n const handleFocus = () => setIsFocused(true);\n const handleBlur = () => setIsFocused(false);\n\n // React Aria press state handlers for tactile scale animation (mouse)\n const handleMouseDown = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n if (!disabled) {\n setIsPressed(true);\n }\n props.onMouseDown?.(e);\n }, [disabled, props]);\n\n const handleMouseUp = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n setIsPressed(false);\n props.onMouseUp?.(e);\n }, [props]);\n\n const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n setIsPressed(false);\n props.onMouseLeave?.(e);\n }, [props]);\n\n // React Aria press state handlers for keyboard interactions (Space/Enter)\n const handleKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {\n if (!disabled && (e.key === \" \" || e.key === \"Enter\")) {\n setIsPressed(true);\n }\n props.onKeyDown?.(e);\n }, [disabled, props]);\n\n const handleKeyUp = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n setIsPressed(false);\n }\n props.onKeyUp?.(e);\n }, [props]);\n\n React.useEffect(() => {\n if (checked !== undefined) {\n setInternalChecked(checked);\n }\n }, [checked]);\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n // Update internal state (needed for uncontrolled mode)\n setInternalChecked(e.target.checked);\n // Call parent handler if provided\n onChange?.(e);\n };\n\n // Filter out boolean props to avoid DOM attribute warnings\n const domProps = Object.fromEntries(\n Object.entries(props).filter(([, value]) => typeof value !== 'boolean')\n );\n\n // Determine if this is a controlled component\n const isControlled = checked !== undefined;\n const displayChecked = isControlled ? checked : internalChecked;\n\n const resolved = resolveCheckboxBaseStyles(styles);\n\n return (\n <div ref={ref} className={cn(\"checkbox-root\", css['checkbox-root'], resolved.root)}>\n <div className={cn((css as any)['checkbox-container'], sizeMap[size])}>\n <input\n ref={inputRef}\n type=\"checkbox\"\n id={id}\n disabled={disabled}\n {...(isControlled ? { checked } : { defaultChecked: internalChecked })}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n className={cn(\n 'checkbox',\n css.checkbox,\n isIndeterminate && css.indeterminate,\n className\n )}\n data-size={size}\n data-selected={displayChecked ? \"true\" : undefined}\n data-disabled={disabled ? \"true\" : undefined}\n data-indeterminate={isIndeterminate ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : undefined}\n {...domProps}\n />\n {displayChecked && !isIndeterminate && (\n <svg\n className={cn('checkbox-root', 'checkbox-checkmark', (css as any)['checkbox-checkmark'])}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n )}\n {isIndeterminate && (\n <svg\n className={cn('checkbox-root', 'checkbox-indeterminate', css['checkbox-indeterminate'])}\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n )}\n </div>\n {label && (\n <label\n htmlFor={id}\n className={cn(\n css.label,\n labelSizeMap[size],\n disabled && css[\"label-disabled\"],\n resolved.label\n )}\n >\n {label}\n </label>\n )}\n {helperText && (\n <p\n className={cn(\n 'checkbox-root',\n 'helper-text',\n css[\"helper-text\"],\n helperTextError && 'helper-text-error',\n helperTextError\n ? css[\"helper-text-error\"]\n : css[\"helper-text-normal\"],\n resolved.helperText\n )}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nCheckbox.displayName = \"Checkbox\";\n",
|
|
4153
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n /* Hidden input element positioned behind visual checkbox */\n .checkbox-input {\n @apply absolute inset-0 h-full w-full cursor-pointer;\n opacity: 0;\n }\n\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .checkbox-container {\n @apply relative inline-flex items-center justify-center;\n }\n\n /* Visual checkbox */\n .checkbox {\n --disabled-opacity: 0.6;\n\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-xs);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n /* Interactive States */\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:focus-visible {\n outline: 2px solid transparent;\n box-shadow: 0 0 0 3px var(--ring-color);\n }\n\n &[data-pressed=\"true\"] {\n transform: scale(0.92);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n /* Checkmark and Indeterminate styles - combined */\n .checkbox-checkmark,\n .checkbox-indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--checkmark-color);\n pointer-events: none;\n }\n\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .label-sm {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-md {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-lg {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-disabled {\n @apply opacity-60 cursor-not-allowed;\n }\n\n .helper-text {\n @apply text-sm ml-8;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .helper-text-normal { color: inherit; }\n\n .helper-text-error { color: var(--error-color); }\n}\n",
|
|
4888
4154
|
"cssTypes": "declare const styles: {\n \"checkbox-root\": string;\n checkbox: string;\n \"checkbox-indeterminate\": string;\n \"size-sm\": string;\n \"size-md\": string;\n \"size-lg\": string;\n indeterminate: string;\n label: string;\n \"label-sm\": string;\n \"label-md\": string;\n \"label-lg\": string;\n \"label-disabled\": string;\n \"helper-text\": string;\n \"helper-text-normal\": string;\n \"helper-text-error\": string;\n};\n\nexport default styles;\n"
|
|
4889
4155
|
},
|
|
4890
4156
|
"code": {
|
|
@@ -4893,14 +4159,14 @@ export const generatedSourceCode = {
|
|
|
4893
4159
|
"cssTypes": "declare const styles: {\n readonly \"code\": string;\n readonly header: string;\n readonly \"header-lang\": string;\n readonly body: string;\n readonly viewport: string;\n readonly \"scroll-track\": string;\n readonly \"expand-button\": string;\n readonly \"expand-icon\": string;\n readonly \"copy-button\": string;\n};\n\nexport default styles;\n"
|
|
4894
4160
|
},
|
|
4895
4161
|
"color": {
|
|
4896
|
-
"tsx": "\"use client\";\n\nimport React, { useState, useEffect, useCallback, useRef } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Color.module.css\";\nimport {\n rgbToHsl,\n hslToRgb,\n rgbToHsv,\n hsvToRgb,\n formatColorHex,\n formatColorRgb,\n parseColor,\n addRecentColor,\n isValidColor,\n} from \"./color-utils\";\nimport { ColorCanvas } from \"./Color.Canvas\";\nimport { ColorHueSlider } from \"./Color.HueSlider\";\nimport { ColorOpacitySlider } from \"./Color.OpacitySlider\";\nimport { ColorRecentColors } from \"./Color.RecentColors\";\nimport { ColorInput } from \"./Color.Input\";\n\nexport interface ColorStyleSlots {\n root?: StyleValue;\n controls?: StyleValue;\n}\n\nexport type ColorStylesProp = StylesProp<ColorStyleSlots>;\n\nconst resolveColorBaseStyles = createStylesResolver(['root', 'controls'] as const);\n\nexport interface ColorProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Controlled color value as a CSS color string */\n value?: string;\n /** Initial color value for uncontrolled usage */\n defaultValue?: string;\n /** Called continuously while the user drags the color picker */\n onChange?: (color: string) => void;\n /** Called once when the user finishes a drag interaction */\n onChangeComplete?: (color: string) => void;\n /** Whether to show the opacity/alpha slider */\n showOpacity?: boolean;\n /** Whether to show a color preview swatch next to the input */\n showPreview?: boolean;\n /** Output format of the color value string */\n format?: \"hex\" | \"rgb\";\n /** Whether the color picker is disabled */\n disabled?: boolean;\n /** Size of the color picker */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Additional CSS class for the root element */\n className?: string;\n /** Classes applied to the root or named slots */\n styles?: ColorStylesProp;\n}\n\nexport const Color = React.forwardRef<HTMLDivElement, ColorProps>(\n (\n {\n value: controlledValue,\n defaultValue = \"#000000\",\n onChange,\n onChangeComplete,\n showOpacity = false,\n showPreview = false,\n format: controlledFormat = \"hex\",\n disabled = false,\n size = \"md\",\n className,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const currentValue = isControlled ? controlledValue : uncontrolledValue;\n\n const [format, setFormat] = useState<\"hex\" | \"rgb\">(controlledFormat);\n const [isDragging, setIsDragging] = useState(false);\n\n // Initialize state using HSV for better canvas mapping\n const initializeState = () => {\n const parsed = parseColor(currentValue);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n return { h, s, v };\n };\n\n const [initialState] = useState(initializeState);\n\n // Source of truth for canvas position (HSV Saturation & Value) and hue\n const [canvasSaturation, setCanvasSaturation] = useState(initialState.s);\n const [canvasBrightness, setCanvasBrightness] = useState(initialState.v);\n const [hue, setHue] = useState(initialState.h);\n const [hueWhenGrayscale, setHueWhenGrayscale] = useState(initialState.h);\n\n // Track the last emitted color to distinguish external updates from internal ones\n const lastEmittedColor = useRef(currentValue);\n\n const parsed = parseColor(currentValue);\n const opacity = parsed.a ?? 1;\n\n // Sync with external updates\n useEffect(() => {\n if (currentValue !== lastEmittedColor.current) {\n const parsed = parseColor(currentValue);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n\n setCanvasSaturation(s);\n setCanvasBrightness(v);\n\n // Preserve hue when desaturated\n if (s > 0) {\n setHue(h);\n setHueWhenGrayscale(h);\n }\n\n lastEmittedColor.current = currentValue;\n }\n }, [currentValue]);\n\n // Compute display color from current state (HSV -> RGB)\n const { r: displayR, g: displayG, b: displayB } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n\n const displayValue =\n format === \"hex\"\n ? formatColorHex(displayR, displayG, displayB, opacity < 1 ? opacity : undefined)\n : formatColorRgb(displayR, displayG, displayB, opacity < 1 ? opacity : undefined);\n \n const resolved = resolveColorBaseStyles(stylesProp);\n\n const handleColorChange = useCallback(\n (newColor: string) => {\n if (!isControlled) {\n setUncontrolledValue(newColor);\n }\n onChange?.(newColor);\n },\n [isControlled, onChange]\n );\n\n const handleChangeComplete = useCallback(\n (newColor: string) => {\n addRecentColor(newColor);\n onChangeComplete?.(newColor);\n },\n [onChangeComplete]\n );\n\n const handleCanvasChange = useCallback(\n (saturation: number, brightness: number) => {\n setIsDragging(true);\n setCanvasSaturation(saturation);\n setCanvasBrightness(brightness);\n\n const { r, g, b } = hsvToRgb(hue, saturation, brightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n lastEmittedColor.current = newColor;\n handleColorChange(newColor);\n },\n [hue, opacity, format, handleColorChange]\n );\n\n const handleCanvasChangeComplete = useCallback(() => {\n setIsDragging(false);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n handleChangeComplete(newColor);\n }, [hue, canvasSaturation, canvasBrightness, opacity, format, handleChangeComplete]);\n\n const handleHueChange = useCallback(\n (newHue: number) => {\n setIsDragging(true);\n setHue(newHue);\n if (canvasSaturation > 0) {\n setHueWhenGrayscale(newHue);\n }\n\n const { r, g, b } = hsvToRgb(newHue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n lastEmittedColor.current = newColor;\n handleColorChange(newColor);\n },\n [canvasSaturation, canvasBrightness, opacity, format, handleColorChange]\n );\n\n const handleHueChangeComplete = useCallback(() => {\n setIsDragging(false);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n handleChangeComplete(newColor);\n }, [hue, canvasSaturation, canvasBrightness, opacity, format, handleChangeComplete]);\n\n const handleOpacityChange = useCallback(\n (newOpacity: number) => {\n setIsDragging(true);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, newOpacity < 1 ? newOpacity : undefined)\n : formatColorRgb(r, g, b, newOpacity < 1 ? newOpacity : undefined);\n\n lastEmittedColor.current = newColor;\n handleColorChange(newColor);\n },\n [hue, canvasSaturation, canvasBrightness, format, handleColorChange]\n );\n\n const handleOpacityChangeComplete = useCallback(() => {\n setIsDragging(false);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n handleChangeComplete(newColor);\n }, [hue, canvasSaturation, canvasBrightness, opacity, format, handleChangeComplete]);\n\n const handleRecentColorSelect = useCallback(\n (color: string) => {\n // Update internal state immediately\n const parsed = parseColor(color);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n setCanvasSaturation(s);\n setCanvasBrightness(v);\n if (s > 0) {\n setHue(h);\n setHueWhenGrayscale(h);\n }\n\n lastEmittedColor.current = color;\n handleColorChange(color);\n handleChangeComplete(color);\n },\n [handleColorChange, handleChangeComplete]\n );\n\n const handleInputChange = useCallback(\n (newValue: string) => {\n if (isValidColor(newValue)) {\n // Update internal state immediately\n const parsed = parseColor(newValue);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n setCanvasSaturation(s);\n setCanvasBrightness(v);\n if (s > 0) {\n setHue(h);\n setHueWhenGrayscale(h);\n }\n\n lastEmittedColor.current = newValue;\n handleColorChange(newValue);\n handleChangeComplete(newValue);\n }\n },\n [handleColorChange, handleChangeComplete]\n );\n\n const handleFormatChange = useCallback(\n (newFormat: \"hex\" | \"rgb\") => {\n setFormat(newFormat);\n },\n []\n );\n\n return (\n <div\n ref={ref}\n className={cn('color', styles.color, className, resolved.root)}\n data-size={size}\n data-disabled={disabled || undefined}\n {...props}\n >\n {/* Recent Colors */}\n <ColorRecentColors\n onSelect={handleRecentColorSelect}\n disabled={disabled}\n size={size}\n />\n\n {/* Canvas for saturation/brightness (HSV) */}\n <ColorCanvas\n hue={hue}\n saturation={canvasSaturation}\n brightness={canvasBrightness}\n onChange={handleCanvasChange}\n disabled={disabled}\n size={size}\n />\n\n <div className={cn(styles.colorControls, resolved.controls)}>\n {/* Hue Slider */}\n <ColorHueSlider\n value={hue}\n onChange={handleHueChange}\n disabled={disabled}\n size={size}\n />\n\n {/* Opacity Slider */}\n {showOpacity && (\n <ColorOpacitySlider\n value={opacity}\n color={formatColorRgb(parsed.r, parsed.g, parsed.b)}\n onChange={handleOpacityChange}\n disabled={disabled}\n size={size}\n />\n )}\n\n {/* Input & Format Selector */}\n <ColorInput\n value={displayValue}\n format={format}\n onValueChange={handleInputChange}\n onFormatChange={handleFormatChange}\n disabled={disabled}\n size={size}\n showPreview={showPreview}\n previewColor={formatColorRgb(\n displayR,\n displayG,\n displayB,\n opacity < 1 ? opacity : undefined\n )}\n />\n </div>\n </div>\n );\n }\n);\n\nColor.displayName = \"Color\";\n",
|
|
4162
|
+
"tsx": "\"use client\";\n\nimport React, { useState, useEffect, useCallback, useRef } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Color.module.css\";\nimport {\n rgbToHsl,\n hslToRgb,\n rgbToHsv,\n hsvToRgb,\n formatColorHex,\n formatColorRgb,\n parseColor,\n addRecentColor,\n isValidColor,\n} from \"./color-utils\";\nimport { ColorCanvas } from \"./Color.Canvas\";\nimport { ColorHueSlider } from \"./Color.HueSlider\";\nimport { ColorOpacitySlider } from \"./Color.OpacitySlider\";\nimport { ColorRecentColors } from \"./Color.RecentColors\";\nimport { ColorInput } from \"./Color.Input\";\n\ninterface ColorStyleSlots {\n root?: StyleValue;\n controls?: StyleValue;\n}\n\ntype ColorStylesProp = StylesProp<ColorStyleSlots>;\n\nconst resolveColorBaseStyles = createStylesResolver(['root', 'controls'] as const);\n\nexport interface ColorProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Controlled color value as a CSS color string */\n value?: string;\n /** Initial color value for uncontrolled usage */\n defaultValue?: string;\n /** Called continuously while the user drags the color picker */\n onChange?: (color: string) => void;\n /** Called once when the user finishes a drag interaction */\n onChangeComplete?: (color: string) => void;\n /** Whether to show the opacity/alpha slider */\n showOpacity?: boolean;\n /** Whether to show a color preview swatch next to the input */\n showPreview?: boolean;\n /** Output format of the color value string */\n format?: \"hex\" | \"rgb\";\n /** Whether the color picker is disabled */\n disabled?: boolean;\n /** Size of the color picker */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Additional CSS class for the root element */\n className?: string;\n /** Classes applied to the root or named slots */\n styles?: ColorStylesProp;\n}\n\nexport const Color = React.forwardRef<HTMLDivElement, ColorProps>(\n (\n {\n value: controlledValue,\n defaultValue = \"#000000\",\n onChange,\n onChangeComplete,\n showOpacity = false,\n showPreview = false,\n format: controlledFormat = \"hex\",\n disabled = false,\n size = \"md\",\n className,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const currentValue = isControlled ? controlledValue : uncontrolledValue;\n\n const [format, setFormat] = useState<\"hex\" | \"rgb\">(controlledFormat);\n const [isDragging, setIsDragging] = useState(false);\n\n // Initialize state using HSV for better canvas mapping\n const initializeState = () => {\n const parsed = parseColor(currentValue);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n return { h, s, v };\n };\n\n const [initialState] = useState(initializeState);\n\n // Source of truth for canvas position (HSV Saturation & Value) and hue\n const [canvasSaturation, setCanvasSaturation] = useState(initialState.s);\n const [canvasBrightness, setCanvasBrightness] = useState(initialState.v);\n const [hue, setHue] = useState(initialState.h);\n const [hueWhenGrayscale, setHueWhenGrayscale] = useState(initialState.h);\n\n // Track the last emitted color to distinguish external updates from internal ones\n const lastEmittedColor = useRef(currentValue);\n\n const parsed = parseColor(currentValue);\n const opacity = parsed.a ?? 1;\n\n // Sync with external updates\n useEffect(() => {\n if (currentValue !== lastEmittedColor.current) {\n const parsed = parseColor(currentValue);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n\n setCanvasSaturation(s);\n setCanvasBrightness(v);\n\n // Preserve hue when desaturated\n if (s > 0) {\n setHue(h);\n setHueWhenGrayscale(h);\n }\n\n lastEmittedColor.current = currentValue;\n }\n }, [currentValue]);\n\n // Compute display color from current state (HSV -> RGB)\n const { r: displayR, g: displayG, b: displayB } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n\n const displayValue =\n format === \"hex\"\n ? formatColorHex(displayR, displayG, displayB, opacity < 1 ? opacity : undefined)\n : formatColorRgb(displayR, displayG, displayB, opacity < 1 ? opacity : undefined);\n \n const resolved = resolveColorBaseStyles(stylesProp);\n\n const handleColorChange = useCallback(\n (newColor: string) => {\n if (!isControlled) {\n setUncontrolledValue(newColor);\n }\n onChange?.(newColor);\n },\n [isControlled, onChange]\n );\n\n const handleChangeComplete = useCallback(\n (newColor: string) => {\n addRecentColor(newColor);\n onChangeComplete?.(newColor);\n },\n [onChangeComplete]\n );\n\n const handleCanvasChange = useCallback(\n (saturation: number, brightness: number) => {\n setIsDragging(true);\n setCanvasSaturation(saturation);\n setCanvasBrightness(brightness);\n\n const { r, g, b } = hsvToRgb(hue, saturation, brightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n lastEmittedColor.current = newColor;\n handleColorChange(newColor);\n },\n [hue, opacity, format, handleColorChange]\n );\n\n const handleCanvasChangeComplete = useCallback(() => {\n setIsDragging(false);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n handleChangeComplete(newColor);\n }, [hue, canvasSaturation, canvasBrightness, opacity, format, handleChangeComplete]);\n\n const handleHueChange = useCallback(\n (newHue: number) => {\n setIsDragging(true);\n setHue(newHue);\n if (canvasSaturation > 0) {\n setHueWhenGrayscale(newHue);\n }\n\n const { r, g, b } = hsvToRgb(newHue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n lastEmittedColor.current = newColor;\n handleColorChange(newColor);\n },\n [canvasSaturation, canvasBrightness, opacity, format, handleColorChange]\n );\n\n const handleHueChangeComplete = useCallback(() => {\n setIsDragging(false);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n handleChangeComplete(newColor);\n }, [hue, canvasSaturation, canvasBrightness, opacity, format, handleChangeComplete]);\n\n const handleOpacityChange = useCallback(\n (newOpacity: number) => {\n setIsDragging(true);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, newOpacity < 1 ? newOpacity : undefined)\n : formatColorRgb(r, g, b, newOpacity < 1 ? newOpacity : undefined);\n\n lastEmittedColor.current = newColor;\n handleColorChange(newColor);\n },\n [hue, canvasSaturation, canvasBrightness, format, handleColorChange]\n );\n\n const handleOpacityChangeComplete = useCallback(() => {\n setIsDragging(false);\n const { r, g, b } = hsvToRgb(hue, canvasSaturation, canvasBrightness);\n const newColor = format === \"hex\"\n ? formatColorHex(r, g, b, opacity < 1 ? opacity : undefined)\n : formatColorRgb(r, g, b, opacity < 1 ? opacity : undefined);\n\n handleChangeComplete(newColor);\n }, [hue, canvasSaturation, canvasBrightness, opacity, format, handleChangeComplete]);\n\n const handleRecentColorSelect = useCallback(\n (color: string) => {\n // Update internal state immediately\n const parsed = parseColor(color);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n setCanvasSaturation(s);\n setCanvasBrightness(v);\n if (s > 0) {\n setHue(h);\n setHueWhenGrayscale(h);\n }\n\n lastEmittedColor.current = color;\n handleColorChange(color);\n handleChangeComplete(color);\n },\n [handleColorChange, handleChangeComplete]\n );\n\n const handleInputChange = useCallback(\n (newValue: string) => {\n if (isValidColor(newValue)) {\n // Update internal state immediately\n const parsed = parseColor(newValue);\n const { h, s, v } = rgbToHsv(parsed.r, parsed.g, parsed.b);\n setCanvasSaturation(s);\n setCanvasBrightness(v);\n if (s > 0) {\n setHue(h);\n setHueWhenGrayscale(h);\n }\n\n lastEmittedColor.current = newValue;\n handleColorChange(newValue);\n handleChangeComplete(newValue);\n }\n },\n [handleColorChange, handleChangeComplete]\n );\n\n const handleFormatChange = useCallback(\n (newFormat: \"hex\" | \"rgb\") => {\n setFormat(newFormat);\n },\n []\n );\n\n return (\n <div\n ref={ref}\n className={cn('color', styles.color, className, resolved.root)}\n data-size={size}\n data-disabled={disabled || undefined}\n {...props}\n >\n {/* Recent Colors */}\n <ColorRecentColors\n onSelect={handleRecentColorSelect}\n disabled={disabled}\n size={size}\n />\n\n {/* Canvas for saturation/brightness (HSV) */}\n <ColorCanvas\n hue={hue}\n saturation={canvasSaturation}\n brightness={canvasBrightness}\n onChange={handleCanvasChange}\n disabled={disabled}\n size={size}\n />\n\n <div className={cn(styles.colorControls, resolved.controls)}>\n {/* Hue Slider */}\n <ColorHueSlider\n value={hue}\n onChange={handleHueChange}\n disabled={disabled}\n size={size}\n />\n\n {/* Opacity Slider */}\n {showOpacity && (\n <ColorOpacitySlider\n value={opacity}\n color={formatColorRgb(parsed.r, parsed.g, parsed.b)}\n onChange={handleOpacityChange}\n disabled={disabled}\n size={size}\n />\n )}\n\n {/* Input & Format Selector */}\n <ColorInput\n value={displayValue}\n format={format}\n onValueChange={handleInputChange}\n onFormatChange={handleFormatChange}\n disabled={disabled}\n size={size}\n showPreview={showPreview}\n previewColor={formatColorRgb(\n displayR,\n displayG,\n displayB,\n opacity < 1 ? opacity : undefined\n )}\n />\n </div>\n </div>\n );\n }\n);\n\nColor.displayName = \"Color\";\n",
|
|
4897
4163
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .color {\n --disabled-opacity: 0.5;\n @apply flex flex-col gap-4;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n width: 260px;\n }\n\n .color[data-disabled=\"true\"] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n .colorControls,\n .color-controls {\n @apply pb-3 px-3 space-y-3;\n }\n\n /* Input styles */\n .inputGroup,\n .input-group {\n @apply flex w-full;\n }\n\n .inputGroup > div:nth-child(1),\n .input-group > div:nth-child(1) {\n flex: 1;\n @apply min-w-0;\n }\n\n .colorInput,\n .color-input { @apply w-full; }\n\n .formatSelect,\n .format-select { @apply shrink-0; width: 85px; }\n\n .color[data-size=\"sm\"] .formatSelect,\n .color[data-size=\"sm\"] .format-select { width: 75px; }\n\n /* Canvas Styles */\n .canvasContainer,\n .canvas-container {\n @apply relative mx-auto mt-2 flex flex-col;\n width: 96%;\n cursor: crosshair;\n touch-action: none;\n min-height: 160px;\n }\n\n .canvasContainer[data-focus-visible=\"true\"],\n .canvas-container[data-focus-visible=\"true\"] {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .canvas {\n @apply relative w-full flex-1 overflow-hidden;\n flex: 1;\n border-radius: none;\n }\n\n .canvasGradientHue,\n .canvas-gradient-hue {\n @apply absolute inset-0 overflow-hidden;\n }\n\n .canvasGradientSaturation,\n .canvasGradientLightness,\n .canvas-gradient-saturation,\n .canvas-gradient-lightness {\n @apply absolute inset-0;\n border-radius: none;\n }\n\n .canvas-gradient-saturation {\n background: linear-gradient(to right, rgb(255, 255, 255), transparent);\n }\n\n .canvas-gradient-lightness {\n background: linear-gradient(to top, rgb(0, 0, 0), transparent);\n }\n\n .canvasPointer,\n .canvas-pointer {\n @apply absolute h-3 w-3;\n border-radius: var(--radius-full);\n border: 2px solid var(--pointer-border);\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n transform: translate(-50%, -50%);\n z-index: 10;\n }\n\n /* Hue Slider Styles */\n .hueSlider,\n .hue-slider {\n @apply relative flex items-center overflow-hidden;\n height: 16px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .hueTrack,\n .hue-track {\n @apply relative h-full w-full;\n border-radius: var(--radius-full);\n background: linear-gradient(\n to right,\n hsl(0, 100%, 50%),\n hsl(60, 100%, 50%),\n hsl(120, 100%, 50%),\n hsl(180, 100%, 50%),\n hsl(240, 100%, 50%),\n hsl(300, 100%, 50%),\n hsl(360, 100%, 50%)\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .hueThumb,\n .opacityThumb,\n .hue-thumb,\n .opacity-thumb {\n @apply absolute;\n border-radius: var(--radius-full);\n border: 2px solid var(--thumb-border);\n top: 50%;\n background: var(--thumb-background);\n pointer-events: none;\n }\n\n .hueThumb,\n .hue-thumb {\n @apply h-3 w-3;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .hueSlider[data-focus-visible=\"true\"] .hueThumb,\n .hue-slider[data-focus-visible=\"true\"] .hue-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .hue-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .hue-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Opacity Slider Styles */\n .opacitySlider,\n .opacity-slider {\n @apply relative flex items-center overflow-hidden;\n height: 12px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .opacityTrack,\n .opacity-track {\n @apply relative h-full w-full overflow-hidden;\n border-radius: var(--radius-full);\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-dark),\n var(--checkerboard-dark) 10px,\n var(--checkerboard-light) 10px,\n var(--checkerboard-light) 20px\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .opacityThumb,\n .opacity-thumb {\n @apply h-2.5 w-2.5;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .opacitySlider[data-focus-visible=\"true\"] .opacityThumb,\n .opacity-slider[data-focus-visible=\"true\"] .opacity-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .opacity-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .opacity-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Recent Colors Styles */\n .recentColors,\n .recent-colors {\n @apply flex gap-2 overflow-x-auto pb-1;\n }\n\n .recentColorSwatch,\n .recent-color-swatch {\n @apply h-8 w-8 shrink-0 cursor-pointer p-0;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n background: none;\n outline: none;\n }\n\n .recentColorSwatch:hover,\n .recent-color-swatch:hover {\n transform: scale(1.1);\n box-shadow: 0 0 0 2px var(--ring-color);\n }\n\n .recentColorSwatch:active,\n .recent-color-swatch:active {\n transform: scale(0.95);\n }\n\n .recentColorSwatch:focus-visible,\n .recent-color-swatch:focus-visible {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n\n /* Preview Container - deprecated, use preview-swatch instead */\n .previewContainer,\n .preview-container {\n @apply flex justify-center py-2;\n }\n\n /* Preview Swatch - inline with input */\n .previewSwatch,\n .preview-swatch {\n @apply relative h-9 w-9 shrink-0 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n\n .previewSwatch::before,\n .preview-swatch::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 6px,\n var(--checkerboard-dark) 6px,\n var(--checkerboard-dark) 12px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .preview {\n @apply relative h-16 w-16 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n }\n\n .preview::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 10px,\n var(--checkerboard-dark) 10px,\n var(--checkerboard-dark) 20px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .previewSwatch::after,\n .preview-swatch::after,\n .preview::after {\n content: \"\";\n @apply absolute inset-0;\n background-color: var(--preview-color, transparent);\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 2;\n }\n}\n",
|
|
4898
4164
|
"cssTypes": "export const color: string;\nexport const colorControls: string;\nexport const inputGroup: string;\nexport const colorInput: string;\nexport const formatSelect: string;\nexport const canvasContainer: string;\nexport const canvas: string;\nexport const canvasGradientHue: string;\nexport const canvasGradientSaturation: string;\nexport const canvasGradientLightness: string;\nexport const canvasPointer: string;\nexport const hueSlider: string;\nexport const hueTrack: string;\nexport const hueThumb: string;\nexport const opacitySlider: string;\nexport const opacityTrack: string;\nexport const opacityThumb: string;\nexport const recentColors: string;\nexport const recentColorSwatch: string;\nexport const previewContainer: string;\nexport const previewSwatch: string;\nexport const preview: string;\n"
|
|
4899
4165
|
},
|
|
4900
4166
|
"command": {
|
|
4901
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\"\n\nimport { createPortal } from \"react-dom\";\n\nimport { useDialog } from \"@react-aria/dialog\";\nimport { FocusScope } from \"@react-aria/focus\";\n\nimport { useOverlayTriggerState } from \"@react-stately/overlays\";\n\nimport { filterDOMProps } from \"@react-aria/utils\";\nimport { cn } from \"./utils\";\nimport { Search } from \"lucide-react\";\nimport { useScrollLock } from \"../../hooks/useScrollLock\";\n\nimport { Card } from \"../Card\";\nimport { Scroll } from \"../Scroll\";\nimport { Badge } from \"../Badge\";\n\nimport type { Key } from \"react-aria\";\nimport styles from \"./Command.module.css\";\n\nexport interface CommandItem {\n id: string;\n label: string;\n description?: string;\n category?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n keywords?: string[];\n action: () => void | Promise<void>;\n hint?: string;\n}\n\nexport interface CommandGroupedItems {\n category: string | undefined;\n items: CommandItem[];\n}\n\ninterface CommandContextValue {\n isOpen: boolean;\n close: () => void;\n focusedKey: Key | null;\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>;\n registerItem: (key: Key, textValue: string) => void;\n unregisterItem: (key: Key) => void;\n actionRef: React.MutableRefObject<Map<Key, () => void | Promise<void>>>;\n searchInputRef: React.MutableRefObject<HTMLInputElement | null>;\n scrollableRef: React.MutableRefObject<HTMLDivElement | null>;\n searchValue: string;\n setSearchValue: React.Dispatch<React.SetStateAction<string>>;\n filteredItems: CommandItem[];\n groupedItems: CommandGroupedItems[];\n}\n\nconst CommandContext = React.createContext<CommandContextValue | undefined>(\n undefined,\n);\n\nfunction useCommandContext() {\n const ctx = React.useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"Command sub-components must be used within Command\");\n }\n return ctx;\n}\n\nfunction scoreCommandRelevance(\n text: string,\n query: string,\n): number {\n const t = text.toLowerCase();\n const q = query.toLowerCase();\n\n if (t === q) return 1000;\n if (t.startsWith(q)) return 900;\n if (t.split(/\\s+/).some((word) => word === q)) return 800;\n if (t.includes(q)) {\n const index = t.indexOf(q);\n return 710 - Math.min(index, 10);\n }\n return 0;\n}\n\nexport interface CommandProps {\n /** Whether the command palette is open */\n open?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Additional CSS class for the palette dialog */\n className?: string;\n /** Additional CSS class for the backdrop overlay */\n overlayClassName?: string;\n /** List of command items to display */\n items?: CommandItem[];\n /** Custom filter function for commands against the query */\n filter?: (command: CommandItem, query: string) => boolean;\n /** Child elements rendered inside the palette */\n children?: React.ReactNode;\n}\n\nconst Command = React.forwardRef<HTMLDivElement, CommandProps>(\n (\n { open = false, onOpenChange, className, overlayClassName, items = [], filter, children },\n ref,\n ) => {\n const [mounted, setMounted] = React.useState(false);\n const overlayState = useOverlayTriggerState({\n isOpen: open,\n onOpenChange,\n });\n\n const modalRef = React.useRef<HTMLDivElement>(null);\n const paletteRef = React.useRef<HTMLDivElement>(null);\n const searchInputRef = React.useRef<HTMLInputElement>(null);\n const scrollableRef = React.useRef<HTMLDivElement>(null);\n\n useScrollLock(overlayState.isOpen, scrollableRef.current);\n const itemsRef = React.useRef<Map<Key, string>>(new Map());\n const actionRef = React.useRef<Map<Key, () => void | Promise<void>>>(\n new Map(),\n );\n const focusedKeyRef = React.useRef<Key | null>(null);\n\n const [focusedKey, setFocusedKey] = React.useState<Key | null>(null);\n const [itemCount, setItemCount] = React.useState(0);\n const [searchValue, setSearchValue] = React.useState(\"\");\n\n const filteredItems = items.filter((cmd) => !filter || filter(cmd, searchValue));\n\n const groupedItems = React.useMemo(() => {\n const groups = new Map<string | undefined, CommandItem[]>();\n filteredItems.forEach((cmd) => {\n const cat = cmd.category;\n if (!groups.has(cat)) {\n groups.set(cat, []);\n }\n groups.get(cat)!.push(cmd);\n });\n\n // Maintain category order from original items\n const categoryOrder = new Map<string | undefined, number>();\n let idx = 0;\n items.forEach((cmd) => {\n if (!categoryOrder.has(cmd.category)) {\n categoryOrder.set(cmd.category, idx++);\n }\n });\n\n return Array.from(groups.entries())\n .sort(\n ([a], [b]) =>\n (categoryOrder.get(a) ?? Infinity) - (categoryOrder.get(b) ?? Infinity),\n )\n .map(([category, items]) => ({ category, items }));\n }, [filteredItems, items]);\n\n React.useImperativeHandle(ref, () => paletteRef.current as HTMLDivElement);\n\n React.useEffect(() => {\n setMounted(true);\n }, []);\n\n // Sync focusedKeyRef with focusedKey\n React.useEffect(() => {\n focusedKeyRef.current = focusedKey;\n }, [focusedKey]);\n\n // Auto-focus search input when opening\n React.useEffect(() => {\n if (overlayState.isOpen && searchInputRef.current) {\n setTimeout(() => searchInputRef.current?.focus(), 0);\n }\n }, [overlayState.isOpen]);\n\n // Cleanup state when overlay closes\n React.useEffect(() => {\n if (!overlayState.isOpen) {\n scrollableRef.current = null;\n setSearchValue(\"\");\n }\n }, [overlayState.isOpen]);\n\n // Cmd+K global listener\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const isMac =\n navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0 ||\n navigator.userAgent.indexOf(\"Mac\") !== -1;\n const isCommandKey = isMac ? event.metaKey : event.ctrlKey;\n\n if (isCommandKey && event.key === \"k\") {\n event.preventDefault();\n overlayState.open();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [overlayState]);\n\n // Auto-focus first item when items change (filtering, opening)\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n if (!searchValue) {\n setFocusedKey(null);\n return;\n }\n\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length > 0) {\n setFocusedKey(keys[0]);\n } else {\n setFocusedKey(null);\n }\n }, [itemCount, overlayState.isOpen, searchValue]);\n\n // Keyboard navigation\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[0]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[(idx + 1) % keys.length]);\n }\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[keys.length - 1]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[idx === 0 ? keys.length - 1 : idx - 1]);\n }\n break;\n }\n case \"Enter\": {\n event.preventDefault();\n if (focusedKey !== null) {\n const action = actionRef.current.get(focusedKey);\n if (action) {\n action();\n overlayState.close();\n }\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n overlayState.close();\n break;\n }\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [overlayState.isOpen, focusedKey]);\n\n const registerItem = React.useCallback((key: Key, textValue: string) => {\n itemsRef.current.set(key, textValue);\n setItemCount((c) => c + 1);\n }, []);\n\n const unregisterItem = React.useCallback((key: Key) => {\n itemsRef.current.delete(key);\n setItemCount((c) => c + 1);\n }, []);\n\n // Click outside to close\n const handleOverlayClick = React.useCallback(\n (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n overlayState.close();\n }\n },\n [overlayState],\n );\n\n const { dialogProps } = useDialog(\n { \"aria-label\": \"Command palette\" },\n modalRef,\n );\n\n if (!mounted || !overlayState.isOpen) {\n return null;\n }\n\n return createPortal(\n <FocusScope contain restoreFocus>\n <div\n className={cn(\n \"command\",\n styles[\"overlay\"],\n overlayClassName,\n )}\n onClick={handleOverlayClick}\n >\n <Card\n {...filterDOMProps(dialogProps)}\n ref={modalRef}\n className={cn(\"content\", styles[\"content\"], className)}\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <CommandContext.Provider\n value={{\n isOpen: overlayState.isOpen,\n close: overlayState.close,\n focusedKey,\n setFocusedKey,\n registerItem,\n unregisterItem,\n actionRef,\n searchInputRef,\n scrollableRef,\n searchValue,\n setSearchValue,\n filteredItems,\n groupedItems,\n }}\n >\n {children}\n </CommandContext.Provider>\n </Card>\n </div>\n </FocusScope>,\n document.body,\n );\n },\n);\n\nCommand.displayName = \"Command\";\n\ninterface CommandSearchInputProps {\n /** Controlled search text value */\n value?: string;\n /** Called when the search text changes */\n onChange?: (value: string) => void;\n /** Placeholder text for the search input */\n placeholder?: string;\n /** Additional CSS class for the search input */\n className?: string;\n}\n\nconst CommandSearchInput = React.forwardRef<\n HTMLInputElement,\n CommandSearchInputProps\n>(({ value: externalValue, onChange: externalOnChange, placeholder = \"Search...\" }, ref) => {\n const { searchInputRef, searchValue, setSearchValue } = useCommandContext();\n\n // Use external value/onChange if provided, otherwise use internal context state\n const value = externalValue !== undefined ? externalValue : searchValue;\n const onChange = externalOnChange || setSearchValue;\n\n // Use internal Command ref for auto-focus, or user-provided ref\n const inputRef = (ref || searchInputRef) as React.RefObject<HTMLInputElement>;\n\n return (\n <Card.Header className={styles[\"search\"]}>\n <div className={styles[\"search-container\"]}>\n <div className={styles[\"search-icon\"]}>\n <Search className=\"w-4 h-4\" />\n </div>\n <input\n ref={inputRef}\n type=\"text\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder={placeholder}\n className={styles[\"search-input\"]}\n aria-label=\"Search commands\"\n />\n {value && (\n <button\n aria-label=\"Clear search\"\n onClick={() => onChange(\"\")}\n className={styles[\"search-clear\"]}\n >\n ✕\n </button>\n )}\n </div>\n </Card.Header>\n );\n});\n\nCommandSearchInput.displayName = \"Command.SearchInput\";\n\ninterface CommandListProps {\n /** Child elements rendered inside the list */\n children?: React.ReactNode;\n /** Message shown when no items match the search */\n emptyMessage?: string;\n /** Additional CSS class for the list container */\n className?: string;\n}\n\n/** Scrollable container that renders the filtered command items */\nconst CommandListComponent = React.forwardRef<\n HTMLDivElement,\n CommandListProps\n>(({ children, emptyMessage = \"No items found.\", className }, ref) => {\n const { scrollableRef } = useCommandContext();\n\n return (\n <div className={cn(styles[\"inner\"], className)}>\n <Scroll\n ref={(el) => {\n if (ref) {\n if (typeof ref === \"function\") {\n ref(el);\n } else {\n ref.current = el;\n }\n }\n scrollableRef.current = el;\n }}\n className={styles[\"list\"]}\n maxHeight=\"44dvh\"\n fadeY={true}\n >\n <div role=\"listbox\" aria-label=\"Commands\">\n {!children ? (\n <div className={styles[\"empty\"]}>{emptyMessage}</div>\n ) : (\n children\n )}\n </div>\n </Scroll>\n </div>\n );\n});\n\nCommandListComponent.displayName = \"Command.List\";\n\ninterface CommandItemProps {\n /** Unique key identifying this command item */\n value: Key;\n /** Plain-text label used for keyboard navigation lookup */\n textValue: string;\n /** Called when the item is selected */\n action: () => void | Promise<void>;\n /** Child elements rendered inside the item */\n children?: React.ReactNode;\n /** Additional CSS class for the item */\n className?: string;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the command item */\n hint?: string;\n}\n\nconst CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>(\n ({ value, textValue, action, children, className, hint }, ref) => {\n const { focusedKey, registerItem, unregisterItem, actionRef } =\n useCommandContext();\n\n React.useEffect(() => {\n registerItem(value, textValue);\n actionRef.current.set(value, action);\n return () => {\n unregisterItem(value);\n actionRef.current.delete(value);\n };\n }, [value, textValue, action, registerItem, unregisterItem, actionRef]);\n\n const isHighlighted = focusedKey === value;\n\n return (\n <div\n ref={ref}\n data-highlighted={isHighlighted}\n role=\"option\"\n aria-selected={isHighlighted}\n onClick={() => action()}\n className={cn(\"item\", styles[\"item\"], className)}\n >\n <div className={styles[\"item-content\"]}>{children}</div>\n {hint && (\n <Badge variant=\"secondary\" size=\"sm\" className={styles[\"hint-wrapper\"]}>\n {hint}\n </Badge>\n )}\n </div>\n );\n },\n);\n\nCommandItem.displayName = \"Command.Item\";\n\ninterface CommandCategoryProps {\n /** Child elements rendered inside the category header */\n children?: React.ReactNode;\n /** Additional CSS class for the category */\n className?: string;\n}\n\n/** Labeled section grouping related commands */\nconst CommandCategory = React.forwardRef<\n HTMLDivElement,\n CommandCategoryProps\n>(({ children, className }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(styles[\"category-header\"], className)}\n >\n {children}\n </div>\n );\n});\n\nCommandCategory.displayName = \"Command.Category\";\n\ninterface CommandFooterProps {\n /** Child elements rendered inside the footer */\n children?: React.ReactNode;\n /** Additional CSS class applied to the footer */\n className?: string;\n}\n\n/** Fixed bottom bar in the command palette for hints or actions */\nconst CommandFooter = React.forwardRef<HTMLDivElement, CommandFooterProps>(\n ({ children, className }, ref) => {\n return (\n <Card.Footer ref={ref} className={cn(styles[\"footer\"], className)}>\n {children}\n </Card.Footer>\n );\n },\n);\n\nCommandFooter.displayName = \"Command.Footer\";\n\nexport interface CommandGroupsProps {\n /** Renders a category header for the given category name */\n renderCategory?: (category: string | undefined) => React.ReactNode;\n /** Renders a single command item row */\n renderItem: (command: CommandItem, hint?: string) => React.ReactNode;\n /** Additional CSS class for the groups container */\n className?: string;\n}\n\n/** Wrapper that renders multiple Command.Category sections */\nconst CommandGroups = React.forwardRef<HTMLDivElement, CommandGroupsProps>(\n ({ renderCategory, renderItem, className }, ref) => {\n const { groupedItems } = useCommandContext();\n\n return (\n <div ref={ref} className={className}>\n {groupedItems.map(({ category, items }) => (\n <div key={category || \"uncategorized\"}>\n {renderCategory && renderCategory(category)}\n {items.map((cmd) => (\n <React.Fragment key={cmd.id}>{renderItem(cmd, cmd.hint)}</React.Fragment>\n ))}\n </div>\n ))}\n </div>\n );\n },\n);\n\nCommandGroups.displayName = \"Command.Groups\";\n\ninterface CommandComponent\n extends React.ForwardRefExoticComponent<\n CommandProps & React.RefAttributes<HTMLDivElement>\n > {\n SearchInput: typeof CommandSearchInput;\n List: typeof CommandListComponent;\n Item: typeof CommandItem;\n Category: typeof CommandCategory;\n Footer: typeof CommandFooter;\n Groups: typeof CommandGroups;\n}\n\nconst CommandWithSubcomponents = Object.assign(Command, {\n SearchInput: CommandSearchInput,\n List: CommandListComponent,\n Item: CommandItem,\n Category: CommandCategory,\n Footer: CommandFooter,\n Groups: CommandGroups,\n}) as CommandComponent;\n\nexport { CommandWithSubcomponents as Command };\nexport { CommandSearchInput as CommandInput };\nexport { CommandListComponent as CommandListComponent };\nexport { CommandCategory };\nexport { CommandFooter };\nexport { CommandGroups };\nexport { scoreCommandRelevance };\nexport { useCommandContext };\n",
|
|
4902
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n /* Overlay Container */\n .overlay {\n @apply fixed inset-0 flex items-start justify-center overflow-hidden;\n z-index: 999;\n padding-top: 20vh;\n /* Apply backdrop styles directly to avoid creating a containing block that disrupts sticky elements */\n background-color: var(--overlay);\n backdrop-filter: var(--overlay-backdrop);\n }\n\n /* Content */\n .content {\n @apply relative m-2 w-full max-w-[28rem];\n border-radius: var(--radius-sm);\n background: var(--background-default);\n margin-inline: 1rem;\n box-shadow: var(--shadow);\n animation: fade-in-zoom-in 0.2s ease-out;\n }\n\n .inner {\n border-radius: var(--radius-sm) var(--radius-sm) 0 0;\n border-top: var(--border-width-base) solid var(--border-default);\n @apply overflow-hidden;\n }\n\n /* Search Section */\n .search {\n @apply border-none flex p-
|
|
4903
|
-
"cssTypes": "export interface Styles {\n overlay: string;\n backdrop: string;\n content: string;\n inner: string;\n search: string;\n
|
|
4167
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\"\n\nimport { createPortal } from \"react-dom\";\n\nimport { useDialog } from \"@react-aria/dialog\";\nimport { FocusScope } from \"@react-aria/focus\";\n\nimport { useOverlayTriggerState } from \"@react-stately/overlays\";\n\nimport { filterDOMProps } from \"@react-aria/utils\";\nimport { cn } from \"./utils\";\nimport { Search } from \"lucide-react\";\nimport { useScrollLock } from \"../../hooks/useScrollLock\";\n\nimport { Card } from \"../Card\";\nimport { Scroll } from \"../Scroll\";\nimport { Badge } from \"../Badge\";\nimport { Input, type InputProps } from \"../Input\";\n\nimport type { Key } from \"react-aria\";\nimport styles from \"./Command.module.css\";\n\nexport interface CommandItem {\n id: string;\n label: string;\n description?: string;\n category?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n keywords?: string[];\n action: () => void | Promise<void>;\n hint?: string;\n}\n\nexport interface CommandGroupedItems {\n category: string | undefined;\n items: CommandItem[];\n}\n\ninterface CommandContextValue {\n isOpen: boolean;\n close: () => void;\n focusedKey: Key | null;\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>;\n registerItem: (key: Key, textValue: string) => void;\n unregisterItem: (key: Key) => void;\n actionRef: React.MutableRefObject<Map<Key, () => void | Promise<void>>>;\n searchInputRef: React.MutableRefObject<HTMLInputElement | null>;\n scrollableRef: React.MutableRefObject<HTMLDivElement | null>;\n searchValue: string;\n setSearchValue: React.Dispatch<React.SetStateAction<string>>;\n filteredItems: CommandItem[];\n groupedItems: CommandGroupedItems[];\n}\n\nconst CommandContext = React.createContext<CommandContextValue | undefined>(\n undefined,\n);\n\nfunction useCommandContext() {\n const ctx = React.useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"Command sub-components must be used within Command\");\n }\n return ctx;\n}\n\nfunction scoreCommandRelevance(\n text: string,\n query: string,\n): number {\n const t = text.toLowerCase();\n const q = query.toLowerCase();\n\n if (t === q) return 1000;\n if (t.startsWith(q)) return 900;\n if (t.split(/\\s+/).some((word) => word === q)) return 800;\n if (t.includes(q)) {\n const index = t.indexOf(q);\n return 710 - Math.min(index, 10);\n }\n return 0;\n}\n\nexport interface CommandProps {\n /** Whether the command palette is open */\n open?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Additional CSS class for the palette dialog */\n className?: string;\n /** Additional CSS class for the backdrop overlay */\n overlayClassName?: string;\n /** List of command items to display */\n items?: CommandItem[];\n /** Custom filter function for commands against the query */\n filter?: (command: CommandItem, query: string) => boolean;\n /** Child elements rendered inside the palette */\n children?: React.ReactNode;\n}\n\nconst Command = React.forwardRef<HTMLDivElement, CommandProps>(\n (\n { open = false, onOpenChange, className, overlayClassName, items = [], filter, children },\n ref,\n ) => {\n const [mounted, setMounted] = React.useState(false);\n const overlayState = useOverlayTriggerState({\n isOpen: open,\n onOpenChange,\n });\n\n const modalRef = React.useRef<HTMLDivElement>(null);\n const paletteRef = React.useRef<HTMLDivElement>(null);\n const searchInputRef = React.useRef<HTMLInputElement>(null);\n const scrollableRef = React.useRef<HTMLDivElement>(null);\n\n useScrollLock(overlayState.isOpen, scrollableRef.current);\n const itemsRef = React.useRef<Map<Key, string>>(new Map());\n const actionRef = React.useRef<Map<Key, () => void | Promise<void>>>(\n new Map(),\n );\n const focusedKeyRef = React.useRef<Key | null>(null);\n\n const [focusedKey, setFocusedKey] = React.useState<Key | null>(null);\n const [itemCount, setItemCount] = React.useState(0);\n const [searchValue, setSearchValue] = React.useState(\"\");\n\n const filteredItems = items.filter((cmd) => !filter || filter(cmd, searchValue));\n\n const groupedItems = React.useMemo(() => {\n const groups = new Map<string | undefined, CommandItem[]>();\n filteredItems.forEach((cmd) => {\n const cat = cmd.category;\n if (!groups.has(cat)) {\n groups.set(cat, []);\n }\n groups.get(cat)!.push(cmd);\n });\n\n // Maintain category order from original items\n const categoryOrder = new Map<string | undefined, number>();\n let idx = 0;\n items.forEach((cmd) => {\n if (!categoryOrder.has(cmd.category)) {\n categoryOrder.set(cmd.category, idx++);\n }\n });\n\n return Array.from(groups.entries())\n .sort(\n ([a], [b]) =>\n (categoryOrder.get(a) ?? Infinity) - (categoryOrder.get(b) ?? Infinity),\n )\n .map(([category, items]) => ({ category, items }));\n }, [filteredItems, items]);\n\n React.useImperativeHandle(ref, () => paletteRef.current as HTMLDivElement);\n\n React.useEffect(() => {\n setMounted(true);\n }, []);\n\n // Sync focusedKeyRef with focusedKey\n React.useEffect(() => {\n focusedKeyRef.current = focusedKey;\n }, [focusedKey]);\n\n // Auto-focus search input when opening\n React.useEffect(() => {\n if (overlayState.isOpen && searchInputRef.current) {\n setTimeout(() => searchInputRef.current?.focus(), 0);\n }\n }, [overlayState.isOpen]);\n\n // Cleanup state when overlay closes\n React.useEffect(() => {\n if (!overlayState.isOpen) {\n scrollableRef.current = null;\n setSearchValue(\"\");\n }\n }, [overlayState.isOpen]);\n\n // Cmd+K global listener\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const isMac =\n navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0 ||\n navigator.userAgent.indexOf(\"Mac\") !== -1;\n const isCommandKey = isMac ? event.metaKey : event.ctrlKey;\n\n if (isCommandKey && event.key === \"k\") {\n event.preventDefault();\n overlayState.open();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [overlayState]);\n\n // Auto-focus first item when items change (filtering, opening)\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n if (!searchValue) {\n setFocusedKey(null);\n return;\n }\n\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length > 0) {\n setFocusedKey(keys[0]);\n } else {\n setFocusedKey(null);\n }\n }, [itemCount, overlayState.isOpen, searchValue]);\n\n // Keyboard navigation\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[0]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[(idx + 1) % keys.length]);\n }\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[keys.length - 1]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[idx === 0 ? keys.length - 1 : idx - 1]);\n }\n break;\n }\n case \"Enter\": {\n event.preventDefault();\n if (focusedKey !== null) {\n const action = actionRef.current.get(focusedKey);\n if (action) {\n action();\n overlayState.close();\n }\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n overlayState.close();\n break;\n }\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [overlayState.isOpen, focusedKey]);\n\n const registerItem = React.useCallback((key: Key, textValue: string) => {\n itemsRef.current.set(key, textValue);\n setItemCount((c) => c + 1);\n }, []);\n\n const unregisterItem = React.useCallback((key: Key) => {\n itemsRef.current.delete(key);\n setItemCount((c) => c + 1);\n }, []);\n\n // Click outside to close\n const handleOverlayClick = React.useCallback(\n (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n overlayState.close();\n }\n },\n [overlayState],\n );\n\n const { dialogProps } = useDialog(\n { \"aria-label\": \"Command palette\" },\n modalRef,\n );\n\n if (!mounted || !overlayState.isOpen) {\n return null;\n }\n\n return createPortal(\n <FocusScope contain restoreFocus>\n <div\n className={cn(\n \"command\",\n styles[\"overlay\"],\n overlayClassName,\n )}\n onClick={handleOverlayClick}\n >\n <Card\n {...filterDOMProps(dialogProps)}\n ref={modalRef}\n className={cn(\"content\", styles[\"content\"], className)}\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <CommandContext.Provider\n value={{\n isOpen: overlayState.isOpen,\n close: overlayState.close,\n focusedKey,\n setFocusedKey,\n registerItem,\n unregisterItem,\n actionRef,\n searchInputRef,\n scrollableRef,\n searchValue,\n setSearchValue,\n filteredItems,\n groupedItems,\n }}\n >\n {children}\n </CommandContext.Provider>\n </Card>\n </div>\n </FocusScope>,\n document.body,\n );\n },\n);\n\nCommand.displayName = \"Command\";\n\ninterface CommandInputProps extends InputProps {}\n\nconst CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(\n ({ value: externalValue, onChange: externalOnChange, icon, actions, placeholder = \"Search...\", ...props }, ref) => {\n const { searchInputRef, searchValue, setSearchValue } = useCommandContext();\n\n const value = externalValue !== undefined ? externalValue : searchValue;\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n externalOnChange?.(e);\n };\n\n const inputRef = (ref ?? searchInputRef) as React.RefObject<HTMLInputElement>;\n\n const resolvedActions = actions ?? (value ? [{ icon: <>✕</>, title: \"Clear search\", onClick: () => { setSearchValue(\"\"); } }] : []);\n\n return (\n <Card.Header className={styles[\"search\"]}>\n <Input\n ref={inputRef}\n value={value as string}\n onChange={handleChange}\n icon={icon ?? <Search className=\"w-4 h-4\" />}\n actions={resolvedActions}\n placeholder={placeholder}\n aria-label=\"Search commands\"\n styles={{ root: styles[\"input\"] }}\n {...props}\n />\n </Card.Header>\n );\n }\n);\n\nCommandInput.displayName = \"Command.Input\";\n\ninterface CommandListProps {\n /** Child elements rendered inside the list */\n children?: React.ReactNode;\n /** Message shown when no items match the search */\n emptyMessage?: string;\n /** Additional CSS class for the list container */\n className?: string;\n}\n\n/** Scrollable container that renders the filtered command items */\nconst CommandListComponent = React.forwardRef<\n HTMLDivElement,\n CommandListProps\n>(({ children, emptyMessage = \"No items found.\", className }, ref) => {\n const { scrollableRef } = useCommandContext();\n\n return (\n <div className={cn(styles[\"inner\"], className)}>\n <Scroll\n ref={(el) => {\n if (ref) {\n if (typeof ref === \"function\") {\n ref(el);\n } else {\n ref.current = el;\n }\n }\n scrollableRef.current = el;\n }}\n className={styles[\"list\"]}\n maxHeight=\"44dvh\"\n fade-y\n >\n <div role=\"listbox\" aria-label=\"Commands\">\n {!children ? (\n <div className={styles[\"empty\"]}>{emptyMessage}</div>\n ) : (\n children\n )}\n </div>\n </Scroll>\n </div>\n );\n});\n\nCommandListComponent.displayName = \"Command.List\";\n\ninterface CommandItemProps {\n /** Unique key identifying this command item */\n value: Key;\n /** Plain-text label used for keyboard navigation lookup */\n textValue: string;\n /** Called when the item is selected */\n action: () => void | Promise<void>;\n /** Child elements rendered inside the item */\n children?: React.ReactNode;\n /** Additional CSS class for the item */\n className?: string;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the command item */\n hint?: string;\n}\n\nconst CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>(\n ({ value, textValue, action, children, className, hint }, ref) => {\n const { focusedKey, registerItem, unregisterItem, actionRef, close } =\n useCommandContext();\n\n React.useEffect(() => {\n registerItem(value, textValue);\n actionRef.current.set(value, action);\n return () => {\n unregisterItem(value);\n actionRef.current.delete(value);\n };\n }, [value, textValue, action, registerItem, unregisterItem, actionRef]);\n\n const isHighlighted = focusedKey === value;\n\n return (\n <div\n ref={ref}\n data-highlighted={isHighlighted}\n role=\"option\"\n aria-selected={isHighlighted}\n onClick={() => { action(); close(); }}\n className={cn(\"item\", styles[\"item\"], className)}\n >\n <div className={styles[\"item-content\"]}>{children}</div>\n {hint && (\n <Badge variant=\"secondary\" size=\"sm\" className={styles[\"hint-wrapper\"]}>\n {hint}\n </Badge>\n )}\n </div>\n );\n },\n);\n\nCommandItem.displayName = \"Command.Item\";\n\ninterface CommandCategoryProps {\n /** Child elements rendered inside the category header */\n children?: React.ReactNode;\n /** Additional CSS class for the category */\n className?: string;\n}\n\n/** Labeled section grouping related commands */\nconst CommandCategory = React.forwardRef<\n HTMLDivElement,\n CommandCategoryProps\n>(({ children, className }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(styles[\"category-header\"], className)}\n >\n {children}\n </div>\n );\n});\n\nCommandCategory.displayName = \"Command.Category\";\n\ninterface CommandFooterProps {\n /** Child elements rendered inside the footer */\n children?: React.ReactNode;\n /** Additional CSS class applied to the footer */\n className?: string;\n}\n\n/** Fixed bottom bar in the command palette for hints or actions */\nconst CommandFooter = React.forwardRef<HTMLDivElement, CommandFooterProps>(\n ({ children, className }, ref) => {\n return (\n <Card.Footer ref={ref} className={cn(styles[\"footer\"], className)}>\n {children}\n </Card.Footer>\n );\n },\n);\n\nCommandFooter.displayName = \"Command.Footer\";\n\nexport interface CommandGroupsProps {\n /** Renders a category header for the given category name */\n renderCategory?: (category: string | undefined) => React.ReactNode;\n /** Renders a single command item row */\n renderItem: (command: CommandItem, hint?: string) => React.ReactNode;\n /** Additional CSS class for the groups container */\n className?: string;\n}\n\n/** Wrapper that renders multiple Command.Category sections */\nconst CommandGroups = React.forwardRef<HTMLDivElement, CommandGroupsProps>(\n ({ renderCategory, renderItem, className }, ref) => {\n const { groupedItems } = useCommandContext();\n\n return (\n <div ref={ref} className={className}>\n {groupedItems.map(({ category, items }) => (\n <div key={category || \"uncategorized\"}>\n {renderCategory && renderCategory(category)}\n {items.map((cmd) => (\n <React.Fragment key={cmd.id}>{renderItem(cmd, cmd.hint)}</React.Fragment>\n ))}\n </div>\n ))}\n </div>\n );\n },\n);\n\nCommandGroups.displayName = \"Command.Groups\";\n\ninterface CommandComponent\n extends React.ForwardRefExoticComponent<\n CommandProps & React.RefAttributes<HTMLDivElement>\n > {\n Input: typeof CommandInput;\n List: typeof CommandListComponent;\n Item: typeof CommandItem;\n Category: typeof CommandCategory;\n Footer: typeof CommandFooter;\n Groups: typeof CommandGroups;\n}\n\nconst CommandWithSubcomponents = Object.assign(Command, {\n Input: CommandInput,\n List: CommandListComponent,\n Item: CommandItem,\n Category: CommandCategory,\n Footer: CommandFooter,\n Groups: CommandGroups,\n}) as CommandComponent;\n\nexport { CommandWithSubcomponents as Command };\nexport { scoreCommandRelevance };\nexport { useCommandContext };\n",
|
|
4168
|
+
"css": "@reference \"tailwindcss\";\n\n:global(.command) :global(.input) {\n --background: var(--background-input);\n}\n\n@layer components {\n /* Overlay Container */\n .overlay {\n @apply fixed inset-0 flex items-start justify-center overflow-hidden;\n z-index: 999;\n padding-top: 20vh;\n /* Apply backdrop styles directly to avoid creating a containing block that disrupts sticky elements */\n background-color: var(--overlay);\n backdrop-filter: var(--overlay-backdrop);\n }\n\n /* Content */\n .content {\n @apply relative m-2 w-full max-w-[28rem];\n border-radius: var(--radius-sm);\n background: var(--background-default);\n margin-inline: 1rem;\n box-shadow: var(--shadow);\n animation: fade-in-zoom-in 0.2s ease-out;\n }\n\n .inner {\n border-radius: var(--radius-sm) var(--radius-sm) 0 0;\n border-top: var(--border-width-base) solid var(--border-default);\n @apply overflow-hidden;\n }\n\n /* Search Section */\n .search {\n @apply border-none flex p-1.5;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .input {\n border-color: transparent;\n background: transparent;\n box-shadow: none;\n\n &[data-active],\n &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n\n /* List Section */\n .list {\n @apply py-0.5 px-2 space-y-2;\n background-color: var(--list-background);\n }\n\n .list :global([role=\"listbox\"]) {\n @apply flex w-full flex-col;\n }\n\n .item {\n @apply flex items-center justify-between rounded-sm px-2 py-0.5 cursor-pointer;\n border-radius: 0.375rem;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .item:hover {\n background-color: var(--background-hover);\n }\n\n .item[data-highlighted=\"true\"] {\n background-color: var(--background-selected);\n }\n\n .item-content {\n @apply flex min-w-0 flex-1 items-center gap-2.5;\n flex: 1;\n }\n\n .item-icon {\n @apply flex h-6 w-6 shrink-0 items-center justify-center;\n color: var(--color-icon);\n }\n\n .item-labels {\n flex: 1;\n @apply min-w-0;\n }\n\n .item-label {\n font-size: var(--text-sm);\n color: var(--color-default);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n color: var(--color-muted);\n font-size: 0.875rem;\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .hint-wrapper {\n @apply flex items-center;\n }\n\n .category-header {\n @apply px-2 py-1.5 mt-2 first:mt-0;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-semibold);\n color: var(--color-muted);\n }\n\n /* Empty State */\n .empty {\n padding: 1.5rem 1rem;\n text-align: center;\n font-size: 0.875rem;\n color: var(--color-muted);\n }\n\n /* Footer */\n .footer {\n @apply flex w-full items-center gap-2 px-1.5 py-2;\n background-color: var(--footer-background);\n border-top: 1px solid var(--border-default);\n justify-content: flex-between;\n }\n\n /* Animations */\n @keyframes fade-in-zoom-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n}\n",
|
|
4169
|
+
"cssTypes": "export interface Styles {\n overlay: string;\n backdrop: string;\n content: string;\n inner: string;\n search: string;\n input: string;\n list: string;\n item: string;\n \"item-content\": string;\n \"item-icon\": string;\n \"item-labels\": string;\n \"item-label\": string;\n \"item-description\": string;\n \"category-header\": string;\n empty: string;\n footer: string;\n \"hint-wrapper\": string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4904
4170
|
},
|
|
4905
4171
|
"confirm": {
|
|
4906
4172
|
"tsx": "\"use client\"\n\nimport React, { useState, useEffect } from \"react\"\nimport { cn } from \"./utils\"\nimport { Button } from \"../Button\"\nimport { Card } from \"../Card\"\nimport { CircleAlert, TriangleAlert, Info } from \"lucide-react\"\nimport styles from \"./Confirm.module.css\"\n\nexport interface ConfirmProps {\n /** Display mode: inline expands in place, dialog shows a modal, auto chooses based on severity */\n mode?: \"inline\" | \"dialog\" | \"auto\"\n /** Severity level that affects styling and default mode selection */\n severity?: \"low\" | \"medium\" | \"high\" | \"critical\"\n /** Called when the user confirms the action */\n onConfirm: () => void | Promise<void>\n /** Called when the user cancels the action */\n onCancel?: () => void\n /** Label for the trigger button */\n triggerLabel: string\n /** Label for the confirm button */\n confirmLabel?: string\n /** Label for the cancel button */\n cancelLabel?: string\n /** Whether the trigger button is disabled */\n disabled?: boolean\n /** Title shown in dialog mode */\n title?: string\n /** Description text shown during the confirm step */\n description?: string\n /** Custom icon shown in the confirm header */\n icon?: React.ReactNode\n /** Warning message displayed in a colored box before confirming */\n destructiveActionWarning?: string\n /** Seconds the user must wait before the confirm button becomes active */\n countdownSeconds?: number\n /** Whether the user must type confirmText to enable the confirm button */\n requiresReason?: boolean\n /** Text the user must type to confirm when requiresReason is true */\n confirmText?: string\n /** Milliseconds after which the inline confirm auto-resets to idle state */\n autoResetAfter?: number\n}\n\nconst severityConfig = {\n low: {\n icon: <Info className=\"w-5 h-5 text-blue-500\" />,\n warningBoxClass: styles[\"warning-box-low\"],\n buttonVariant: \"primary\" as const,\n },\n medium: {\n icon: <TriangleAlert className=\"w-5 h-5 text-yellow-500\" />,\n warningBoxClass: styles[\"warning-box-medium\"],\n buttonVariant: \"secondary\" as const,\n },\n high: {\n icon: <CircleAlert className=\"w-5 h-5 text-orange-500\" />,\n warningBoxClass: styles[\"warning-box-high\"],\n buttonVariant: \"secondary\" as const,\n },\n critical: {\n icon: <TriangleAlert className=\"w-5 h-5 text-red-500\" />,\n warningBoxClass: styles[\"warning-box-critical\"],\n buttonVariant: \"secondary\" as const,\n },\n} as const\n\n/** Modal dialog for confirming destructive actions with context and choices */\nconst Confirm = React.forwardRef<HTMLDivElement, ConfirmProps>(\n (\n {\n mode = \"auto\",\n severity = \"medium\",\n onConfirm,\n onCancel,\n triggerLabel,\n confirmLabel = \"Confirm\",\n cancelLabel = \"Cancel\",\n disabled = false,\n title,\n description,\n icon,\n destructiveActionWarning,\n countdownSeconds,\n requiresReason = false,\n confirmText,\n autoResetAfter,\n },\n ref\n ) => {\n const [isConfirming, setIsConfirming] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [countdown, setCountdown] = useState(countdownSeconds || 0)\n const [inputValue, setInputValue] = useState(\"\")\n const [showDialogMode, setShowDialogMode] = useState(false)\n\n // Determine actual mode\n const effectiveMode = mode === \"auto\"\n ? (severity === \"low\" || severity === \"medium\") ? \"inline\" : \"dialog\"\n : mode\n\n // Handle countdown timer\n useEffect(() => {\n if (!isConfirming || countdown <= 0) return\n\n const timer = setTimeout(() => {\n setCountdown(countdown - 1)\n }, 1000)\n\n return () => clearTimeout(timer)\n }, [isConfirming, countdown])\n\n // Auto-reset inline confirms\n useEffect(() => {\n if (!isConfirming || !autoResetAfter) return\n\n const timer = setTimeout(() => {\n resetConfirm()\n }, autoResetAfter)\n\n return () => clearTimeout(timer)\n }, [isConfirming, autoResetAfter])\n\n const resetConfirm = () => {\n setIsConfirming(false)\n setError(null)\n setCountdown(countdownSeconds || 0)\n setInputValue(\"\")\n setShowDialogMode(false)\n }\n\n const handleTrigger = () => {\n if (effectiveMode === \"dialog\") {\n setShowDialogMode(true)\n setIsConfirming(true)\n } else {\n setIsConfirming(true)\n }\n setCountdown(countdownSeconds || 0)\n }\n\n const handleConfirm = async () => {\n if (requiresReason && inputValue !== confirmText) {\n setError(`Please type \"${confirmText}\" to confirm`)\n return\n }\n\n if (countdownSeconds && countdown > 0) {\n setError(`Please wait ${countdown} seconds before confirming`)\n return\n }\n\n setIsLoading(true)\n setError(null)\n\n try {\n await Promise.resolve(onConfirm())\n resetConfirm()\n } catch (err) {\n setError(err instanceof Error ? err.message : \"An error occurred\")\n setIsLoading(false)\n }\n }\n\n const handleCancel = () => {\n onCancel?.()\n resetConfirm()\n }\n\n const config = severityConfig[severity]\n const canConfirm = !countdownSeconds || countdown === 0\n const confirmValid = !requiresReason || inputValue === confirmText\n\n if (effectiveMode === \"inline\" && !showDialogMode) {\n return (\n <div ref={ref} className={styles.container}>\n {!isConfirming ? (\n <Button\n onClick={handleTrigger}\n isDisabled={disabled || isLoading}\n variant={config.buttonVariant}\n >\n {triggerLabel}\n </Button>\n ) : (\n <Card className={cn(styles.card)}>\n <Card.Body className={cn(styles.body, styles['body-compact'])}>\n {description && (\n <p className={styles.description}>{description}</p>\n )}\n {error && (\n <p className={styles['error-message']}>{error}</p>\n )}\n <div className={cn(styles.actions, styles['actions-inline'])}>\n <Button\n size=\"sm\"\n variant=\"primary\"\n onClick={handleConfirm}\n isDisabled={!canConfirm || !confirmValid || isLoading}\n >\n {isLoading ? \"...\" : confirmLabel}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={handleCancel}\n isDisabled={isLoading}\n >\n {cancelLabel}\n </Button>\n </div>\n </Card.Body>\n </Card>\n )}\n </div>\n )\n }\n\n // Dialog mode\n if (showDialogMode) {\n return (\n <div ref={ref}>\n {isConfirming && (\n <div className={styles['dialog-overlay']}>\n <Card className={cn(styles['dialog-card'])}>\n <Card.Header className={styles.body}>\n <div className={styles.header}>\n {icon || config.icon}\n <div className={styles['header-content']}>\n <h4 className={styles['header-title']}>\n {title || triggerLabel}\n </h4>\n </div>\n </div>\n </Card.Header>\n <Card.Body className={cn(styles.body)}>\n {description && (\n <p className={styles.description}>{description}</p>\n )}\n {destructiveActionWarning && (\n <div className={cn(\n styles['warning-box'],\n config.warningBoxClass\n )}>\n {destructiveActionWarning}\n </div>\n )}\n {countdownSeconds && countdown > 0 && (\n <div className={styles['countdown-text']}>\n Please wait {countdown}s before confirming\n </div>\n )}\n {requiresReason && (\n <div>\n <label className={styles['input-label']}>\n Type \"{confirmText}\" to confirm:\n </label>\n <input\n type=\"text\"\n value={inputValue}\n onChange={(e) => {\n setInputValue(e.target.value)\n setError(null)\n }}\n placeholder={confirmText}\n className={styles.input}\n />\n </div>\n )}\n {error && (\n <p className={styles['error-message']}>{error}</p>\n )}\n </Card.Body>\n <Card.Footer className={cn(styles.actions, styles['actions-dialog'])}>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={handleCancel}\n isDisabled={isLoading}\n >\n {cancelLabel}\n </Button>\n <Button\n size=\"sm\"\n variant=\"primary\"\n onClick={handleConfirm}\n isDisabled={!canConfirm || !confirmValid || isLoading}\n >\n {isLoading ? \"...\" : confirmLabel}\n </Button>\n </Card.Footer>\n </Card>\n </div>\n )}\n </div>\n )\n }\n\n return (\n <div ref={ref} className={styles.container}>\n <Button\n onClick={handleTrigger}\n isDisabled={disabled || isLoading}\n variant={config.buttonVariant}\n >\n {triggerLabel}\n </Button>\n </div>\n )\n }\n)\n\nConfirm.displayName = \"Confirm\"\n\nexport { Confirm }\n",
|
|
@@ -4908,8 +4174,8 @@ export const generatedSourceCode = {
|
|
|
4908
4174
|
"cssTypes": "export interface Styles {\n container: string;\n card: string;\n \"card-compact\": string;\n \"dialog-overlay\": string;\n \"dialog-card\": string;\n header: string;\n \"header-content\": string;\n \"header-title\": string;\n body: string;\n \"body-compact\": string;\n description: string;\n \"error-message\": string;\n \"warning-box\": string;\n \"warning-box-low\": string;\n \"warning-box-medium\": string;\n \"warning-box-high\": string;\n \"warning-box-critical\": string;\n \"countdown-text\": string;\n \"input-label\": string;\n input: string;\n actions: string;\n \"actions-inline\": string;\n \"actions-dialog\": string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4909
4175
|
},
|
|
4910
4176
|
"date": {
|
|
4911
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\n\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\n\nimport { type StyleValue, cn } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\n\nimport dateModuleStyles from \"./Date.module.css\"\n\n// Alias global Date to avoid shadowing by component name\nconst NativeDate = globalThis.Date;\n\nexport interface DateStyleSlots {\n root?: StyleValue;\n header?: StyleValue;\n \"day-headers\"?: StyleValue;\n grid?: StyleValue;\n \"day-cell\"?: StyleValue; // individual date button\n}\n\nexport type DateStylesProp = StylesProp<DateStyleSlots>;\n\nconst dateStyleSlotKeys = ['root', 'header', 'day-headers', 'grid', 'day-cell'] as const;\nconst resolveDateBaseStyles = createStylesResolver(dateStyleSlotKeys);\n\nfunction normalizeDateStyles(styles: DateStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return styles;\n }\n\n return {\n root: styles.root,\n header: styles.header,\n \"day-headers\": styles[\"day-headers\"],\n grid: styles.grid,\n \"day-cell\": styles[\"day-cell\"],\n };\n}\n\n/**\n * Context type for Calendar state management\n */\nexport interface DateContextValue {\n selectedDate: Date | null\n focusedDate: Date | null\n currentMonth: Date | null\n today: Date | null\n selectDate: (date: Date) => void\n focusDate: (date: Date) => void\n navigateMonth: (offset: number) => void\n isDateDisabled: (date: Date) => boolean\n isDateOutOfRange: (date: Date) => boolean\n}\n\nconst DateContext = React.createContext<DateContextValue | null>(null)\n\nexport function useDateContext() {\n const context = React.useContext(DateContext)\n if (!context) {\n throw new Error(\"Date component must be used within Date root\")\n }\n return context\n}\n\n/**\n * Props for Calendar component\n */\nexport interface DateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Controlled selected date */\n value?: Date | null\n /** Called when the user selects a date */\n onChange?: (date: Date) => void\n /** Function returning true for dates that should be unselectable */\n disabled?: (date: Date) => boolean\n /** Earliest selectable date */\n minDate?: Date\n /** Latest selectable date */\n maxDate?: Date\n /** Month shown initially when no date is selected */\n defaultMonth?: Date;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: DateStylesProp;\n}\n\n/**\n * Helper functions for date calculations\n */\nfunction getDaysInMonth(date: Date): number {\n return new NativeDate(date.getFullYear(), date.getMonth() + 1, 0).getDate()\n}\n\nfunction getFirstDayOfMonth(date: Date): number {\n return new NativeDate(date.getFullYear(), date.getMonth(), 1).getDay()\n}\n\nfunction isSameDay(date1: Date, date2: Date): boolean {\n return (\n date1.getFullYear() === date2.getFullYear() &&\n date1.getMonth() === date2.getMonth() &&\n date1.getDate() === date2.getDate()\n )\n}\n\nfunction isToday(date: Date, today: Date | null): boolean {\n if (!today) return false;\n return isSameDay(date, today)\n}\n\n/**\n * Calendar grid computation\n */\nfunction getCalendarGrid(currentMonth: Date | null): Date[][] {\n if (!currentMonth) return [];\n\n const daysInMonth = getDaysInMonth(currentMonth)\n const firstDay = getFirstDayOfMonth(currentMonth)\n\n const grid: Date[] = []\n\n // Handle previous month's days\n if (firstDay > 0) {\n const prevMonth = new NativeDate(currentMonth.getFullYear(), currentMonth.getMonth(), 0)\n const daysInPrevMonth = getDaysInMonth(prevMonth)\n\n for (let i = firstDay - 1; i >= 0; i--) {\n const date = new NativeDate(prevMonth.getFullYear(), prevMonth.getMonth(), daysInPrevMonth - i)\n grid.push(date)\n }\n }\n\n // Current month days\n for (let i = 1; i <= daysInMonth; i++) {\n grid.push(new NativeDate(currentMonth.getFullYear(), currentMonth.getMonth(), i))\n }\n\n // Pad with next month's days\n while (grid.length % 7 !== 0) {\n const nextDay = grid.length - firstDay - daysInMonth + 1\n const date = new NativeDate(currentMonth.getFullYear(), currentMonth.getMonth() + 1, nextDay)\n grid.push(date)\n }\n\n // Convert to rows\n const rows: Date[][] = []\n for (let i = 0; i < grid.length; i += 7) {\n rows.push(grid.slice(i, i + 7))\n }\n\n return rows\n}\n\nconst Date = React.forwardRef<HTMLDivElement, DateProps>(\n (\n {\n value: controlledValue,\n onChange,\n disabled: disabledProp = () => false,\n minDate,\n maxDate,\n defaultMonth,\n className,\n styles,\n ...props\n },\n ref\n ) => {\n const [uncontrolledValue, setUncontrolledValue] = React.useState<Date | null>(null)\n const [today, setToday] = React.useState<Date | null>(null)\n const [currentMonth, setCurrentMonth] = React.useState<Date | null>(null)\n const [focusedDate, setFocusedDate] = React.useState<Date | null>(null)\n\n const selectedDate = controlledValue !== undefined ? controlledValue : uncontrolledValue\n\n const resolved = resolveDateBaseStyles(normalizeDateStyles(styles));\n\n const isDateDisabled = React.useCallback(\n (date: Date): boolean => {\n if (disabledProp(date)) return true\n if (minDate && date < minDate) return true\n if (maxDate && date > maxDate) return true\n return false\n },\n [disabledProp, minDate, maxDate]\n )\n\n const isDateOutOfRange = React.useCallback(\n (date: Date): boolean => {\n if (!currentMonth) return false;\n return (\n date.getMonth() !== currentMonth.getMonth() ||\n date.getFullYear() !== currentMonth.getFullYear()\n )\n },\n [currentMonth]\n )\n\n const selectDate = React.useCallback(\n (date: Date) => {\n if (!isDateDisabled(date)) {\n if (controlledValue === undefined) {\n setUncontrolledValue(date)\n }\n onChange?.(date)\n setFocusedDate(null)\n }\n },\n [controlledValue, onChange, isDateDisabled]\n )\n\n const focusDate = React.useCallback((date: Date) => {\n setFocusedDate(date)\n }, [])\n\n const navigateMonth = React.useCallback((offset: number) => {\n setCurrentMonth(prev => {\n const baseDate = prev ?? new NativeDate(); // Handle null prev\n const newMonth = new NativeDate(baseDate.getFullYear(), baseDate.getMonth() + offset, 1)\n return newMonth\n })\n }, [])\n\n const calendarGrid = React.useMemo(\n () => currentMonth ? getCalendarGrid(currentMonth) : [],\n [currentMonth]\n )\n\n const contextValue: DateContextValue = React.useMemo(\n () => ({\n selectedDate,\n focusedDate,\n currentMonth,\n today,\n selectDate,\n focusDate,\n navigateMonth,\n isDateDisabled,\n isDateOutOfRange,\n }),\n [selectedDate, focusedDate, currentMonth, today, selectDate, focusDate, navigateMonth, isDateDisabled, isDateOutOfRange]\n )\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (!focusedDate) return\n\n let newFocusedDate: Date | null = null\n\n switch (e.key) {\n case \"ArrowUp\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() - 7)\n break\n case \"ArrowDown\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() + 7)\n break\n case \"ArrowLeft\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() - 1)\n break\n case \"ArrowRight\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() + 1)\n break\n case \"Home\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), 1)\n break\n case \"End\":\n e.preventDefault()\n const daysInMonth = getDaysInMonth(focusedDate)\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), daysInMonth)\n break\n case \"PageUp\":\n e.preventDefault()\n navigateMonth(-1)\n return\n case \"PageDown\":\n e.preventDefault()\n navigateMonth(1)\n return\n case \"Enter\":\n case \" \":\n e.preventDefault()\n selectDate(focusedDate)\n return\n }\n\n if (newFocusedDate) {\n setFocusedDate(newFocusedDate)\n // Auto-navigate month if needed\n if (newFocusedDate.getMonth() !== currentMonth!.getMonth() || newFocusedDate.getFullYear() !== currentMonth!.getFullYear()) {\n setCurrentMonth(new NativeDate(newFocusedDate.getFullYear(), newFocusedDate.getMonth(), 1))\n }\n }\n },\n [focusedDate, currentMonth, selectDate, navigateMonth]\n )\n\n // Set initial focus, today, and current month on client mount\n React.useEffect(() => {\n const now = new NativeDate()\n setToday(now)\n\n if (currentMonth === null) { // Only set if not yet initialized\n setCurrentMonth(defaultMonth ?? now)\n }\n\n if (focusedDate === null) { // Only set if not yet initialized\n setFocusedDate(selectedDate ?? now)\n }\n }, [defaultMonth, currentMonth, focusedDate, selectedDate]) // Add relevant dependencies\n\n return (\n <DateContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn(\"date\", dateModuleStyles.calendar, className, resolved.root)}\n role=\"application\"\n aria-label=\"Date picker calendar\"\n onKeyDown={handleKeyDown}\n {...props}\n >\n {currentMonth && (\n <>\n <DateHeader className={resolved.header} />\n <DateDayHeaders className={resolved[\"day-headers\"]} />\n <DateGrid grid={calendarGrid} className={resolved.grid} dayCellClassName={resolved[\"day-cell\"]} />\n </>\n )}\n </div>\n </DateContext.Provider>\n )\n }\n)\n\nDate.displayName = \"Date\"\n\n/**\n * Calendar Header component\n */\ninterface DateHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS class for the header */\n className?: string;\n}\n\n/** Navigation header with month/year display and prev/next controls */\nconst DateHeader = React.forwardRef<HTMLDivElement, DateHeaderProps>(\n ({ className, ...props }, ref) => {\n const { currentMonth, navigateMonth } = useDateContext()\n\n const monthYear = currentMonth\n ? currentMonth.toLocaleDateString(\"en-US\", {\n month: \"long\",\n year: \"numeric\",\n })\n : \"\"\n\n return (\n <div\n ref={ref}\n className={cn(\"date\", \"date-header\", dateModuleStyles.header, className)}\n {...props}\n >\n <div className={cn(\"date-month-year\", dateModuleStyles[\"month-year\"])}>\n {monthYear}\n </div>\n <div>\n <button\n onClick={() => navigateMonth(-1)}\n className={cn(\"date\", \"date-nav-button\", \"date-prev-button\", dateModuleStyles[\"nav-button\"])}\n aria-label=\"Previous month\"\n >\n <ChevronLeft size={16} />\n </button>\n <button\n onClick={() => navigateMonth(1)}\n className={cn(\"date\", \"date-nav-button\", \"date-next-button\", dateModuleStyles[\"nav-button\"])}\n aria-label=\"Next month\"\n >\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )\n }\n)\n\nDateHeader.displayName = \"Date.Header\"\n\n/**\n * Calendar Day Headers component\n */\ninterface DateDayHeadersProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS class for the day headers row */\n className?: string;\n}\n\n/** Row of weekday abbreviation labels above the calendar grid */\nconst DateDayHeaders = React.forwardRef<HTMLDivElement, DateDayHeadersProps>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"date\", \"date-day-headers\", dateModuleStyles[\"day-headers\"], className)}\n {...props}\n >\n {[\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"].map((day) => (\n <div\n key={day}\n className={cn(\"date\", \"date-day-header\", dateModuleStyles[\"day-header\"])}\n >\n {day}\n </div>\n ))}\n </div>\n )\n }\n)\n\nDateDayHeaders.displayName = \"Date.DayHeaders\"\n\n/**\n * Calendar Grid component\n */\ninterface DateGridProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Calendar grid rows, each containing 7 Date objects */\n grid: Date[][]\n /** Classes applied to each individual date cell (DateDay component) */\n dayCellClassName?: string;\n}\n\n/** The 7-column calendar grid containing date cells */\nconst DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(\n ({ grid, className, dayCellClassName, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"date\", \"date-grid\", dateModuleStyles.grid, className)}\n role=\"grid\"\n {...props}\n >\n {/* Week headers */}\n {[\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"].map((day) => (\n <div\n key={day}\n className={cn(\"date-day-header\", dateModuleStyles[\"week-header\"])}\n role=\"columnheader\"\n >\n {day}\n </div>\n ))}\n\n {/* Calendar rows */}\n {grid.map((week: Date[], weekIndex: number) => {\n return (\n <React.Fragment key={weekIndex}>\n {week.map((date: Date, dayIndex: number) => (\n <DateDay key={`${weekIndex}-${dayIndex}`} date={date} className={dayCellClassName} />\n ))}\n </React.Fragment>\n )\n })}\n </div>\n )\n }\n)\n\nDateGrid.displayName = \"Date.Grid\"\n\n/**\n * Calendar Day component\n */\ninterface DateDayProps extends React.HTMLAttributes<HTMLButtonElement> {\n /** The date this cell represents */\n date: Date\n}\n/**\n * Individual date cell in the calendar grid\n */\nconst DateDay = React.forwardRef<HTMLButtonElement, DateDayProps>(\n ({ date, className, onClick, ...props }, ref) => {\n const {\n selectedDate,\n focusedDate,\n today,\n selectDate,\n focusDate,\n isDateDisabled,\n isDateOutOfRange,\n } = useDateContext()\n\n const isDisabled = isDateDisabled(date)\n\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const { focusProps, isFocusVisible } = useFocusRing()\n const { hoverProps } = useHover({ isDisabled })\n\n const isSelected = selectedDate ? isSameDay(date, selectedDate) : false\n const isFocused = focusedDate ? isSameDay(date, focusedDate) : false\n const isCurrentToday = isToday(date, today)\n const isOutOfRange = isDateOutOfRange(date)\n const handleClick = React.useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n selectDate(date)\n focusDate(date)\n onClick?.(e)\n },\n [date, selectDate, focusDate, onClick]\n )\n\n const handleFocus = React.useCallback(() => {\n focusDate(date)\n }, [date, focusDate])\n\n React.useEffect(() => {\n if (isFocused && buttonRef.current) {\n buttonRef.current.focus({ preventScroll: true })\n }\n }, [isFocused])\n\n return (\n <button\n ref={buttonRef}\n onClick={handleClick}\n onFocus={handleFocus}\n className={cn(\"date\", \"date-day\", dateModuleStyles[\"day-cell\"], className)}\n data-selected={isSelected ? \"true\" : undefined}\n data-today={isCurrentToday ? \"true\" : undefined}\n data-disabled={isDisabled ? \"true\" : undefined}\n data-out-of-range={isOutOfRange ? \"true\" : undefined}\n data-focus-visible={isFocusVisible && isFocused ? \"true\" : undefined}\n disabled={isDisabled}\n aria-selected={isSelected}\n aria-label={date.toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n month: \"long\",\n day: \"numeric\",\n })}\n {...mergeProps(focusProps, hoverProps, props)}\n >\n {date.getDate()}\n </button>\n )\n }\n)\n\nDateDay.displayName = \"Date.Day\"\n\nexport { Date, DateDayHeaders, DateHeader, DateGrid, DateDay }\n",
|
|
4912
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .calendar {\n --disabled-opacity: 0.5;\n\n @apply inline-flex flex-col overflow-hidden gap-0;\n border-radius: var(--radius-md);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n }\n\n .day-headers {\n @apply grid gap-2 px-4 pt-3 pb-1;\n grid-template-columns: repeat(7, 1fr);\n background: var(--day-headers-background);\n border-top: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-md) var(--radius-md) 0 0;\n }\n\n .day-header {\n @apply flex items-center justify-center;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-
|
|
4177
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\n\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\n\nimport { type StyleValue, cn } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\n\nimport dateModuleStyles from \"./Date.module.css\"\n\n// Alias global Date to avoid shadowing by component name\nconst NativeDate = globalThis.Date;\n\ninterface DateStyleSlots {\n root?: StyleValue;\n header?: StyleValue;\n \"day-headers\"?: StyleValue;\n grid?: StyleValue;\n \"day-cell\"?: StyleValue; // individual date button\n}\n\ntype DateStylesProp = StylesProp<DateStyleSlots>;\n\nconst dateStyleSlotKeys = ['root', 'header', 'day-headers', 'grid', 'day-cell'] as const;\nconst resolveDateBaseStyles = createStylesResolver(dateStyleSlotKeys);\n\nfunction normalizeDateStyles(styles: DateStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return styles;\n }\n\n return {\n root: styles.root,\n header: styles.header,\n \"day-headers\": styles[\"day-headers\"],\n grid: styles.grid,\n \"day-cell\": styles[\"day-cell\"],\n };\n}\n\n/**\n * Context type for Calendar state management\n */\nexport interface DateContextValue {\n selectedDate: Date | null\n focusedDate: Date | null\n currentMonth: Date | null\n today: Date | null\n selectDate: (date: Date) => void\n focusDate: (date: Date) => void\n navigateMonth: (offset: number) => void\n isDateDisabled: (date: Date) => boolean\n isDateOutOfRange: (date: Date) => boolean\n}\n\nconst DateContext = React.createContext<DateContextValue | null>(null)\n\nfunction useDateContext() {\n const context = React.useContext(DateContext)\n if (!context) {\n throw new Error(\"Date component must be used within Date root\")\n }\n return context\n}\n\n/**\n * Props for Calendar component\n */\nexport interface DateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Controlled selected date */\n value?: Date | null\n /** Called when the user selects a date */\n onChange?: (date: Date) => void\n /** Function returning true for dates that should be unselectable */\n disabled?: (date: Date) => boolean\n /** Earliest selectable date */\n minDate?: Date\n /** Latest selectable date */\n maxDate?: Date\n /** Month shown initially when no date is selected */\n defaultMonth?: Date;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: DateStylesProp;\n}\n\n/**\n * Helper functions for date calculations\n */\nfunction getDaysInMonth(date: Date): number {\n return new NativeDate(date.getFullYear(), date.getMonth() + 1, 0).getDate()\n}\n\nfunction getFirstDayOfMonth(date: Date): number {\n return new NativeDate(date.getFullYear(), date.getMonth(), 1).getDay()\n}\n\nfunction isSameDay(date1: Date, date2: Date): boolean {\n return (\n date1.getFullYear() === date2.getFullYear() &&\n date1.getMonth() === date2.getMonth() &&\n date1.getDate() === date2.getDate()\n )\n}\n\nfunction isToday(date: Date, today: Date | null): boolean {\n if (!today) return false;\n return isSameDay(date, today)\n}\n\n/**\n * Calendar grid computation\n */\nfunction getCalendarGrid(currentMonth: Date | null): Date[][] {\n if (!currentMonth) return [];\n\n const daysInMonth = getDaysInMonth(currentMonth)\n const firstDay = getFirstDayOfMonth(currentMonth)\n\n const grid: Date[] = []\n\n // Handle previous month's days\n if (firstDay > 0) {\n const prevMonth = new NativeDate(currentMonth.getFullYear(), currentMonth.getMonth(), 0)\n const daysInPrevMonth = getDaysInMonth(prevMonth)\n\n for (let i = firstDay - 1; i >= 0; i--) {\n const date = new NativeDate(prevMonth.getFullYear(), prevMonth.getMonth(), daysInPrevMonth - i)\n grid.push(date)\n }\n }\n\n // Current month days\n for (let i = 1; i <= daysInMonth; i++) {\n grid.push(new NativeDate(currentMonth.getFullYear(), currentMonth.getMonth(), i))\n }\n\n // Pad with next month's days\n while (grid.length % 7 !== 0) {\n const nextDay = grid.length - firstDay - daysInMonth + 1\n const date = new NativeDate(currentMonth.getFullYear(), currentMonth.getMonth() + 1, nextDay)\n grid.push(date)\n }\n\n // Convert to rows\n const rows: Date[][] = []\n for (let i = 0; i < grid.length; i += 7) {\n rows.push(grid.slice(i, i + 7))\n }\n\n return rows\n}\n\nconst Date = React.forwardRef<HTMLDivElement, DateProps>(\n (\n {\n value: controlledValue,\n onChange,\n disabled: disabledProp = () => false,\n minDate,\n maxDate,\n defaultMonth,\n className,\n styles,\n ...props\n },\n ref\n ) => {\n const [uncontrolledValue, setUncontrolledValue] = React.useState<Date | null>(null)\n const [today, setToday] = React.useState<Date | null>(null)\n const [currentMonth, setCurrentMonth] = React.useState<Date | null>(null)\n const [focusedDate, setFocusedDate] = React.useState<Date | null>(null)\n\n const selectedDate = controlledValue !== undefined ? controlledValue : uncontrolledValue\n\n const resolved = resolveDateBaseStyles(normalizeDateStyles(styles));\n\n const isDateDisabled = React.useCallback(\n (date: Date): boolean => {\n if (disabledProp(date)) return true\n if (minDate && date < minDate) return true\n if (maxDate && date > maxDate) return true\n return false\n },\n [disabledProp, minDate, maxDate]\n )\n\n const isDateOutOfRange = React.useCallback(\n (date: Date): boolean => {\n if (!currentMonth) return false;\n return (\n date.getMonth() !== currentMonth.getMonth() ||\n date.getFullYear() !== currentMonth.getFullYear()\n )\n },\n [currentMonth]\n )\n\n const selectDate = React.useCallback(\n (date: Date) => {\n if (!isDateDisabled(date)) {\n if (controlledValue === undefined) {\n setUncontrolledValue(date)\n }\n onChange?.(date)\n setFocusedDate(null)\n }\n },\n [controlledValue, onChange, isDateDisabled]\n )\n\n const focusDate = React.useCallback((date: Date) => {\n setFocusedDate(date)\n }, [])\n\n const navigateMonth = React.useCallback((offset: number) => {\n setCurrentMonth(prev => {\n const baseDate = prev ?? new NativeDate(); // Handle null prev\n const newMonth = new NativeDate(baseDate.getFullYear(), baseDate.getMonth() + offset, 1)\n return newMonth\n })\n }, [])\n\n const calendarGrid = React.useMemo(\n () => currentMonth ? getCalendarGrid(currentMonth) : [],\n [currentMonth]\n )\n\n const contextValue: DateContextValue = React.useMemo(\n () => ({\n selectedDate,\n focusedDate,\n currentMonth,\n today,\n selectDate,\n focusDate,\n navigateMonth,\n isDateDisabled,\n isDateOutOfRange,\n }),\n [selectedDate, focusedDate, currentMonth, today, selectDate, focusDate, navigateMonth, isDateDisabled, isDateOutOfRange]\n )\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (!focusedDate) return\n\n let newFocusedDate: Date | null = null\n\n switch (e.key) {\n case \"ArrowUp\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() - 7)\n break\n case \"ArrowDown\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() + 7)\n break\n case \"ArrowLeft\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() - 1)\n break\n case \"ArrowRight\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), focusedDate.getDate() + 1)\n break\n case \"Home\":\n e.preventDefault()\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), 1)\n break\n case \"End\":\n e.preventDefault()\n const daysInMonth = getDaysInMonth(focusedDate)\n newFocusedDate = new NativeDate(focusedDate.getFullYear(), focusedDate.getMonth(), daysInMonth)\n break\n case \"PageUp\":\n e.preventDefault()\n navigateMonth(-1)\n return\n case \"PageDown\":\n e.preventDefault()\n navigateMonth(1)\n return\n case \"Enter\":\n case \" \":\n e.preventDefault()\n selectDate(focusedDate)\n return\n }\n\n if (newFocusedDate) {\n setFocusedDate(newFocusedDate)\n // Auto-navigate month if needed\n if (newFocusedDate.getMonth() !== currentMonth!.getMonth() || newFocusedDate.getFullYear() !== currentMonth!.getFullYear()) {\n setCurrentMonth(new NativeDate(newFocusedDate.getFullYear(), newFocusedDate.getMonth(), 1))\n }\n }\n },\n [focusedDate, currentMonth, selectDate, navigateMonth]\n )\n\n // Set initial focus, today, and current month on client mount\n React.useEffect(() => {\n const now = new NativeDate()\n setToday(now)\n\n if (currentMonth === null) { // Only set if not yet initialized\n setCurrentMonth(defaultMonth ?? now)\n }\n\n if (focusedDate === null) { // Only set if not yet initialized\n setFocusedDate(selectedDate ?? now)\n }\n }, [defaultMonth, currentMonth, focusedDate, selectedDate]) // Add relevant dependencies\n\n return (\n <DateContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn(\"date\", dateModuleStyles.calendar, className, resolved.root)}\n role=\"application\"\n aria-label=\"Date picker calendar\"\n onKeyDown={handleKeyDown}\n {...props}\n >\n {currentMonth && (\n <>\n <DateHeader className={resolved.header} />\n <DateDayHeaders className={resolved[\"day-headers\"]} />\n <DateGrid grid={calendarGrid} className={resolved.grid} dayCellClassName={resolved[\"day-cell\"]} />\n </>\n )}\n </div>\n </DateContext.Provider>\n )\n }\n)\n\nDate.displayName = \"Date\"\n\n/**\n * Calendar Header component\n */\ninterface DateHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS class for the header */\n className?: string;\n}\n\n/** Navigation header with month/year display and prev/next controls */\nconst DateHeader = React.forwardRef<HTMLDivElement, DateHeaderProps>(\n ({ className, ...props }, ref) => {\n const { currentMonth, navigateMonth } = useDateContext()\n\n const monthYear = currentMonth\n ? currentMonth.toLocaleDateString(\"en-US\", {\n month: \"long\",\n year: \"numeric\",\n })\n : \"\"\n\n return (\n <div\n ref={ref}\n className={cn(\"date\", \"date-header\", dateModuleStyles.header, className)}\n {...props}\n >\n <div className={cn(\"date-month-year\", dateModuleStyles[\"month-year\"])}>\n {monthYear}\n </div>\n <div>\n <button\n onClick={() => navigateMonth(-1)}\n className={cn(\"date\", \"date-nav-button\", \"date-prev-button\", dateModuleStyles[\"nav-button\"])}\n aria-label=\"Previous month\"\n >\n <ChevronLeft size={16} />\n </button>\n <button\n onClick={() => navigateMonth(1)}\n className={cn(\"date\", \"date-nav-button\", \"date-next-button\", dateModuleStyles[\"nav-button\"])}\n aria-label=\"Next month\"\n >\n <ChevronRight size={16} />\n </button>\n </div>\n </div>\n )\n }\n)\n\nDateHeader.displayName = \"Date.Header\"\n\n/**\n * Calendar Day Headers component\n */\ninterface DateDayHeadersProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Additional CSS class for the day headers row */\n className?: string;\n}\n\n/** Row of weekday abbreviation labels above the calendar grid */\nconst DateDayHeaders = React.forwardRef<HTMLDivElement, DateDayHeadersProps>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"date\", \"date-day-headers\", dateModuleStyles[\"day-headers\"], className)}\n {...props}\n >\n {[\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"].map((day) => (\n <div\n key={day}\n className={cn(\"date\", \"date-day-header\", dateModuleStyles[\"day-header\"])}\n >\n {day}\n </div>\n ))}\n </div>\n )\n }\n)\n\nDateDayHeaders.displayName = \"Date.DayHeaders\"\n\n/**\n * Calendar Grid component\n */\ninterface DateGridProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Calendar grid rows, each containing 7 Date objects */\n grid: Date[][]\n /** Classes applied to each individual date cell (DateDay component) */\n dayCellClassName?: string;\n}\n\n/** The 7-column calendar grid containing date cells */\nconst DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(\n ({ grid, className, dayCellClassName, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\"date\", \"date-grid\", dateModuleStyles.grid, className)}\n role=\"grid\"\n {...props}\n >\n {/* Week headers */}\n {[\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"].map((day) => (\n <div\n key={day}\n className={cn(\"date-day-header\", dateModuleStyles[\"week-header\"])}\n role=\"columnheader\"\n >\n {day}\n </div>\n ))}\n\n {/* Calendar rows */}\n {grid.map((week: Date[], weekIndex: number) => {\n return (\n <React.Fragment key={weekIndex}>\n {week.map((date: Date, dayIndex: number) => (\n <DateDay key={`${weekIndex}-${dayIndex}`} date={date} className={dayCellClassName} />\n ))}\n </React.Fragment>\n )\n })}\n </div>\n )\n }\n)\n\nDateGrid.displayName = \"Date.Grid\"\n\n/**\n * Calendar Day component\n */\ninterface DateDayProps extends React.HTMLAttributes<HTMLButtonElement> {\n /** The date this cell represents */\n date: Date\n}\n/**\n * Individual date cell in the calendar grid\n */\nconst DateDay = React.forwardRef<HTMLButtonElement, DateDayProps>(\n ({ date, className, onClick, ...props }, ref) => {\n const {\n selectedDate,\n focusedDate,\n today,\n selectDate,\n focusDate,\n isDateDisabled,\n isDateOutOfRange,\n } = useDateContext()\n\n const isDisabled = isDateDisabled(date)\n\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const { focusProps, isFocusVisible } = useFocusRing()\n const { hoverProps } = useHover({ isDisabled })\n\n const isSelected = selectedDate ? isSameDay(date, selectedDate) : false\n const isFocused = focusedDate ? isSameDay(date, focusedDate) : false\n const isCurrentToday = isToday(date, today)\n const isOutOfRange = isDateOutOfRange(date)\n const handleClick = React.useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n selectDate(date)\n focusDate(date)\n onClick?.(e)\n },\n [date, selectDate, focusDate, onClick]\n )\n\n const handleFocus = React.useCallback(() => {\n focusDate(date)\n }, [date, focusDate])\n\n React.useEffect(() => {\n if (isFocused && buttonRef.current) {\n buttonRef.current.focus({ preventScroll: true })\n }\n }, [isFocused])\n\n return (\n <button\n ref={buttonRef}\n onClick={handleClick}\n onFocus={handleFocus}\n className={cn(\"date\", \"date-day\", dateModuleStyles[\"day-cell\"], className)}\n data-selected={isSelected ? \"true\" : undefined}\n data-today={isCurrentToday ? \"true\" : undefined}\n data-disabled={isDisabled ? \"true\" : undefined}\n data-out-of-range={isOutOfRange ? \"true\" : undefined}\n data-focus-visible={isFocusVisible && isFocused ? \"true\" : undefined}\n disabled={isDisabled}\n aria-selected={isSelected}\n aria-label={date.toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n month: \"long\",\n day: \"numeric\",\n })}\n {...mergeProps(focusProps, hoverProps, props)}\n >\n {date.getDate()}\n </button>\n )\n }\n)\n\nDateDay.displayName = \"Date.Day\"\n\nexport { Date, DateHeader, DateGrid, DateDay }\n",
|
|
4178
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .calendar {\n --disabled-opacity: 0.5;\n\n @apply inline-flex flex-col overflow-hidden gap-0;\n border-radius: var(--radius-md);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n }\n\n .day-headers {\n @apply grid gap-2 px-4 pt-3 pb-1;\n grid-template-columns: repeat(7, 1fr);\n background: var(--day-headers-background);\n border-top: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-md) var(--radius-md) 0 0;\n }\n\n .day-header {\n @apply flex items-center justify-center;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n color: var(--day-header-color);\n }\n\n .header {\n @apply flex items-center justify-between gap-4 pl-2 pr-1.5 py-1.5;\n color: var(--header-color);\n }\n\n .month-year {\n @apply ml-2;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n text-align: center;\n }\n\n .nav-button {\n @apply inline-flex min-h-8 min-w-8 items-center justify-center cursor-pointer;\n border-radius: var(--radius-sm);\n background-color: transparent;\n color: var(--nav-button-color);\n border: 1px solid transparent;\n font-size: var(--text-sm);\n font-weight: 500;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .nav-button:hover { background-color: var(--nav-button-background-hover); }\n\n .nav-button:focus-visible {\n background: var(--nav-button-background-hover);\n border-radius: 0px;\n outline: 0px solid var(--accent-500);\n }\n\n .grid {\n @apply grid gap-1 px-4 pb-4;\n grid-template-columns: repeat(7, 1fr); /* 7 days only */\n background: var(--grid-background);\n border-radius: 0 0 var(--radius-sm) var(--radius-sm);\n }\n\n .day-cell {\n --cell-background: transparent;\n\n @apply flex min-h-8 items-center justify-center px-2.5 py-2 cursor-pointer;\n border-radius: var(--radius-base);\n background-color: var(--cell-background);\n color: var(--cell-text);\n border: 2px solid transparent;\n font-size: var(--text-sm);\n font-weight: 400;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .week-header {\n display: none;\n }\n\n .week-number {\n display: none;\n }\n}\n\n/* Variant states - these are outside @layer */\n.day-cell[data-selected=\"true\"] {\n font-weight: 500;\n}\n\n.day-cell[data-today=\"true\"] {\n border-color: transparent;\n}\n\n.day-cell[data-disabled=\"true\"],\n.day-cell[data-out-of-range=\"true\"] {\n opacity: var(--disabled-opacity);\n}\n\n.day-cell[data-disabled=\"true\"] { cursor: not-allowed; }\n\n.day-cell[data-focus-visible=\"true\"]:not([data-disabled=\"true\"]) { outline: 2px solid var(--focus-ring); outline-offset: 2px; }\n",
|
|
4913
4179
|
"cssTypes": "declare const styles: {\n calendar: string\n \"day-headers\": string\n \"day-header\": string\n header: string\n \"month-year\": string\n \"nav-button\": string\n grid: string\n \"day-cell\": string\n \"week-header\": string\n \"week-number\": string\n}\n\nexport default styles\n"
|
|
4914
4180
|
},
|
|
4915
4181
|
"divider": {
|
|
@@ -4918,104 +4184,104 @@ export const generatedSourceCode = {
|
|
|
4918
4184
|
"cssTypes": ""
|
|
4919
4185
|
},
|
|
4920
4186
|
"expand": {
|
|
4921
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { useToggleState, ToggleState } from \"react-stately\";\nimport { useButton, useFocusRing, mergeProps } from \"react-aria\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Divider, DividerProps } from \"@/components/Divider\";\nimport styles from \"./Expand.module.css\";\nimport { ChevronDown } from \"lucide-react\";\n\
|
|
4187
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { useToggleState, ToggleState } from \"react-stately\";\nimport { useButton, useFocusRing, mergeProps } from \"react-aria\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Divider, DividerProps } from \"@/components/Divider\";\nimport styles from \"./Expand.module.css\";\nimport { ChevronDown } from \"lucide-react\";\n\ninterface ExpandStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n}\n\ntype ExpandStylesProp = StylesProp<ExpandStyleSlots>;\n\nconst resolveExpandBaseStyles = createStylesResolver(['root', 'trigger', 'content'] as const);\n\ninterface ExpandContextValue {\n state: ToggleState;\n isDisabled: boolean;\n}\n\nconst ExpandContext = React.createContext<ExpandContextValue | null>(null);\n\nconst useExpandContext = () => {\n const context = React.useContext(ExpandContext);\n if (!context) {\n throw new Error(\n \"Expand compound components must be used within an Expand component\",\n );\n }\n return context;\n};\n\n// --- Sub-components ---\n\nexport interface ExpandIconProps\n extends React.HTMLAttributes<HTMLSpanElement> {\n /** Custom icon element; defaults to a chevron */\n children?: React.ReactNode;\n}\n\n/** Animated chevron icon that rotates when the section is open */\nconst ExpandIcon = React.forwardRef<HTMLSpanElement, ExpandIconProps>(\n ({ children, className, ...props }, ref) => {\n const context = React.useContext(ExpandContext);\n return (\n <span\n ref={ref}\n className={cn(styles.icon, className)}\n data-expanded={context?.state.isSelected || undefined}\n {...props}\n >\n {children ?? <ChevronDown size={16} className=\"text-foreground-400\" />}\n </span>\n );\n },\n);\nExpandIcon.displayName = \"Expand.Icon\";\n\ninterface ExpandTriggerProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"title\"> {\n /** Label or content of the trigger button */\n children?: React.ReactNode;\n /** ReactNode label rendered in the title span (overrides HTML title tooltip) */\n title?: React.ReactNode;\n}\n\n/** Clickable button that toggles the expand/collapse state */\nconst ExpandTrigger = React.forwardRef<HTMLButtonElement, ExpandTriggerProps>(\n ({ children, className, title, ...props }, ref) => {\n const { state, isDisabled } = useExpandContext();\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n React.useImperativeHandle(\n ref,\n () => triggerRef.current as HTMLButtonElement,\n );\n\n const { buttonProps, isPressed } = useButton(\n {\n isDisabled,\n onPress: () => state.toggle(),\n // Filter out form-related props that useButton doesn't support\n ...Object.fromEntries(\n Object.entries(props).filter(\n ([key]) =>\n ![\n \"formAction\",\n \"formEncType\",\n \"formMethod\",\n \"formNoValidate\",\n \"formTarget\",\n ].includes(key),\n ),\n ),\n },\n triggerRef,\n );\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n\n const hasElementChildren = React.Children.toArray(children).some(\n (child) => React.isValidElement(child),\n );\n\n // Default: styled button with title span + auto-injected chevron\n return (\n <button\n ref={triggerRef}\n {...mergeProps(buttonProps, focusProps)}\n className={cn(styles.trigger, className)}\n aria-expanded={state.isSelected}\n data-expanded={state.isSelected || undefined}\n data-disabled={isDisabled || undefined}\n data-focused={isFocused || undefined}\n data-focus-visible={isFocusVisible || undefined}\n data-pressed={isPressed || undefined}\n >\n {hasElementChildren && title === undefined ? (\n children\n ) : (\n <>\n <span className={styles.title}>{title ?? children}</span>\n <ExpandIcon />\n </>\n )}\n </button>\n );\n },\n);\nExpandTrigger.displayName = \"Expand.Trigger\";\n\ninterface ExpandContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Content shown when the expand is open */\n children: React.ReactNode;\n /** Direction the content reveals from the trigger */\n from?: \"below\" | \"above\" | \"left\" | \"right\";\n}\n\n/** Collapsible content area revealed when expanded */\nconst ExpandContent = React.forwardRef<HTMLDivElement, ExpandContentProps>(\n ({ children, className, from, ...props }, ref) => {\n const { state } = useExpandContext();\n\n return (\n <div\n ref={ref}\n className={cn(styles.content, className)}\n data-expanded={state.isSelected || undefined}\n data-from={from && from !== \"below\" ? from : undefined}\n aria-hidden={!state.isSelected}\n {...props}\n >\n <div className={styles[\"content-inner\"]}>{children}</div>\n </div>\n );\n },\n);\nExpandContent.displayName = \"Expand.Content\";\n\n/** Separator line between expand sections */\nconst ExpandDivider = React.forwardRef<HTMLDivElement, DividerProps>(\n ({ className, spacing = \"none\", ...props }, ref) => {\n return (\n <Divider\n ref={ref}\n className={cn(\"mt-2\", className)}\n spacing={spacing}\n {...props}\n />\n );\n },\n);\nExpandDivider.displayName = \"Expand.Divider\";\n\n// --- Main Expand Component ---\n\nexport interface ExpandProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\" | \"onChange\"> {\n /** Header text or element for the trigger button in preset (non-compound) mode */\n title?: React.ReactNode;\n /** Controlled expanded state */\n isExpanded?: boolean;\n /** Initial expanded state for uncontrolled usage */\n defaultExpanded?: boolean;\n /** Called when the expanded state changes */\n onExpandedChange?: (isExpanded: boolean) => void;\n /** Alias for onExpandedChange */\n onChange?: (isExpanded: boolean) => void;\n /** Whether the expand is disabled */\n isDisabled?: boolean;\n /** Compound sub-components or content nodes */\n children?: React.ReactNode;\n /** Additional CSS class for the trigger button */\n triggerClassName?: string;\n /** Additional CSS class for the content area */\n contentClassName?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: ExpandStylesProp;\n}\n\nconst ExpandRoot = React.forwardRef<HTMLDivElement, ExpandProps>(\n (\n {\n isExpanded,\n defaultExpanded = false,\n onExpandedChange,\n onChange,\n isDisabled = false,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const state = useToggleState({\n isSelected: isExpanded,\n defaultSelected: defaultExpanded,\n onChange: onExpandedChange || onChange,\n });\n\n const { title, triggerClassName, contentClassName, styles: expandStyles, ...divProps } = props;\n const resolved = resolveExpandBaseStyles(expandStyles);\n\n return (\n <ExpandContext.Provider value={{ state, isDisabled }}>\n <div\n ref={ref}\n className={cn(\"expand\", styles.expand, className, resolved.root)}\n data-disabled={isDisabled || undefined}\n {...divProps}\n >\n {children}\n </div>\n </ExpandContext.Provider>\n );\n },\n);\nExpandRoot.displayName = \"Expand\";\n\n// Compatibility wrapper to support both old API and new Compound API\nconst Expand = React.forwardRef<\n HTMLDivElement,\n ExpandProps & {\n Trigger?: typeof ExpandTrigger;\n Content?: typeof ExpandContent;\n Divider?: typeof ExpandDivider;\n Icon?: typeof ExpandIcon;\n }\n>((props, ref) => {\n const { title, children, triggerClassName, contentClassName, ...rootProps } =\n props;\n const resolved = resolveExpandBaseStyles(props.styles);\n\n // If title is provided, use the \"Preset\" structure (Backward Compatibility)\n if (title !== undefined) {\n const childrenArray = React.Children.toArray(children);\n const customDivider = childrenArray.find(\n (child) => React.isValidElement(child) && child.type === ExpandDivider,\n );\n const filteredChildren = childrenArray.filter(\n (child) => !(React.isValidElement(child) && child.type === ExpandDivider),\n );\n\n return (\n <ExpandRoot ref={ref} {...rootProps}>\n <ExpandTrigger className={cn(triggerClassName, resolved.trigger)}>{title}</ExpandTrigger>\n {customDivider || <ExpandDivider />}\n <ExpandContent className={cn(contentClassName, resolved.content)}>\n {filteredChildren}\n </ExpandContent>\n </ExpandRoot>\n );\n }\n\n // Otherwise, use Compound structure (children are expected to include Trigger/Content/Divider)\n return (\n <ExpandRoot ref={ref} {...rootProps}>\n {children}\n </ExpandRoot>\n );\n}) as React.ForwardRefExoticComponent<\n ExpandProps & React.RefAttributes<HTMLDivElement>\n> & {\n Trigger: typeof ExpandTrigger;\n Content: typeof ExpandContent;\n Divider: typeof ExpandDivider;\n Icon: typeof ExpandIcon;\n};\n\nExpand.displayName = \"Expand\";\n\n// Attach sub-components\nExpand.Trigger = ExpandTrigger;\nExpand.Content = ExpandContent;\nExpand.Divider = ExpandDivider;\nExpand.Icon = ExpandIcon;\n\nexport { Expand };\n",
|
|
4922
4188
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .expand {\n --disabled-opacity: 0.6;\n\n @apply flex flex-col;\n }\n\n .expand[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .trigger {\n @apply flex w-full items-stretch justify-between p-0 text-left cursor-pointer;\n\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--trigger-foreground);\n background-color: var(--trigger-background);\n\n border: none;\n border-radius: var(--radius-sm);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &[data-disabled] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n }\n\n .icon {\n @apply flex shrink-0 items-center justify-center px-3 py-2;\n color: inherit;\n border-radius: var(--radius-sm);\n\n @media (hover: hover) {\n .trigger:not([data-disabled]):hover & {\n background-color: var(--trigger-background-hover);\n border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n }\n\n /* When the icon itself is hovered, it should be isolated and fully rounded */\n .trigger:not([data-disabled]) &:hover {\n border-radius: var(--radius-sm);\n }\n }\n }\n\n .icon > * {\n transition: transform 250ms var(--ease-smooth-settle);\n }\n\n .expand:has(.trigger[data-expanded=\"true\"]) .icon > *,\n .icon[data-expanded=\"true\"] > * {\n transform: rotate(180deg);\n }\n\n /* from=\"above\": content expands upward above the trigger */\n .expand:has(.content[data-from=\"above\"]) {\n flex-direction: column-reverse;\n\n .icon > * {\n transform: rotate(180deg);\n }\n\n &:has(.trigger[data-expanded=\"true\"]) .icon > * {\n transform: rotate(0deg);\n }\n }\n\n /* from=\"left\": content appears left of trigger */\n .expand:has(.content[data-from=\"left\"]) {\n @apply flex-row-reverse items-start;\n\n .trigger {\n @apply w-auto flex-col;\n }\n\n .icon > * {\n transform: rotate(-90deg);\n }\n\n &:has(.trigger[data-expanded=\"true\"]) .icon > * {\n transform: rotate(90deg);\n }\n }\n\n /* from=\"right\": content appears right of trigger */\n .expand:has(.content[data-from=\"right\"]) {\n @apply flex-row items-start;\n\n .trigger {\n @apply w-auto flex-col;\n }\n\n .icon > * {\n transform: rotate(90deg);\n }\n\n &:has(.trigger[data-expanded=\"true\"]) .icon > * {\n transform: rotate(-90deg);\n }\n }\n\n /* Horizontal content animation */\n .content[data-from=\"left\"],\n .content[data-from=\"right\"] {\n grid-template-rows: 1fr;\n grid-template-columns: 0fr;\n transition: grid-template-columns 300ms var(--ease-smooth-settle);\n\n &[data-expanded=\"true\"] {\n grid-template-columns: 1fr;\n }\n\n .content-inner {\n min-height: unset;\n min-width: 0;\n }\n }\n\n .title {\n @apply flex flex-1 min-w-0 items-center overflow-hidden py-2 pl-3;\n\n font-weight: var(--font-weight-medium);\n border-radius: var(--radius-sm) 0 0 var(--radius-sm);\n\n @media (hover: hover) {\n .trigger:not([data-disabled]):hover & {\n background-color: var(--trigger-background-hover);\n }\n\n /* When icon is hovered, remove background from title */\n .trigger:not([data-disabled]):has(.icon:hover) & {\n background-color: transparent;\n }\n }\n\n .trigger:not([data-disabled]) {\n background-color: transparent;\n }\n }\n\n .content {\n @apply grid overflow-hidden;\n grid-template-rows: 0fr;\n transition: grid-template-rows 300ms var(--ease-smooth-settle);\n\n &[data-expanded=\"true\"] {\n grid-template-rows: 1fr;\n }\n }\n\n .content-inner {\n @apply min-h-0 overflow-hidden;\n color: var(--content-foreground);\n background-color: var(--content-background);\n }\n\n .expand:has(.trigger[data-disabled]) {\n pointer-events: none;\n }\n}\n",
|
|
4923
4189
|
"cssTypes": "declare const styles: {\n expand: string;\n trigger: string;\n icon: string;\n title: string;\n content: string;\n \"content-inner\": string;\n};\n\nexport default styles;\n"
|
|
4924
4190
|
},
|
|
4925
4191
|
"flex": {
|
|
4926
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Flex.module.css\";\n\ntype FlexDirection = \"row\" | \"column\";\ntype FlexWrap = \"wrap\" | \"nowrap\";\ntype FlexJustify =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\ntype FlexAlign =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"stretch\"\n | \"baseline\";\ntype FlexGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\
|
|
4192
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Flex.module.css\";\n\ntype FlexDirection = \"row\" | \"column\";\ntype FlexWrap = \"wrap\" | \"nowrap\";\ntype FlexJustify =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\ntype FlexAlign =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"stretch\"\n | \"baseline\";\ntype FlexGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\ninterface FlexStyleSlots {\n root?: StyleValue;\n}\n\ntype FlexStylesProp = StylesProp<FlexStyleSlots>;\n\nconst resolveFlexBaseStyles = createStylesResolver(['root'] as const);\n\nexport interface FlexProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Direction of the flex container */\n direction?: FlexDirection;\n /** Whether items wrap to the next line when they overflow */\n wrap?: FlexWrap;\n /** Gap between flex items */\n gap?: FlexGap;\n /** Alignment of items along the main axis */\n justify?: FlexJustify;\n /** Alignment of items along the cross axis */\n align?: FlexAlign;\n /** Wraps the flex container in a container query parent for breakpoint-aware responsiveness */\n containerQueryResponsive?: boolean;\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: FlexStylesProp;\n}\n\nconst directionMap = {\n row: styles[\"row\"],\n column: styles[\"column\"],\n} as const;\n\nconst wrapMap = {\n wrap: styles[\"wrap\"],\n nowrap: styles[\"nowrap\"],\n} as const;\n\nconst justifyMap = {\n \"flex-start\": styles[\"justify-flex-start\"],\n \"flex-end\": styles[\"justify-flex-end\"],\n center: styles[\"justify-center\"],\n \"space-between\": styles[\"justify-space-between\"],\n \"space-around\": styles[\"justify-space-around\"],\n \"space-evenly\": styles[\"justify-space-evenly\"],\n} as const;\n\nconst alignMap = {\n \"flex-start\": styles[\"align-flex-start\"],\n \"flex-end\": styles[\"align-flex-end\"],\n center: styles[\"align-center\"],\n stretch: styles[\"align-stretch\"],\n baseline: styles[\"align-baseline\"],\n} as const;\n\nconst gapMap = {\n xs: styles[\"gap-xs\"],\n sm: styles[\"gap-sm\"],\n md: styles[\"gap-md\"],\n lg: styles[\"gap-lg\"],\n xl: styles[\"gap-xl\"],\n} as const;\n\nconst Flex = React.forwardRef<HTMLDivElement, FlexProps>(\n (\n {\n className,\n styles: stylesProp,\n direction = \"row\",\n wrap = \"nowrap\",\n gap = \"md\",\n justify = \"flex-start\",\n align = \"stretch\",\n containerQueryResponsive = false,\n children,\n ...props\n },\n ref\n ) => {\n const resolved = resolveFlexBaseStyles(stylesProp);\n if (containerQueryResponsive) {\n return (\n <div\n ref={ref}\n className={cn(styles[\"container-query-parent\"], className, resolved.root)}\n data-container-responsive=\"true\"\n {...props}\n >\n <div\n className={cn(\n styles.flex,\n directionMap[direction],\n wrapMap[wrap],\n gapMap[gap],\n justifyMap[justify],\n alignMap[align],\n styles[\"container-responsive\"]\n )}\n data-direction={direction}\n data-wrap={wrap}\n data-gap={gap}\n data-justify={justify}\n data-align={align}\n >\n {children}\n </div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n styles.flex,\n directionMap[direction],\n wrapMap[wrap],\n gapMap[gap],\n justifyMap[justify],\n alignMap[align],\n className,\n resolved.root\n )}\n data-direction={direction}\n data-wrap={wrap}\n data-gap={gap}\n data-justify={justify}\n data-align={align}\n data-container-responsive={containerQueryResponsive || undefined}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nFlex.displayName = \"Flex\";\n\nexport { Flex };\n",
|
|
4927
4193
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .flex {\n @apply flex w-full flex-row;\n flex-wrap: nowrap;\n gap: var(--spacing-md);\n justify-content: flex-start;\n align-items: stretch;\n }\n\n /* Direction variants */\n .flex.row { flex-direction: row; }\n .flex.column { flex-direction: column; }\n\n /* Wrap variants */\n .flex.wrap { flex-wrap: wrap; }\n .flex.nowrap { flex-wrap: nowrap; }\n\n /* Gap variants */\n .flex.gap-xs { gap: var(--spacing-xs); }\n .flex.gap-sm { gap: var(--spacing-sm); }\n .flex.gap-md { gap: var(--spacing-md); }\n .flex.gap-lg { gap: var(--spacing-lg); }\n .flex.gap-xl { gap: var(--spacing-xl); }\n\n /* Justify-content variants */\n .flex.justify-flex-start { justify-content: flex-start; }\n .flex.justify-flex-end { justify-content: flex-end; }\n .flex.justify-center { justify-content: center; }\n .flex.justify-space-between { justify-content: space-between; }\n .flex.justify-space-around { justify-content: space-around; }\n .flex.justify-space-evenly { justify-content: space-evenly; }\n\n /* Align-items variants */\n .flex.align-flex-start { align-items: flex-start; }\n .flex.align-flex-end { align-items: flex-end; }\n .flex.align-center { align-items: center; }\n .flex.align-stretch { align-items: stretch; }\n .flex.align-baseline { align-items: baseline; }\n\n /* Container query parent - establishes containment context */\n .container-query-parent {\n container-type: inline-size;\n container-name: flex-parent;\n @apply w-full;\n }\n\n /* Container query responsive behavior - use .flex.container-responsive for specificity parity with base variants */\n @container flex-parent (width < 400px) {\n .flex.container-responsive {\n flex-direction: column;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (400px <= width < 500px) {\n .flex.container-responsive {\n flex-wrap: wrap;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (500px <= width < 900px) {\n .flex.container-responsive {\n gap: var(--spacing-md);\n }\n }\n\n @container flex-parent (width >= 900px) {\n .flex.container-responsive {\n gap: var(--spacing-lg);\n }\n }\n}\n",
|
|
4928
4194
|
"cssTypes": "declare const styles: {\n flex: string;\n row: string;\n column: string;\n wrap: string;\n nowrap: string;\n \"gap-xs\": string;\n \"gap-sm\": string;\n \"gap-md\": string;\n \"gap-lg\": string;\n \"gap-xl\": string;\n \"justify-flex-start\": string;\n \"justify-flex-end\": string;\n \"justify-center\": string;\n \"justify-space-between\": string;\n \"justify-space-around\": string;\n \"justify-space-evenly\": string;\n \"align-flex-start\": string;\n \"align-flex-end\": string;\n \"align-center\": string;\n \"align-stretch\": string;\n \"align-baseline\": string;\n \"container-query-parent\": string;\n \"container-responsive\": string;\n};\n\nexport default styles;\n"
|
|
4929
4195
|
},
|
|
4930
4196
|
"frame": {
|
|
4931
|
-
"tsx": "\"use client\";\n\nimport React, { useId } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Frame.module.css\";\n\
|
|
4197
|
+
"tsx": "\"use client\";\n\nimport React, { useId } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Frame.module.css\";\n\ninterface FrameStyleSlots {\n root?: StyleValue;\n}\n\ntype FrameStylesProp = StylesProp<FrameStyleSlots>;\n\nconst resolveFrameBaseStyles = createStylesResolver(['root'] as const);\n\nconst frameVariants = cva(\"relative w-full group isolate\", {\n variants: {\n variant: {\n default: \"text-zinc-500\",\n accent: \"text-emerald-500\",\n },\n padding: {\n none: \"p-0\",\n small: \"p-2\",\n medium: \"p-4\",\n large: \"p-6\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n padding: \"medium\",\n },\n});\n\nexport interface FrameProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof frameVariants> {\n /** SVG path data for the notch or tab shape cut into the frame border */\n path?: string;\n /** Width of the path shape in pixels */\n pathWidth?: number;\n /** Which side of the frame the path shape appears on */\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /** Corner radius of the frame border in pixels */\n cornerRadius?: number;\n /** Fill color applied behind the frame content area */\n fill?: string;\n /** Whether the path shape indents into the frame or extends out from it */\n shapeMode?: \"indent\" | \"extend\";\n /** Stroke width of the frame border in pixels */\n borderWidth?: number;\n /** Color of the frame border stroke */\n borderColor?: string;\n /** Visual color style of the frame */\n variant?: \"default\" | \"accent\" | null;\n /** Internal padding preset */\n padding?: \"none\" | \"small\" | \"medium\" | \"large\" | null;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: FrameStylesProp;\n}\n\nconst Frame = React.forwardRef<HTMLDivElement, FrameProps>(\n ({ children, variant, padding, className, styles, style, path, pathWidth = 0, side = \"top\", cornerRadius, fill, shapeMode = \"indent\", borderWidth, borderColor = \"var(--frame-stroke-color, var(--background-700))\", ...props }, ref) => {\n const maskId = useId();\n const borderMaskId = `border-${maskId}`;\n const bgMaskId = `bg-${maskId}`;\n\n const borderStroke = borderWidth ?? 1;\n const halfStroke = borderStroke / 2;\n\n const positionMap = {\n top: { x: \"50%\", y: \"0\", rotate: 0 },\n bottom: { x: \"50%\", y: \"100%\", rotate: 180 },\n left: { x: \"0\", y: \"50%\", rotate: -90 },\n right: { x: \"100%\", y: \"50%\", rotate: 90 },\n };\n\n const { x, y, rotate } = positionMap[side];\n\n const resolved = resolveFrameBaseStyles(styles);\n\n return (\n <div\n ref={ref}\n className={cn(frameVariants({ variant, padding }), css.root, className, resolved.root)}\n style={{\n ...(cornerRadius !== undefined && { \"--frame-radius\": `${cornerRadius}px` }),\n ...(borderWidth !== undefined && { \"--frame-stroke-width\": `${borderWidth}px` }),\n maskImage: path && shapeMode === \"indent\" ? `url(#${maskId})` : undefined,\n WebkitMaskImage: path && shapeMode === \"indent\" ? `url(#${maskId})` : undefined,\n ...style,\n } as React.CSSProperties}\n {...props}\n >\n <svg\n className=\"absolute inset-0 w-full h-full pointer-events-none z-0 overflow-visible\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <defs>\n {/* Mask for the Content/Background: Cuts the path shape (curvature) */}\n <mask id={maskId}>\n <rect width=\"100%\" height=\"100%\" fill=\"white\" className={css.shape} />\n {path && (\n <svg x={x} y={y} overflow=\"visible\">\n <g transform={`rotate(${rotate}) scale(1.010, 0.990)`}>\n <path\n d={path}\n fill=\"black\"\n transform={`translate(-${pathWidth / 2}, ${borderStroke / 2})`}\n />\n </g>\n </svg>\n )}\n </mask>\n\n {/* Mask for the Border: Cuts a clean gap for the stroke connection */}\n <mask id={borderMaskId}>\n <rect x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\" fill=\"white\" />\n {path && (\n <svg x={x} y={y} overflow=\"visible\">\n <g transform={`rotate(${rotate})`}>\n <rect\n x={-pathWidth / 2}\n y={-borderStroke * 2}\n width={pathWidth}\n height={borderStroke * 4}\n fill=\"black\"\n />\n </g>\n </svg>\n )}\n </mask>\n\n {/* Mask for the Background Fill (Union or Difference) */}\n <mask id={bgMaskId}>\n <rect width=\"100%\" height=\"100%\" fill=\"white\" className={css.shape} />\n {path && (\n <svg x={x} y={y} overflow=\"visible\">\n <g transform={`rotate(${rotate}) scale(1.010, 0.990)`}>\n <path\n d={path}\n fill={shapeMode === \"extend\" ? \"white\" : \"black\"}\n transform={`translate(-${pathWidth / 2}, ${borderStroke / 2})`}\n />\n </g>\n </svg>\n )}\n </mask>\n </defs>\n\n {/* Background Fill Layer */}\n <rect\n x=\"-50%\"\n y=\"-50%\"\n width=\"200%\"\n height=\"200%\"\n fill={fill ?? \"var(--frame-fill, transparent)\"}\n mask={`url(#${bgMaskId})`}\n />\n\n {/* Border Stroke Layer */}\n <rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n fill=\"none\"\n stroke={borderColor}\n strokeWidth={borderStroke}\n mask={`url(#${borderMaskId})`}\n className={cn(css.shape, css.stroke)}\n />\n\n {/* Layer 2: The Notch/Tab Path Stroke */}\n {path && (\n <svg x={x} y={y} overflow=\"visible\">\n <g transform={`rotate(${rotate}) scale(1.010, 0.990)`}>\n <path\n d={path}\n fill=\"none\"\n stroke={borderColor}\n strokeWidth={borderStroke}\n transform={`translate(-${pathWidth / 2}, ${borderStroke / 2})`}\n className={css.stroke}\n />\n </g>\n </svg>\n )}\n </svg>\n\n <div className=\"relative z-10\">{children}</div>\n </div>\n );\n }\n);\n\nFrame.displayName = \"Frame\";\n\nexport { Frame };\n",
|
|
4932
4198
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n --frame-radius: var(--radius-sm, 24px);\n --frame-stroke-width: var(--border-width-base, 1px);\n }\n\n .shape {\n rx: var(--frame-radius);\n }\n\n .stroke {\n stroke-width: var(--frame-stroke-width);\n vector-effect: non-scaling-stroke;\n }\n\n}\n",
|
|
4933
4199
|
"cssTypes": "declare const styles: {\n root: string;\n shape: string;\n stroke: string;\n};\n\nexport default styles;\n"
|
|
4934
4200
|
},
|
|
4935
4201
|
"gallery": {
|
|
4936
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { useFocusRing, useHover, usePress, mergeProps } from \"react-aria\"\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { Grid } from \"../Grid\"\nimport styles from \"./Gallery.module.css\"\n\n// Types\ntype GridColumns = \
|
|
4202
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { useFocusRing, useHover, usePress, mergeProps } from \"react-aria\"\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { Grid } from \"../Grid\"\nimport styles from \"./Gallery.module.css\"\n\n// Types\ntype GridColumns = number\ntype GridGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"\ntype ResponsiveColumns = {\n sm?: GridColumns\n md?: GridColumns\n lg?: GridColumns\n xl?: GridColumns\n}\n\ninterface GalleryProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Number of columns in the gallery grid */\n columns?: GridColumns | ResponsiveColumns\n /** Gap between gallery items */\n gap?: GridGap | number | string\n /** Number of rows in the gallery grid */\n rows?: \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"auto\"\n /** Whether to enable container-query-based responsive columns */\n responsive?: boolean\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: GalleryStylesProp\n}\n\ninterface GalleryItemProps extends React.HTMLAttributes<HTMLElement> {\n /** URL the item links to */\n href?: string\n /** Called when the item is pressed */\n onPress?: (href?: string) => void\n /** Number of columns this item spans */\n columnSpan?: number\n /** Number of rows this item spans */\n rowSpan?: number\n /** Controls the item's layout orientation */\n orientation?: \"vertical\" | \"horizontal\"\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: GalleryStylesProp\n}\n\ninterface GalleryViewProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Aspect ratio of the view area (e.g. \"16/9\") */\n aspectRatio?: string\n}\n\ninterface GalleryBodyProps extends React.HTMLAttributes<HTMLDivElement> { }\n\ninterface GalleryStyleSlots {\n root?: StyleValue;\n}\n\ntype GalleryStylesProp = StylesProp<GalleryStyleSlots>;\n\nconst resolveGalleryBaseStyles = createStylesResolver(['root'] as const);\n\n// Helper to map numeric columns to Grid's column values\nconst mapColumnsToGrid = (columns?: GridColumns | ResponsiveColumns): GridColumns | ResponsiveColumns => {\n if (!columns) return 3\n if (typeof columns === \"object\") return columns as ResponsiveColumns\n return columns\n}\n\n// Helper to map gap values to Grid's gap values\nconst mapGapToGrid = (gap?: GridGap | number | string): GridGap => {\n if (!gap) return \"md\"\n if (typeof gap === \"string\" && [\"xs\", \"sm\", \"md\", \"lg\", \"xl\"].includes(gap)) {\n return gap as GridGap\n }\n if (typeof gap === \"number\") {\n // Map numeric gap values (in pixels) to Grid gap presets\n if (gap <= 4) return \"xs\"\n if (gap <= 8) return \"sm\"\n if (gap <= 16) return \"md\"\n if (gap <= 24) return \"lg\"\n return \"xl\"\n }\n return \"md\" // default fallback\n}\n\n// Gallery Root Component\nconst GalleryRoot = React.forwardRef<HTMLDivElement, GalleryProps>(\n ({ columns = 3, gap = \"md\", rows, responsive, className, styles: stylesProp, children, ...props }, ref) => {\n const gridColumns = mapColumnsToGrid(columns)\n const gridGap = mapGapToGrid(gap)\n const resolved = resolveGalleryBaseStyles(stylesProp);\n\n return (\n <Grid\n ref={ref}\n columns={gridColumns as GridColumns | ResponsiveColumns}\n gap={gridGap}\n rows={rows}\n responsive={responsive}\n className={cn(className, resolved.root)}\n {...props}\n >\n {children}\n </Grid>\n )\n }\n)\nGalleryRoot.displayName = \"Gallery\"\n\n// Gallery Item Component\n/** A single media or content tile in the gallery grid */\nconst GalleryItem = React.forwardRef<HTMLElement, GalleryItemProps>(\n ({ href, onPress, columnSpan, rowSpan, orientation = \"vertical\", className, style, styles: stylesProp, children, ...props }, ref) => {\n const resolved = resolveGalleryBaseStyles(stylesProp);\n const elementRef = React.useRef<HTMLElement>(null)\n const combinedRef = (node: HTMLElement | null) => {\n (elementRef as React.MutableRefObject<HTMLElement | null>).current = node\n if (typeof ref === \"function\") {\n ref(node)\n } else if (ref) {\n ref.current = node\n }\n }\n\n const { focusProps, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({})\n\n // Use usePress for button interaction\n const { pressProps, isPressed } = usePress({\n onPress: () => onPress?.(href),\n })\n\n const spanStyles: React.CSSProperties = {\n ...(columnSpan && { gridColumn: `span ${columnSpan}` }),\n ...(rowSpan && { gridRow: `span ${rowSpan}` }),\n ...style,\n }\n\n // Ensure accessible name: aria-label, aria-labelledby, or text content\n const ariaLabel = props[\"aria-label\"] || props[\"aria-labelledby\"]\n const hasAccessibleName = ariaLabel || React.Children.count(children) > 0\n\n const commonProps = mergeProps(\n focusProps,\n hoverProps,\n pressProps,\n {\n className: cn('gallery', 'item', styles.item, className, resolved.root),\n style: spanStyles,\n \"data-focus-visible\": isFocusVisible || undefined,\n \"data-hovered\": isHovered || undefined,\n \"data-pressed\": isPressed || undefined,\n \"data-orientation\": orientation,\n ...(!hasAccessibleName && { \"aria-label\": \"Gallery item\" }),\n ...props,\n }\n )\n\n return (\n <div\n ref={combinedRef as React.Ref<HTMLDivElement>}\n role=\"button\"\n tabIndex={0}\n {...commonProps}\n >\n {children}\n </div>\n )\n }\n)\nGalleryItem.displayName = \"Gallery.Item\"\n\n// Gallery View Component\n/** Expanded full-screen view overlay for a selected gallery item */\nconst GalleryView = React.forwardRef<HTMLDivElement, GalleryViewProps>(\n ({ aspectRatio = \"16/9\", className, style, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(styles.view, className)}\n style={{\n \"--gallery-aspect-ratio\": aspectRatio,\n ...style\n } as React.CSSProperties}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nGalleryView.displayName = \"Gallery.View\"\n\n// Gallery Body Component\n/** Container for the gallery item's visible content */\nconst GalleryBody = React.forwardRef<HTMLDivElement, GalleryBodyProps>(\n ({ className, children, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn('gallery', 'body', styles.body, className)}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nGalleryBody.displayName = \"Gallery.Body\"\n\n// Compound Component\nconst Gallery = Object.assign(GalleryRoot, {\n Item: GalleryItem,\n View: GalleryView,\n Body: GalleryBody,\n})\n\nexport { Gallery, GalleryItem, GalleryView, GalleryBody }\nexport type { GalleryProps, GalleryItemProps, GalleryViewProps, GalleryBodyProps }\n",
|
|
4937
4203
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .item {\n @apply flex flex-col border overflow-hidden no-underline cursor-pointer;\n\n background: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n color: inherit;\n }\n\n .item:focus {\n outline: none;\n }\n\n .item[data-focus-visible] {\n outline: 2px solid var(--border-focus);\n outline-offset: 2px;\n }\n\n .item[data-hovered] {\n border-color: var(--border-hover);\n }\n\n .item[data-pressed] {\n border-color: var(--border-focus);\n }\n\n .item[data-orientation=\"horizontal\"] {\n @apply flex-row;\n }\n\n .item[data-orientation=\"horizontal\"] .view {\n width: var(--gallery-horizontal-view-width, 200px);\n }\n\n .view {\n --aspect-ratio: var(--gallery-aspect-ratio, 16/9);\n\n @apply relative overflow-hidden;\n aspect-ratio: var(--aspect-ratio);\n background: var(--background);\n }\n\n .view > img,\n .view > video {\n @apply w-full h-full object-cover;\n }\n\n .body {\n @apply flex flex-col gap-1 p-3 self-start min-w-0;\n }\n\n .item[data-orientation=\"horizontal\"] .body {\n flex: 1;\n align-self: stretch;\n }\n\n .body > :first-child {\n font-weight: var(--font-weight-medium);\n color: var(--title-color);\n }\n\n .body > :not(:first-child) {\n font-size: var(--text-sm);\n color: var(--description-color);\n }\n}\n",
|
|
4938
4204
|
"cssTypes": "declare const styles: {\n readonly item: string;\n readonly view: string;\n readonly body: string;\n};\n\nexport default styles;\n"
|
|
4939
4205
|
},
|
|
4940
4206
|
"grid": {
|
|
4941
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Grid.module.css\";\n\
|
|
4207
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Grid.module.css\";\n\ninterface GridStyleSlots {\n root?: StyleValue;\n}\n\ntype GridStylesProp = StylesProp<GridStyleSlots>;\n\nconst resolveGridBaseStyles = createStylesResolver(['root'] as const);\n\ntype GridColumns = number | \"auto-fit\" | \"auto-fill\";\ntype GridRows = \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"auto\" | \"masonry\";\ntype GridGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\ntype GridJustifyItems = \"start\" | \"end\" | \"center\" | \"stretch\";\ntype GridAlignItems = \"start\" | \"end\" | \"center\" | \"stretch\" | \"baseline\";\ntype GridJustifyContent = \"start\" | \"end\" | \"center\" | \"stretch\" | \"space-between\" | \"space-around\" | \"space-evenly\";\ntype GridAlignContent = \"start\" | \"end\" | \"center\" | \"stretch\" | \"space-between\" | \"space-around\" | \"space-evenly\";\ntype GridAutoFlow = \"row\" | \"column\" | \"row-dense\" | \"column-dense\";\ntype GridTemplateColumns = GridColumns | (string & {});\n\ntype ResponsiveValue<T> = { sm?: T; md?: T; lg?: T; xl?: T };\n\nexport interface GridProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Grid template columns value, or responsive object per breakpoint */\n columns?: GridTemplateColumns | ResponsiveValue<GridTemplateColumns>;\n /** Number of grid rows, or responsive object per breakpoint */\n rows?: GridRows | ResponsiveValue<GridRows>;\n /** Gap between all grid cells, or responsive object per breakpoint */\n gap?: GridGap | ResponsiveValue<GridGap>;\n /** Override gap between rows only */\n rowGap?: GridGap;\n /** Override gap between columns only */\n columnGap?: GridGap;\n /** Horizontal alignment of items within their cells */\n justifyItems?: GridJustifyItems;\n /** Vertical alignment of items within their cells */\n alignItems?: GridAlignItems;\n /** Horizontal distribution of the grid within its container */\n justifyContent?: GridJustifyContent;\n /** Vertical distribution of the grid rows within its container */\n alignContent?: GridAlignContent;\n /** Direction items are auto-placed when no explicit placement is set */\n autoFlow?: GridAutoFlow;\n /** Wraps the grid in a container query parent for breakpoint-aware responsiveness */\n responsive?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GridStylesProp;\n}\n\nconst isResponsive = <T,>(v: unknown): v is ResponsiveValue<T> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\nconst colsToTpl = (c: GridTemplateColumns): string => {\n if (c === \"auto-fit\") return \"repeat(auto-fit, minmax(200px, 1fr))\";\n if (c === \"auto-fill\") return \"repeat(auto-fill, minmax(200px, 1fr))\";\n if (typeof c === \"number\") return `repeat(${c}, 1fr)`;\n return c;\n};\n\nconst rowsToTpl = (r: GridRows): string => {\n if (r === \"masonry\" || r === \"auto\") return r;\n return `repeat(${r}, auto)`;\n};\n\nconst gapVal: Record<GridGap, string> = {\n xs: \"calc(var(--spacing, 0.25rem) * 1)\",\n sm: \"calc(var(--spacing, 0.25rem) * 2)\",\n md: \"calc(var(--spacing, 0.25rem) * 4)\",\n lg: \"calc(var(--spacing, 0.25rem) * 6)\",\n xl: \"calc(var(--spacing, 0.25rem) * 8)\",\n};\n\nconst flowVal: Record<GridAutoFlow, string> = {\n row: \"row\",\n column: \"column\",\n \"row-dense\": \"row dense\",\n \"column-dense\": \"column dense\",\n};\n\nconst Grid = React.forwardRef<HTMLDivElement, GridProps>(\n (\n {\n className,\n style,\n columns = 3,\n rows = \"auto\",\n gap = \"md\",\n rowGap,\n columnGap,\n justifyItems = \"stretch\",\n alignItems = \"stretch\",\n justifyContent = \"start\",\n alignContent = \"start\",\n autoFlow = \"row\",\n responsive = false,\n styles,\n children,\n ...props\n },\n ref\n ) => {\n const resolved = resolveGridBaseStyles(styles);\n const responsiveCols = isResponsive<GridTemplateColumns>(columns);\n const responsiveRows = isResponsive<GridRows>(rows);\n const responsiveGap = isResponsive<GridGap>(gap);\n const needsContainer = responsiveCols || responsiveRows || responsiveGap || responsive;\n\n const vars: Record<string, string> = {};\n\n if (responsiveCols) {\n const rc = columns as ResponsiveValue<GridTemplateColumns>;\n if (rc.sm) vars[\"--grid-tpl-sm\"] = colsToTpl(rc.sm);\n if (rc.md) vars[\"--grid-tpl-md\"] = colsToTpl(rc.md);\n if (rc.lg) vars[\"--grid-tpl-lg\"] = colsToTpl(rc.lg);\n if (rc.xl) vars[\"--grid-tpl-xl\"] = colsToTpl(rc.xl);\n } else {\n vars[\"--grid-tpl\"] = colsToTpl(columns as GridTemplateColumns);\n }\n\n if (responsiveRows) {\n const rr = rows as ResponsiveValue<GridRows>;\n if (rr.sm) vars[\"--grid-rows-sm\"] = rowsToTpl(rr.sm);\n if (rr.md) vars[\"--grid-rows-md\"] = rowsToTpl(rr.md);\n if (rr.lg) vars[\"--grid-rows-lg\"] = rowsToTpl(rr.lg);\n if (rr.xl) vars[\"--grid-rows-xl\"] = rowsToTpl(rr.xl);\n } else {\n vars[\"--grid-rows\"] = rowsToTpl(rows as GridRows);\n }\n\n if (responsiveGap) {\n const rg = gap as ResponsiveValue<GridGap>;\n if (rg.sm) vars[\"--grid-gap-sm\"] = gapVal[rg.sm];\n if (rg.md) vars[\"--grid-gap-md\"] = gapVal[rg.md];\n if (rg.lg) vars[\"--grid-gap-lg\"] = gapVal[rg.lg];\n if (rg.xl) vars[\"--grid-gap-xl\"] = gapVal[rg.xl];\n } else {\n vars[\"--grid-gap\"] = gapVal[gap as GridGap];\n }\n\n if (rowGap) vars[\"--grid-row-gap\"] = gapVal[rowGap];\n if (columnGap) vars[\"--grid-col-gap\"] = gapVal[columnGap];\n\n vars[\"--grid-ji\"] = justifyItems;\n vars[\"--grid-ai\"] = alignItems;\n vars[\"--grid-jc\"] = justifyContent;\n vars[\"--grid-ac\"] = alignContent;\n vars[\"--grid-flow\"] = flowVal[autoFlow];\n\n const gridClasses = cn(\n css.grid,\n responsiveCols && css[\"responsive-cols\"],\n responsiveGap && css[\"responsive-gap\"],\n responsiveRows && css[\"responsive-rows\"],\n rowGap && css[\"has-row-gap\"],\n columnGap && css[\"has-col-gap\"],\n );\n\n if (needsContainer) {\n return (\n <div\n ref={ref}\n className={cn(css.container, className, resolved.root)}\n style={style}\n {...props}\n >\n <div className={gridClasses} style={vars as React.CSSProperties}>\n {children}\n </div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(gridClasses, className, resolved.root)}\n style={{ ...vars, ...style } as React.CSSProperties}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nGrid.displayName = \"Grid\";\n\nexport { Grid };\n",
|
|
4942
4208
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .grid {\n @apply grid w-full;\n grid-template-columns: var(--grid-tpl, repeat(3, 1fr));\n grid-template-rows: var(--grid-rows, auto);\n gap: var(--grid-gap, calc(var(--spacing, 0.25rem) * 4));\n justify-items: var(--grid-ji, stretch);\n align-items: var(--grid-ai, stretch);\n justify-content: var(--grid-jc, start);\n align-content: var(--grid-ac, start);\n grid-auto-flow: var(--grid-flow, row);\n }\n\n .container {\n container-type: inline-size;\n container-name: grid-ctx;\n }\n\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-sm, 1fr);\n }\n\n @media (min-width: 640px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-md, var(--grid-tpl-sm, 1fr));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-lg, var(--grid-tpl-md, var(--grid-tpl-sm, 1fr)));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-xl, var(--grid-tpl-lg, var(--grid-tpl-md, var(--grid-tpl-sm, 1fr))));\n }\n }\n\n .grid.responsive-gap {\n gap: var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 2));\n }\n\n @media (min-width: 640px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4)));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-lg, var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4))));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-xl, var(--grid-gap-lg, var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4)))));\n }\n }\n\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-sm, auto);\n }\n\n @media (min-width: 640px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-md, var(--grid-rows-sm, auto));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-lg, var(--grid-rows-md, var(--grid-rows-sm, auto)));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-xl, var(--grid-rows-lg, var(--grid-rows-md, var(--grid-rows-sm, auto))));\n }\n }\n\n .grid.has-row-gap { row-gap: var(--grid-row-gap); }\n .grid.has-col-gap { column-gap: var(--grid-col-gap); }\n\n @container grid-ctx (width < 400px) {\n .container .grid {\n grid-template-columns: 1fr;\n gap: calc(var(--spacing, 0.25rem) * 2);\n }\n }\n}\n",
|
|
4943
4209
|
"cssTypes": "declare const styles: {\n readonly grid: string;\n readonly container: string;\n readonly \"responsive-cols\": string;\n readonly \"responsive-gap\": string;\n readonly \"responsive-rows\": string;\n readonly \"has-row-gap\": string;\n readonly \"has-col-gap\": string;\n};\n\nexport default styles;\n"
|
|
4944
4210
|
},
|
|
4945
4211
|
"group": {
|
|
4946
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"./utils\"\nimport { Button, type ButtonProps } from \"../Button\"\nimport { Input, type InputProps } from \"../Input\"\nimport { Select, type SelectProps } from \"../Select\"\nimport { SelectTriggerContext } from \"../Select/Select.Trigger\"\nimport styles from \"./Group.module.css\"\n\ntype Orientation = \"horizontal\" | \"vertical\"\ntype Spacing = \"none\" | \"xs\" | \"sm\"\ntype Variant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\"\n\
|
|
4947
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --radius-basis: calc(var(--spacing) * 1.5);\n --padding: var(--radius-basis);\n\n @apply flex overflow-hidden;\n width: fit-content;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n
|
|
4212
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"./utils\"\nimport { Button, type ButtonProps } from \"../Button\"\nimport { Input, type InputProps } from \"../Input\"\nimport { Select, type SelectProps } from \"../Select\"\nimport { SelectTriggerContext } from \"../Select/Select.Trigger\"\nimport styles from \"./Group.module.css\"\n\ntype Orientation = \"horizontal\" | \"vertical\"\ntype Spacing = \"none\" | \"xs\" | \"sm\"\ntype Variant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\"\n\nexport interface GroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Controls the axis that children are arranged along */\n orientation?: Orientation\n /** Controls the gap between group items */\n spacing?: Spacing\n /** Controls the shared visual style applied to group items */\n variant?: Variant\n /** Whether all items in the group are non-interactive */\n isDisabled?: boolean\n /** The currently active button value for toggle group behavior */\n value?: string\n /** Called when a button with a value prop is pressed */\n onChange?: (value: string) => void\n}\n\ninterface GroupContextValue {\n isInGroup: boolean\n groupVariant: Variant\n groupOrientation: Orientation\n groupSpacing: Spacing\n groupIsDisabled: boolean\n groupValue?: string\n groupOnChange?: (value: string) => void\n}\n\n// Context\nconst GroupContext = React.createContext<GroupContextValue | null>(null)\n\nfunction useGroupContext() {\n const context = React.useContext(GroupContext)\n if (!context) {\n throw new Error(\"Group sub-components must be used within Group\")\n }\n return context\n}\n\n// Variant and orientation maps\nconst orientationMap: Record<Orientation, string> = {\n horizontal: styles.horizontal,\n vertical: styles.vertical,\n}\n\nconst spacingMap: Record<Spacing, string> = {\n none: styles.none,\n xs: styles.xs,\n sm: styles.sm,\n}\n\nconst variantMap: Record<Variant, string | undefined> = {\n primary: undefined,\n secondary: undefined,\n outline: undefined,\n ghost: styles.ghost,\n}\n\n// Detect Divider elements by checking for separator role or orientation prop\nfunction isDivider(child: React.ReactNode): boolean {\n if (!React.isValidElement(child)) return false\n const props = (child.props || {}) as Record<string, unknown>\n return props.role === \"separator\" || \"orientation\" in props\n}\n\n// Root component\n/** Button group that groups related buttons together */\nconst GroupRoot = React.forwardRef<HTMLDivElement, GroupProps>(\n (\n {\n className,\n orientation = \"horizontal\",\n spacing = \"none\",\n variant = \"primary\",\n children,\n isDisabled = false,\n value,\n onChange,\n ...props\n },\n ref\n ) => {\n const isVertical = orientation === \"vertical\"\n\n const childrenArray = React.Children.toArray(children).filter(\n (child) => child !== null && child !== undefined\n )\n\n const contextValue: GroupContextValue = {\n isInGroup: true,\n groupVariant: variant,\n groupOrientation: orientation,\n groupSpacing: spacing,\n groupIsDisabled: isDisabled,\n groupValue: value,\n groupOnChange: onChange,\n }\n\n return (\n <GroupContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn(\n 'group',\n orientation,\n variant,\n styles.group,\n orientationMap[orientation],\n spacingMap[spacing],\n variantMap[variant],\n className\n )}\n role=\"group\"\n aria-disabled={isDisabled || undefined}\n {...props}\n >\n {childrenArray.map((child, index) => {\n const isFirst = index === 0\n const isLast = index === childrenArray.length - 1\n const isDividerChild = isDivider(child)\n \n // Extract layout-related classes from child to apply to the item wrapper\n const childProps = React.isValidElement(child) ? (child.props as any) : {}\n const childClassName = childProps.className || \"\"\n const shouldGrow = childClassName.includes('w-full') || childClassName.includes('flex-1')\n\n return (\n <div\n key={`item-${index}`}\n className={cn(\n 'item',\n styles.item,\n isVertical ? styles.vertical : styles.horizontal,\n isFirst && styles.first,\n isLast && styles.last,\n isDividerChild && styles.divider,\n shouldGrow && styles.grow\n )}\n >\n {child}\n </div>\n )\n })}\n </div>\n </GroupContext.Provider>\n )\n }\n)\nGroupRoot.displayName = \"Group\"\n\n// Group.Button component\ninterface GroupButtonProps extends ButtonProps {\n /** Whether this button is in an active/pressed state */\n active?: boolean\n /** Identifier used for toggle group behavior when Group has value/onChange */\n value?: string\n}\n\ntype GroupButtonIconSlots = {\n left?: React.ReactNode\n right?: React.ReactNode\n}\n\nfunction isGroupButtonIconSlots(icon: ButtonProps[\"icon\"]): icon is GroupButtonIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon)\n}\n\nfunction resolveGroupButtonIcon(icon: ButtonProps[\"icon\"]): GroupButtonIconSlots | undefined {\n if (!icon) return undefined\n if (isGroupButtonIconSlots(icon)) {\n return icon\n }\n\n return { left: icon as React.ReactNode, right: undefined }\n}\n\n/** Button styled to merge seamlessly with adjacent group items */\nconst GroupButton = React.forwardRef<HTMLButtonElement, GroupButtonProps>(\n ({ active, value, variant, className, onPress, ...restProps }, ref) => {\n const context = useGroupContext()\n const isInSelectTrigger = React.useContext(SelectTriggerContext)\n\n // Merge disabled state from group context\n const isDisabled = restProps.isDisabled ?? context.groupIsDisabled\n\n // Derive active and onPress from toggle group context when value is provided\n const isActive = value !== undefined && context.groupValue !== undefined\n ? value === context.groupValue\n : active\n const handlePress = value !== undefined && context.groupOnChange !== undefined\n ? () => context.groupOnChange!(value)\n : onPress\n\n if (isInSelectTrigger) {\n const icon = resolveGroupButtonIcon(restProps.icon)\n\n return (\n <span className={cn(styles['group-item'], className)}>\n {icon?.left}\n {restProps.children}\n {icon?.right}\n </span>\n )\n }\n\n let buttonVariant = variant\n if (variant === undefined) {\n if (context.groupVariant === \"ghost\") {\n buttonVariant = isActive ? \"default\" : \"ghost\"\n } else {\n buttonVariant = \"ghost\"\n }\n }\n\n const buttonProps = {\n ...restProps,\n onPress: handlePress,\n variant: buttonVariant,\n isDisabled,\n className: cn(\n styles['group-item'],\n isActive && styles.active,\n className\n ),\n }\n\n return <Button ref={ref} {...buttonProps} />\n }\n)\nGroupButton.displayName = \"Group.Button\"\n\n// Group.Input component\ninterface GroupInputProps extends InputProps { }\n\n/** Input field integrated into the button group */\nconst GroupInput = React.forwardRef<HTMLInputElement, GroupInputProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div className={cn(styles['group-input-wrapper'], className)}>\n <Input\n ref={ref}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInput.displayName = \"Group.Input\"\n\n// Group.InputWrapper component - preserves Input styling (for use with ghost variant)\ninterface GroupInputWrapperProps extends InputProps { }\n\n/** Input variant that preserves Input styling within the group */\nconst GroupInputWrapper = React.forwardRef<HTMLInputElement, GroupInputWrapperProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div className={cn(styles['group-input-wrapper'], className)}>\n <Input\n ref={ref}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInputWrapper.displayName = \"Group.InputWrapper\"\n\n// Group.Select component\ninterface GroupSelectProps extends SelectProps<any> { }\n\n/** Select dropdown integrated into the button group */\nconst GroupSelect = React.forwardRef<HTMLDivElement, GroupSelectProps>(\n ({ className, isDisabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const disabled = isDisabled ?? context.groupIsDisabled\n\n return (\n <Select\n ref={ref}\n {...props}\n isDisabled={disabled}\n className={cn('groupSelectWrapper', styles['group-select-wrapper'], className)}\n />\n )\n }\n)\nGroupSelect.displayName = \"Group.Select\"\n\n// Assemble compound component\nconst Group = Object.assign(GroupRoot, {\n Button: GroupButton,\n Input: GroupInput,\n InputWrapper: GroupInputWrapper,\n Select: GroupSelect,\n})\n\nexport { Group, GroupContext }\n",
|
|
4213
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --radius-basis: calc(var(--spacing) * 1.5);\n --padding: var(--radius-basis);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply flex overflow-hidden;\n width: fit-content;\n flex-shrink: 0;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n\n border-radius: var(--radius);\n padding: var(--padding);\n }\n\n /* Orientations */\n .group.horizontal {\n @apply flex-row items-stretch;\n }\n\n .group.vertical {\n @apply flex-col;\n }\n\n /* Spacing */\n .group.none {\n --padding: 0;\n @apply gap-0;\n }\n\n .group.xs {\n --radius-basis: calc(var(--spacing) * 0.875);\n --padding: var(--radius-basis);\n @apply space-x-0.5;\n }\n\n .group.sm {\n --radius-basis: calc(var(--spacing) * 1.25);\n --padding: var(--radius-basis);\n @apply space-x-1;\n }\n\n /* Variants */\n .group.ghost {\n border: none;\n overflow: visible;\n @apply gap-1;\n }\n\n .item {\n @apply flex min-w-0 items-stretch;\n }\n\n .item.grow {\n flex: 1;\n }\n\n .group:not(.ghost) .item .group-item,\n .group:not(.ghost) .item .group-select-wrapper {\n border: none;\n }\n\n .group:not(.ghost) .group-input-wrapper {\n --input-border-color: transparent;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .group.none:not(.ghost) .item .group-item,\n .group.none:not(.ghost) .item .group-select-wrapper,\n .group.none:not(.ghost) .item .trigger {\n border-radius: 0;\n }\n\n .group.none:not(.ghost) .group-input-wrapper {\n --input-border-radius: 0;\n }\n\n .group.none:not(.ghost) .item .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n }\n\n .group.xs:not(.ghost) .item .group-item,\n .group.xs:not(.ghost) .item .trigger,\n .group.xs:not(.ghost) .group-select-wrapper .group-item,\n .group.xs:not(.ghost) .group-select-wrapper .trigger,\n .group.sm:not(.ghost) .item .group-item,\n .group.sm:not(.ghost) .item .trigger,\n .group.sm:not(.ghost) .group-select-wrapper .group-item,\n .group.sm:not(.ghost) .group-select-wrapper .trigger {\n border-radius: var(--inner-radius);\n }\n\n .group.xs:not(.ghost) .group-input-wrapper,\n .group.sm:not(.ghost) .group-input-wrapper {\n --input-border-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .group-item {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .group-item {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .item.divider {\n @apply flex items-stretch p-0;\n }\n\n .item.divider > [role=\"separator\"] {\n @apply h-full w-full;\n }\n\n .group.horizontal .item.divider {\n margin-top: calc(var(--padding) * -1);\n margin-bottom: calc(var(--padding) * -1);\n }\n\n .group.vertical .item.divider {\n margin-left: calc(var(--padding) * -1);\n margin-right: calc(var(--padding) * -1);\n }\n\n .group.ghost:not(.none) .item .group-item:not(.active) {\n border-radius: var(--inner-radius);\n border: var(--border-width-base) solid transparent;\n }\n\n /* ghost + none: flat children — no borders or radius */\n .group.ghost.none {\n @apply gap-0;\n }\n\n .group.ghost.none .item .group-item,\n .group.ghost.none .group-item.active {\n border: none;\n border-radius: 0;\n }\n\n .group.ghost.none .group-input-wrapper {\n --input-border-color: transparent;\n --input-border-radius: 0;\n }\n\n .group.ghost.none .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n border: none;\n border-radius: 0;\n }\n\n .group:not(.ghost) .item .group-item:focus-visible,\n .group:not(.ghost) .item .trigger:focus-visible,\n .group:not(.ghost) .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group.ghost .item .group-item:focus-visible,\n .group.ghost .item .trigger:focus-visible,\n .group.ghost .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group-input-wrapper {\n @apply flex h-full items-stretch;\n flex: 1;\n overflow: visible;\n }\n\n .group-input-wrapper input {\n height: 100%;\n }\n\n .item .group-item {\n @apply flex h-full;\n }\n\n .group.vertical .item .group-item {\n @apply w-full;\n }\n\n .group.xs .item button.group-item {\n padding: calc(var(--spacing) * 1.00) calc(var(--spacing) * 1.50);\n }\n\n .group.sm .item button.group-item {\n padding: calc(var(--spacing) * 1.50) calc(var(--spacing) * 2.00);\n }\n\n .group.none .item button.group-item {\n padding: calc(var(--spacing) * 2.00) calc(var(--spacing) * 2.50);\n }\n\n .group-select-wrapper {\n @apply flex items-stretch p-0;\n border: none;\n background-color: transparent;\n }\n\n .group.none:not(.ghost) .trigger {\n border-radius: 0;\n }\n\n .trigger {\n border: none;\n }\n\n .group-select-wrapper .select {\n @apply h-full w-full;\n }\n\n .group-item.active {\n @apply relative;\n }\n\n .group.ghost .group-item.active {\n border-radius: var(--inner-radius);\n }\n\n .group:not(.ghost) .group-item.active {\n background-color: var(--active-background);\n font-weight: 500;\n }\n}\n",
|
|
4948
4214
|
"cssTypes": "declare const styles: {\n group: string;\n horizontal: string;\n vertical: string;\n none: string;\n xs: string;\n sm: string;\n ghost: string;\n item: string;\n grow: string;\n divider: string;\n first: string;\n last: string;\n separator: string;\n \"group-item\": string;\n \"group-input-wrapper\": string;\n \"group-select-wrapper\": string;\n active: string;\n trigger: string;\n};\n\nexport default styles;\n"
|
|
4949
4215
|
},
|
|
4950
4216
|
"input": {
|
|
4951
|
-
"tsx": "\"use client\";\n\nimport React, { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { mergeProps, } from \"@react-aria/utils\";\n\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Input.module.css\";\n\ntype Variant = \"default\" | \"ghost\";\n\nexport interface InputStyleSlots {\n root?: StyleValue;\n}\n\nexport type InputStylesProp = StylesProp<InputStyleSlots>;\n\nconst resolveInputStyles = createStylesResolver(['root'] as const);\n\nexport interface InputProps extends Omit<ComponentPropsWithoutRef<\"input\">, \"size\"> {\n /** Controls the visual style of the input */\n variant?: Variant;\n /** Whether the input is in an error state */\n error?: boolean;\n /** Icon displayed before the input value */\n
|
|
4952
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n\n @apply
|
|
4953
|
-
"cssTypes": "declare const styles: {\n input: string;\n \"icon-wrapper\": string;\n \"prefix-icon\": string;\n \"suffix-icon\": string;\n container: string;\n \"number-controls\": string;\n disabled: string;\n \"spin-button\": string;\n};\n\nexport default styles;\n"
|
|
4217
|
+
"tsx": "\"use client\";\n\nimport React, { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { mergeProps, } from \"@react-aria/utils\";\n\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Tooltip } from \"@/components/Tooltip\";\nimport css from \"./Input.module.css\";\n\ntype Variant = \"default\" | \"ghost\";\n\nexport interface InputStyleSlots {\n root?: StyleValue;\n}\n\nexport type InputStylesProp = StylesProp<InputStyleSlots>;\n\nexport type InputAction = InputActionDef | React.ReactNode;\ntype InputIconSlots = {\n prefix?: React.ReactNode;\n suffix?: React.ReactNode;\n};\n\nexport type InputActionDef = {\n icon: React.ReactNode;\n title: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n};\n\ntype InputActionSlots = {\n left?: InputAction[];\n right?: InputAction[];\n};\n\nconst resolveInputStyles = createStylesResolver(['root'] as const);\n\nexport interface InputProps extends Omit<ComponentPropsWithoutRef<\"input\">, \"size\"> {\n /** Controls the visual style of the input */\n variant?: Variant;\n /** Whether the input is in an error state */\n error?: boolean;\n /** Icon displayed before the input value by default, or in named prefix/suffix slots */\n icon?: React.ReactNode | InputIconSlots;\n /** Inline actions rendered on the left or right side of the input. Passing an array keeps the existing right-side behavior. */\n actions?: InputAction[] | InputActionSlots;\n /** Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts. */\n hint?: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: InputStylesProp;\n}\n\nfunction isInputIconSlots(icon: InputProps[\"icon\"]): icon is InputIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && (\"prefix\" in icon || \"suffix\" in icon);\n}\n\nfunction resolveInputIcon(icon: InputProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isInputIconSlots(icon)) {\n return icon;\n }\n\n return { prefix: icon };\n}\n\nfunction isInputActionSlots(actions: InputProps[\"actions\"]): actions is InputActionSlots {\n return typeof actions === \"object\" && actions !== null && !Array.isArray(actions) && !React.isValidElement(actions) && (\"left\" in actions || \"right\" in actions);\n}\n\nfunction resolveInputActions(actions: InputProps[\"actions\"]): InputActionSlots {\n if (!actions) {\n return {};\n }\n\n if (isInputActionSlots(actions)) {\n return actions;\n }\n\n return { right: actions };\n}\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return React.useCallback((value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n }, refs);\n}\n\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n variant = \"default\",\n error = false,\n disabled,\n icon,\n actions,\n hint,\n type = \"text\",\n onFocus,\n onBlur,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const resolvedActions = resolveInputActions(actions);\n const resolvedIcon = resolveInputIcon(icon);\n const leftActions = resolvedActions.left ?? [];\n const rightActions = resolvedActions.right ?? [];\n const hasPrefix = !!resolvedIcon?.prefix;\n const hasSuffix = !!resolvedIcon?.suffix;\n const hasLeftActions = leftActions.length > 0;\n const hasRightActions = rightActions.length > 0;\n const hasHint = hint !== undefined && hint !== null;\n const hasStartAdornment = hasPrefix || hasLeftActions;\n const isNumberType = type === \"number\";\n const [isFocused, setIsFocused] = React.useState(false);\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const mergedRef = useMergedRef(ref, inputRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n onBlur?.(e);\n };\n\n const handleSpinClick = (direction: \"up\" | \"down\") => {\n if (!inputRef.current || disabled) return;\n\n const input = inputRef.current;\n\n if (direction === \"up\") {\n input.stepUp();\n } else {\n input.stepDown();\n }\n\n // Dispatch native input event to trigger React onChange handlers\n const event = new Event(\"input\", { bubbles: true });\n input.dispatchEvent(event);\n };\n\n const resolved = resolveInputStyles(stylesProp);\n const hasEndAdornment = hasSuffix || hasRightActions || hasHint || isNumberType;\n const inputPaddingStyle: React.CSSProperties = {\n ...(hasStartAdornment ? { paddingLeft: '8px' } : {}),\n ...(hasEndAdornment ? { paddingRight: '8px' } : {}),\n };\n\n const renderAction = (action: InputAction, index: number) => {\n const key = React.isValidElement(action) ? index : ((action as InputActionDef).title || index);\n\n return React.isValidElement(action) ? (\n <React.Fragment key={key}>{action}</React.Fragment>\n ) : (\n <Tooltip key={key} content={(action as InputActionDef).title} position=\"top\">\n <button\n type=\"button\"\n className={css.action}\n aria-label={(action as InputActionDef).title}\n onClick={(action as InputActionDef).onClick}\n >\n {(action as InputActionDef).icon}\n </button>\n </Tooltip>\n );\n };\n\n return (\n <div\n className={cn('input', css.container)}\n data-active={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n >\n {hasStartAdornment && (\n <div className={css['start-adornments']} data-start-adornments>\n {hasPrefix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], css['prefix-icon'])}>\n {resolvedIcon?.prefix}\n </div>\n )}\n {hasLeftActions && (\n <div className={css.actions} data-actions data-actions-position=\"left\">\n {leftActions.map(renderAction)}\n </div>\n )}\n </div>\n )}\n <input\n ref={mergedRef}\n type={type}\n disabled={disabled}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-active={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n className={cn(\n 'input',\n css.input,\n className,\n resolved.root\n )}\n style={inputPaddingStyle}\n {...mergeProps(focusProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n ...props,\n })}\n />\n {hasEndAdornment && (\n <div className={css['end-adornments']} data-end-adornments>\n {hasSuffix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], css['suffix-icon'])}>\n {resolvedIcon?.suffix}\n </div>\n )}\n {hasRightActions && (\n <div className={css.actions} data-actions data-actions-position=\"right\">\n {rightActions.map(renderAction)}\n </div>\n )}\n {hasHint && <span data-hint>{hint}</span>}\n {isNumberType && (\n <div\n className={cn(css['number-controls'], disabled && css.disabled)}\n data-disabled={disabled || undefined}\n >\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'])}\n onClick={() => handleSpinClick(\"up\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Increment\"\n >\n <ChevronUp size={12} />\n </button>\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'])}\n onClick={() => handleSpinClick(\"down\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Decrement\"\n >\n <ChevronDown size={12} />\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n }\n);\n\nInput.displayName = \"Input\";\n",
|
|
4218
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n\n flex: 1;\n min-width: 0;\n @apply py-2 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n color: var(--icon-color);\n pointer-events: none;\n }\n\n .prefix-icon {\n @apply relative;\n }\n\n .suffix-icon {\n @apply relative;\n }\n\n .container {\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--input-border-color, var(--border));\n border-radius: var(--input-border-radius, var(--radius-sm));\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-active] {\n border-color: var(--input-active-border-color, var(--ring-color));\n box-shadow: var(--input-active-box-shadow, 0 0 0 1px mix(var(--ring-color) 20%, transparent));\n }\n\n &[data-focus-visible] {\n @apply ring-0;\n border-color: var(--input-active-border-color, var(--ring-color));\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n &[data-error] {\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n\n &[data-variant=\"ghost\"] {\n background-color: transparent;\n border-color: transparent;\n &[data-active], &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n }\n\n .start-adornments,\n .end-adornments {\n @apply flex items-center gap-1;\n flex-shrink: 0;\n pointer-events: none;\n }\n\n .start-adornments {\n @apply pl-2;\n }\n\n .end-adornments {\n @apply pr-2;\n }\n\n .actions {\n @apply flex items-center gap-1;\n pointer-events: auto;\n }\n\n .action {\n @apply flex items-center justify-center p-2;\n border-radius: 0.25rem;\n color: var(--action-color);\n }\n\n .action:hover {\n background-color: var(--background-hover);\n color: var(--action-hover-color);\n }\n\n .number-controls {\n @apply flex w-6 flex-col;\n pointer-events: auto;\n }\n\n .number-controls.disabled {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .spin-button {\n @apply flex w-full flex-1 items-center justify-center p-0 cursor-pointer;\n flex: 1;\n background-color: transparent;\n border: none;\n color: var(--spin-color);\n transition: color 150ms ease-out, background-color 150ms ease-out;\n\n &:hover:not(:disabled) {\n background-color: var(--spin-hover-background);\n color: var(--spin-hover-color);\n }\n\n &:active:not(:disabled) {\n background-color: var(--spin-active-background);\n color: var(--spin-active-color);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n }\n}\n",
|
|
4219
|
+
"cssTypes": "declare const styles: {\n input: string;\n \"icon-wrapper\": string;\n \"prefix-icon\": string;\n \"suffix-icon\": string;\n container: string;\n \"start-adornments\": string;\n \"end-adornments\": string;\n actions: string;\n hint: string;\n action: string;\n \"number-controls\": string;\n disabled: string;\n \"spin-button\": string;\n};\n\nexport default styles;\n"
|
|
4954
4220
|
},
|
|
4955
4221
|
"label": {
|
|
4956
|
-
"tsx": "import { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Label.module.css\";\n\
|
|
4957
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .label {\n display: block;\n font-family: inherit;\n font-size: var(--text-sm);\n color: var(--foreground);\n transition: color 150ms ease;\n\n &[data-size=\"sm\"] { font-size: var(--text-
|
|
4222
|
+
"tsx": "import { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Label.module.css\";\n\ninterface LabelStyleSlots {\n root?: StyleValue;\n requiredIndicator?: StyleValue;\n helperText?: StyleValue;\n}\n\ntype LabelStylesProp = StylesProp<LabelStyleSlots>;\n\nconst resolveLabelBaseStyles = createStylesResolver(['root', 'requiredIndicator', 'helperText'] as const);\n\nexport interface LabelProps\n extends React.LabelHTMLAttributes<HTMLLabelElement> {\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: LabelStylesProp;\n /** Whether to show a required asterisk indicator */\n required?: boolean;\n /** Helper text shown below the label */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Size of the label text */\n size?: \"sm\" | \"md\" | \"lg\" | null;\n /** Whether the label appears disabled */\n disabled?: boolean | null;\n /** Whether to apply error styling */\n error?: boolean | null;\n}\n\nconst Label = ({\n className,\n styles,\n size = \"md\",\n disabled,\n error,\n required,\n helperText,\n helperTextError,\n children,\n ...props\n}: LabelProps) => {\n const resolved = resolveLabelBaseStyles(styles);\n return (\n <div className=\"w-full\">\n <label\n className={cn('label', css.label, className, resolved.root)}\n data-size={size ?? 'md'}\n data-disabled={disabled || undefined}\n data-error={error || undefined}\n {...props}\n >\n {children}\n {required && (\n <span className={cn('label', 'required-indicator', css['required-indicator'], resolved.requiredIndicator)} aria-label=\"required\">\n *\n </span>\n )}\n </label>\n {helperText && (\n <p\n className={cn('label', 'helper-text', css['helper-text'], resolved.helperText)}\n data-error={helperTextError || undefined}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n};\n\nLabel.displayName = \"Label\";\n\nexport { Label };\n",
|
|
4223
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .label {\n display: block;\n font-family: inherit;\n font-size: var(--text-sm);\n color: var(--foreground);\n transition: color 150ms ease;\n\n &[data-size=\"sm\"] { font-size: var(--text-sm); }\n &[data-size=\"lg\"] { font-size: var(--text-md); }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n &[data-error] {\n color: var(--error-foreground);\n }\n }\n\n .required-indicator {\n margin-left: 0.25rem;\n color: var(--required-color);\n }\n\n .helper-text {\n display: block;\n font-size: var(--text-sm);\n margin-top: 0.25rem;\n transition: color 150ms ease;\n color: var(--helper-color);\n\n &[data-error] {\n color: var(--helper-error-color);\n }\n }\n}\n",
|
|
4958
4224
|
"cssTypes": "declare const styles: {\n label: string;\n \"required-indicator\": string;\n \"helper-text\": string;\n};\n\nexport default styles;\n"
|
|
4959
4225
|
},
|
|
4960
4226
|
"list": {
|
|
4961
|
-
"tsx": "'use client';\n\nimport React from 'react';\nimport { cn } from \"./utils\";\nimport { Divider as FoldDivider } from '@/components/Divider';\nimport styles from './List.module.css';\nimport { ListContext } from './list.context';\nimport {\n ListContainerProps,\n ListHeaderProps,\n ListNavigateCallbacks,\n ListRef,\n ActionGroupComponentProps,\n FooterComponentProps,\n} from './list.types';\nimport { DividerProps } from '@/components/Divider';\n\n// Ref container for keyboard navigation\nconst Container = React.forwardRef<ListRef, ListContainerProps>(\n ({ items = [], variant = 'default', spacing = 'default', onNavigate, children, className, ...props }, ref) => {\n const [highlightedIndex, setHighlightedIndex] = React.useState<number | null>(null);\n const [isKeyboardMode, setIsKeyboardMode] = React.useState(false);\n const itemRefsContainer = React.useRef<(HTMLElement | null)[]>([]);\n const itemCountRef = React.useRef(0);\n const prevItemsLengthRef = React.useRef(items.length);\n\n // Reset counter if items length changes significantly\n if (items.length !== prevItemsLengthRef.current) {\n itemCountRef.current = 0;\n itemRefsContainer.current = [];\n prevItemsLengthRef.current = items.length;\n }\n\n // Expose ref methods for keyboard navigation\n React.useImperativeHandle(ref, () => ({\n focusNext: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex((prev) => {\n const next = prev === null ? 0 : Math.min(prev + 1, items.length - 1);\n onNavigate?.down?.();\n return next;\n });\n },\n focusPrev: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex((prev) => {\n const next = prev === null ? items.length - 1 : Math.max(prev - 1, 0);\n onNavigate?.up?.();\n return next;\n });\n },\n focusFirst: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex(0);\n onNavigate?.down?.();\n },\n focusLast: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex(items.length - 1);\n onNavigate?.up?.();\n },\n selectHighlighted: () => {\n onNavigate?.enter?.();\n },\n clearHighlight: () => {\n setHighlightedIndex(null);\n },\n getHighlightedIndex: () => highlightedIndex,\n }), [highlightedIndex, items.length, onNavigate]);\n\n React.useEffect(() => {\n const el = highlightedIndex !== null ? itemRefsContainer.current[highlightedIndex] : null;\n if (!el) return;\n let scroller: HTMLElement | null = el.parentElement;\n while (scroller && scroller !== document.body && scroller.scrollHeight <= scroller.clientHeight) {\n scroller = scroller.parentElement;\n }\n if (!scroller || scroller === document.body) return;\n const scrollerRect = scroller.getBoundingClientRect();\n const itemRect = el.getBoundingClientRect();\n const buffer = el.offsetHeight * 2;\n const itemTop = itemRect.top - scrollerRect.top;\n const itemBottom = itemRect.bottom - scrollerRect.top;\n if (itemTop < buffer) {\n scroller.scrollTo({ top: Math.max(0, scroller.scrollTop + itemTop - buffer), behavior: 'smooth' });\n } else if (itemBottom > scroller.clientHeight - buffer) {\n scroller.scrollTo({ top: scroller.scrollTop + itemBottom - scroller.clientHeight + buffer, behavior: 'smooth' });\n }\n }, [highlightedIndex]);\n\n const registerItem = React.useCallback((ref: HTMLElement | null) => {\n const index = itemCountRef.current;\n itemRefsContainer.current[index] = ref;\n itemCountRef.current++;\n return index;\n }, [items.length]);\n\n const contextValue = React.useMemo(\n () => ({\n highlightedIndex,\n isKeyboardMode,\n focusItem: (index: number) => {\n setIsKeyboardMode(false);\n setHighlightedIndex(index);\n },\n registerItem,\n itemRefs: itemRefsContainer,\n }),\n [highlightedIndex, isKeyboardMode, registerItem]\n );\n\n return (\n <ListContext.Provider value={contextValue}>\n <div\n role=\"list\"\n className={cn('list', styles.container, className)}\n data-variant={variant}\n data-spacing={spacing}\n data-keyboard-mode={isKeyboardMode ? 'true' : undefined}\n {...(props as React.HTMLAttributes<HTMLDivElement>)}\n >\n {children}\n </div>\n </ListContext.Provider>\n );\n }\n);\nContainer.displayName = 'List';\n\n/** Sticky heading row above a section of list items */\nconst Header = React.forwardRef<HTMLElement, ListHeaderProps>(\n ({ sticky, children, className, ...props }, ref) => (\n <header\n ref={ref}\n className={cn(styles.header, sticky && styles.sticky, className)}\n {...props}\n >\n {children}\n </header>\n )\n);\nHeader.displayName = 'List.Header';\n\n/** Row of action buttons aligned to the right of a list item */\nconst ActionGroup = React.forwardRef<HTMLDivElement, ActionGroupComponentProps>(\n ({ justify = 'flex-start', children, className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(styles.actionGroup, className)}\n data-justify={justify}\n {...props}\n >\n {children}\n </div>\n )\n);\nActionGroup.displayName = 'List.ActionGroup';\n\n/** Horizontal separator between list sections */\nconst Divider = React.forwardRef<HTMLDivElement, DividerProps>(\n ({ className, ...props }, ref) => (\n <FoldDivider\n ref={ref}\n className={className}\n {...props}\n />\n )\n);\nDivider.displayName = 'List.Divider';\n\n/** Fixed bottom row beneath the list body */\nconst Footer = React.forwardRef<HTMLElement, FooterComponentProps>(\n ({ align = 'center', children, className, ...props }, ref) => (\n <footer\n ref={ref}\n className={cn(styles.footer, className)}\n data-align={align}\n {...props}\n >\n {children}\n </footer>\n )\n);\nFooter.displayName = 'List.Footer';\n\n// Compound component\nconst List = Object.assign(Container, {\n Header,\n Item: null as any, // Set in index.ts\n Checkbox: null as any,\n CheckboxIndicator: null as any,\n Switch: null as any,\n Input: null as any,\n Select: null as any,\n Media: null as any,\n Desc: null as any,\n ActionGroup,\n Divider,\n Footer,\n});\n\nexport {
|
|
4962
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .container {\n @apply mx-auto;\n max-width: 28rem;\n font-family: var(--font-sans, system-ui, -apple-system, sans-serif);\n color: var(--foreground);\n }\n\n .header {\n @apply flex items-center justify-between;\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n padding-top: 1rem;\n padding-bottom: 1rem;\n backdrop-filter: blur(12px);\n z-index: 10;\n }\n\n .header.sticky { position: sticky; top: 0; }\n\n .container[data-spacing=\"sm\"] .header {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n }\n\n .header > :first-child {\n font-weight: var(--font-weight-semibold);\n font-size: 1.125rem;\n color: var(--header-title-color);\n }\n\n .header > :last-child { color: var(--header-subtitle-color); }\n\n .item {\n @apply flex flex-row items-center gap-3 px-2 py-1 cursor-pointer;\n
|
|
4227
|
+
"tsx": "'use client';\n\nimport React from 'react';\nimport { cn } from \"./utils\";\nimport { Divider as FoldDivider } from '@/components/Divider';\nimport styles from './List.module.css';\nimport { ListContext } from './list.context';\nimport {\n ListContainerProps,\n ListHeaderProps,\n ListNavigateCallbacks,\n ListRef,\n ActionGroupComponentProps,\n FooterComponentProps,\n} from './list.types';\nimport { DividerProps } from '@/components/Divider';\n\n// Ref container for keyboard navigation\nconst Container = React.forwardRef<ListRef, ListContainerProps>(\n ({ items = [], variant = 'default', spacing = 'default', onNavigate, children, className, ...props }, ref) => {\n const [highlightedIndex, setHighlightedIndex] = React.useState<number | null>(null);\n const [isKeyboardMode, setIsKeyboardMode] = React.useState(false);\n const itemRefsContainer = React.useRef<(HTMLElement | null)[]>([]);\n const itemCountRef = React.useRef(0);\n const prevItemsLengthRef = React.useRef(items.length);\n\n // Reset counter if items length changes significantly\n if (items.length !== prevItemsLengthRef.current) {\n itemCountRef.current = 0;\n itemRefsContainer.current = [];\n prevItemsLengthRef.current = items.length;\n }\n\n // Expose ref methods for keyboard navigation\n React.useImperativeHandle(ref, () => ({\n focusNext: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex((prev) => {\n const next = prev === null ? 0 : Math.min(prev + 1, items.length - 1);\n onNavigate?.down?.();\n return next;\n });\n },\n focusPrev: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex((prev) => {\n const next = prev === null ? items.length - 1 : Math.max(prev - 1, 0);\n onNavigate?.up?.();\n return next;\n });\n },\n focusFirst: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex(0);\n onNavigate?.down?.();\n },\n focusLast: () => {\n setIsKeyboardMode(true);\n setHighlightedIndex(items.length - 1);\n onNavigate?.up?.();\n },\n selectHighlighted: () => {\n onNavigate?.enter?.();\n },\n clearHighlight: () => {\n setHighlightedIndex(null);\n },\n getHighlightedIndex: () => highlightedIndex,\n }), [highlightedIndex, items.length, onNavigate]);\n\n React.useEffect(() => {\n const el = highlightedIndex !== null ? itemRefsContainer.current[highlightedIndex] : null;\n if (!el) return;\n let scroller: HTMLElement | null = el.parentElement;\n while (scroller && scroller !== document.body && scroller.scrollHeight <= scroller.clientHeight) {\n scroller = scroller.parentElement;\n }\n if (!scroller || scroller === document.body) return;\n const scrollerRect = scroller.getBoundingClientRect();\n const itemRect = el.getBoundingClientRect();\n const buffer = el.offsetHeight * 2;\n const itemTop = itemRect.top - scrollerRect.top;\n const itemBottom = itemRect.bottom - scrollerRect.top;\n if (itemTop < buffer) {\n scroller.scrollTo({ top: Math.max(0, scroller.scrollTop + itemTop - buffer), behavior: 'smooth' });\n } else if (itemBottom > scroller.clientHeight - buffer) {\n scroller.scrollTo({ top: scroller.scrollTop + itemBottom - scroller.clientHeight + buffer, behavior: 'smooth' });\n }\n }, [highlightedIndex]);\n\n const registerItem = React.useCallback((ref: HTMLElement | null) => {\n const index = itemCountRef.current;\n itemRefsContainer.current[index] = ref;\n itemCountRef.current++;\n return index;\n }, [items.length]);\n\n const contextValue = React.useMemo(\n () => ({\n highlightedIndex,\n isKeyboardMode,\n focusItem: (index: number) => {\n setIsKeyboardMode(false);\n setHighlightedIndex(index);\n },\n registerItem,\n itemRefs: itemRefsContainer,\n }),\n [highlightedIndex, isKeyboardMode, registerItem]\n );\n\n return (\n <ListContext.Provider value={contextValue}>\n <div\n role=\"list\"\n className={cn('list', styles.container, className)}\n data-variant={variant}\n data-spacing={spacing}\n data-keyboard-mode={isKeyboardMode ? 'true' : undefined}\n {...(props as React.HTMLAttributes<HTMLDivElement>)}\n >\n {children}\n </div>\n </ListContext.Provider>\n );\n }\n);\nContainer.displayName = 'List';\n\n/** Sticky heading row above a section of list items */\nconst Header = React.forwardRef<HTMLElement, ListHeaderProps>(\n ({ sticky, children, className, ...props }, ref) => (\n <header\n ref={ref}\n className={cn(styles.header, sticky && styles.sticky, className)}\n {...props}\n >\n {children}\n </header>\n )\n);\nHeader.displayName = 'List.Header';\n\n/** Row of action buttons aligned to the right of a list item */\nconst ActionGroup = React.forwardRef<HTMLDivElement, ActionGroupComponentProps>(\n ({ justify = 'flex-start', children, className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(styles.actionGroup, className)}\n data-justify={justify}\n {...props}\n >\n {children}\n </div>\n )\n);\nActionGroup.displayName = 'List.ActionGroup';\n\n/** Horizontal separator between list sections */\nconst Divider = React.forwardRef<HTMLDivElement, DividerProps>(\n ({ className, ...props }, ref) => (\n <FoldDivider\n ref={ref}\n className={className}\n {...props}\n />\n )\n);\nDivider.displayName = 'List.Divider';\n\n/** Fixed bottom row beneath the list body */\nconst Footer = React.forwardRef<HTMLElement, FooterComponentProps>(\n ({ align = 'center', children, className, ...props }, ref) => (\n <footer\n ref={ref}\n className={cn(styles.footer, className)}\n data-align={align}\n {...props}\n >\n {children}\n </footer>\n )\n);\nFooter.displayName = 'List.Footer';\n\n// Compound component\nconst List = Object.assign(Container, {\n Header,\n Item: null as any, // Set in index.ts\n Checkbox: null as any,\n CheckboxIndicator: null as any,\n Switch: null as any,\n Input: null as any,\n Select: null as any,\n Media: null as any,\n Desc: null as any,\n ActionGroup,\n Divider,\n Footer,\n});\n\nexport { Container, Header, ActionGroup, Divider, Footer };\n",
|
|
4228
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .container {\n @apply mx-auto;\n max-width: 28rem;\n font-family: var(--font-sans, system-ui, -apple-system, sans-serif);\n color: var(--foreground);\n }\n\n .header {\n @apply flex items-center justify-between;\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n padding-top: 1rem;\n padding-bottom: 1rem;\n backdrop-filter: blur(12px);\n z-index: 10;\n }\n\n .header.sticky { position: sticky; top: 0; }\n\n .container[data-spacing=\"sm\"] .header {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n }\n\n .header > :first-child {\n font-weight: var(--font-weight-semibold);\n font-size: 1.125rem;\n color: var(--header-title-color);\n }\n\n .header > :last-child { color: var(--header-subtitle-color); }\n\n .item {\n @apply flex flex-row items-center gap-3 px-2 py-1 cursor-pointer;\n }\n\n .container .item:hover {\n background-color: var(--background-hover);\n }\n\n .container[data-keyboard-mode=\"true\"] .item[data-highlighted=\"true\"] {\n background-color: var(--background-highlighted);\n }\n\n .container[data-spacing=\"sm\"] .item {\n padding: 0.5rem 0.75rem;\n gap: 0.375rem;\n }\n\n .checkbox,\n .control,\n .media {\n @apply flex items-center justify-center flex-shrink-0;\n }\n\n .control { margin-left: auto; }\n\n .media {\n @apply h-8 w-8;\n }\n\n .desc {\n font-size: var(--text-sm);\n color: var(--desc-color);\n @apply truncate;\n }\n\n .action-group {\n @apply flex items-center;\n padding-left: 0.25rem;\n padding-right: 0.25rem;\n }\n\n .action-group[data-justify=\"space-between\"] { justify-content: space-between; }\n .action-group[data-justify=\"flex-start\"] { justify-content: flex-start; }\n .action-group[data-justify=\"flex-end\"] { justify-content: flex-end; }\n\n .actions {\n align-items: center;\n gap: 0.25rem;\n margin-left: auto;\n flex-shrink: 0;\n @apply p-1.5 hidden group-hover:flex;\n }\n\n .action {\n @apply flex items-center justify-center;\n border-radius: 0.25rem;\n color: var(--action-color);\n @apply p-2;\n }\n\n .action:hover {\n background-color: var(--background-hover);\n color: var(--action-hover-color);\n }\n\n .footer {\n @apply flex p-6 pb-12;\n }\n\n .footer[data-align=\"center\"] { justify-content: center; }\n .footer[data-align=\"flex-start\"] { justify-content: flex-start; }\n .footer[data-align=\"flex-end\"] { justify-content: flex-end; }\n\n .container[data-spacing=\"sm\"] .footer {\n padding: 0.375rem 0.75rem;\n padding-bottom: 0.375rem;\n }\n}\n",
|
|
4963
4229
|
"cssTypes": "declare const styles: {\n readonly container: string;\n readonly header: string;\n readonly sticky: string;\n readonly item: string;\n readonly actionGroup: string;\n readonly actions: string;\n readonly action: string;\n readonly checkbox: string;\n readonly control: string;\n readonly media: string;\n readonly desc: string;\n readonly footer: string;\n};\n\nexport default styles;\n"
|
|
4964
4230
|
},
|
|
4965
4231
|
"mask": {
|
|
4966
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"./utils\";\nimport styles from \"./Mask.module.css\";\n\ninterface MaskContextValue {\n maskFilters: string[];\n clipPath?: string;\n}\n\nconst MaskContext = React.createContext<MaskContextValue | undefined>(undefined);\n\nconst useMaskContext = () => {\n const context = React.useContext(MaskContext);\n if (!context) {\n throw new Error(\"Mask sub-components must be used within a Mask component\");\n }\n return context;\n};\n\nexport interface MaskProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nconst MaskRoot = React.forwardRef<HTMLDivElement, MaskProps>(\n ({ className, children, style, ...props }, ref) => {\n const childArray = React.Children.toArray(children);\n const maskFilters: string[] = [];\n let clipPath: string | undefined;\n let hasFixedFade = false;\n let contentChildren: React.ReactNode[] = [];\n\n childArray.forEach((child) => {\n if (React.isValidElement(child)) {\n if (child.type === MaskFade) {\n const fadeChild = child as React.ReactElement<MaskFadeProps>;\n if (fadeChild.props.fixed) hasFixedFade = true;\n maskFilters.push(generateFadeMask(fadeChild.props.direction, fadeChild.props.intensity, fadeChild.props.fixed));\n } else if (child.type === MaskClip) {\n const clipChild = child as React.ReactElement<MaskClipProps>;\n clipPath = clipChild.props.shape;\n } else {\n contentChildren.push(child);\n }\n } else {\n contentChildren.push(child);\n }\n });\n\n const contextValue: MaskContextValue = { maskFilters, clipPath };\n\n const maskStyles = {\n ...style,\n ...(hasFixedFade ? { maxHeight: \"inherit\", overflow: \"hidden\" as const } : {}),\n ...(clipPath ? { \"--mask-clip-path\": clipPath } as Record<string, string> : {}),\n ...(maskFilters.length > 0 ? {\n WebkitMaskImage: maskFilters.join(\", \"),\n maskImage: maskFilters.join(\", \"),\n WebkitMaskComposite: maskFilters.length > 1 ? \"source-in\" : \"source-over\",\n maskComposite: maskFilters.length > 1 ? \"intersect\" : \"add\",\n } : {}),\n } as React.CSSProperties;\n\n return (\n <MaskContext.Provider value={contextValue}>\n <div\n {...props}\n ref={ref}\n className={cn(\"mask\", styles.mask, className)}\n style={maskStyles}\n >\n {contentChildren}\n </div>\n </MaskContext.Provider>\n );\n }\n);\n\nMaskRoot.displayName = \"Mask\";\n\
|
|
4232
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"./utils\";\nimport styles from \"./Mask.module.css\";\n\ninterface MaskContextValue {\n maskFilters: string[];\n clipPath?: string;\n}\n\nconst MaskContext = React.createContext<MaskContextValue | undefined>(undefined);\n\nconst useMaskContext = () => {\n const context = React.useContext(MaskContext);\n if (!context) {\n throw new Error(\"Mask sub-components must be used within a Mask component\");\n }\n return context;\n};\n\nexport interface MaskProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nconst MaskRoot = React.forwardRef<HTMLDivElement, MaskProps>(\n ({ className, children, style, ...props }, ref) => {\n const childArray = React.Children.toArray(children);\n const maskFilters: string[] = [];\n let clipPath: string | undefined;\n let hasFixedFade = false;\n let contentChildren: React.ReactNode[] = [];\n\n childArray.forEach((child) => {\n if (React.isValidElement(child)) {\n if (child.type === MaskFade) {\n const fadeChild = child as React.ReactElement<MaskFadeProps>;\n if (fadeChild.props.fixed) hasFixedFade = true;\n maskFilters.push(generateFadeMask(fadeChild.props.direction, fadeChild.props.intensity, fadeChild.props.fixed));\n } else if (child.type === MaskClip) {\n const clipChild = child as React.ReactElement<MaskClipProps>;\n clipPath = clipChild.props.shape;\n } else {\n contentChildren.push(child);\n }\n } else {\n contentChildren.push(child);\n }\n });\n\n const contextValue: MaskContextValue = { maskFilters, clipPath };\n\n const maskStyles = {\n ...style,\n ...(hasFixedFade ? { maxHeight: \"inherit\", overflow: \"hidden\" as const } : {}),\n ...(clipPath ? { \"--mask-clip-path\": clipPath } as Record<string, string> : {}),\n ...(maskFilters.length > 0 ? {\n WebkitMaskImage: maskFilters.join(\", \"),\n maskImage: maskFilters.join(\", \"),\n WebkitMaskComposite: maskFilters.length > 1 ? \"source-in\" : \"source-over\",\n maskComposite: maskFilters.length > 1 ? \"intersect\" : \"add\",\n } : {}),\n } as React.CSSProperties;\n\n return (\n <MaskContext.Provider value={contextValue}>\n <div\n {...props}\n ref={ref}\n className={cn(\"mask\", styles.mask, className)}\n style={maskStyles}\n >\n {contentChildren}\n </div>\n </MaskContext.Provider>\n );\n }\n);\n\nMaskRoot.displayName = \"Mask\";\n\ninterface MaskGradientProps extends React.HTMLAttributes<HTMLDivElement> {\n /** CSS gradient string applied as the mask image */\n gradient: string;\n}\n\nconst MaskGradient = React.forwardRef<HTMLDivElement, MaskGradientProps>(\n ({ className, gradient, style, children, ...props }, ref) => {\n const maskStyles = {\n ...style,\n \"--mask-gradient\": gradient,\n } as React.CSSProperties;\n\n return (\n <div\n {...props}\n ref={ref}\n className={cn(styles.mask, styles[\"mask-gradient\"], className)}\n style={maskStyles}\n >\n {children}\n </div>\n );\n }\n);\n\nMaskGradient.displayName = \"MaskGradient\";\n\ninterface MaskFadeProps {\n /** Edge of the container where the fade starts */\n direction?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /** Controls the size of the fade — higher values produce a longer fade */\n intensity?: number;\n /** Uses percentage-based fade size instead of pixel-based, for fixed-height containers */\n fixed?: boolean;\n}\n\nconst MaskFade: React.FC<MaskFadeProps> = () => null;\nMaskFade.displayName = \"MaskFade\";\n\nfunction generateFadeMask(direction: string = \"bottom\", intensity: number = 1, fixed?: boolean): string {\n const fadeSize = fixed ? `${Math.min(50, 15 * intensity)}%` : `${Math.min(200, 40 * intensity)}px`;\n const directionMap = {\n top: `linear-gradient(to bottom, transparent 0, black ${fadeSize})`,\n bottom: `linear-gradient(to bottom, black calc(100% - ${fadeSize}), transparent 100%)`,\n left: `linear-gradient(to right, transparent 0, black ${fadeSize})`,\n right: `linear-gradient(to right, black calc(100% - ${fadeSize}), transparent 100%)`,\n };\n return directionMap[direction as keyof typeof directionMap] || directionMap.bottom;\n}\n\n\ninterface MaskClipProps {\n /** CSS clip-path value applied to the container (e.g. polygon, circle) */\n shape: string;\n}\n\nconst MaskClip: React.FC<MaskClipProps> = () => null;\nMaskClip.displayName = \"MaskClip\";\n\nconst Mask = Object.assign(MaskRoot, {\n Gradient: MaskGradient,\n Fade: MaskFade,\n Clip: MaskClip,\n});\n\nexport { Mask };\n",
|
|
4967
4233
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .mask {\n @apply relative h-full w-full;\n }\n}\n\n.mask[style*=\"mask-image\"],\n.mask[style*=\"-webkit-mask-image\"] {\n -webkit-mask-size: 100% 100%;\n mask-size: 100% 100%;\n}\n\n.mask[style*=\"--mask-clip-path\"] {\n clip-path: var(--mask-clip-path);\n}\n\n.mask-gradient {\n background: var(--mask-gradient);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n color: transparent;\n}\n",
|
|
4968
4234
|
"cssTypes": "declare const styles: {\n mask: string;\n \"mask-gradient\": string;\n};\n\nexport default styles;\n"
|
|
4969
4235
|
},
|
|
4970
4236
|
"menu": {
|
|
4971
|
-
"tsx": "import * as React from \"react\"\nimport type { Key } from \"react-aria\"\nimport { useListNavigation } from \"../../utils/list-navigation\"\nimport type {\n MenuContextValue,\n MenuSubmenuContextValue,\n RadioGroupContextValue,\n MenuProps,\n MenuPortalProps,\n MenuItemExtras,\n} from \"./menu.types\"\n\
|
|
4972
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .content,\n .sub-content {\n --padding: calc(var(--spacing) * 1.5);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n --menu-animation: none;\n --disabled-opacity: 0.5;\n }\n\n .trigger {\n &[data-type=\"pop-over\"][data-active] {\n opacity: 1
|
|
4237
|
+
"tsx": "import * as React from \"react\"\nimport type { Key } from \"react-aria\"\nimport { useListNavigation } from \"../../utils/list-navigation\"\nimport type {\n MenuContextValue,\n MenuSubmenuContextValue,\n RadioGroupContextValue,\n MenuProps,\n MenuPortalProps,\n MenuItemExtras,\n} from \"./menu.types\"\n\nconst MenuContext = React.createContext<MenuContextValue | null>(null)\n\nexport function useMenuContext() {\n const context = React.useContext(MenuContext)\n if (!context) {\n throw new Error(\"Menu component must be used within Menu root\")\n }\n return context\n}\n\nexport const MenuSubmenuContext = React.createContext<MenuSubmenuContextValue | null>(null)\n\nexport function useMenuSubmenuContext() {\n return React.useContext(MenuSubmenuContext)\n}\n\nexport const RadioGroupContext = React.createContext<RadioGroupContextValue | null>(null)\n\nexport function useRadioGroupContext() {\n return React.useContext(RadioGroupContext)\n}\n\nconst MenuPortal = ({ children }: MenuPortalProps) => {\n return <>{children}</>\n}\nMenuPortal.displayName = \"MenuPortal\"\nconst Menu = ({\n children,\n type = \"context-menu\",\n selectionMode = \"none\",\n selectedKeys: controlledSelectedKeys,\n defaultSelectedKeys,\n onSelectionChange,\n}: MenuProps) => {\n const [isOpen, setIsOpen] = React.useState(false)\n const [uncontrolledSelectedKeys, setUncontrolledSelectedKeys] = React.useState<Set<Key>>(\n defaultSelectedKeys ?? new Set()\n )\n const [radioGroups, setRadioGroups] = React.useState<Map<string, Key | null>>(new Map())\n const [activeSubmenuKey, setActiveSubmenuKey] = React.useState<Key | null>(null)\n\n const selectedKeys = controlledSelectedKeys !== undefined ? controlledSelectedKeys : uncontrolledSelectedKeys\n\n const nav = useListNavigation({ isOpen })\n const itemExtrasRef = React.useRef<Map<Key, MenuItemExtras>>(new Map())\n const mouseMoveDetectedRef = React.useRef(true)\n const clickPositionRef = React.useRef({ x: 0, y: 0 })\n const triggerRef = React.useRef<HTMLDivElement | null>(null)\n\n const registerItem = React.useCallback((key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => {\n nav.registerItem(key, textValue, isDisabled)\n itemExtrasRef.current.set(key, { onSelect, isSubmenuTrigger })\n }, [nav.registerItem])\n\n const unregisterItem = React.useCallback((key: Key) => {\n nav.unregisterItem(key)\n itemExtrasRef.current.delete(key)\n }, [nav.unregisterItem])\n\n const handleSelectionChange = React.useCallback((keys: Set<Key>) => {\n if (controlledSelectedKeys === undefined) {\n setUncontrolledSelectedKeys(keys)\n }\n onSelectionChange?.(keys)\n }, [controlledSelectedKeys, onSelectionChange])\n\n const toggleSelection = React.useCallback((key: Key) => {\n const newKeys = new Set(selectedKeys)\n if (selectionMode === \"single\") {\n newKeys.clear()\n newKeys.add(key)\n } else if (selectionMode === \"multiple\") {\n if (newKeys.has(key)) {\n newKeys.delete(key)\n } else {\n newKeys.add(key)\n }\n }\n handleSelectionChange(newKeys)\n }, [selectedKeys, selectionMode, handleSelectionChange])\n\n const close = React.useCallback(() => {\n setIsOpen(false)\n nav.setFocusedKey(null)\n }, [nav.setFocusedKey])\n\n const selectFocusedItem = React.useCallback(() => {\n if (nav.focusedKey === null) return\n const item = nav.items.find(i => i.key === nav.focusedKey)\n if (item?.isDisabled) return\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n extras?.onSelect?.()\n }, [nav.focusedKey, nav.items])\n\n const isFocusedItemSubmenu = React.useCallback(() => {\n if (nav.focusedKey === null) return false\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n return extras?.isSubmenuTrigger ?? false\n }, [nav.focusedKey])\n\n const setRadioGroupValue = React.useCallback((groupName: string, value: Key | null) => {\n setRadioGroups(prev => {\n const next = new Map(prev)\n next.set(groupName, value)\n return next\n })\n }, [])\n\n const getRadioGroupValue = React.useCallback((groupName: string) => {\n return radioGroups.get(groupName) ?? null\n }, [radioGroups])\n\n React.useEffect(() => {\n if (isOpen && nav.focusedKey === null && nav.enabledFilteredItems.length > 0) {\n nav.setFocusedKey(nav.enabledFilteredItems[0].key)\n }\n }, [isOpen, nav.enabledFilteredItems, nav.focusedKey, nav.setFocusedKey])\n\n const contextValue = React.useMemo(() => ({\n isOpen,\n setIsOpen,\n type,\n close,\n selectionMode,\n selectedKeys,\n onSelectionChange: handleSelectionChange,\n toggleSelection,\n items: nav.items,\n registerItem,\n unregisterItem,\n focusedKey: nav.focusedKey,\n setFocusedKey: nav.setFocusedKey,\n navigateToNextItem: nav.navigateToNextItem,\n navigateToPrevItem: nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n radioGroups,\n setRadioGroupValue,\n getRadioGroupValue,\n triggerRef,\n mouseMoveDetectedRef,\n clickPositionRef,\n activeSubmenuKey,\n setActiveSubmenuKey,\n } satisfies MenuContextValue), [\n isOpen,\n setIsOpen,\n type,\n close,\n selectionMode,\n selectedKeys,\n handleSelectionChange,\n toggleSelection,\n nav.items,\n registerItem,\n unregisterItem,\n nav.focusedKey,\n nav.setFocusedKey,\n nav.navigateToNextItem,\n nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n radioGroups,\n setRadioGroupValue,\n getRadioGroupValue,\n activeSubmenuKey,\n setActiveSubmenuKey,\n ])\n\n return (\n <MenuContext.Provider value={contextValue}>\n {children}\n </MenuContext.Provider>\n )\n}\nMenu.displayName = \"Menu\"\n\nexport { Menu, MenuPortal }\nexport type {\n MenuProps,\n MenuTriggerProps,\n MenuPortalProps,\n MenuContentProps,\n MenuGroupProps,\n MenuItemProps,\n MenuCheckboxItemProps,\n MenuRadioGroupProps,\n MenuRadioItemProps,\n MenuLabelProps,\n MenuSeparatorProps,\n MenuShortcutProps,\n MenuSubProps,\n MenuSubTriggerProps,\n MenuSubContentProps,\n} from \"./menu.types\"\n",
|
|
4238
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .content,\n .sub-content {\n --padding: calc(var(--spacing) * 1.5);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n --menu-animation: none;\n --disabled-opacity: 0.5;\n }\n\n .trigger {\n &[data-type=\"pop-over\"][data-active] {\n opacity: 1;\n background-color: var(--trigger-active-background);\n border-radius: var(--radius-sm, 0.375rem);\n }\n }\n\n .content {\n @apply absolute min-w-40 max-w-80 overflow-hidden;\n z-index: 50000;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n\n &[data-state=\"open\"] {\n animation: var(--menu-animation, slide-in-from-top 0.15s var(--ease-snappy-pop));\n }\n\n &[data-state=\"closed\"] {\n animation: var(--menu-animation, slide-out-to-top 0.15s var(--ease-snappy-pop));\n }\n }\n\n .list {\n @apply space-y-1;\n max-height: 24rem;\n overflow-y: auto;\n }\n\n .item,\n .checkbox-item,\n .radio-item {\n @apply flex min-w-0 items-center gap-2;\n padding: var(--padding);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n cursor: default;\n user-select: none;\n outline: none;\n color: var(--item-foreground);\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .item,\n .sub-trigger {\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .item-indicator {\n @apply ml-auto flex h-4 w-4 shrink-0 items-center justify-center;\n color: var(--item-indicator-color);\n }\n\n .sub-trigger {\n @apply flex min-w-0 items-center gap-2;\n padding: var(--padding);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n cursor: default;\n user-select: none;\n outline: none;\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-state=\"open\"]:not([data-highlighted]) {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply ml-auto h-4 w-4 shrink-0;\n }\n\n .sub-content {\n @apply absolute min-w-40 max-w-80 overflow-hidden;\n z-index: 50000;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n\n &[data-state=\"open\"] {\n animation: var(--menu-animation, slide-in-from-top 0.15s var(--ease-snappy-pop));\n }\n\n &[data-state=\"closed\"] {\n animation: var(--menu-animation, slide-out-to-top 0.15s var(--ease-snappy-pop));\n }\n }\n\n .label {\n padding: var(--padding);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--label-foreground);\n\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .separator {\n @apply -mx-1 my-1 h-px;\n background-color: var(--separator-background);\n }\n\n .shortcut {\n margin-left: auto;\n font-size: var(--text-sm);\n letter-spacing: 0.1em;\n color: var(--shortcut-foreground);\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-to-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n}\n",
|
|
4973
4239
|
"cssTypes": "declare const styles: {\n trigger: string\n content: string\n list: string\n item: string\n 'checkbox-item': string\n 'radio-item': string\n 'item-indicator': string\n 'sub-trigger': string\n 'sub-trigger-chevron': string\n 'sub-content': string\n label: string\n separator: string\n shortcut: string\n}\n\nexport default styles\n"
|
|
4974
4240
|
},
|
|
4975
4241
|
"modal": {
|
|
4976
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useDialog } from \"react-aria\";\nimport { useOverlayTriggerState } from \"react-stately\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { X } from \"lucide-react\";\nimport css from \"./Modal.module.css\";\n\nconst useModalKeyboard = (\n ref: React.RefObject<HTMLDivElement | null>,\n isOpen: boolean,\n isDismissable: boolean,\n isKeyboardDismissDisabled: boolean,\n onClose: () => void\n) => {\n React.useEffect(() => {\n if (!isOpen || !ref.current) return;\n\n ref.current.focus();\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && isDismissable && !isKeyboardDismissDisabled) {\n e.preventDefault();\n
|
|
4977
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .overlay {\n --disabled-opacity: 0.5;\n }\n\n .backdrop {\n @apply absolute inset-0 cursor-pointer;\n background-color: var(--backdrop-background);\n backdrop-filter: blur(4px);\n }\n\n .modal {\n @apply relative flex w-full flex-col overflow-hidden;\n z-index: 1;\n max-height: 90vh;\n margin: 1rem;\n background-color: var(--modal-background);\n border: var(--border-width-base) solid var(--modal-border);\n border-radius: var(--radius-sm);\n pointer-events: auto;\n overflow: hidden;\n }\n\n .header {\n @apply flex shrink-0 items-center justify-between gap-2 px-6 py-4;\n border-bottom: var(--border-width-base) solid var(--modal-border);\n }\n\n .title {\n @apply m-0;\n font-size: 1.125rem;\n font-weight: var(--font-weight-semibold);\n color: var(--modal-title-color);\n }\n\n .spacer {\n flex: 1;\n }\n\n .close {\n @apply ml-auto flex items-center justify-center cursor-pointer;\n background: none;\n border: none;\n color: var(--modal-close-color);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .close:hover {\n color: var(--modal-close-hover);\n }\n\n .close:active {\n transform: scale(0.92);\n }\n\n .close:focus {\n outline: 2px solid var(--modal-close-hover);\n outline-offset: 2px;\n border-radius: 0.25rem;\n }\n\n .closeIcon {\n @apply h-5 w-5;\n }\n\n .content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n color: var(--modal-text-color);\n }\n\n .content::-webkit-scrollbar {\n width: 6px;\n }\n\n .content::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .content::-webkit-scrollbar-thumb {\n background: var(--modal-border);\n border-radius: 3px;\n transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .content::-webkit-scrollbar-thumb:hover {\n background: var(--modal-close-color);\n }\n\n .footer {\n @apply flex shrink-0 items-center justify-between gap-4 px-6 py-4;\n background-color: var(--footer-background);\n border-top: var(--border-width-base) solid var(--modal-border);\n }\n\n /* Size variants */\n .size-fit {\n width: fit-content;\n }\n\n .size-auto {\n max-width: min(90vw, 28rem);\n }\n\n /* Media queries for smaller screens */\n @media (max-width: 640px) {\n .modal {\n margin: 1rem;\n }\n\n .content {\n max-height: calc(100vh - 10rem);\n }\n }\n}\n",
|
|
4242
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useDialog } from \"react-aria\";\nimport { useOverlayTriggerState } from \"react-stately\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { X } from \"lucide-react\";\nimport css from \"./Modal.module.css\";\n\nconst useModalKeyboard = (\n ref: React.RefObject<HTMLDivElement | null>,\n isOpen: boolean,\n isDismissable: boolean,\n isKeyboardDismissDisabled: boolean,\n onClose: () => void\n) => {\n const onCloseRef = React.useRef(onClose);\n onCloseRef.current = onClose;\n\n React.useEffect(() => {\n if (!isOpen || !ref.current) return;\n\n ref.current.focus();\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && isDismissable && !isKeyboardDismissDisabled) {\n e.preventDefault();\n onCloseRef.current();\n }\n };\n\n ref.current.addEventListener(\"keydown\", handleKeyDown);\n return () => ref.current?.removeEventListener(\"keydown\", handleKeyDown);\n }, [isOpen, isDismissable, isKeyboardDismissDisabled]);\n};\n\ninterface ModalStyleSlots {\n root?: StyleValue;\n overlay?: StyleValue;\n backdrop?: StyleValue;\n header?: StyleValue;\n title?: StyleValue;\n spacer?: StyleValue;\n close?: StyleValue;\n closeIcon?: StyleValue;\n content?: StyleValue;\n footer?: StyleValue;\n}\n\ntype ModalStylesProp = StylesProp<ModalStyleSlots>;\n\nconst resolveModalBaseStyles = createStylesResolver([\n 'root', 'overlay', 'backdrop', 'header', 'title', 'spacer', 'close', 'closeIcon', 'content', 'footer'\n] as const);\n\nexport interface ModalProps {\n /** Whether the modal is open */\n isOpen?: boolean;\n /** Callback when the open state changes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Optional title rendered in the modal header bar */\n title?: React.ReactNode;\n /** Modal body content */\n children: React.ReactNode;\n /** Optional footer content rendered below the body */\n footer?: React.ReactNode;\n /** Whether to show the X close button in the header */\n close?: boolean;\n /** Controls modal width: \"fit\" adapts to content, \"auto\" uses default width */\n size?: \"fit\" | \"auto\";\n /** Whether clicking the backdrop dismisses the modal */\n isDismissable?: boolean;\n /** Prevents the Escape key from dismissing the modal */\n isKeyboardDismissDisabled?: boolean;\n /** Additional class for the modal panel */\n className?: string;\n /** Additional class for the inner content area */\n contentClassName?: string;\n /** Additional class for the backdrop overlay */\n overlayClassName?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ModalStylesProp;\n}\n\nconst sizeClasses: Record<string, string> = {\n fit: (css as any)[\"size-fit\"],\n auto: (css as any)[\"size-auto\"],\n};\n\n/**\n * Modal component that displays content in a centered dialog with a backdrop overlay.\n * Built with React Aria for full accessibility support including focus management,\n * keyboard handling, and screen reader announcements.\n */\nconst ModalBase = React.forwardRef<HTMLDivElement, ModalProps>(\n (\n {\n isOpen: controlledIsOpen,\n onOpenChange,\n title,\n children,\n footer,\n close = true,\n size = \"auto\",\n isDismissable = true,\n isKeyboardDismissDisabled = false,\n className,\n contentClassName,\n overlayClassName,\n styles,\n },\n ref\n ) => {\n const modalRef = React.useRef<HTMLDivElement>(null);\n const [mounted, setMounted] = React.useState(false);\n\n const resolved = resolveModalBaseStyles(styles);\n\n // Use uncontrolled state management via useOverlayTriggerState\n const state = useOverlayTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange: onOpenChange,\n });\n\n // Handle mount to prevent hydration issues\n React.useEffect(() => {\n setMounted(true);\n }, []);\n\n // Use forwardRef callback to expose modalRef\n React.useImperativeHandle(ref, () => modalRef.current as HTMLDivElement);\n\n // Handle keyboard events and auto-focus\n useModalKeyboard(\n modalRef,\n state.isOpen,\n isDismissable,\n isKeyboardDismissDisabled,\n () => state.close()\n );\n\n // useDialog hook provides accessibility attributes and title handling\n const { dialogProps, titleProps } = useDialog({}, modalRef);\n\n if (!mounted || !state.isOpen) return null;\n\n const handleClose = () => state.close();\n\n return createPortal(\n <div\n className={cn(\n \"modal\",\n \"fixed inset-0 z-9999 flex items-center justify-center\",\n css.overlay,\n overlayClassName,\n resolved.overlay\n )}\n >\n {/* Backdrop overlay - click outside to dismiss */}\n <div\n className={cn(\"backdrop\", css.backdrop, resolved.backdrop)}\n onMouseDown={() => { if (isDismissable) state.close(); }}\n />\n\n {/* Modal content */}\n <div\n {...asElementProps<\"div\">(dialogProps)}\n aria-modal=\"true\"\n ref={modalRef}\n className={cn(\n css.modal,\n sizeClasses[size],\n className,\n resolved.root\n )}\n onClick={(e) => e.stopPropagation()}\n tabIndex={-1}\n data-open={state.isOpen || undefined}\n >\n {/* Header */}\n {(title || close) && (\n <div className={cn(css.header, resolved.header)}>\n {title && (\n <h4 {...asElementProps<\"h4\">(titleProps)} className={cn(css.title, resolved.title)}>\n {title}\n </h4>\n )}\n {!title && close && <div className={cn(css.spacer, resolved.spacer)} />}\n {close && (\n <button\n onClick={handleClose}\n className={cn(css.close, resolved.close)}\n aria-label=\"Close modal\"\n >\n <X className={cn(css.closeIcon, resolved.closeIcon)} />\n </button>\n )}\n </div>\n )}\n\n {/* Body */}\n <div className={cn(css.content, contentClassName, resolved.content)}>\n {children}\n </div>\n\n {/* Footer */}\n {footer && (\n <div className={cn(css.footer, resolved.footer)}>\n {footer}\n </div>\n )}\n </div>\n </div>,\n document.body\n );\n }\n);\n\nModalBase.displayName = \"Modal\";\n\n/**\n * ModalHeader component for use with compound Modal pattern\n */\nconst ModalHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode }\n>(({ children, ...props }, ref) => (\n <div ref={ref} className={css.header} {...props}>\n {children}\n </div>\n));\n\nModalHeader.displayName = \"Modal.Header\";\n\n/**\n * ModalBody component for use with compound Modal pattern\n */\nconst ModalBody = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode }\n>(({ children, ...props }, ref) => (\n <div ref={ref} className={css.content} {...props}>\n {children}\n </div>\n));\n\nModalBody.displayName = \"Modal.Body\";\n\n/**\n * ModalFooter component for use with compound Modal pattern\n */\nconst ModalFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode }\n>(({ children, ...props }, ref) => (\n <div ref={ref} className={cn('footer', css.footer)} {...props}>\n {children}\n </div>\n));\n\nModalFooter.displayName = \"Modal.Footer\";\n\nconst Modal = Object.assign(ModalBase, {\n Header: ModalHeader,\n Body: ModalBody,\n Footer: ModalFooter,\n});\n\nexport { Modal, ModalHeader, ModalBody, ModalFooter };\n",
|
|
4243
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .overlay {\n --disabled-opacity: 0.5;\n }\n\n .backdrop {\n @apply absolute inset-0 cursor-pointer;\n background-color: var(--backdrop-background);\n backdrop-filter: blur(4px);\n }\n\n .modal:focus-visible {\n outline: 2px solid var(--modal-focus-ring);\n outline-offset: 2px;\n }\n\n .modal {\n @apply relative flex w-full flex-col overflow-hidden;\n z-index: 1;\n max-height: 90vh;\n margin: 1rem;\n background-color: var(--modal-background);\n border: var(--border-width-base) solid var(--modal-border);\n border-radius: var(--radius-sm);\n pointer-events: auto;\n overflow: hidden;\n }\n\n .header {\n @apply flex shrink-0 items-center justify-between gap-2 px-6 py-4;\n border-bottom: var(--border-width-base) solid var(--modal-border);\n }\n\n .title {\n @apply m-0;\n font-size: 1.125rem;\n font-weight: var(--font-weight-semibold);\n color: var(--modal-title-color);\n }\n\n .spacer {\n flex: 1;\n }\n\n .close {\n @apply ml-auto flex items-center justify-center cursor-pointer;\n background: none;\n border: none;\n color: var(--modal-close-color);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .close:hover {\n color: var(--modal-close-hover);\n }\n\n .close:active {\n transform: scale(0.92);\n }\n\n .close:focus {\n outline: 2px solid var(--modal-close-hover);\n outline-offset: 2px;\n border-radius: 0.25rem;\n }\n\n .closeIcon {\n @apply h-5 w-5;\n }\n\n .content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n color: var(--modal-text-color);\n }\n\n .content::-webkit-scrollbar {\n width: 6px;\n }\n\n .content::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .content::-webkit-scrollbar-thumb {\n background: var(--modal-border);\n border-radius: 3px;\n transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .content::-webkit-scrollbar-thumb:hover {\n background: var(--modal-close-color);\n }\n\n .footer {\n @apply flex shrink-0 items-center justify-between gap-4 px-6 py-4;\n background-color: var(--footer-background);\n border-top: var(--border-width-base) solid var(--modal-border);\n }\n\n /* Size variants */\n .size-fit {\n width: fit-content;\n }\n\n .size-auto {\n max-width: min(90vw, 28rem);\n }\n\n /* Media queries for smaller screens */\n @media (max-width: 640px) {\n .modal {\n margin: 1rem;\n }\n\n .content {\n max-height: calc(100vh - 10rem);\n }\n }\n}\n",
|
|
4978
4244
|
"cssTypes": "declare const styles: {\n overlay: string;\n backdrop: string;\n modal: string;\n header: string;\n title: string;\n spacer: string;\n close: string;\n closeIcon: string;\n content: string;\n footer: string;\n \"size-sm\": string;\n \"size-md\": string;\n \"size-lg\": string;\n \"size-xl\": string;\n};\n\nexport default styles;\n"
|
|
4979
4245
|
},
|
|
4980
4246
|
"page": {
|
|
4981
|
-
"tsx": "\"use client\"\n\nimport * as React from 'react';\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport { PageContext } from './page.context';\nimport { PageProps, PageContextValue, PagePadding } from './page.types';\nimport css from './Page.module.css';\n\
|
|
4247
|
+
"tsx": "\"use client\"\n\nimport * as React from 'react';\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport { PageContext } from './page.context';\nimport { PageProps, PageContextValue, PagePadding } from './page.types';\nimport css from './Page.module.css';\n\ninterface PageStyleSlots {\n root?: StyleValue;\n}\n\ntype PageStylesProp = StylesProp<PageStyleSlots>;\n\nconst resolvePageBaseStyles = createStylesResolver(['root'] as const);\n\ninterface PageRootProps extends PageProps {\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PageStylesProp;\n}\n\nconst paddingMap: Record<PagePadding, string> = {\n none: css['padding-none'],\n sm: css['padding-sm'],\n md: css['padding-md'],\n lg: css['padding-lg'],\n xl: css['padding-xl'],\n};\n\nconst PageRoot = React.forwardRef<HTMLDivElement, PageRootProps>(\n (\n {\n maxWidth = '1400px',\n padding = 'md',\n centered = true,\n fullscreen = false,\n className,\n children,\n styles: stylesProp, // Renamed to avoid conflict with the module import\n ...props\n },\n ref\n ) => {\n const [isMobile, setIsMobile] = React.useState(false);\n\n React.useEffect(() => {\n const mediaQuery = window.matchMedia('(max-width: 768px)');\n setIsMobile(mediaQuery.matches);\n\n const handleChange = (e: MediaQueryListEvent) => {\n setIsMobile(e.matches);\n };\n\n mediaQuery.addEventListener('change', handleChange);\n return () => mediaQuery.removeEventListener('change', handleChange);\n }, []);\n\n const contextValue: PageContextValue = {\n pageWidth: fullscreen ? undefined : maxWidth,\n isMobile,\n pageMaxWidth: fullscreen ? undefined : maxWidth,\n pagePadding: padding,\n };\n\n const paddingClass = paddingMap[padding];\n const { root: resolvedRoot } = resolvePageBaseStyles(stylesProp);\n\n return (\n <PageContext.Provider value={contextValue}>\n <div\n ref={ref}\n role=\"main\"\n className={cn(css.page, paddingClass, className, resolvedRoot)}\n data-centered={centered}\n data-fullscreen={fullscreen}\n style={\n {\n maxWidth: !fullscreen ? maxWidth : undefined,\n ...props.style,\n } as React.CSSProperties\n }\n {...props}\n >\n {children}\n </div>\n </PageContext.Provider>\n );\n }\n);\n\nPageRoot.displayName = 'Page';\n\nexport const Page = PageRoot;\n",
|
|
4982
4248
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .page {\n @apply flex flex-col w-full relative;\n }\n\n .page[data-centered=\"true\"] {\n @apply items-center;\n }\n\n .page[data-fullscreen=\"false\"] {\n margin-left: auto;\n margin-right: auto;\n }\n\n .padding-none { padding: 0; }\n\n .padding-sm { padding: var(--spacing-sm, 0.5rem); }\n\n .padding-md { padding: var(--spacing-md, 1rem); }\n\n .padding-lg { padding: var(--spacing-lg, 1.5rem); }\n\n .padding-xl { padding: var(--spacing-xl, 2rem); }\n}\n",
|
|
4983
|
-
"cssTypes": "export interface Styles {\n \"page\": string;\n \"padding-none\": string;\n \"padding-sm\": string;\n \"padding-md\": string;\n \"padding-lg\": string;\n \"padding-xl\": string;\n}\n\nexport
|
|
4249
|
+
"cssTypes": "export interface Styles {\n \"page\": string;\n \"padding-none\": string;\n \"padding-sm\": string;\n \"padding-md\": string;\n \"padding-lg\": string;\n \"padding-xl\": string;\n}\n\nexport default styles;\n"
|
|
4984
4250
|
},
|
|
4985
4251
|
"panel": {
|
|
4986
|
-
"tsx": "'use client'\n\nimport React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'\nimport {\n PanelProps,\n PanelHeaderProps,\n PanelContentProps,\n PanelFooterProps,\n PanelSidebarProps,\n PanelToggleProps,\n PanelGroupProps,\n PanelResizeProps,\n PanelGroupContextValue,\n PanelStylesProp, // Added\n} from './panel.types'\nimport { PanelContext, PanelGroupContext } from './panel.context'\nimport { StyleValue, cn } from '../../lib/utils' // Added/Modified\nimport { createStylesResolver } from '../../lib/styles' // Added\nimport styles from './Panel.module.css'\n\nconst resolvePanelBaseStyles = createStylesResolver(['root'] as const) // Added\n\n/** Flexible multi-panel layout with header, content, footer, and sidebar */\nconst PanelRoot = React.forwardRef<HTMLDivElement, PanelProps>(\n ({ spacing = 'md', variant = 'default', className, children, styles: stylesProp, ...props }, ref) => { // Modified: added `styles: stylesProp`\n const [isStacked, setIsStacked] = useState(false)\n const [sidebarOpen, setSidebarOpen] = useState(true)\n const containerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const container = containerRef.current || (ref && 'current' in ref ? ref.current : null)\n if (!container) return\n\n // Initial check\n const checkViewport = () => {\n setIsStacked(window.innerWidth < 768)\n }\n\n checkViewport()\n\n // Setup ResizeObserver to detect viewport changes\n const observer = new ResizeObserver(() => {\n checkViewport()\n })\n\n observer.observe(document.documentElement)\n\n // Also listen to window resize as fallback\n window.addEventListener('resize', checkViewport)\n\n return () => {\n observer.disconnect()\n window.removeEventListener('resize', checkViewport)\n }\n }, [ref])\n\n const contextValue = useMemo(\n () => ({\n spacing,\n isStacked,\n variant,\n sidebarOpen,\n toggleSidebar: () => setSidebarOpen((prev) => !prev),\n }),\n [spacing, isStacked, variant, sidebarOpen]\n )\n\n const spacingClass =\n {\n none: styles.spacingNone,\n sm: styles.spacingSm,\n md: styles.spacingMd,\n lg: styles.spacingLg,\n }[spacing] || styles.spacingMd\n\n const variantClass = variant === 'compact' ? styles.compact : ''\n const stackedClass = isStacked ? styles.stacked : ''\n\n const panelRef = ref && 'current' in ref ? ref : containerRef\n\n // Modified: use the new style resolver\n const resolvedStyles = resolvePanelBaseStyles(stylesProp)\n\n return (\n <div\n ref={panelRef}\n className={cn(styles.panel, spacingClass, variantClass, stackedClass, className, resolvedStyles.root)} // Modified\n data-spacing={spacing}\n data-variant={variant}\n data-stacked={isStacked}\n {...props}\n >\n <PanelContext.Provider value={contextValue}>{children}</PanelContext.Provider>\n </div>\n )\n }\n)\n\nPanelRoot.displayName = 'Panel'\n\n/** Top bar of the panel, typically for a title and actions */\nconst PanelHeader = React.forwardRef<HTMLElement, PanelHeaderProps>(\n ({ sticky = true, className, ...props }, ref) => {\n const stickyClass = sticky ? styles.sticky : ''\n\n return (\n <header ref={ref} className={`${styles.header} ${stickyClass} ${className || ''}`} {...props} />\n )\n }\n)\n\nPanelHeader.displayName = 'Panel.Header'\n\n/** Main scrollable body area of the panel */\nconst PanelContent = React.forwardRef<HTMLDivElement, PanelContentProps>(\n ({ className, ...props }, ref) => {\n return <div ref={ref} role=\"main\" className={`${styles.content} ${className || ''}`} {...props} />\n }\n)\n\nPanelContent.displayName = 'Panel.Content'\n\n/** Bottom bar of the panel, typically for controls or status */\nconst PanelFooter = React.forwardRef<HTMLElement, PanelFooterProps>(\n ({ fixed = false, className, ...props }, ref) => {\n const fixedClass = fixed ? styles.fixed : ''\n\n return (\n <footer ref={ref} className={`${styles.footer} ${fixedClass} ${className || ''}`} {...props} />\n )\n }\n)\n\nPanelFooter.displayName = 'Panel.Footer'\n\n/** Collapsible side panel that slides in from left or right */\nconst PanelSidebar = React.forwardRef<HTMLElement, PanelSidebarProps>(\n ({ side = 'left', defaultOpen = true, width = '240px', collapsedWidth = '0', className, ...props }, ref) => {\n const { sidebarOpen } = usePanelContext()\n const isOpen = defaultOpen && sidebarOpen\n\n const sidebarStyle: React.CSSProperties = {\n width: isOpen ? width : collapsedWidth,\n transition: 'width 0.2s ease',\n overflow: 'hidden',\n flexShrink: 0,\n [side === 'right' ? 'marginLeft' : 'marginRight']: 'auto',\n }\n\n return (\n <aside\n ref={ref}\n className={`${styles['sidebar']} ${className || ''}`}\n data-open={isOpen}\n data-side={side}\n style={sidebarStyle}\n {...props}\n />\n )\n }\n)\n\nPanelSidebar.displayName = 'Panel.Sidebar'\n\n/** Button that shows/hides the Panel.Sidebar */\nconst PanelToggle = React.forwardRef<HTMLDivElement, PanelToggleProps>(\n ({ children, ...props }, ref) => {\n const { toggleSidebar } = usePanelContext()\n\n const handleClick = () => {\n toggleSidebar()\n }\n\n const clonedChild = React.cloneElement(children as React.ReactElement<any>, {\n onClick: (e: React.MouseEvent) => {\n handleClick()\n ;(children as any).props?.onClick?.(e)\n },\n })\n\n return (\n <div ref={ref} className={styles['toggle']} {...props}>\n {clonedChild}\n </div>\n )\n }\n)\n\nPanelToggle.displayName = 'Panel.Toggle'\n\n/** Container that manages side-by-side resizable panel columns */\nconst PanelGroup = React.forwardRef<HTMLDivElement, PanelGroupProps>(\n ({ direction = 'horizontal', className, children, ...props }, ref) => {\n const containerRef = useRef<HTMLDivElement>(null)\n const [sizes, setSizes] = useState<number[]>([])\n const resizeIndexRef = useRef(0)\n\n // Extract panel children (skip Resize handles)\n const panelChildren = React.Children.toArray(children).filter(\n (child) =>\n React.isValidElement(child) &&\n child.type !== PanelResize &&\n (child.props as any).children !== undefined\n )\n\n const panelCount = panelChildren.length\n\n useEffect(() => {\n // Initialize sizes as equal percentages\n if (panelCount > 0) {\n setSizes(Array(panelCount).fill(100 / panelCount))\n }\n }, [panelCount])\n\n const handleSetSize = useCallback(\n (resizeIndex: number, delta: number) => {\n setSizes((prev) => {\n if (prev.length === 0) return prev\n const newSizes = [...prev]\n const containerSize =\n direction === 'horizontal'\n ? containerRef.current?.clientWidth || 1\n : containerRef.current?.clientHeight || 1\n\n const deltaPercent = (delta / containerSize) * 100\n const minSize = 10\n\n if (resizeIndex + 1 < newSizes.length) {\n // For paired panels: maintain total, apply min/max constraints\n const totalSize = newSizes[resizeIndex] + newSizes[resizeIndex + 1]\n const maxSize = totalSize - minSize\n\n let newSizeA = Math.max(minSize, Math.min(maxSize, newSizes[resizeIndex] + deltaPercent))\n let newSizeB = totalSize - newSizeA\n\n newSizes[resizeIndex] = newSizeA\n newSizes[resizeIndex + 1] = Math.max(minSize, newSizeB)\n } else {\n // Single panel, just apply min constraint\n newSizes[resizeIndex] = Math.max(minSize, newSizes[resizeIndex] + deltaPercent)\n }\n\n return newSizes\n })\n },\n [direction]\n )\n\n const contextValue = useMemo(\n () => ({\n sizes,\n setSize: handleSetSize,\n direction,\n containerRef: containerRef as React.RefObject<HTMLDivElement>,\n }),\n [sizes, direction, handleSetSize]\n )\n\n const groupRef = ref && 'current' in ref ? ref : containerRef\n\n // Render children, injecting sizes into panels and tracking resize indices\n let panelIndex = 0\n let resizeIndex = 0\n const renderedChildren = React.Children.map(children, (child) => {\n if (!React.isValidElement(child)) return child\n\n if (child.type === PanelResize) {\n const currentResizeIndex = resizeIndex\n resizeIndex++\n return React.cloneElement(child as React.ReactElement<any>, {\n 'data-resize-index': currentResizeIndex,\n })\n }\n\n if (child.type !== PanelResize && (child.props as any).children !== undefined) {\n const currentPanelIndex = panelIndex\n const size = sizes[currentPanelIndex] ?? 100 / panelCount\n panelIndex++\n\n const style: React.CSSProperties = {\n ...((child.props as any).style || {}),\n flex: `0 0 ${size}%`,\n overflow: 'hidden',\n }\n\n return React.cloneElement(child as React.ReactElement<any>, {\n style,\n })\n }\n\n return child\n })\n\n return (\n <div\n ref={groupRef}\n className={`${styles['group']} ${className || ''}`}\n data-direction={direction}\n {...props}\n >\n <PanelGroupContext.Provider value={contextValue}>{renderedChildren}</PanelGroupContext.Provider>\n </div>\n )\n }\n)\n\nPanelGroup.displayName = 'Panel.Group'\n\n/** Drag handle between Panel.Group columns for resizing */\nconst PanelResize = React.forwardRef<HTMLDivElement, PanelResizeProps & { 'data-resize-index'?: number }>(\n ({ className, 'data-resize-index': resizeIndexProp, ...props }, ref) => {\n const { direction, setSize } = usePanelGroupContext()\n const [isDragging, setIsDragging] = useState(false)\n const startPosRef = useRef(0)\n const resizeIndexRef = useRef(resizeIndexProp ?? 0)\n\n // Update index if it changes\n useEffect(() => {\n resizeIndexRef.current = resizeIndexProp ?? 0\n }, [resizeIndexProp])\n\n const handleMouseDown = (e: React.MouseEvent) => {\n e.preventDefault()\n setIsDragging(true)\n startPosRef.current = direction === 'horizontal' ? e.clientX : e.clientY\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const currentPos = direction === 'horizontal' ? moveEvent.clientX : moveEvent.clientY\n const delta = currentPos - startPosRef.current\n setSize(resizeIndexRef.current, delta)\n startPosRef.current = currentPos\n }\n\n const handleMouseUp = () => {\n setIsDragging(false)\n document.removeEventListener('mousemove', handleMouseMove)\n document.removeEventListener('mouseup', handleMouseUp)\n }\n\n document.addEventListener('mousemove', handleMouseMove)\n document.addEventListener('mouseup', handleMouseUp)\n }\n\n return (\n <div\n ref={ref}\n className={`${styles['resize']} ${className || ''}`}\n data-resizing={isDragging}\n data-direction={direction}\n onMouseDown={handleMouseDown}\n {...props}\n />\n )\n }\n)\n\nPanelResize.displayName = 'Panel.Resize'\n\n// Helper function for internal use\nfunction usePanelContext() {\n const context = React.useContext(PanelContext)\n if (!context) {\n throw new Error('usePanelContext must be used within a Panel component')\n }\n return context\n}\n\nfunction usePanelGroupContext() {\n const context = React.useContext(PanelGroupContext)\n if (!context) {\n throw new Error('usePanelGroupContext must be used within a Panel.Group component')\n }\n return context\n}\n\nexport const Panel = Object.assign(PanelRoot, {\n Header: PanelHeader,\n Content: PanelContent,\n Footer: PanelFooter,\n Sidebar: PanelSidebar,\n Toggle: PanelToggle,\n Group: PanelGroup,\n Resize: PanelResize,\n})\n\nexport {\n PanelRoot,\n PanelHeader,\n PanelContent,\n PanelFooter,\n PanelSidebar,\n PanelToggle,\n PanelGroup,\n PanelResize,\n PanelContext,\n PanelGroupContext,\n}\nexport type { PanelContextValue } from './panel.types'\n",
|
|
4252
|
+
"tsx": "'use client'\n\nimport React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'\nimport {\n PanelProps,\n PanelHeaderProps,\n PanelContentProps,\n PanelFooterProps,\n PanelSidebarProps,\n PanelToggleProps,\n PanelGroupProps,\n PanelResizeProps,\n PanelGroupContextValue,\n PanelStylesProp, // Added\n} from './panel.types'\nimport { PanelContext, PanelGroupContext } from './panel.context'\nimport { StyleValue, cn } from '../../lib/utils' // Added/Modified\nimport { createStylesResolver } from '../../lib/styles' // Added\nimport styles from './Panel.module.css'\n\nconst resolvePanelBaseStyles = createStylesResolver(['root'] as const) // Added\n\n/** Flexible multi-panel layout with header, content, footer, and sidebar */\nconst PanelRoot = React.forwardRef<HTMLDivElement, PanelProps>(\n ({ spacing = 'md', variant = 'default', className, children, styles: stylesProp, ...props }, ref) => { // Modified: added `styles: stylesProp`\n const [isStacked, setIsStacked] = useState(false)\n const [sidebarOpen, setSidebarOpen] = useState(true)\n const containerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const container = containerRef.current || (ref && 'current' in ref ? ref.current : null)\n if (!container) return\n\n // Initial check\n const checkViewport = () => {\n setIsStacked(window.innerWidth < 768)\n }\n\n checkViewport()\n\n // Setup ResizeObserver to detect viewport changes\n const observer = new ResizeObserver(() => {\n checkViewport()\n })\n\n observer.observe(document.documentElement)\n\n // Also listen to window resize as fallback\n window.addEventListener('resize', checkViewport)\n\n return () => {\n observer.disconnect()\n window.removeEventListener('resize', checkViewport)\n }\n }, [ref])\n\n const contextValue = useMemo(\n () => ({\n spacing,\n isStacked,\n variant,\n sidebarOpen,\n toggleSidebar: () => setSidebarOpen((prev) => !prev),\n }),\n [spacing, isStacked, variant, sidebarOpen]\n )\n\n const spacingClass =\n {\n none: styles.spacingNone,\n sm: styles.spacingSm,\n md: styles.spacingMd,\n lg: styles.spacingLg,\n }[spacing] || styles.spacingMd\n\n const variantClass = variant === 'compact' ? styles.compact : ''\n const stackedClass = isStacked ? styles.stacked : ''\n\n const panelRef = ref && 'current' in ref ? ref : containerRef\n\n // Modified: use the new style resolver\n const resolvedStyles = resolvePanelBaseStyles(stylesProp)\n\n return (\n <div\n ref={panelRef}\n className={cn(styles.panel, spacingClass, variantClass, stackedClass, className, resolvedStyles.root)} // Modified\n data-spacing={spacing}\n data-variant={variant}\n data-stacked={isStacked}\n {...props}\n >\n <PanelContext.Provider value={contextValue}>{children}</PanelContext.Provider>\n </div>\n )\n }\n)\n\nPanelRoot.displayName = 'Panel'\n\n/** Top bar of the panel, typically for a title and actions */\nconst PanelHeader = React.forwardRef<HTMLElement, PanelHeaderProps>(\n ({ sticky = true, className, ...props }, ref) => {\n const stickyClass = sticky ? styles.sticky : ''\n\n return (\n <header ref={ref} className={`${styles.header} ${stickyClass} ${className || ''}`} {...props} />\n )\n }\n)\n\nPanelHeader.displayName = 'Panel.Header'\n\n/** Main scrollable body area of the panel */\nconst PanelContent = React.forwardRef<HTMLDivElement, PanelContentProps>(\n ({ className, ...props }, ref) => {\n return <div ref={ref} role=\"main\" className={`${styles.content} ${className || ''}`} {...props} />\n }\n)\n\nPanelContent.displayName = 'Panel.Content'\n\n/** Bottom bar of the panel, typically for controls or status */\nconst PanelFooter = React.forwardRef<HTMLElement, PanelFooterProps>(\n ({ fixed = false, className, ...props }, ref) => {\n const fixedClass = fixed ? styles.fixed : ''\n\n return (\n <footer ref={ref} className={`${styles.footer} ${fixedClass} ${className || ''}`} {...props} />\n )\n }\n)\n\nPanelFooter.displayName = 'Panel.Footer'\n\n/** Collapsible side panel that slides in from left or right */\nconst PanelSidebar = React.forwardRef<HTMLElement, PanelSidebarProps>(\n ({ side = 'left', defaultOpen = true, width = '240px', collapsedWidth = '0', className, ...props }, ref) => {\n const { sidebarOpen } = usePanelContext()\n const isOpen = defaultOpen && sidebarOpen\n\n const sidebarStyle: React.CSSProperties = {\n width: isOpen ? width : collapsedWidth,\n transition: 'width 0.2s ease',\n overflow: 'hidden',\n flexShrink: 0,\n [side === 'right' ? 'marginLeft' : 'marginRight']: 'auto',\n }\n\n return (\n <aside\n ref={ref}\n className={`${styles['sidebar']} ${className || ''}`}\n data-open={isOpen}\n data-side={side}\n style={sidebarStyle}\n {...props}\n />\n )\n }\n)\n\nPanelSidebar.displayName = 'Panel.Sidebar'\n\n/** Button that shows/hides the Panel.Sidebar */\nconst PanelToggle = React.forwardRef<HTMLDivElement, PanelToggleProps>(\n ({ children, ...props }, ref) => {\n const { toggleSidebar } = usePanelContext()\n\n const handleClick = () => {\n toggleSidebar()\n }\n\n const clonedChild = React.cloneElement(children as React.ReactElement<any>, {\n onClick: (e: React.MouseEvent) => {\n handleClick()\n ;(children as any).props?.onClick?.(e)\n },\n })\n\n return (\n <div ref={ref} className={styles['toggle']} {...props}>\n {clonedChild}\n </div>\n )\n }\n)\n\nPanelToggle.displayName = 'Panel.Toggle'\n\n/** Container that manages side-by-side resizable panel columns */\nconst PanelGroup = React.forwardRef<HTMLDivElement, PanelGroupProps>(\n ({ direction = 'horizontal', className, children, ...props }, ref) => {\n const containerRef = useRef<HTMLDivElement>(null)\n const [sizes, setSizes] = useState<number[]>([])\n const resizeIndexRef = useRef(0)\n\n // Extract panel children (skip Resize handles)\n const panelChildren = React.Children.toArray(children).filter(\n (child) =>\n React.isValidElement(child) &&\n child.type !== PanelResize &&\n (child.props as any).children !== undefined\n )\n\n const panelCount = panelChildren.length\n\n useEffect(() => {\n // Initialize sizes as equal percentages\n if (panelCount > 0) {\n setSizes(Array(panelCount).fill(100 / panelCount))\n }\n }, [panelCount])\n\n const handleSetSize = useCallback(\n (resizeIndex: number, delta: number) => {\n setSizes((prev) => {\n if (prev.length === 0) return prev\n const newSizes = [...prev]\n const containerSize =\n direction === 'horizontal'\n ? containerRef.current?.clientWidth || 1\n : containerRef.current?.clientHeight || 1\n\n const deltaPercent = (delta / containerSize) * 100\n const minSize = 10\n\n if (resizeIndex + 1 < newSizes.length) {\n // For paired panels: maintain total, apply min/max constraints\n const totalSize = newSizes[resizeIndex] + newSizes[resizeIndex + 1]\n const maxSize = totalSize - minSize\n\n let newSizeA = Math.max(minSize, Math.min(maxSize, newSizes[resizeIndex] + deltaPercent))\n let newSizeB = totalSize - newSizeA\n\n newSizes[resizeIndex] = newSizeA\n newSizes[resizeIndex + 1] = Math.max(minSize, newSizeB)\n } else {\n // Single panel, just apply min constraint\n newSizes[resizeIndex] = Math.max(minSize, newSizes[resizeIndex] + deltaPercent)\n }\n\n return newSizes\n })\n },\n [direction]\n )\n\n const contextValue = useMemo(\n () => ({\n sizes,\n setSize: handleSetSize,\n direction,\n containerRef: containerRef as React.RefObject<HTMLDivElement>,\n }),\n [sizes, direction, handleSetSize]\n )\n\n const groupRef = ref && 'current' in ref ? ref : containerRef\n\n // Render children, injecting sizes into panels and tracking resize indices\n let panelIndex = 0\n let resizeIndex = 0\n const renderedChildren = React.Children.map(children, (child) => {\n if (!React.isValidElement(child)) return child\n\n if (child.type === PanelResize) {\n const currentResizeIndex = resizeIndex\n resizeIndex++\n return React.cloneElement(child as React.ReactElement<any>, {\n 'data-resize-index': currentResizeIndex,\n })\n }\n\n if (child.type !== PanelResize && (child.props as any).children !== undefined) {\n const currentPanelIndex = panelIndex\n const size = sizes[currentPanelIndex] ?? 100 / panelCount\n panelIndex++\n\n const style: React.CSSProperties = {\n ...((child.props as any).style || {}),\n flex: `0 0 ${size}%`,\n overflow: 'hidden',\n }\n\n return React.cloneElement(child as React.ReactElement<any>, {\n style,\n })\n }\n\n return child\n })\n\n return (\n <div\n ref={groupRef}\n className={`${styles['group']} ${className || ''}`}\n data-direction={direction}\n {...props}\n >\n <PanelGroupContext.Provider value={contextValue}>{renderedChildren}</PanelGroupContext.Provider>\n </div>\n )\n }\n)\n\nPanelGroup.displayName = 'Panel.Group'\n\n/** Drag handle between Panel.Group columns for resizing */\nconst PanelResize = React.forwardRef<HTMLDivElement, PanelResizeProps & { 'data-resize-index'?: number }>(\n ({ className, 'data-resize-index': resizeIndexProp, ...props }, ref) => {\n const { direction, setSize } = usePanelGroupContext()\n const [isDragging, setIsDragging] = useState(false)\n const startPosRef = useRef(0)\n const resizeIndexRef = useRef(resizeIndexProp ?? 0)\n\n // Update index if it changes\n useEffect(() => {\n resizeIndexRef.current = resizeIndexProp ?? 0\n }, [resizeIndexProp])\n\n const handleMouseDown = (e: React.MouseEvent) => {\n e.preventDefault()\n setIsDragging(true)\n startPosRef.current = direction === 'horizontal' ? e.clientX : e.clientY\n\n const handleMouseMove = (moveEvent: MouseEvent) => {\n const currentPos = direction === 'horizontal' ? moveEvent.clientX : moveEvent.clientY\n const delta = currentPos - startPosRef.current\n setSize(resizeIndexRef.current, delta)\n startPosRef.current = currentPos\n }\n\n const handleMouseUp = () => {\n setIsDragging(false)\n document.removeEventListener('mousemove', handleMouseMove)\n document.removeEventListener('mouseup', handleMouseUp)\n }\n\n document.addEventListener('mousemove', handleMouseMove)\n document.addEventListener('mouseup', handleMouseUp)\n }\n\n return (\n <div\n ref={ref}\n className={`${styles['resize']} ${className || ''}`}\n data-resizing={isDragging}\n data-direction={direction}\n onMouseDown={handleMouseDown}\n {...props}\n />\n )\n }\n)\n\nPanelResize.displayName = 'Panel.Resize'\n\n// Helper function for internal use\nfunction usePanelContext() {\n const context = React.useContext(PanelContext)\n if (!context) {\n throw new Error('usePanelContext must be used within a Panel component')\n }\n return context\n}\n\nfunction usePanelGroupContext() {\n const context = React.useContext(PanelGroupContext)\n if (!context) {\n throw new Error('usePanelGroupContext must be used within a Panel.Group component')\n }\n return context\n}\n\nexport const Panel = Object.assign(PanelRoot, {\n Header: PanelHeader,\n Content: PanelContent,\n Footer: PanelFooter,\n Sidebar: PanelSidebar,\n Toggle: PanelToggle,\n Group: PanelGroup,\n Resize: PanelResize,\n})\n\nexport {\n PanelContext,\n PanelGroupContext,\n}\n",
|
|
4987
4253
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .panel {\n @apply flex h-full w-full min-h-0 min-w-0 flex-row;\n background: inherit;\n }\n\n .panel[data-stacked=\"true\"] { flex-direction: column; }\n\n .header,\n .footer {\n @apply shrink-0;\n background: inherit;\n }\n\n .sticky {\n position: sticky;\n top: 0;\n z-index: 10;\n }\n\n .content {\n @apply flex min-h-0 min-w-0;\n flex: 1;\n overflow: auto;\n }\n\n .fixed {\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 5;\n }\n\n /* Sidebar */\n .sidebar {\n @apply shrink-0 overflow-hidden;\n overflow: hidden;\n transition: width 0.2s ease;\n border-right: var(--border-width-base) solid var(--background-700);\n }\n\n .sidebar[data-side=\"right\"] {\n border-right: none;\n border-left: var(--border-width-base) solid var(--background-700);\n }\n\n /* Toggle */\n .toggle {\n @apply flex items-center;\n }\n\n /* Group */\n .group {\n @apply flex w-full h-full;\n background: inherit;\n }\n\n .group[data-direction=\"vertical\"] { flex-direction: column; }\n\n /* Resize handle */\n .resize {\n @apply relative shrink-0;\n cursor: col-resize;\n background: transparent;\n width: 10px;\n }\n\n .resize::before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n background: var(--background-700, #374151);\n transform: translateX(-50%);\n transition: width 0.15s ease;\n }\n\n .resize[data-direction=\"vertical\"] {\n cursor: row-resize;\n height: 10px;\n }\n\n .resize[data-direction=\"vertical\"]::before {\n top: 50%;\n bottom: auto;\n left: 0;\n right: 0;\n width: auto;\n height: 1px;\n transform: translateY(-50%);\n }\n\n .resize:hover::before,\n .resize[data-resizing=\"true\"]::before { width: 2px; }\n\n .resize[data-direction=\"vertical\"]:hover::before,\n .resize[data-direction=\"vertical\"][data-resizing=\"true\"]::before {\n width: auto;\n height: 2px;\n }\n\n /* Spacing variants */\n .spacingNone,\n .spacing-none { gap: 0; }\n\n .spacingSm,\n .spacing-sm { gap: var(--spacing-sm, 0.5rem); }\n\n .spacingMd,\n .spacing-md { gap: var(--spacing-md, 1rem); }\n\n .spacingLg,\n .spacing-lg { gap: var(--spacing-lg, 1.5rem); }\n\n /* Compact variant */\n .compact {\n gap: calc(var(--spacing-sm, 0.5rem) / 2);\n }\n\n /* Responsive stacking (mobile) */\n @media (max-width: 767px) {\n .stacked { flex-direction: column; }\n }\n}\n",
|
|
4988
4254
|
"cssTypes": "declare const styles: {\n readonly panel: string;\n readonly header: string;\n readonly sticky: string;\n readonly content: string;\n readonly footer: string;\n readonly fixed: string;\n readonly sidebar: string;\n readonly toggle: string;\n readonly group: string;\n readonly resize: string;\n readonly spacingNone: string;\n readonly spacingSm: string;\n readonly spacingMd: string;\n readonly spacingLg: string;\n readonly compact: string;\n readonly stacked: string;\n};\n\nexport default styles;\n"
|
|
4989
4255
|
},
|
|
4990
4256
|
"path": {
|
|
4991
|
-
"tsx": "'use client';\n\nimport React, { ReactNode, forwardRef } from 'react';\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport css from \"./Path.module.css\";\n\nexport interface PathItemProps {\n /** URL this path item links to */\n href?: string;\n /** Called when the path item is pressed */\n onPress?: () => void;\n children: ReactNode;\n /** Whether this is the current/active page */\n isCurrent?: boolean;\n /** Whether the item is non-interactive */\n isDisabled?: boolean;\n /** Additional CSS class names */\n className?: string;\n}\n\
|
|
4992
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n --foreground: var(--foreground-primary);\n --foreground-muted: var(--foreground-secondary);\n --separator-color: var(--border-secondary);\n --focus-ring-color: var(--accent-500);\n --disabled-opacity: 0.6;\n\n @apply block;\n }\n\n .path-list {\n list-style: none;\n @apply m-0 flex flex-wrap items-center gap-2 p-0;\n flex-wrap: wrap;\n }\n\n .path-list.with-custom-separator .path-item:not(:last-child)::after {\n content: none;\n }\n\n .path-item {\n @apply m-0 flex items-center gap-2 p-0;\n }\n\n /* Separator after each item except the last */\n .path-item:not(:last-child)::after {\n content: '/';\n color: var(--separator-color);\n margin-left: 0.5rem;\n user-select: none;\n pointer-events: none;\n }\n\n /* Custom separator element */\n .separator {\n list-style: none;\n @apply m-0 flex items-center p-0;\n color: var(--separator-color);\n user-select: none;\n pointer-events: none;\n }\n\n .path-item-link {\n @apply relative cursor-pointer rounded-sm px-2 py-1;\n color: var(--foreground);\n text-decoration: none;\n font-size: var(--text-
|
|
4993
|
-
"cssTypes": "export interface Styles {\n \"path\": string;\n \"path-list\": string;\n \"with-custom-separator\": string;\n \"path-item\": string;\n \"separator\": string;\n \"path-item-link\": string;\n}\n\nexport
|
|
4257
|
+
"tsx": "'use client';\n\nimport React, { ReactNode, forwardRef } from 'react';\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport css from \"./Path.module.css\";\n\nexport interface PathItemProps {\n /** URL this path item links to */\n href?: string;\n /** Called when the path item is pressed */\n onPress?: () => void;\n children: ReactNode;\n /** Whether this is the current/active page */\n isCurrent?: boolean;\n /** Whether the item is non-interactive */\n isDisabled?: boolean;\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface PathStyleSlots {\n root?: StyleValue;\n list?: StyleValue;\n separator?: StyleValue;\n}\n\ntype PathStylesProp = StylesProp<PathStyleSlots>;\n\nexport interface PathProps {\n children: ReactNode;\n /** Additional CSS class for the path container */\n className?: string;\n /** Custom separator element between path items */\n separator?: ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PathStylesProp;\n}\n\nconst resolvePathBaseStyles = createStylesResolver(['root', 'list', 'separator'] as const);\n\nconst PathItem = forwardRef<HTMLLIElement, PathItemProps>(\n ({ href, onPress, children, isCurrent = false, isDisabled = false, className }, ref) => {\n const isInteractive = !isCurrent && !isDisabled && (href || onPress);\n\n return (\n <li ref={ref} className={css['path-item']}>\n {isInteractive ? (\n <a\n href={href}\n className={cn(css['path-item-link'], className || '')}\n data-disabled={isDisabled || undefined}\n data-current={isCurrent || undefined}\n aria-current={isCurrent ? 'page' : undefined}\n onClick={(e) => {\n if (onPress) {\n e.preventDefault();\n onPress();\n }\n }}\n >\n {children}\n </a>\n ) : (\n <span\n className={`${css['path-item-link']} ${className || ''}`}\n data-disabled={isDisabled || undefined}\n data-current={isCurrent || undefined}\n aria-current={isCurrent ? 'page' : undefined}\n >\n {children}\n </span>\n )}\n </li>\n );\n }\n);\n\nPathItem.displayName = 'Path.Item';\n\nconst Path = forwardRef<HTMLElement, PathProps>(\n ({ children, className, separator, styles }, ref) => {\n const childArray = React.Children.toArray(children);\n const childCount = childArray.length;\n\n const resolved = resolvePathBaseStyles(styles);\n\n return (\n <nav\n ref={ref}\n className={cn(css.path, className, resolved.root)}\n aria-label=\"Path\"\n >\n <ol className={cn(\n css['path-list'],\n resolved.list,\n separator && css['with-custom-separator']\n )}>\n {React.Children.map(childArray, (child, index) => {\n const isLastChild = index === childCount - 1;\n if (React.isValidElement(child)) {\n const element = React.cloneElement(child as React.ReactElement<PathItemProps>, {\n isCurrent: isLastChild,\n });\n\n // Add separator after each item except the last\n if (separator && !isLastChild) {\n return (\n <React.Fragment key={index}>\n {element}\n <li className={cn(css.separator, resolved.separator)} aria-hidden=\"true\">\n {separator}\n </li>\n </React.Fragment>\n );\n }\n return element;\n }\n return child;\n })}\n </ol>\n </nav>\n );\n }\n);\n\nPath.displayName = 'Path';\n\nexport { Path, PathItem };\n",
|
|
4258
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n --foreground: var(--foreground-primary);\n --foreground-muted: var(--foreground-secondary);\n --separator-color: var(--border-secondary);\n --focus-ring-color: var(--accent-500);\n --disabled-opacity: 0.6;\n\n @apply block;\n }\n\n .path-list {\n list-style: none;\n @apply m-0 flex flex-wrap items-center gap-2 p-0;\n flex-wrap: wrap;\n }\n\n .path-list.with-custom-separator .path-item:not(:last-child)::after {\n content: none;\n }\n\n .path-item {\n @apply m-0 flex items-center gap-2 p-0;\n }\n\n /* Separator after each item except the last */\n .path-item:not(:last-child)::after {\n content: '/';\n color: var(--separator-color);\n margin-left: 0.5rem;\n user-select: none;\n pointer-events: none;\n }\n\n /* Custom separator element */\n .separator {\n list-style: none;\n @apply m-0 flex items-center p-0;\n color: var(--separator-color);\n user-select: none;\n pointer-events: none;\n }\n\n .path-item-link {\n @apply relative cursor-pointer rounded-sm px-2 py-1;\n color: var(--foreground);\n text-decoration: none;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n line-height: 1.5;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover:not([data-disabled='true']) {\n background-color: var(--background-hover, rgba(0, 0, 0, 0.04));\n color: var(--accent-600);\n }\n\n &:active:not([data-disabled='true']) {\n background-color: var(--background-active, rgba(0, 0, 0, 0.08));\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring-color);\n outline-offset: 2px;\n }\n\n &[data-current='true'] {\n color: var(--foreground-muted);\n cursor: default;\n font-weight: var(--font-weight-medium);\n\n &:hover {\n background-color: transparent;\n }\n }\n\n &[data-disabled='true'] {\n color: var(--foreground-muted);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n\n &:hover {\n background-color: transparent;\n }\n }\n }\n}\n",
|
|
4259
|
+
"cssTypes": "export interface Styles {\n \"path\": string;\n \"path-list\": string;\n \"with-custom-separator\": string;\n \"path-item\": string;\n \"separator\": string;\n \"path-item-link\": string;\n}\n\nexport default styles;\n"
|
|
4994
4260
|
},
|
|
4995
4261
|
"popover": {
|
|
4996
|
-
"tsx": "\"use client\"\n\nimport React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useOverlayTrigger, useDialog, mergeProps } from \"react-aria\";\nimport { useOverlayTriggerState } from \"react-stately\";\nimport { useFloating
|
|
4997
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--popover-fill);\n --frame-stroke-color: var(--popover-border-color);\n opacity: 0;\n transform: scale(0.95);\n transition: opacity 0.2s ease-out, transform 0.2s ease-out;\n pointer-events: none;\n min-width: 200px;\n max-width: 400px;\n padding: 0.75rem;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n }\n\n .content[data-instant] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-
|
|
4262
|
+
"tsx": "\"use client\"\n\nimport React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useOverlayTrigger, useDialog, mergeProps } from \"react-aria\";\nimport { useOverlayTriggerState } from \"react-stately\";\nimport { useFloating } from \"../../hooks/useFloat/react/useFloating\";\nimport { flip } from \"../../hooks/useFloat/core/middleware/flip\";\nimport { offset } from \"../../hooks/useFloat/core/middleware/offset\";\nimport { shift } from \"../../hooks/useFloat/core/middleware/shift\";\nimport { autoUpdate } from \"../../hooks/useFloat/dom/autoUpdate\";\nimport { cn } from \"./utils\";\nimport { type StyleValue } from \"./utils\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { Frame } from \"../Frame\";\nimport css from \"./Popover.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\n\nconst ARROW_PATH = \"M 0 0 L 6 -12 L 12 0\";\nconst ARROW_WIDTH = 12;\nconst POPOVER_GAP = 8;\nconst ARROW_POSITIONING_SIZE = 6;\n\ntype PopoverPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: PopoverPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\n/**\n * Maps placement to initial transform for directional entrance animation.\n * When animating in, the component slides from its placement direction toward the center.\n * For example, \"top\" placement slides up (-Y) and fades in.\n */\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\ninterface PopoverStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n trigger?: StyleValue;\n}\n\ntype PopoverStylesProp = StylesProp<PopoverStyleSlots>;\n\nexport interface PopoverProps {\n children: React.ReactNode;\n /** Content to display inside the popover panel */\n content: React.ReactNode;\n /** Preferred side of the trigger where the popover appears */\n position?: PopoverPosition;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PopoverStylesProp;\n /** Additional CSS class for the trigger element. Merged with `styles.root`. */\n className?: string;\n /** Additional CSS class for the popover content panel. Merged with `styles.content`. */\n contentClassName?: string;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the popover opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n}\n\nconst Popover = React.forwardRef<HTMLDivElement, PopoverProps>(\n ({ children, content, position = \"bottom\", styles, className: externalClassName, contentClassName: externalContentClassName, isOpen: controlledIsOpen, onOpenChange, showArrow = false }, ref) => {\n\n const resolvePopoverBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'trigger',\n ] as const);\n\n const resolved = resolvePopoverBaseStyles(styles);\n\n const triggerRef = React.useRef<HTMLDivElement>(null);\n const popoverContentRef = React.useRef<HTMLDivElement>(null);\n const [isAnimating, setIsAnimating] = React.useState(false);\n const [isExiting, setIsExiting] = React.useState(false);\n\n const state = useOverlayTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange,\n });\n\n const { triggerProps, overlayProps } = useOverlayTrigger({ type: \"dialog\" }, state, triggerRef);\n const { dialogProps } = useDialog({}, popoverContentRef);\n\n const placementMap: Record<PopoverPosition, \"top\" | \"bottom\" | \"left\" | \"right\"> = {\n top: \"top\",\n bottom: \"bottom\",\n left: \"left\",\n right: \"right\",\n };\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: placementMap[position],\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(POPOVER_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n // Trigger animation when popover is opened and positioned\n React.useEffect(() => {\n if (state.isOpen && isPositioned) {\n setIsExiting(false);\n setIsAnimating(true);\n }\n }, [state.isOpen, isPositioned]);\n\n // Handle exit animation when closing\n React.useEffect(() => {\n if (!state.isOpen && isAnimating) {\n // First, enable exit mode so element stays in DOM\n setIsExiting(true);\n\n requestAnimationFrame(() => setIsAnimating(false));\n const timer = setTimeout(() => setIsExiting(false), 50)\n return () => clearTimeout(timer);\n }\n }, [state.isOpen, isAnimating]);\n\n React.useLayoutEffect(() => {\n refs.setReference(triggerRef.current);\n }, [refs]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n triggerRef.current &&\n !triggerRef.current.contains(target) &&\n popoverContentRef.current &&\n !popoverContentRef.current.contains(target)\n ) {\n state.close();\n }\n };\n document.addEventListener(\"click\", handleClickOutside);\n return () => document.removeEventListener(\"click\", handleClickOutside);\n }, [state.isOpen, state]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") state.close();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [state.isOpen, state]);\n\n const mergedRef = React.useCallback(\n (el: HTMLDivElement | null) => {\n (triggerRef as React.RefObject<HTMLDivElement | null>).current = el;\n refs.setReference(el);\n if (typeof ref === \"function\") ref(el);\n else if (ref) ref.current = el;\n },\n [refs, ref]\n );\n\n const mergedContentRef = React.useCallback(\n (el: HTMLDivElement | null) => {\n (popoverContentRef as React.RefObject<HTMLDivElement | null>).current = el;\n refs.setFloating(el);\n },\n [refs]\n );\n\n // Convert React Aria's onPress to onClick for native HTML elements\n const nativeProps = React.useMemo(() => {\n const props: any = { ...triggerProps };\n if (props.onPress && typeof props.onPress === 'function') {\n const onPress = props.onPress;\n props.onClick = (e: React.MouseEvent) => {\n onPress({ target: e.currentTarget, type: 'press', pointerType: 'mouse', ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey, altKey: e.altKey });\n };\n delete props.onPress;\n }\n return props;\n }, [triggerProps]);\n\n const triggerElement = React.isValidElement(children)\n ? React.cloneElement(children as React.ReactElement<{ className?: string; ref?: React.Ref<HTMLButtonElement | HTMLDivElement> }>, {\n ...nativeProps,\n className: cn((children as React.ReactElement<{ className?: string }>).props.className, externalClassName, css.trigger, resolved.trigger),\n ref: mergedRef,\n })\n : (\n <span ref={mergedRef} {...nativeProps} className={cn(css.trigger, externalClassName, resolved.trigger)}>\n {children}\n </span>\n );\n\n return (\n <>\n {triggerElement}\n {(state.isOpen || isExiting) &&\n createPortal(\n <div\n ref={mergedContentRef}\n {...asElementProps<\"div\">(mergeProps(overlayProps, dialogProps))}\n className={cn(css.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn('popover', 'content', css.content)}\n style={{\n opacity: isAnimating ? 1 : 0,\n transform: isAnimating ? \"scale(1)\" : getInitialTransform(placement),\n pointerEvents: isAnimating ? 'auto' : 'none',\n }}\n >\n <Frame\n role=\"dialog\"\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n cornerRadius={8}\n padding=\"none\"\n className={cn('popover', 'frame', css.frame, externalContentClassName, resolved.content)}\n >\n {content}\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nPopover.displayName = \"Popover\";\n\nexport { Popover };\n",
|
|
4263
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--popover-fill);\n --frame-stroke-color: var(--popover-border-color);\n opacity: 0;\n transform: scale(0.95);\n transition: opacity 0.2s ease-out, transform 0.2s ease-out;\n pointer-events: none;\n min-width: 200px;\n max-width: 400px;\n padding: 0.75rem;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n }\n\n .content[data-instant] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n}\n",
|
|
4998
4264
|
"cssTypes": "export interface Styles {\n trigger: string;\n root: string;\n content: string;\n frame: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4999
4265
|
},
|
|
5000
4266
|
"progress": {
|
|
5001
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Progress.module.css\";\n\ntype ProgressSize = \"sm\" | \"md\" | \"lg\";\n\
|
|
4267
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Progress.module.css\";\n\ntype ProgressSize = \"sm\" | \"md\" | \"lg\";\n\ninterface ProgressStyleSlots {\n root?: StyleValue;\n ['label-row']?: StyleValue;\n label?: StyleValue;\n value?: StyleValue;\n progress?: StyleValue;\n fill?: StyleValue;\n}\n\ntype ProgressStylesProp = StylesProp<ProgressStyleSlots>;\n\nexport interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Current progress value */\n value?: number;\n /** Maximum value that represents 100% */\n max?: number;\n /** Visual color variant indicating progress state */\n variant?: string;\n /** Size of the progress bar */\n size?: ProgressSize;\n /** Whether to show an infinite loading animation instead of a fixed value */\n indeterminate?: boolean;\n /** Accessible label describing what is progressing */\n label?: string;\n /** Whether to display the percentage value next to the label */\n showValue?: boolean;\n /** Whether to show a shimmer animation on the progress fill */\n animated?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ProgressStylesProp;\n}\n\nconst sizeMap = {\n sm: css.sm,\n md: css.md,\n lg: css.lg,\n} as const;\n\nconst resolveProgressBaseStyles = createStylesResolver([\n 'root',\n 'label-row',\n 'label',\n 'value',\n 'progress',\n 'fill',\n] as const);\n\nconst Progress = React.forwardRef<HTMLDivElement, ProgressProps>(\n (\n {\n className,\n value = 0,\n max = 100,\n variant = \"default\",\n size = \"md\",\n indeterminate = false,\n label,\n showValue = false,\n animated = false,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const clampedValue = Math.min(Math.max(value, 0), max);\n const percentage = (clampedValue / max) * 100;\n const hasLabelContent = label || showValue;\n\n const resolved = resolveProgressBaseStyles(stylesProp);\n\n return (\n <div\n className={cn(css.wrapper, hasLabelContent && css.hasLabel, resolved.root)}\n >\n {hasLabelContent && (\n <div className={cn('progress', 'label-row', css['label-row'], resolved['label-row'])}>\n {label && (\n <span className={cn(css.label, resolved.label)}>\n {label}\n </span>\n )}\n {showValue && !indeterminate && (\n <span className={cn(css.value, resolved.value)}>{Math.round(percentage)}%</span>\n )}\n </div>\n )}\n <div\n ref={ref}\n role=\"progressbar\"\n aria-valuenow={indeterminate ? undefined : clampedValue}\n aria-valuemin={0}\n aria-valuemax={max}\n aria-label={label}\n className={cn('progress', variant, css.progress, sizeMap[size], className, resolved.progress)}\n data-variant={variant}\n data-size={size}\n data-indeterminate={indeterminate || undefined}\n {...props}\n >\n <div\n className={cn('progress', 'fill', variant, css.fill, (animated || indeterminate) && css.animated, indeterminate && css.indeterminate, resolved.fill)}\n style={indeterminate ? undefined : { width: `${percentage}%` }}\n />\n </div>\n </div>\n );\n }\n);\n\nProgress.displayName = \"Progress\";\n\nexport { Progress };\n",
|
|
5002
4268
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .progress {\n @apply relative w-full overflow-hidden;\n border-radius: var(--radius-full);\n background-color: var(--track-background);\n }\n\n .progress.sm { height: 0.25rem; }\n .progress.md { height: 0.5rem; }\n .progress.lg { height: 0.75rem; }\n\n .fill {\n @apply h-full;\n border-radius: var(--radius-full);\n background-color: var(--fill-background);\n transition: width 300ms var(--ease-snappy-pop);\n }\n\n .fill.animated {\n animation: pulse 2s var(--ease-gentle-ease) infinite;\n }\n\n .fill.indeterminate {\n width: 33.333%;\n animation: progress-indeterminate 1.5s var(--ease-gentle-ease) infinite;\n }\n\n .wrapper {\n @apply w-full;\n }\n\n .wrapper.has-label {\n @apply space-y-1;\n }\n\n .label-row {\n @apply flex items-center justify-between;\n font-size: var(--text-sm);\n color: var(--foreground);\n }\n\n .label {\n user-select: none;\n }\n\n .value {\n font-variant-numeric: tabular-nums;\n }\n\n @keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n }\n\n @keyframes progress-indeterminate {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(400%); }\n }\n}\n",
|
|
5003
4269
|
"cssTypes": "export interface Styles {\n progress: string;\n sm: string;\n md: string;\n lg: string;\n fill: string;\n default: string;\n success: string;\n warning: string;\n error: string;\n animated: string;\n indeterminate: string;\n wrapper: string;\n hasLabel: string;\n \"label-row\": string;\n label: string;\n value: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
5004
4270
|
},
|
|
5005
4271
|
"radio": {
|
|
5006
|
-
"tsx": "\"use client\";\n\nimport React, { useId, createContext, useContext } from \"react\";\nimport { useRadioGroupState } from \"react-stately\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useRadioGroup, useRadio } from \"@react-aria/radio\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport styles from \"./Radio.module.css\";\n\nexport interface RadioStyleSlots {\n root?: StyleValue;\n label?: StyleValue;\n description?: StyleValue;\n helperText?: StyleValue;\n}\n\nexport type RadioStylesProp = StylesProp<RadioStyleSlots>;\n\nconst resolveRadioBaseStyles = createStylesResolver(['root', 'label', 'description', 'helperText'] as const);\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\n// Context for Radio.Group\ninterface RadioGroupContextType {\n state?: ReturnType<typeof useRadioGroupState>;\n disabled?: boolean;\n size?: Size;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextType | undefined>(undefined);\n\nconst useRadioGroupContext = () => {\n const context = useContext(RadioGroupContext);\n return context;\n};\n\n// Radio.Group Component\nexport interface RadioGroupProps {\n /** Controlled selected radio value */\n value?: string;\n /** Initial selected value for uncontrolled usage */\n defaultValue?: string;\n /** Called when the selected value changes */\n onValueChange?: (value: string) => void;\n /** Whether all radios in the group are disabled */\n disabled?: boolean;\n /** Size of all radio buttons in the group */\n size?: Size;\n children: React.ReactNode;\n /** Additional CSS class names */\n className?: string;\n /** Accessible label for the radio group */\n label?: string;\n /** Descriptive text shown below the group label */\n description?: string;\n}\n\nconst RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(\n ({\n value: controlledValue,\n defaultValue,\n onValueChange,\n disabled = false,\n size = \"md\",\n children,\n className,\n label,\n description,\n }, ref) => {\n const state = useRadioGroupState({\n value: controlledValue,\n defaultValue,\n onChange: onValueChange,\n isDisabled: disabled,\n });\n\n useRadioGroup(\n {\n isDisabled: disabled,\n label,\n description,\n },\n state\n );\n\n return (\n <RadioGroupContext.Provider\n value={{ state, disabled, size }}\n >\n <div\n ref={ref}\n className={className}\n role=\"group\"\n >\n {label && (\n <label\n className={cn(\n 'radio', 'radio-label', styles[\"radio-label\"],\n disabled && 'radio-label-disabled', disabled && styles[\"radio-label-disabled\"]\n )}\n >\n {label}\n </label>\n )}\n {description && (\n <p className=\"text-sm text-foreground-400\">\n {description}\n </p>\n )}\n <div className={styles[\"radio-group\"]}>\n {children}\n </div>\n </div>\n </RadioGroupContext.Provider>\n );\n }\n);\n\nRadioGroup.displayName = \"RadioGroup\";\n\n// Radio.Item Component\nexport interface RadioItemProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Value submitted when this radio is selected */\n value: string;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioItem = React.forwardRef<HTMLInputElement, RadioItemProps>(\n ({\n className,\n size: sizeProp,\n disabled: disabledProp = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n value,\n id,\n styles: stylesProp,\n ...props\n }, ref) => {\n const radioGroupContext = useRadioGroupContext();\n const generatedId = useId();\n const radioId = id || `radio-${generatedId}`;\n\n if (!radioGroupContext?.state) {\n throw new Error(\"RadioItem must be used within a Radio.Group\");\n }\n\n const { state } = radioGroupContext;\n const size = sizeProp || radioGroupContext?.size || \"md\";\n const disabled = disabledProp ?? radioGroupContext?.disabled ?? false;\n const isSelected = state.selectedValue === value;\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n // Extract aria-label from props if provided, fallback to label if it's a string\n const ariaLabelFromProps = props[\"aria-label\"];\n const ariaLabelValue =\n ariaLabelFromProps ||\n (typeof label === \"string\" ? label : undefined);\n\n const { inputProps } = useRadio(\n {\n value,\n isDisabled: disabled,\n ...(ariaLabelValue && { \"aria-label\": ariaLabelValue }),\n },\n state,\n inputRef\n );\n\n const { focusProps, isFocusVisible } = useFocusRing();\n const resolved = resolveRadioBaseStyles(stylesProp);\n\n return (\n <div className=\"w-full\">\n <div\n className={styles[\"radio-item\"]}\n data-disabled={disabled || undefined}\n >\n <div className=\"relative\">\n <div\n className={cn(\n 'radio', styles.radio,\n styles[size],\n className,\n resolved.root\n )}\n data-checked={isSelected || undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-focus-visible={isFocusVisible || undefined}\n role=\"presentation\"\n >\n {isSelected && (\n <div className={cn(styles[\"radio-dot\"], styles[size])} />\n )}\n </div>\n <input\n {...asElementProps<\"input\">(mergeProps(inputProps, focusProps))}\n ref={ref || inputRef}\n type=\"radio\"\n id={radioId}\n className={styles[\"radio-input\"]}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\n 'radio', 'radio-label', styles[\"radio-label\"],\n disabled && 'radio-label-disabled', disabled && styles[\"radio-label-disabled\"],\n resolved.label\n )}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n 'radio', 'radio-description', styles[\"radio-description\"],\n error && 'radio-description-error', error && styles[\"radio-description-error\"],\n resolved.description\n )}\n >\n {description}\n </p>\n )}\n </div>\n )}\n </div>\n {helperText && (\n <p\n className={cn(\n \"text-xs mt-2 ml-8 transition-colors\",\n helperTextError ? \"text-danger-600\" : \"text-foreground-400\",\n resolved.helperText\n )}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioItem.displayName = \"RadioItem\";\n\n// Standalone Radio component for backward compatibility\nexport interface RadioProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(\n ({\n className,\n size = \"md\",\n disabled = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n checked: checkedProp,\n defaultChecked,\n onChange,\n id,\n styles: stylesProp,\n ...props\n }, ref) => {\n const [internalChecked, setInternalChecked] = React.useState(checkedProp ?? defaultChecked ?? false);\n const generatedId = useId();\n\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(e.target.checked);\n }\n onChange?.(e);\n };\n\n const radioId = id || `radio-${generatedId}`;\n const inputRef = React.useRef<HTMLInputElement>(null);\n const resolved = resolveRadioBaseStyles(stylesProp);\n\n return (\n <div className=\"w-full\">\n <div\n className={styles[\"radio-item\"]}\n data-disabled={disabled || undefined}\n >\n <div className=\"relative\">\n <div\n className={cn(\n 'radio', styles.radio,\n styles[size],\n className,\n resolved.root\n )}\n data-checked={checked || undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-focus-visible={isFocusVisible || undefined}\n role=\"presentation\"\n >\n {checked && (\n <div className={cn(styles[\"radio-dot\"], styles[size])} />\n )}\n </div>\n <input\n {...asElementProps<\"input\">(focusProps)}\n ref={inputRef}\n type=\"radio\"\n id={radioId}\n checked={checked}\n onChange={handleChange}\n disabled={disabled ?? false}\n className={styles[\"radio-input\"]}\n aria-label={typeof label === \"string\" ? label : undefined}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\n 'radio', 'radio-label', styles[\"radio-label\"],\n disabled && 'radio-label-disabled', disabled && styles[\"radio-label-disabled\"],\n resolved.label\n )}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n 'radio', 'radio-description', styles[\"radio-description\"],\n error && 'radio-description-error', error && styles[\"radio-description-error\"],\n resolved.description\n )}\n >\n {description}\n </p>\n )}\n </div>\n )}\n </div>\n {helperText && (\n <p\n className={cn(\n \"text-xs mt-2 ml-8 transition-colors\",\n helperTextError ? \"text-danger-600\" : \"text-foreground-400\",\n resolved.helperText\n )}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioBase.displayName = \"Radio\";\n\n// Compound component\nconst Radio = Object.assign(RadioBase, {\n Group: RadioGroup,\n Item: RadioItem,\n});\n\nexport { Radio };\n",
|
|
4272
|
+
"tsx": "\"use client\";\n\nimport React, { useId, createContext, useContext } from \"react\";\nimport { useRadioGroupState } from \"react-stately\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useRadioGroup, useRadio } from \"@react-aria/radio\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport styles from \"./Radio.module.css\";\n\ninterface RadioStyleSlots {\n root?: StyleValue;\n label?: StyleValue;\n description?: StyleValue;\n helperText?: StyleValue;\n}\n\ntype RadioStylesProp = StylesProp<RadioStyleSlots>;\n\nconst resolveRadioBaseStyles = createStylesResolver(['root', 'label', 'description', 'helperText'] as const);\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\n// Context for Radio.Group\ninterface RadioGroupContextType {\n state?: ReturnType<typeof useRadioGroupState>;\n disabled?: boolean;\n size?: Size;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextType | undefined>(undefined);\n\nconst useRadioGroupContext = () => {\n const context = useContext(RadioGroupContext);\n return context;\n};\n\n// Radio.Group Component\nexport interface RadioGroupProps {\n /** Controlled selected radio value */\n value?: string;\n /** Initial selected value for uncontrolled usage */\n defaultValue?: string;\n /** Called when the selected value changes */\n onValueChange?: (value: string) => void;\n /** Whether all radios in the group are disabled */\n disabled?: boolean;\n /** Size of all radio buttons in the group */\n size?: Size;\n children: React.ReactNode;\n /** Additional CSS class names */\n className?: string;\n /** Accessible label for the radio group */\n label?: string;\n /** Descriptive text shown below the group label */\n description?: string;\n}\n\nconst RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(\n ({\n value: controlledValue,\n defaultValue,\n onValueChange,\n disabled = false,\n size = \"md\",\n children,\n className,\n label,\n description,\n }, ref) => {\n const state = useRadioGroupState({\n value: controlledValue,\n defaultValue,\n onChange: onValueChange,\n isDisabled: disabled,\n });\n\n useRadioGroup(\n {\n isDisabled: disabled,\n label,\n description,\n },\n state\n );\n\n return (\n <RadioGroupContext.Provider\n value={{ state, disabled, size }}\n >\n <div\n ref={ref}\n className={className}\n role=\"group\"\n >\n {label && (\n <label\n className={cn(\n 'radio', 'radio-label', styles[\"radio-label\"],\n disabled && 'radio-label-disabled', disabled && styles[\"radio-label-disabled\"]\n )}\n >\n {label}\n </label>\n )}\n {description && (\n <p className=\"text-sm text-foreground-400\">\n {description}\n </p>\n )}\n <div className={styles[\"radio-group\"]}>\n {children}\n </div>\n </div>\n </RadioGroupContext.Provider>\n );\n }\n);\n\nRadioGroup.displayName = \"RadioGroup\";\n\n// Radio.Item Component\nexport interface RadioItemProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Value submitted when this radio is selected */\n value: string;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioItem = React.forwardRef<HTMLInputElement, RadioItemProps>(\n ({\n className,\n size: sizeProp,\n disabled: disabledProp = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n value,\n id,\n styles: stylesProp,\n ...props\n }, ref) => {\n const radioGroupContext = useRadioGroupContext();\n const generatedId = useId();\n const radioId = id || `radio-${generatedId}`;\n\n if (!radioGroupContext?.state) {\n throw new Error(\"RadioItem must be used within a Radio.Group\");\n }\n\n const { state } = radioGroupContext;\n const size = sizeProp || radioGroupContext?.size || \"md\";\n const disabled = disabledProp ?? radioGroupContext?.disabled ?? false;\n const isSelected = state.selectedValue === value;\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n // Extract aria-label from props if provided, fallback to label if it's a string\n const ariaLabelFromProps = props[\"aria-label\"];\n const ariaLabelValue =\n ariaLabelFromProps ||\n (typeof label === \"string\" ? label : undefined);\n\n const { inputProps } = useRadio(\n {\n value,\n isDisabled: disabled,\n ...(ariaLabelValue && { \"aria-label\": ariaLabelValue }),\n },\n state,\n inputRef\n );\n\n const { focusProps, isFocusVisible } = useFocusRing();\n const resolved = resolveRadioBaseStyles(stylesProp);\n\n return (\n <div className=\"w-full\">\n <div\n className={styles[\"radio-item\"]}\n data-disabled={disabled || undefined}\n >\n <div className=\"relative\">\n <div\n className={cn(\n 'radio', styles.radio,\n styles[size],\n className,\n resolved.root\n )}\n data-checked={isSelected || undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-focus-visible={isFocusVisible || undefined}\n role=\"presentation\"\n >\n {isSelected && (\n <div className={cn(styles[\"radio-dot\"], styles[size])} />\n )}\n </div>\n <input\n {...asElementProps<\"input\">(mergeProps(inputProps, focusProps))}\n ref={ref || inputRef}\n type=\"radio\"\n id={radioId}\n className={styles[\"radio-input\"]}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\n 'radio', 'radio-label', styles[\"radio-label\"],\n disabled && 'radio-label-disabled', disabled && styles[\"radio-label-disabled\"],\n resolved.label\n )}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n 'radio', 'radio-description', styles[\"radio-description\"],\n error && 'radio-description-error', error && styles[\"radio-description-error\"],\n resolved.description\n )}\n >\n {description}\n </p>\n )}\n </div>\n )}\n </div>\n {helperText && (\n <p\n className={cn(\n \"text-sm mt-2 ml-8 transition-colors\",\n helperTextError ? \"text-danger-600\" : \"text-foreground-400\",\n resolved.helperText\n )}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioItem.displayName = \"RadioItem\";\n\n// Standalone Radio component for backward compatibility\nexport interface RadioProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(\n ({\n className,\n size = \"md\",\n disabled = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n checked: checkedProp,\n defaultChecked,\n onChange,\n id,\n styles: stylesProp,\n ...props\n }, ref) => {\n const [internalChecked, setInternalChecked] = React.useState(checkedProp ?? defaultChecked ?? false);\n const generatedId = useId();\n\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(e.target.checked);\n }\n onChange?.(e);\n };\n\n const radioId = id || `radio-${generatedId}`;\n const inputRef = React.useRef<HTMLInputElement>(null);\n const resolved = resolveRadioBaseStyles(stylesProp);\n\n return (\n <div className=\"w-full\">\n <div\n className={styles[\"radio-item\"]}\n data-disabled={disabled || undefined}\n >\n <div className=\"relative\">\n <div\n className={cn(\n 'radio', styles.radio,\n styles[size],\n className,\n resolved.root\n )}\n data-checked={checked || undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-focus-visible={isFocusVisible || undefined}\n role=\"presentation\"\n >\n {checked && (\n <div className={cn(styles[\"radio-dot\"], styles[size])} />\n )}\n </div>\n <input\n {...asElementProps<\"input\">(focusProps)}\n ref={inputRef}\n type=\"radio\"\n id={radioId}\n checked={checked}\n onChange={handleChange}\n disabled={disabled ?? false}\n className={styles[\"radio-input\"]}\n aria-label={typeof label === \"string\" ? label : undefined}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\n 'radio', 'radio-label', styles[\"radio-label\"],\n disabled && 'radio-label-disabled', disabled && styles[\"radio-label-disabled\"],\n resolved.label\n )}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n 'radio', 'radio-description', styles[\"radio-description\"],\n error && 'radio-description-error', error && styles[\"radio-description-error\"],\n resolved.description\n )}\n >\n {description}\n </p>\n )}\n </div>\n )}\n </div>\n {helperText && (\n <p\n className={cn(\n \"text-sm mt-2 ml-8 transition-colors\",\n helperTextError ? \"text-danger-600\" : \"text-foreground-400\",\n resolved.helperText\n )}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioBase.displayName = \"Radio\";\n\n// Compound component\nconst Radio = Object.assign(RadioBase, {\n Group: RadioGroup,\n Item: RadioItem,\n});\n\nexport { Radio };\n",
|
|
5007
4273
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .radio-group {\n @apply flex flex-col gap-3;\n }\n\n .radio-item {\n @apply flex items-start gap-3 cursor-pointer select-none;\n }\n\n .radio-input {\n @apply absolute inset-0 w-full h-full opacity-0 cursor-pointer\n }\n\n .radio {\n --disabled-opacity: 0.6;\n\n @apply relative flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center;\n border: var(--border-width-base) solid;\n border-radius: 9999px;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n background-color: var(--radio-background-unchecked);\n border-color: var(--radio-border-unchecked);\n }\n\n .radio-item:active .radio {\n transform: scale(0.92);\n }\n\n .radio-dot {\n border-radius: 9999px;\n background-color: var(--radio-dot-unchecked);\n transform: scale(0);\n transform-origin: center;\n transition: transform 200ms var(--ease-snappy-pop);\n }\n\n .radio[data-checked=\"true\"] {\n --radio-background-unchecked: var(--radio-background-checked);\n --radio-border-unchecked: var(--radio-border-checked);\n --radio-dot-unchecked: var(--radio-dot-checked);\n }\n\n .radio[data-checked=\"true\"] .radio-dot {\n transform: scale(1);\n }\n\n @media (hover: hover) {\n .radio-item:not([data-disabled]):hover .radio {\n --radio-background-unchecked: var(--radio-hover-background);\n --radio-border-unchecked: var(--radio-hover-border);\n opacity: 0.9;\n }\n }\n\n .radio-item[data-disabled] .radio {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n --radio-dot-unchecked: transparent;\n }\n\n .radio[data-error=\"true\"] {\n --radio-border-unchecked: var(--radio-error-border);\n }\n\n .radio[data-error=\"true\"][data-checked=\"true\"] {\n --radio-border-unchecked: var(--radio-border-checked);\n }\n\n .radio[data-focus-visible=\"true\"] {\n outline: 2px solid;\n outline-color: var(--ring-color);\n outline-offset: -2px;\n }\n\n .radio-label {\n @apply cursor-pointer;\n font-weight: var(--font-weight-medium);\n transition: color 200ms var(--ease-snappy-pop);\n color: var(--radio-foreground);\n font-size: inherit;\n line-height: inherit;\n select: none;\n }\n\n .radio-label-disabled {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n color: var(--radio-foreground-disabled);\n }\n\n .radio-description {\n font-size: 0.875rem;\n margin-top: 0.125rem;\n transition: color 200ms var(--ease-snappy-pop);\n color: var(--radio-helper);\n }\n\n .radio-description-error {\n color: var(--radio-helper-error);\n }\n /* Size variants */\n .radio.sm {\n @apply h-4 w-4;\n }\n\n .radio.sm .radio-dot {\n width: 0.375rem;\n height: 0.375rem;\n }\n\n .radio.md {\n @apply h-5 w-5;\n }\n\n .radio.md .radio-dot {\n width: 0.625rem;\n height: 0.625rem;\n }\n\n .radio.lg {\n @apply h-6 w-6;\n }\n\n .radio.lg .radio-dot {\n width: 0.75rem;\n height: 0.75rem;\n }\n}\n",
|
|
5008
4274
|
"cssTypes": "declare const styles: {\n \"radio-group\": string;\n \"radio-item\": string;\n \"radio-input\": string;\n radio: string;\n \"radio-dot\": string;\n sm: string;\n md: string;\n lg: string;\n \"radio-label\": string;\n \"radio-label-disabled\": string;\n \"radio-description\": string;\n \"radio-description-error\": string;\n};\n\nexport default styles;\n"
|
|
5009
4275
|
},
|
|
5010
4276
|
"scroll": {
|
|
5011
|
-
"tsx": "\"use client\";\n\nimport React, { useRef, useLayoutEffect, useState, useCallback, useEffect } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Scroll.module.css\";\n\nexport interface ScrollStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n horizontal?: StyleValue;\n vertical?: StyleValue;\n}\n\nexport type ScrollStylesProp = StylesProp<ScrollStyleSlots>;\n\nexport interface ScrollProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Content to render inside the scroll container */\n children: React.ReactNode;\n /** Maximum height before scrolling becomes active */\n maxHeight?: string;\n /** Maximum width before scrolling becomes active */\n maxWidth?: string;\n /** Scroll direction */\n direction?: \"vertical\" | \"horizontal\";\n /** Padding on the top and bottom of the scrollbar track in pixels */\n paddingY?: string | number;\n /** Whether to apply a fade mask at the top and bottom scroll edges */\n fadeY?: boolean;\n /** Pixels scrolled before the fade mask begins to appear */\n fadeDistance?: number;\n /** Percentage of container height used for the fade gradient */\n fadeSize?: number;\n /** Whether to render the custom scrollbar; when false, renders children without scroll */\n enabled?: boolean;\n /** Whether to hide the scrollbar when not actively scrolling */\n hide?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ScrollStylesProp;\n}\n\nconst resolveScrollBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'track',\n 'thumb',\n 'horizontal',\n 'vertical'\n] as const);\n\nconst Scroll = React.forwardRef<HTMLDivElement, ScrollProps>(\n (\n {\n children,\n className,\n maxHeight = \"100%\",\n maxWidth = \"100%\",\n direction = \"vertical\",\n paddingY = 4,\n fadeY = false,\n fadeDistance = 5,\n fadeSize = 4,\n enabled = true,\n hide = true,\n styles, // Destructure the new styles prop\n ...props\n },\n ref\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const internalContentRef = useRef<HTMLDivElement>(null);\n const contentRef = internalContentRef;\n const thumbRef = useRef<HTMLDivElement>(null);\n const childrenRef = useRef(children);\n const mergedRef = useMergedRef(ref, containerRef);\n\n const resolved = resolveScrollBaseStyles(styles); // Resolve the styles\n\n const [needsScrollbar, setNeedsScrollbar] = useState(false);\n const [isHoveredRight, setIsHoveredRight] = useState(false);\n const [thumbSize, setThumbSize] = useState(0);\n const [thumbPosition, setThumbPosition] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n const [dragStart, setDragStart] = useState({ origin: 0, scrollOrigin: 0 });\n const [isScrolling, setIsScrolling] = useState(false);\n const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n const updateScrollbar = useCallback(() => {\n if (!containerRef.current || !contentRef.current) return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n const paddingYValue = paddingY ? (typeof paddingY === 'number' ? paddingY : parseInt(paddingY)) : 0;\n\n if (direction === \"horizontal\") {\n const containerWidth = container.clientWidth;\n const contentWidth = content.scrollWidth || containerWidth;\n const scrollLeft = content.scrollLeft;\n\n const needs = contentWidth > containerWidth;\n setNeedsScrollbar(needs);\n\n const scrollRatio = containerWidth / Math.max(1, contentWidth);\n const newThumbWidth = Math.max(20, Math.min(containerWidth, containerWidth * scrollRatio));\n const scrollProgress = needs ? scrollLeft / (contentWidth - containerWidth) : 0;\n const maxThumbLeft = containerWidth - newThumbWidth;\n const newThumbLeft = scrollProgress * maxThumbLeft;\n\n setThumbSize(newThumbWidth);\n setThumbPosition(newThumbLeft);\n } else {\n const containerHeight = container.clientHeight;\n const contentHeight = content.scrollHeight || containerHeight;\n const scrollTop = content.scrollTop;\n const trackHeight = containerHeight - (paddingYValue * 2);\n\n const needs = contentHeight > containerHeight;\n setNeedsScrollbar(needs);\n\n const scrollRatio = trackHeight / Math.max(1, contentHeight);\n const newThumbHeight = Math.max(20, Math.min(trackHeight, trackHeight * scrollRatio));\n const scrollProgress = needs ? scrollTop / (contentHeight - containerHeight) : 0;\n const maxThumbTop = trackHeight - newThumbHeight;\n const newThumbTop = scrollProgress * maxThumbTop;\n\n setThumbSize(newThumbHeight);\n setThumbPosition(newThumbTop);\n\n if (fadeY && needs) {\n const maxScroll = contentHeight - containerHeight;\n const topP = Math.min(1, Math.max(0, scrollTop / fadeDistance));\n const botP = Math.min(1, Math.max(0, (maxScroll - scrollTop) / fadeDistance));\n const gradient = `linear-gradient(to bottom, transparent 0%, black ${topP * fadeSize}%, black ${100 - botP * fadeSize}%, transparent 100%)`;\n content.style.maskImage = gradient;\n content.style.webkitMaskImage = gradient;\n } else {\n content.style.maskImage = \"\";\n content.style.webkitMaskImage = \"\";\n }\n }\n }, [contentRef, direction, paddingY, fadeY, fadeDistance, fadeSize]);\n\n const handleScroll = useCallback(() => {\n updateScrollbar();\n setIsScrolling(true);\n if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = setTimeout(() => {\n setIsScrolling(false);\n }, 1500);\n }, [updateScrollbar]);\n\n const handleContainerMouseMove = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!containerRef.current) return;\n\n const rect = containerRef.current.getBoundingClientRect();\n let newIsHovered = false;\n\n if (direction === \"horizontal\") {\n const mouseY = e.clientY - rect.top;\n const hoverZone = 20;\n newIsHovered = mouseY > rect.height - hoverZone;\n } else {\n const mouseX = e.clientX - rect.left;\n const hoverZone = 20;\n newIsHovered = mouseX > rect.width - hoverZone;\n }\n\n if (newIsHovered !== isHoveredRight) {\n setIsHoveredRight(newIsHovered);\n }\n },\n [isHoveredRight, direction]\n );\n\n const handleContainerMouseLeave = useCallback(() => {\n setIsHoveredRight(false);\n }, []);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!contentRef.current) return;\n e.preventDefault();\n setIsDragging(true);\n if (direction === \"horizontal\") {\n setDragStart({\n origin: e.clientX,\n scrollOrigin: contentRef.current.scrollLeft,\n });\n } else {\n setDragStart({\n origin: e.clientY,\n scrollOrigin: contentRef.current.scrollTop,\n });\n }\n },\n [contentRef, direction]\n );\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!isDragging || !contentRef.current || !containerRef.current) return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n\n if (direction === \"horizontal\") {\n const deltaX = e.clientX - dragStart.origin;\n const containerWidth = container.clientWidth;\n const contentWidth = content.scrollWidth;\n const maxScroll = contentWidth - containerWidth;\n const scrollRatio = maxScroll / (containerWidth - thumbSize);\n const newScrollLeft = Math.max(\n 0,\n Math.min(\n maxScroll,\n dragStart.scrollOrigin + deltaX * scrollRatio\n )\n );\n\n content.scrollLeft = newScrollLeft;\n } else {\n const deltaY = e.clientY - dragStart.origin;\n const containerHeight = container.clientHeight;\n const contentHeight = content.scrollHeight;\n const maxScroll = contentHeight - containerHeight;\n const scrollRatio = maxScroll / (containerHeight - thumbSize);\n const newScrollTop = Math.max(\n 0,\n Math.min(\n maxScroll,\n dragStart.scrollOrigin + deltaY * scrollRatio\n )\n );\n\n content.scrollTop = newScrollTop;\n }\n },\n [isDragging, dragStart, thumbSize, contentRef, direction]\n );\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n }, []);\n\n const handleTrackClick = useCallback(\n (e: React.MouseEvent) => {\n if (\n !containerRef.current ||\n !contentRef.current ||\n !thumbRef.current\n )\n return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n const rect = container.getBoundingClientRect();\n const thumbRect = thumbRef.current.getBoundingClientRect();\n const paddingYValue = paddingY ? (typeof paddingY === 'number' ? paddingY : parseInt(paddingY)) : 0;\n\n if (direction === \"horizontal\") {\n const clickX = e.clientX - rect.left;\n const relativeThumbLeft = thumbRect.left - rect.left;\n const relativeThumbRight = thumbRect.right - rect.left;\n\n if (clickX >= relativeThumbLeft && clickX <= relativeThumbRight)\n return;\n\n const containerWidth = container.clientWidth;\n const contentWidth = content.scrollWidth;\n const maxScroll = contentWidth - containerWidth;\n\n const newThumbWidth = Math.max(\n 20,\n containerWidth * (containerWidth / contentWidth)\n );\n const targetThumbCenter = clickX;\n const targetThumbLeft = targetThumbCenter - newThumbWidth / 2;\n const maxThumbLeft = containerWidth - newThumbWidth;\n const clampedThumbLeft = Math.max(\n 0,\n Math.min(maxThumbLeft, targetThumbLeft)\n );\n\n const scrollProgress = clampedThumbLeft / maxThumbLeft;\n const targetScrollLeft = scrollProgress * maxScroll;\n\n content.scrollLeft = Math.max(\n 0,\n Math.min(maxScroll, targetScrollLeft)\n );\n\n setIsDragging(true);\n setDragStart({\n origin: e.clientX,\n scrollOrigin: content.scrollLeft,\n });\n } else {\n const clickY = e.clientY - rect.top - paddingYValue;\n const relativeThumbTop = thumbRect.top - rect.top - paddingYValue;\n const relativeThumbBottom = thumbRect.bottom - rect.top - paddingYValue;\n\n if (clickY >= relativeThumbTop && clickY <= relativeThumbBottom)\n return;\n\n const containerHeight = container.clientHeight;\n const contentHeight = content.scrollHeight;\n const maxScroll = contentHeight - containerHeight;\n const trackHeight = containerHeight - (paddingYValue * 2);\n\n const newThumbHeight = Math.max(\n 20,\n trackHeight * (trackHeight / contentHeight)\n );\n const targetThumbCenter = clickY;\n const targetThumbTop = targetThumbCenter - newThumbHeight / 2;\n const maxThumbTop = trackHeight - newThumbHeight;\n const clampedThumbTop = Math.max(\n 0,\n Math.min(maxThumbTop, targetThumbTop)\n );\n\n const scrollProgress = clampedThumbTop / maxThumbTop;\n const targetScrollTop = scrollProgress * maxScroll;\n\n content.scrollTop = Math.max(\n 0,\n Math.min(maxScroll, targetScrollTop)\n );\n\n setIsDragging(true);\n setDragStart({\n origin: e.clientY,\n scrollOrigin: content.scrollTop,\n });\n }\n },\n [contentRef, direction, paddingY]\n );\n\n const handleWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!contentRef.current) return;\n if (direction !== \"horizontal\") return;\n\n e.preventDefault();\n const scrollAmount = e.deltaY || e.deltaX;\n const content = contentRef.current;\n const containerWidth = content.clientWidth;\n const contentWidth = content.scrollWidth;\n const maxScroll = contentWidth - containerWidth;\n\n const newScrollLeft = Math.max(\n 0,\n Math.min(maxScroll, content.scrollLeft + scrollAmount)\n );\n content.scrollLeft = newScrollLeft;\n },\n [contentRef, direction]\n );\n\n useLayoutEffect(() => {\n updateScrollbar();\n\n const resizeObserver = new ResizeObserver(() => {\n requestAnimationFrame(updateScrollbar);\n });\n\n const mutationObserver = new MutationObserver(() => {\n requestAnimationFrame(updateScrollbar);\n });\n\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current);\n }\n\n if (contentRef.current) {\n resizeObserver.observe(contentRef.current);\n mutationObserver.observe(contentRef.current, {\n childList: true,\n subtree: true,\n });\n }\n\n return () => {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n };\n }, [updateScrollbar, contentRef, enabled]);\n\n useEffect(() => {\n if (childrenRef.current !== children) {\n childrenRef.current = children;\n const timeoutId = setTimeout(() => {\n updateScrollbar();\n }, 0);\n return () => clearTimeout(timeoutId);\n }\n }, [children, updateScrollbar]);\n\n useEffect(() => {\n if (isDragging) {\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n return () => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"\";\n };\n }\n }, [isDragging, handleMouseMove, handleMouseUp]);\n\n useEffect(() => {\n return () => {\n if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);\n };\n }, []);\n\n // When disabled, just render children without scroll functionality\n if (!enabled) {\n const { style: propsStyle, ...restProps } = props;\n return (\n <div\n ref={ref}\n className={cn('scroll', css.root, resolved.root, className)}\n style={{\n ...(direction === \"horizontal\"\n ? { width: \"100%\", maxWidth }\n : { height: \"100%\", maxHeight }),\n ...propsStyle,\n }}\n {...restProps}\n >\n {children}\n </div>\n );\n }\n\n const showOpacity = !hide ? 1 : (needsScrollbar && (isHoveredRight || isDragging || isScrolling) ? 1 : 0);\n\n if (direction === \"horizontal\") {\n const { style: propsStyle, ...restProps } = props;\n return (\n <div\n ref={mergedRef}\n className={cn('scroll', css.root, css.horizontal, className, resolved.root, resolved.horizontal)}\n style={{\n width: \"100%\",\n maxWidth,\n ...propsStyle,\n }}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-dragging={isDragging ? \"true\" : \"false\"}\n {...restProps}\n >\n <div\n ref={contentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n onWheel={handleWheel}\n style={{ maxWidth: \"inherit\" }}\n >\n {children}\n </div>\n\n <div\n className={cn(css.track, resolved.track)}\n data-hide={hide ? \"true\" : \"false\"}\n style={{\n opacity: showOpacity,\n pointerEvents: needsScrollbar ? \"auto\" : \"none\",\n }}\n onMouseDown={handleTrackClick}\n >\n {(needsScrollbar || !hide) && (\n <div\n ref={thumbRef}\n className={cn(css.thumb, resolved.thumb)}\n style={{\n width: `${thumbSize}px`,\n left: `${thumbPosition}px`,\n }}\n onMouseDown={handleMouseDown}\n />\n )}\n </div>\n </div>\n );\n }\n\n const { style: propsStyle, ...restProps } = props;\n const paddingYValue = paddingY ? (typeof paddingY === 'number' ? `${paddingY}px` : paddingY) : undefined;\n return (\n <div\n ref={mergedRef}\n className={cn('scroll', css.root, css.vertical, className, resolved.root, resolved.vertical)}\n style={{\n height: \"100%\",\n maxHeight,\n ...(paddingYValue ? { \"--scroll-padding-y\": paddingYValue } : {}),\n ...propsStyle,\n } as React.CSSProperties}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-dragging={isDragging ? \"true\" : \"false\"}\n {...restProps}\n >\n <div\n ref={contentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n style={{ maxHeight: \"inherit\" }}\n >\n {children}\n </div>\n\n <div\n className={cn(css.track, resolved.track)}\n data-hide={hide ? \"true\" : \"false\"}\n style={{\n opacity: showOpacity,\n pointerEvents: needsScrollbar ? \"auto\" : \"none\",\n }}\n onMouseDown={handleTrackClick}\n >\n {(needsScrollbar || !hide) && (\n <div\n ref={thumbRef}\n className={cn(css.thumb, resolved.thumb)}\n style={{\n height: `${thumbSize}px`,\n top: `${thumbPosition}px`,\n }}\n onMouseDown={handleMouseDown}\n />\n )}\n </div>\n </div>\n );\n }\n);\n\nScroll.displayName = \"Scroll\";\n\nfunction useMergedRef<T>(\n ...refs: (React.Ref<T> | undefined)[]\n): React.RefCallback<T> {\n return (value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\")\n (ref as React.MutableRefObject<T | null>).current = value;\n });\n };\n}\n\nexport { Scroll };\n",
|
|
5012
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply relative;\n }\n\n .vertical {\n --scrollbar-width: 12px;\n min-height: 0;\n }\n\n .horizontal { --scrollbar-height: 12px; }\n\n .content {\n @apply h-full w-full;\n overflow: auto;\n }\n\n .vertical .content {\n overflow-y: auto;\n overflow-x: hidden;\n
|
|
4277
|
+
"tsx": "\"use client\";\n\nimport React, { useRef, useLayoutEffect, useState, useCallback, useEffect } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Scroll.module.css\";\n\ninterface ScrollStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n horizontal?: StyleValue;\n vertical?: StyleValue;\n}\n\ntype ScrollStylesProp = StylesProp<ScrollStyleSlots>;\n\nexport interface ScrollProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Content to render inside the scroll container */\n children: React.ReactNode;\n /** Maximum height before scrolling becomes active */\n maxHeight?: string;\n /** Maximum width before scrolling becomes active */\n maxWidth?: string;\n /** Scroll direction */\n direction?: \"vertical\" | \"horizontal\";\n /** Padding on the top and bottom of the scrollbar track in pixels */\n paddingY?: string | number;\n /** Whether to apply a fade mask at the top and bottom scroll edges */\n \"fade-y\"?: boolean;\n /** Pixels scrolled before the fade mask begins to appear */\n fadeDistance?: number;\n /** Percentage of container height used for the fade gradient */\n fadeSize?: number;\n /** Whether to render the custom scrollbar; when false, renders children without scroll */\n enabled?: boolean;\n /** Whether to hide the scrollbar when not actively scrolling */\n hide?: boolean;\n /** When true, the scrollbar sits inline displacing content; when false (default), it overlays the content */\n inset?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ScrollStylesProp;\n}\n\nconst resolveScrollBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'track',\n 'thumb',\n 'horizontal',\n 'vertical'\n] as const);\n\nconst Scroll = React.forwardRef<HTMLDivElement, ScrollProps>(\n (\n {\n children,\n className,\n maxHeight = \"100%\",\n maxWidth = \"100%\",\n direction = \"vertical\",\n paddingY = 4,\n \"fade-y\": fadeY = false,\n fadeDistance = 5,\n fadeSize = 4,\n enabled = true,\n hide = true,\n inset = false,\n styles, // Destructure the new styles prop\n ...props\n },\n ref\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const internalContentRef = useRef<HTMLDivElement>(null);\n const contentRef = internalContentRef;\n const thumbRef = useRef<HTMLDivElement>(null);\n const childrenRef = useRef(children);\n const mergedRef = useMergedRef(ref, containerRef);\n\n const resolved = resolveScrollBaseStyles(styles); // Resolve the styles\n\n const [needsScrollbar, setNeedsScrollbar] = useState(false);\n const [isHoveredRight, setIsHoveredRight] = useState(false);\n const [thumbSize, setThumbSize] = useState(0);\n const [thumbPosition, setThumbPosition] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n const [dragStart, setDragStart] = useState({ origin: 0, scrollOrigin: 0 });\n const [isScrolling, setIsScrolling] = useState(false);\n const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n const updateScrollbar = useCallback(() => {\n if (!containerRef.current || !contentRef.current) return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n const paddingYValue = paddingY ? (typeof paddingY === 'number' ? paddingY : parseInt(paddingY)) : 0;\n\n if (direction === \"horizontal\") {\n const containerWidth = container.clientWidth;\n const contentWidth = content.scrollWidth || containerWidth;\n const scrollLeft = content.scrollLeft;\n\n const needs = contentWidth > containerWidth;\n setNeedsScrollbar(needs);\n\n const scrollRatio = containerWidth / Math.max(1, contentWidth);\n const newThumbWidth = Math.max(20, Math.min(containerWidth, containerWidth * scrollRatio));\n const scrollProgress = needs ? scrollLeft / (contentWidth - containerWidth) : 0;\n const maxThumbLeft = containerWidth - newThumbWidth;\n const newThumbLeft = scrollProgress * maxThumbLeft;\n\n setThumbSize(newThumbWidth);\n setThumbPosition(newThumbLeft);\n } else {\n const containerHeight = container.clientHeight;\n const contentHeight = content.scrollHeight || containerHeight;\n const scrollTop = content.scrollTop;\n const trackHeight = containerHeight - (paddingYValue * 2);\n\n const needs = contentHeight > containerHeight;\n setNeedsScrollbar(needs);\n\n const scrollRatio = trackHeight / Math.max(1, contentHeight);\n const newThumbHeight = Math.max(20, Math.min(trackHeight, trackHeight * scrollRatio));\n const scrollProgress = needs ? scrollTop / (contentHeight - containerHeight) : 0;\n const maxThumbTop = trackHeight - newThumbHeight;\n const newThumbTop = scrollProgress * maxThumbTop;\n\n setThumbSize(newThumbHeight);\n setThumbPosition(newThumbTop);\n\n if (fadeY && needs) {\n const maxScroll = contentHeight - containerHeight;\n const topP = Math.min(1, Math.max(0, scrollTop / fadeDistance));\n const botP = Math.min(1, Math.max(0, (maxScroll - scrollTop) / fadeDistance));\n const gradient = `linear-gradient(to bottom, transparent 0%, black ${topP * fadeSize}%, black ${100 - botP * fadeSize}%, transparent 100%)`;\n content.style.maskImage = gradient;\n content.style.webkitMaskImage = gradient;\n } else {\n content.style.maskImage = \"\";\n content.style.webkitMaskImage = \"\";\n }\n }\n }, [contentRef, direction, paddingY, fadeY, fadeDistance, fadeSize]);\n\n const handleScroll = useCallback(() => {\n updateScrollbar();\n setIsScrolling(true);\n if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = setTimeout(() => {\n setIsScrolling(false);\n }, 1500);\n }, [updateScrollbar]);\n\n const handleContainerMouseMove = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!containerRef.current) return;\n\n const rect = containerRef.current.getBoundingClientRect();\n let newIsHovered = false;\n\n if (direction === \"horizontal\") {\n const mouseY = e.clientY - rect.top;\n const hoverZone = 20;\n newIsHovered = mouseY > rect.height - hoverZone;\n } else {\n const mouseX = e.clientX - rect.left;\n const hoverZone = 20;\n newIsHovered = mouseX > rect.width - hoverZone;\n }\n\n if (newIsHovered !== isHoveredRight) {\n setIsHoveredRight(newIsHovered);\n }\n },\n [isHoveredRight, direction]\n );\n\n const handleContainerMouseLeave = useCallback(() => {\n setIsHoveredRight(false);\n }, []);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!contentRef.current) return;\n e.preventDefault();\n setIsDragging(true);\n if (direction === \"horizontal\") {\n setDragStart({\n origin: e.clientX,\n scrollOrigin: contentRef.current.scrollLeft,\n });\n } else {\n setDragStart({\n origin: e.clientY,\n scrollOrigin: contentRef.current.scrollTop,\n });\n }\n },\n [contentRef, direction]\n );\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!isDragging || !contentRef.current || !containerRef.current) return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n\n if (direction === \"horizontal\") {\n const deltaX = e.clientX - dragStart.origin;\n const containerWidth = container.clientWidth;\n const contentWidth = content.scrollWidth;\n const maxScroll = contentWidth - containerWidth;\n const scrollRatio = maxScroll / (containerWidth - thumbSize);\n const newScrollLeft = Math.max(\n 0,\n Math.min(\n maxScroll,\n dragStart.scrollOrigin + deltaX * scrollRatio\n )\n );\n\n content.scrollLeft = newScrollLeft;\n } else {\n const deltaY = e.clientY - dragStart.origin;\n const containerHeight = container.clientHeight;\n const contentHeight = content.scrollHeight;\n const maxScroll = contentHeight - containerHeight;\n const scrollRatio = maxScroll / (containerHeight - thumbSize);\n const newScrollTop = Math.max(\n 0,\n Math.min(\n maxScroll,\n dragStart.scrollOrigin + deltaY * scrollRatio\n )\n );\n\n content.scrollTop = newScrollTop;\n }\n },\n [isDragging, dragStart, thumbSize, contentRef, direction]\n );\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n }, []);\n\n const handleTrackClick = useCallback(\n (e: React.MouseEvent) => {\n if (\n !containerRef.current ||\n !contentRef.current ||\n !thumbRef.current\n )\n return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n const rect = container.getBoundingClientRect();\n const thumbRect = thumbRef.current.getBoundingClientRect();\n const paddingYValue = paddingY ? (typeof paddingY === 'number' ? paddingY : parseInt(paddingY)) : 0;\n\n if (direction === \"horizontal\") {\n const clickX = e.clientX - rect.left;\n const relativeThumbLeft = thumbRect.left - rect.left;\n const relativeThumbRight = thumbRect.right - rect.left;\n\n if (clickX >= relativeThumbLeft && clickX <= relativeThumbRight)\n return;\n\n const containerWidth = container.clientWidth;\n const contentWidth = content.scrollWidth;\n const maxScroll = contentWidth - containerWidth;\n\n const newThumbWidth = Math.max(\n 20,\n containerWidth * (containerWidth / contentWidth)\n );\n const targetThumbCenter = clickX;\n const targetThumbLeft = targetThumbCenter - newThumbWidth / 2;\n const maxThumbLeft = containerWidth - newThumbWidth;\n const clampedThumbLeft = Math.max(\n 0,\n Math.min(maxThumbLeft, targetThumbLeft)\n );\n\n const scrollProgress = clampedThumbLeft / maxThumbLeft;\n const targetScrollLeft = scrollProgress * maxScroll;\n\n content.scrollLeft = Math.max(\n 0,\n Math.min(maxScroll, targetScrollLeft)\n );\n\n setIsDragging(true);\n setDragStart({\n origin: e.clientX,\n scrollOrigin: content.scrollLeft,\n });\n } else {\n const clickY = e.clientY - rect.top - paddingYValue;\n const relativeThumbTop = thumbRect.top - rect.top - paddingYValue;\n const relativeThumbBottom = thumbRect.bottom - rect.top - paddingYValue;\n\n if (clickY >= relativeThumbTop && clickY <= relativeThumbBottom)\n return;\n\n const containerHeight = container.clientHeight;\n const contentHeight = content.scrollHeight;\n const maxScroll = contentHeight - containerHeight;\n const trackHeight = containerHeight - (paddingYValue * 2);\n\n const newThumbHeight = Math.max(\n 20,\n trackHeight * (trackHeight / contentHeight)\n );\n const targetThumbCenter = clickY;\n const targetThumbTop = targetThumbCenter - newThumbHeight / 2;\n const maxThumbTop = trackHeight - newThumbHeight;\n const clampedThumbTop = Math.max(\n 0,\n Math.min(maxThumbTop, targetThumbTop)\n );\n\n const scrollProgress = clampedThumbTop / maxThumbTop;\n const targetScrollTop = scrollProgress * maxScroll;\n\n content.scrollTop = Math.max(\n 0,\n Math.min(maxScroll, targetScrollTop)\n );\n\n setIsDragging(true);\n setDragStart({\n origin: e.clientY,\n scrollOrigin: content.scrollTop,\n });\n }\n },\n [contentRef, direction, paddingY]\n );\n\n const handleWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!contentRef.current) return;\n if (direction !== \"horizontal\") return;\n\n e.preventDefault();\n const scrollAmount = e.deltaY || e.deltaX;\n const content = contentRef.current;\n const containerWidth = content.clientWidth;\n const contentWidth = content.scrollWidth;\n const maxScroll = contentWidth - containerWidth;\n\n const newScrollLeft = Math.max(\n 0,\n Math.min(maxScroll, content.scrollLeft + scrollAmount)\n );\n content.scrollLeft = newScrollLeft;\n },\n [contentRef, direction]\n );\n\n useLayoutEffect(() => {\n updateScrollbar();\n\n const resizeObserver = new ResizeObserver(() => {\n requestAnimationFrame(updateScrollbar);\n });\n\n const mutationObserver = new MutationObserver(() => {\n requestAnimationFrame(updateScrollbar);\n });\n\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current);\n }\n\n if (contentRef.current) {\n resizeObserver.observe(contentRef.current);\n mutationObserver.observe(contentRef.current, {\n childList: true,\n subtree: true,\n });\n }\n\n return () => {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n };\n }, [updateScrollbar, contentRef, enabled]);\n\n useEffect(() => {\n if (childrenRef.current !== children) {\n childrenRef.current = children;\n const timeoutId = setTimeout(() => {\n updateScrollbar();\n }, 0);\n return () => clearTimeout(timeoutId);\n }\n }, [children, updateScrollbar]);\n\n useEffect(() => {\n if (isDragging) {\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n return () => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"\";\n };\n }\n }, [isDragging, handleMouseMove, handleMouseUp]);\n\n useEffect(() => {\n return () => {\n if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);\n };\n }, []);\n\n // When disabled, just render children without scroll functionality\n if (!enabled) {\n const { style: propsStyle, ...restProps } = props;\n return (\n <div\n ref={ref}\n className={cn('scroll', css.root, resolved.root, className)}\n style={{\n ...(direction === \"horizontal\"\n ? { width: \"100%\", maxWidth }\n : { height: \"100%\", maxHeight }),\n ...propsStyle,\n }}\n {...restProps}\n >\n {children}\n </div>\n );\n }\n\n const showOpacity = !hide ? 1 : (needsScrollbar && (isHoveredRight || isDragging || isScrolling) ? 1 : 0);\n\n if (direction === \"horizontal\") {\n const { style: propsStyle, ...restProps } = props;\n return (\n <div\n ref={mergedRef}\n className={cn('scroll', css.root, css.horizontal, className, resolved.root, resolved.horizontal)}\n style={{\n width: \"100%\",\n maxWidth,\n ...propsStyle,\n }}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-dragging={isDragging ? \"true\" : \"false\"}\n data-inset={inset ? \"true\" : \"false\"}\n {...restProps}\n >\n <div\n ref={contentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n onWheel={handleWheel}\n style={{ maxWidth: \"inherit\" }}\n >\n {children}\n </div>\n\n <div\n className={cn(css.track, resolved.track)}\n data-hide={hide ? \"true\" : \"false\"}\n style={{\n opacity: showOpacity,\n pointerEvents: needsScrollbar ? \"auto\" : \"none\",\n }}\n onMouseDown={handleTrackClick}\n >\n {(needsScrollbar || !hide) && (\n <div\n ref={thumbRef}\n className={cn(css.thumb, resolved.thumb)}\n style={{\n width: `${thumbSize}px`,\n left: `${thumbPosition}px`,\n }}\n onMouseDown={handleMouseDown}\n />\n )}\n </div>\n </div>\n );\n }\n\n const { style: propsStyle, ...restProps } = props;\n const paddingYValue = paddingY ? (typeof paddingY === 'number' ? `${paddingY}px` : paddingY) : undefined;\n return (\n <div\n ref={mergedRef}\n className={cn('scroll', css.root, css.vertical, className, resolved.root, resolved.vertical)}\n style={{\n height: \"100%\",\n maxHeight,\n ...(paddingYValue ? { \"--scroll-padding-y\": paddingYValue } : {}),\n ...propsStyle,\n } as React.CSSProperties}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-dragging={isDragging ? \"true\" : \"false\"}\n data-inset={inset ? \"true\" : \"false\"}\n {...restProps}\n >\n <div\n ref={contentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n style={{ maxHeight: \"inherit\" }}\n >\n {children}\n </div>\n\n <div\n className={cn(css.track, resolved.track)}\n data-hide={hide ? \"true\" : \"false\"}\n style={{\n opacity: showOpacity,\n pointerEvents: needsScrollbar ? \"auto\" : \"none\",\n }}\n onMouseDown={handleTrackClick}\n >\n {(needsScrollbar || !hide) && (\n <div\n ref={thumbRef}\n className={cn(css.thumb, resolved.thumb)}\n style={{\n height: `${thumbSize}px`,\n top: `${thumbPosition}px`,\n }}\n onMouseDown={handleMouseDown}\n />\n )}\n </div>\n </div>\n );\n }\n);\n\nScroll.displayName = \"Scroll\";\n\nfunction useMergedRef<T>(\n ...refs: (React.Ref<T> | undefined)[]\n): React.RefCallback<T> {\n return (value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\")\n (ref as React.MutableRefObject<T | null>).current = value;\n });\n };\n}\n\nexport { Scroll };\n",
|
|
4278
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply relative;\n }\n\n .vertical {\n --scrollbar-width: 12px;\n min-height: 0;\n }\n\n .horizontal { --scrollbar-height: 12px; }\n\n .content {\n @apply h-full w-full;\n overflow: auto;\n }\n\n .vertical .content {\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .vertical[data-inset=\"true\"] .content {\n padding-right: 16px;\n }\n\n .horizontal .content {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .horizontal[data-inset=\"true\"] .content {\n padding-bottom: 16px;\n }\n\n .vertical .content::-webkit-scrollbar,\n .horizontal .content::-webkit-scrollbar { display: none; }\n\n .track {\n @apply absolute;\n z-index: 10;\n }\n\n .track[data-hide=\"true\"] {\n transition-property: opacity;\n transition-duration: 200ms;\n }\n\n .vertical .track {\n right: 4px;\n top: var(--scroll-padding-y, 0);\n width: 12px;\n height: calc(100% - 2 * var(--scroll-padding-y, 0));\n background-color: var(--track-background);\n box-sizing: border-box;\n }\n\n .horizontal .track {\n bottom: 2px;\n left: 0;\n height: 12px;\n width: 100%;\n background-color: var(--track-background);\n }\n\n .thumb {\n position: absolute;\n border-radius: calc(var(--radius-xs) * 0.80);\n background-color: var(--thumb-background);\n transition-property: background-color, width, height;\n transition-duration: 150ms;\n }\n\n .thumb:hover { background-color: var(--thumb-hover-background); }\n\n .root[data-dragging=\"true\"] .thumb {\n background-color: var(--thumb-dragging-background);\n }\n\n .vertical .thumb {\n width: 6px;\n margin-left: 6px;\n transition-property: background-color, width, margin-left;\n transition-duration: 150ms;\n }\n\n .vertical .thumb:hover,\n .vertical[data-dragging=\"true\"] .thumb {\n width: 8px;\n margin-left: 4px;\n }\n\n .horizontal .thumb {\n height: 6px;\n margin-top: 6px;\n transition-property: background-color, height, margin-top;\n transition-duration: 150ms;\n }\n\n .horizontal .thumb:hover,\n .horizontal[data-dragging=\"true\"] .thumb {\n height: 8px;\n margin-top: 4px;\n }\n}\n",
|
|
5013
4279
|
"cssTypes": "export const root: string;\nexport const vertical: string;\nexport const horizontal: string;\nexport const content: string;\nexport const track: string;\nexport const thumb: string;\n"
|
|
5014
4280
|
},
|
|
5015
4281
|
"select": {
|
|
5016
|
-
"tsx": "import * as React from \"react\"\nimport { Key } from \"@react-types/shared\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport styles from \"./Select.module.css\"\nimport { useListNavigation, useMergedRef, handleListKeyDown, type ItemData } from \"./Select.shared\"\n\nexport type SelectItemData = ItemData\n\nexport type SelectTriggerMode = \"click\" | \"hover\"\nexport type SelectMode = \"single\" | \"multiple\"\n\nexport interface SelectStyleSlots {\n root?: StyleValue;\n}\n\nexport type SelectStylesProp = StylesProp<SelectStyleSlots>;\n\nexport interface SelectContextValue {\n isOpen: boolean\n setIsOpen: React.Dispatch<React.SetStateAction<boolean>>\n contentPlacement: \"top\" | \"bottom\"\n setContentPlacement: React.Dispatch<React.SetStateAction<\"top\" | \"bottom\">>\n triggerType: \"button\" | \"input\"\n mode: SelectMode\n selectedKey: Key | null\n selectedKeys?: Set<Key>\n selectedTextValue: string\n onSelect: (key: Key) => void\n onToggle?: (key: Key) => void\n triggerRef: React.MutableRefObject<HTMLElement | null>\n wrapperRef: React.MutableRefObject<HTMLElement | null>\n contentRef: React.MutableRefObject<HTMLElement | null>\n triggerProps: any\n isFocusVisible: boolean\n isPressed: boolean\n isHovered: boolean\n isDisabled: boolean\n items: SelectItemData[]\n registerItem: (key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => void\n unregisterItem: (key: Key) => void\n searchValue: string\n setSearchValue: React.Dispatch<React.SetStateAction<string>>\n filteredItems: SelectItemData[]\n visibleKeys: Set<Key>\n focusedKey: Key | null\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>\n navigateToNextItem: () => void\n navigateToPrevItem: () => void\n selectFocusedItem: () => void\n isFocusedItemSubmenu: () => boolean\n maxItems: number\n triggerMode: SelectTriggerMode\n handleHoverIntent: (isHovering: boolean) => void\n mouseMoveDetectedRef: React.MutableRefObject<boolean>\n filter?: (item: any) => boolean\n contentId: string\n}\n\nconst SelectContext = React.createContext<SelectContextValue | null>(null)\n\nexport function useSelectContext() {\n const context = React.useContext(SelectContext)\n if (!context) {\n throw new Error(\"Select component must be used within Select root\")\n }\n return context\n}\n\nexport interface SelectProps<T = any> extends React.PropsWithChildren {\n /** Selection mode: \"single\" for one item, \"multiple\" for multi-item selection */\n mode?: SelectMode\n /** External items array — used when items are provided as data rather than JSX */\n items?: Array<T>\n /** Controlled selected key for single-select mode */\n selectedKey?: Key | null\n /** Default selected key for uncontrolled single-select */\n defaultSelectedKey?: Key | null\n /** Controlled selected keys for multi-select mode */\n selectedKeys?: Key[]\n /** Default selected keys for uncontrolled multi-select */\n defaultSelectedKeys?: Key[]\n /** Default display text shown in the trigger when nothing is selected */\n defaultValue?: string | null\n /** Display text for the currently selected value — used for SSR/SSG to avoid\n * flash of placeholder before items register. Provide alongside selectedKey or\n * defaultSelectedKey so the correct label renders on the first pass. */\n valueLabel?: string\n /** Called when selection changes; receives a single key (single) or key array (multiple) */\n onSelectionChange?: (value: any) => void\n /** Disables the entire select and prevents interaction */\n isDisabled?: boolean\n /** Focuses the trigger automatically on mount */\n autoFocus?: boolean\n /** Maximum number of items visible before the dropdown scrolls */\n maxItems?: number\n /** Additional CSS class for the root wrapper */\n className?: string\n /** How the dropdown opens: \"click\" (default) or \"hover\" */\n trigger?: SelectTriggerMode\n /** Custom filter predicate applied to the items array */\n filter?: (item: T) => boolean\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SelectStylesProp;\n}\n\nconst resolveSelectBaseStyles = createStylesResolver(['root'] as const);\n\nconst Select = React.forwardRef<HTMLDivElement, SelectProps<any>>(\n (\n {\n mode = \"single\",\n items: propItems = [],\n selectedKey: controlledSelectedKey,\n defaultSelectedKey,\n selectedKeys: controlledSelectedKeys,\n defaultSelectedKeys = [],\n defaultValue,\n valueLabel,\n onSelectionChange,\n isDisabled = false,\n autoFocus = false,\n maxItems = 6,\n children,\n className,\n trigger: triggerMode = \"click\",\n filter,\n styles: stylesProp,\n },\n ref\n ) => {\n const triggerRef = React.useRef<HTMLElement>(null)\n const wrapperRef = React.useRef<HTMLElement>(null)\n const contentRef = React.useRef<HTMLElement>(null)\n const mouseMoveDetectedRef = React.useRef(true)\n const itemExtrasRef = React.useRef<Map<Key, { onSelect?: () => void; isSubmenuTrigger?: boolean }>>(new Map())\n const [isOpen, setIsOpen] = React.useState(false)\n const [contentPlacement, setContentPlacement] = React.useState<\"top\" | \"bottom\">(\"bottom\")\n const contentId = React.useId()\n const hoverTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const handleHoverIntent = React.useCallback((isHovering: boolean) => {\n if (triggerMode !== \"hover\" || isDisabled) return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n hoverTimeoutRef.current = null\n }\n\n if (isHovering) {\n setIsOpen(true)\n } else {\n hoverTimeoutRef.current = setTimeout(() => {\n setIsOpen(false)\n }, 100)\n }\n }, [triggerMode, isDisabled])\n\n React.useEffect(() => {\n if (!isOpen || triggerMode !== \"hover\" || isDisabled) return\n\n const handleMouseMove = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n const isOver = wrapperRef.current?.contains(target) ||\n contentRef.current?.contains(target)\n\n if (!isOver) {\n handleHoverIntent(false)\n } else {\n handleHoverIntent(true)\n }\n }\n\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [isOpen, triggerMode, isDisabled, handleHoverIntent])\n\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n const [uncontrolledSelectedKey, setUncontrolledSelectedKey] = React.useState<Key | null>(\n defaultSelectedKey ?? null\n )\n const [uncontrolledSelectedKeys, setUncontrolledSelectedKeys] = React.useState<Set<Key>>(\n new Set(defaultSelectedKeys)\n )\n const [selectedTextValue, setSelectedTextValue] = React.useState(valueLabel ?? defaultValue ?? \"\")\n const selectedKey = controlledSelectedKey !== undefined ? controlledSelectedKey : uncontrolledSelectedKey\n const selectedKeys = controlledSelectedKeys !== undefined ? new Set(controlledSelectedKeys) : uncontrolledSelectedKeys\n\n const nav = useListNavigation({\n isOpen,\n externalItems: propItems.length > 0 ? propItems : undefined,\n filter: filter ? (item: any) => filter({ ...item, label: item.textValue } as any) : undefined\n })\n\n const registerItem = React.useCallback((key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => {\n nav.registerItem(key, textValue, isDisabled)\n itemExtrasRef.current.set(key, { onSelect, isSubmenuTrigger })\n }, [nav.registerItem])\n\n const unregisterItem = React.useCallback((key: Key) => {\n nav.unregisterItem(key)\n itemExtrasRef.current.delete(key)\n }, [nav.unregisterItem])\n\n const isFocusedItemSubmenu = React.useCallback(() => {\n if (nav.focusedKey === null) return false\n return itemExtrasRef.current.get(nav.focusedKey)?.isSubmenuTrigger ?? false\n }, [nav.focusedKey])\n\n const onSelect = React.useCallback((key: Key) => {\n const item = nav.items.find(i => i.key === key)\n if (item) {\n setSelectedTextValue(item.textValue)\n }\n if (controlledSelectedKey === undefined) {\n setUncontrolledSelectedKey(key)\n }\n onSelectionChange?.(key)\n setIsOpen(false)\n nav.setSearchValue(\"\")\n }, [controlledSelectedKey, onSelectionChange, nav.items])\n\n const onToggle = React.useCallback((key: Key) => {\n const newKeys = new Set(selectedKeys)\n if (newKeys.has(key)) {\n newKeys.delete(key)\n } else {\n newKeys.add(key)\n }\n if (controlledSelectedKeys === undefined) {\n setUncontrolledSelectedKeys(newKeys)\n }\n onSelectionChange?.(Array.from(newKeys))\n }, [selectedKeys, controlledSelectedKeys, onSelectionChange])\n\n const selectFocusedItem = React.useCallback(() => {\n if (nav.focusedKey !== null) {\n const item = nav.enabledFilteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n if (extras?.onSelect) {\n extras.onSelect()\n } else if (mode === \"multiple\") {\n onToggle(nav.focusedKey)\n } else {\n onSelect(nav.focusedKey)\n }\n }\n }\n }, [nav.focusedKey, nav.enabledFilteredItems, onSelect, onToggle, mode])\n\n React.useEffect(() => {\n if (isOpen) {\n // Only initialize focusedKey if it's not already valid\n if (nav.focusedKey !== null && nav.visibleKeys.has(nav.focusedKey)) {\n const item = nav.filteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n return // Keep current keyboard focus, don't reset it\n }\n }\n\n const focusKey = mode === \"multiple\" && selectedKeys.size > 0\n ? Array.from(selectedKeys)[0]\n : selectedKey\n\n if (focusKey !== null && nav.visibleKeys.has(focusKey)) {\n const item = nav.filteredItems.find(item => item.key === focusKey)\n if (item && !item.isDisabled) {\n nav.setFocusedKey(focusKey)\n return\n }\n }\n if (nav.enabledFilteredItems.length > 0) {\n nav.setFocusedKey(nav.enabledFilteredItems[0].key)\n } else {\n nav.setFocusedKey(null)\n }\n }\n }, [isOpen, selectedKey, selectedKeys, nav.visibleKeys, nav.enabledFilteredItems, nav.filteredItems, mode, nav.focusedKey])\n\n const { buttonProps, isPressed } = useButton({\n isDisabled,\n onPress: (e) => {\n if (isDisabled) return\n // Keyboard interactions are handled by onKeyDown to prevent conflicts\n if (e.pointerType !== 'keyboard') {\n setIsOpen(prev => !prev)\n }\n },\n }, triggerRef)\n const { focusProps, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({\n isDisabled,\n onHoverStart: () => handleHoverIntent(true),\n onHoverEnd: () => handleHoverIntent(false),\n })\n\n const triggerProps = mergeProps(buttonProps, focusProps, hoverProps, {\n 'aria-haspopup': 'listbox' as const,\n 'aria-expanded': isOpen,\n 'aria-controls': isOpen ? contentId : undefined,\n 'aria-disabled': isDisabled || undefined,\n onKeyDown: (e: React.KeyboardEvent) => {\n if (!isOpen) {\n if (e.key === 'ArrowDown' || e.key === 'Enter' || (e.key === ' ' && !isDisabled)) {\n e.preventDefault()\n setIsOpen(true)\n }\n return\n }\n\n handleListKeyDown(e, {\n navigateNext: nav.navigateToNextItem,\n navigatePrev: nav.navigateToPrevItem,\n confirm: selectFocusedItem,\n close: () => {\n setIsOpen(false)\n nav.setSearchValue(\"\")\n triggerRef.current?.focus()\n },\n filteredItems: nav.filteredItems,\n setFocusedKey: nav.setFocusedKey,\n })\n },\n })\n\n React.useEffect(() => {\n if (autoFocus && triggerRef.current) {\n triggerRef.current.focus({ preventScroll: true })\n }\n }, [autoFocus])\n\n React.useEffect(() => {\n if (mode === \"single\") {\n if (selectedKey === null) {\n setSelectedTextValue(\"\")\n } else {\n const selectedItem = nav.items.find(item => item.key === selectedKey)\n if (selectedItem) {\n setSelectedTextValue(selectedItem.textValue)\n } else if (valueLabel !== undefined) {\n setSelectedTextValue(valueLabel)\n } else if (defaultValue !== undefined && defaultValue !== null) {\n setSelectedTextValue(defaultValue)\n }\n }\n }\n }, [selectedKey, nav.items, mode, defaultValue, valueLabel])\n\n const rootRef = useMergedRef<HTMLDivElement>(wrapperRef, ref)\n\n const childrenArray = React.Children.toArray(children)\n const trigger = childrenArray.find(child => React.isValidElement(child) && (\n (child.type as any)?.displayName === 'SelectTrigger' ||\n (child.type as any)?.displayName === 'SearchableTrigger'\n ))\n const contentItems = childrenArray.filter(child => React.isValidElement(child) && ((child.type as any)?.displayName === 'SelectContent' || (child.type as any)?.displayName === 'SearchableContent'))\n const otherContent = childrenArray.filter(child => !React.isValidElement(child) || (\n (child.type as any)?.displayName !== 'SelectTrigger' &&\n (child.type as any)?.displayName !== 'SearchableTrigger' &&\n (child.type as any)?.displayName !== 'SelectContent' &&\n (child.type as any)?.displayName !== 'SearchableContent'\n ))\n const triggerType = React.isValidElement(trigger) && (trigger.type as any)?.displayName === 'SearchableTrigger'\n ? 'input'\n : 'button'\n\n const resolvedStyles = resolveSelectBaseStyles(stylesProp);\n\n return (\n <SelectContext.Provider\n value={{\n isOpen,\n setIsOpen,\n contentPlacement,\n setContentPlacement,\n triggerType,\n mode,\n selectedKey,\n selectedKeys: mode === \"multiple\" ? selectedKeys : undefined,\n selectedTextValue,\n onSelect,\n onToggle: mode === \"multiple\" ? onToggle : undefined,\n triggerRef,\n wrapperRef,\n contentRef,\n triggerProps,\n isFocusVisible,\n isPressed,\n isHovered,\n isDisabled,\n items: nav.items,\n registerItem,\n unregisterItem,\n searchValue: nav.searchValue,\n setSearchValue: nav.setSearchValue,\n filteredItems: nav.filteredItems,\n visibleKeys: nav.visibleKeys,\n focusedKey: nav.focusedKey,\n setFocusedKey: nav.setFocusedKey,\n navigateToNextItem: nav.navigateToNextItem,\n navigateToPrevItem: nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n maxItems,\n triggerMode,\n handleHoverIntent,\n mouseMoveDetectedRef,\n filter,\n contentId,\n }}\n >\n <div\n ref={rootRef}\n className={cn('select', styles.select, className, resolvedStyles.root)}\n data-mode={mode}\n >\n {otherContent}\n {trigger}\n {contentItems}\n </div>\n </SelectContext.Provider>\n )\n }\n)\nSelect.displayName = \"Select\"\n\nexport { Select, SelectContext }\n",
|
|
5017
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n\n --padding-x: calc(var(--spacing) * 2.00);\n --padding-y: calc(var(--spacing) * 1.75);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--border-color);\n border-radius: var(--radius);\n font-size: var(--text-
|
|
5018
|
-
"cssTypes": "declare const styles: {\n select: string;\n trigger: string;\n
|
|
4282
|
+
"tsx": "import * as React from \"react\"\nimport { Key } from \"@react-types/shared\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport styles from \"./Select.module.css\"\nimport { useListNavigation, useMergedRef, handleListKeyDown, type ItemData } from \"./Select.shared\"\n\nexport type SelectItemData = ItemData\n\nexport type SelectTriggerMode = \"click\" | \"hover\"\nexport type SelectMode = \"single\" | \"multiple\"\n\ninterface SelectStyleSlots {\n root?: StyleValue;\n}\n\ntype SelectStylesProp = StylesProp<SelectStyleSlots>;\n\nexport interface SelectContextValue {\n isOpen: boolean\n setIsOpen: React.Dispatch<React.SetStateAction<boolean>>\n contentPlacement: \"top\" | \"bottom\"\n setContentPlacement: React.Dispatch<React.SetStateAction<\"top\" | \"bottom\">>\n triggerType: \"button\" | \"input\"\n mode: SelectMode\n selectedKey: Key | null\n selectedKeys?: Set<Key>\n selectedTextValue: string\n onSelect: (key: Key) => void\n onToggle?: (key: Key) => void\n triggerRef: React.MutableRefObject<HTMLElement | null>\n wrapperRef: React.MutableRefObject<HTMLElement | null>\n contentRef: React.MutableRefObject<HTMLElement | null>\n triggerProps: any\n isFocusVisible: boolean\n isPressed: boolean\n isHovered: boolean\n isDisabled: boolean\n items: SelectItemData[]\n registerItem: (key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => void\n unregisterItem: (key: Key) => void\n searchValue: string\n setSearchValue: React.Dispatch<React.SetStateAction<string>>\n filteredItems: SelectItemData[]\n visibleKeys: Set<Key>\n focusedKey: Key | null\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>\n navigateToNextItem: () => void\n navigateToPrevItem: () => void\n selectFocusedItem: () => void\n isFocusedItemSubmenu: () => boolean\n maxItems: number\n triggerMode: SelectTriggerMode\n handleHoverIntent: (isHovering: boolean) => void\n mouseMoveDetectedRef: React.MutableRefObject<boolean>\n filter?: (item: any) => boolean\n contentId: string\n}\n\nconst SelectContext = React.createContext<SelectContextValue | null>(null)\n\nexport function useSelectContext() {\n const context = React.useContext(SelectContext)\n if (!context) {\n throw new Error(\"Select component must be used within Select root\")\n }\n return context\n}\n\nexport interface SelectProps<T = any> extends React.PropsWithChildren {\n /** Selection mode: \"single\" for one item, \"multiple\" for multi-item selection */\n mode?: SelectMode\n /** External items array — used when items are provided as data rather than JSX */\n items?: Array<T>\n /** Controlled selected key for single-select mode */\n selectedKey?: Key | null\n /** Default selected key for uncontrolled single-select */\n defaultSelectedKey?: Key | null\n /** Controlled selected keys for multi-select mode */\n selectedKeys?: Key[]\n /** Default selected keys for uncontrolled multi-select */\n defaultSelectedKeys?: Key[]\n /** Default display text shown in the trigger when nothing is selected */\n defaultValue?: string | null\n /** Display text for the currently selected value — used for SSR/SSG to avoid\n * flash of placeholder before items register. Provide alongside selectedKey or\n * defaultSelectedKey so the correct label renders on the first pass. */\n valueLabel?: string\n /** Called when selection changes; receives a single key (single) or key array (multiple) */\n onSelectionChange?: (value: any) => void\n /** Disables the entire select and prevents interaction */\n isDisabled?: boolean\n /** Focuses the trigger automatically on mount */\n autoFocus?: boolean\n /** Maximum number of items visible before the dropdown scrolls */\n maxItems?: number\n /** Additional CSS class for the root wrapper */\n className?: string\n /** How the dropdown opens: \"click\" (default) or \"hover\" */\n trigger?: SelectTriggerMode\n /** Custom filter predicate applied to the items array */\n filter?: (item: T) => boolean\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SelectStylesProp;\n}\n\nconst resolveSelectBaseStyles = createStylesResolver(['root'] as const);\n\nconst Select = React.forwardRef<HTMLDivElement, SelectProps<any>>(\n (\n {\n mode = \"single\",\n items: propItems = [],\n selectedKey: controlledSelectedKey,\n defaultSelectedKey,\n selectedKeys: controlledSelectedKeys,\n defaultSelectedKeys = [],\n defaultValue,\n valueLabel,\n onSelectionChange,\n isDisabled = false,\n autoFocus = false,\n maxItems = 6,\n children,\n className,\n trigger: triggerMode = \"click\",\n filter,\n styles: stylesProp,\n },\n ref\n ) => {\n const triggerRef = React.useRef<HTMLElement>(null)\n const wrapperRef = React.useRef<HTMLElement>(null)\n const contentRef = React.useRef<HTMLElement>(null)\n const mouseMoveDetectedRef = React.useRef(true)\n const itemExtrasRef = React.useRef<Map<Key, { onSelect?: () => void; isSubmenuTrigger?: boolean }>>(new Map())\n const [isOpen, setIsOpen] = React.useState(false)\n const [contentPlacement, setContentPlacement] = React.useState<\"top\" | \"bottom\">(\"bottom\")\n const contentId = React.useId()\n const hoverTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const handleHoverIntent = React.useCallback((isHovering: boolean) => {\n if (triggerMode !== \"hover\" || isDisabled) return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n hoverTimeoutRef.current = null\n }\n\n if (isHovering) {\n setIsOpen(true)\n } else {\n hoverTimeoutRef.current = setTimeout(() => {\n setIsOpen(false)\n }, 100)\n }\n }, [triggerMode, isDisabled])\n\n React.useEffect(() => {\n if (!isOpen || triggerMode !== \"hover\" || isDisabled) return\n\n const handleMouseMove = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n const isOver = wrapperRef.current?.contains(target) ||\n contentRef.current?.contains(target)\n\n if (!isOver) {\n handleHoverIntent(false)\n } else {\n handleHoverIntent(true)\n }\n }\n\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [isOpen, triggerMode, isDisabled, handleHoverIntent])\n\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n const [uncontrolledSelectedKey, setUncontrolledSelectedKey] = React.useState<Key | null>(\n defaultSelectedKey ?? null\n )\n const [uncontrolledSelectedKeys, setUncontrolledSelectedKeys] = React.useState<Set<Key>>(\n new Set(defaultSelectedKeys)\n )\n const [selectedTextValue, setSelectedTextValue] = React.useState(valueLabel ?? defaultValue ?? \"\")\n const selectedKey = controlledSelectedKey !== undefined ? controlledSelectedKey : uncontrolledSelectedKey\n const selectedKeys = controlledSelectedKeys !== undefined ? new Set(controlledSelectedKeys) : uncontrolledSelectedKeys\n\n const nav = useListNavigation({\n isOpen,\n externalItems: propItems.length > 0 ? propItems : undefined,\n filter: filter ? (item: any) => filter({ ...item, label: item.textValue } as any) : undefined\n })\n\n const registerItem = React.useCallback((key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => {\n nav.registerItem(key, textValue, isDisabled)\n itemExtrasRef.current.set(key, { onSelect, isSubmenuTrigger })\n }, [nav.registerItem])\n\n const unregisterItem = React.useCallback((key: Key) => {\n nav.unregisterItem(key)\n itemExtrasRef.current.delete(key)\n }, [nav.unregisterItem])\n\n const isFocusedItemSubmenu = React.useCallback(() => {\n if (nav.focusedKey === null) return false\n return itemExtrasRef.current.get(nav.focusedKey)?.isSubmenuTrigger ?? false\n }, [nav.focusedKey])\n\n const onSelect = React.useCallback((key: Key) => {\n const item = nav.items.find(i => i.key === key)\n if (item) {\n setSelectedTextValue(item.textValue)\n }\n if (controlledSelectedKey === undefined) {\n setUncontrolledSelectedKey(key)\n }\n onSelectionChange?.(key)\n setIsOpen(false)\n nav.setSearchValue(\"\")\n }, [controlledSelectedKey, onSelectionChange, nav.items])\n\n const onToggle = React.useCallback((key: Key) => {\n const newKeys = new Set(selectedKeys)\n if (newKeys.has(key)) {\n newKeys.delete(key)\n } else {\n newKeys.add(key)\n }\n if (controlledSelectedKeys === undefined) {\n setUncontrolledSelectedKeys(newKeys)\n }\n onSelectionChange?.(Array.from(newKeys))\n }, [selectedKeys, controlledSelectedKeys, onSelectionChange])\n\n const selectFocusedItem = React.useCallback(() => {\n if (nav.focusedKey !== null) {\n const item = nav.enabledFilteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n if (extras?.onSelect) {\n extras.onSelect()\n } else if (mode === \"multiple\") {\n onToggle(nav.focusedKey)\n } else {\n onSelect(nav.focusedKey)\n }\n }\n }\n }, [nav.focusedKey, nav.enabledFilteredItems, onSelect, onToggle, mode])\n\n React.useEffect(() => {\n if (isOpen) {\n // Only initialize focusedKey if it's not already valid\n if (nav.focusedKey !== null && nav.visibleKeys.has(nav.focusedKey)) {\n const item = nav.filteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n return // Keep current keyboard focus, don't reset it\n }\n }\n\n const focusKey = mode === \"multiple\" && selectedKeys.size > 0\n ? Array.from(selectedKeys)[0]\n : selectedKey\n\n if (focusKey !== null && nav.visibleKeys.has(focusKey)) {\n const item = nav.filteredItems.find(item => item.key === focusKey)\n if (item && !item.isDisabled) {\n nav.setFocusedKey(focusKey)\n return\n }\n }\n if (nav.enabledFilteredItems.length > 0) {\n nav.setFocusedKey(nav.enabledFilteredItems[0].key)\n } else {\n nav.setFocusedKey(null)\n }\n }\n }, [isOpen, selectedKey, selectedKeys, nav.visibleKeys, nav.enabledFilteredItems, nav.filteredItems, mode, nav.focusedKey])\n\n const { buttonProps, isPressed } = useButton({\n isDisabled,\n onPress: (e) => {\n if (isDisabled) return\n // Keyboard interactions are handled by onKeyDown to prevent conflicts\n if (e.pointerType !== 'keyboard') {\n setIsOpen(prev => !prev)\n }\n },\n }, triggerRef)\n const { focusProps, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({\n isDisabled,\n onHoverStart: () => handleHoverIntent(true),\n onHoverEnd: () => handleHoverIntent(false),\n })\n\n const triggerProps = mergeProps(buttonProps, focusProps, hoverProps, {\n 'aria-haspopup': 'listbox' as const,\n 'aria-expanded': isOpen,\n 'aria-controls': isOpen ? contentId : undefined,\n 'aria-disabled': isDisabled || undefined,\n onKeyDown: (e: React.KeyboardEvent) => {\n if (!isOpen) {\n if (e.key === 'ArrowDown' || e.key === 'Enter' || (e.key === ' ' && !isDisabled)) {\n e.preventDefault()\n setIsOpen(true)\n }\n return\n }\n\n handleListKeyDown(e, {\n navigateNext: nav.navigateToNextItem,\n navigatePrev: nav.navigateToPrevItem,\n confirm: selectFocusedItem,\n close: () => {\n setIsOpen(false)\n nav.setSearchValue(\"\")\n triggerRef.current?.focus()\n },\n filteredItems: nav.filteredItems,\n setFocusedKey: nav.setFocusedKey,\n })\n },\n })\n\n React.useEffect(() => {\n if (autoFocus && triggerRef.current) {\n triggerRef.current.focus({ preventScroll: true })\n }\n }, [autoFocus])\n\n React.useEffect(() => {\n if (mode === \"single\") {\n if (selectedKey === null) {\n setSelectedTextValue(\"\")\n } else {\n const selectedItem = nav.items.find(item => item.key === selectedKey)\n if (selectedItem) {\n setSelectedTextValue(selectedItem.textValue)\n } else if (valueLabel !== undefined) {\n setSelectedTextValue(valueLabel)\n } else if (defaultValue !== undefined && defaultValue !== null) {\n setSelectedTextValue(defaultValue)\n }\n }\n }\n }, [selectedKey, nav.items, mode, defaultValue, valueLabel])\n\n const rootRef = useMergedRef<HTMLDivElement>(wrapperRef, ref)\n\n const childrenArray = React.Children.toArray(children)\n const trigger = childrenArray.find(child => React.isValidElement(child) && (\n (child.type as any)?.displayName === 'SelectTrigger' ||\n (child.type as any)?.displayName === 'SearchableTrigger'\n ))\n const contentItems = childrenArray.filter(child => React.isValidElement(child) && ((child.type as any)?.displayName === 'SelectContent' || (child.type as any)?.displayName === 'SearchableContent'))\n const otherContent = childrenArray.filter(child => !React.isValidElement(child) || (\n (child.type as any)?.displayName !== 'SelectTrigger' &&\n (child.type as any)?.displayName !== 'SearchableTrigger' &&\n (child.type as any)?.displayName !== 'SelectContent' &&\n (child.type as any)?.displayName !== 'SearchableContent'\n ))\n const triggerType = React.isValidElement(trigger) && (trigger.type as any)?.displayName === 'SearchableTrigger'\n ? 'input'\n : 'button'\n\n const resolvedStyles = resolveSelectBaseStyles(stylesProp);\n\n return (\n <SelectContext.Provider\n value={{\n isOpen,\n setIsOpen,\n contentPlacement,\n setContentPlacement,\n triggerType,\n mode,\n selectedKey,\n selectedKeys: mode === \"multiple\" ? selectedKeys : undefined,\n selectedTextValue,\n onSelect,\n onToggle: mode === \"multiple\" ? onToggle : undefined,\n triggerRef,\n wrapperRef,\n contentRef,\n triggerProps,\n isFocusVisible,\n isPressed,\n isHovered,\n isDisabled,\n items: nav.items,\n registerItem,\n unregisterItem,\n searchValue: nav.searchValue,\n setSearchValue: nav.setSearchValue,\n filteredItems: nav.filteredItems,\n visibleKeys: nav.visibleKeys,\n focusedKey: nav.focusedKey,\n setFocusedKey: nav.setFocusedKey,\n navigateToNextItem: nav.navigateToNextItem,\n navigateToPrevItem: nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n maxItems,\n triggerMode,\n handleHoverIntent,\n mouseMoveDetectedRef,\n filter,\n contentId,\n }}\n >\n <div\n ref={rootRef}\n className={cn('select', styles.select, className, resolvedStyles.root)}\n data-mode={mode}\n >\n {otherContent}\n {trigger}\n {contentItems}\n </div>\n </SelectContext.Provider>\n )\n }\n)\nSelect.displayName = \"Select\"\n\nexport { Select, SelectContext }\n",
|
|
4283
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n\n --padding-x: calc(var(--spacing) * 2.00);\n --padding-y: calc(var(--spacing) * 1.75);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--border-color);\n border-radius: var(--radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed]:not([data-disabled]) {\n background-color: var(--pressed-background);\n }\n\n &[aria-expanded=\"true\"] {\n background-color: var(--hover-background);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not(:disabled):hover .icon-section,\n &:not(:disabled):hover .value-section:not(:empty) {\n background-color: var(--hover-background);\n }\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 2px var(--focus-ring-background), 0 0 0 4px var(--ring-color);\n @apply outline-none;\n }\n\n :global(.group) &:focus-visible {\n @apply outline-none;\n }\n }\n\n button.trigger { @apply p-0; }\n\n .value-section {\n @apply flex items-center flex-1 min-w-0 gap-0.5;\n\n padding: var(--padding-y) var(--padding-x);\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n\n &:only-child {\n border-radius: var(--inner-radius);\n justify-content: center;\n }\n &:empty {\n flex: 0;\n padding: 0;\n min-width: auto;\n }\n }\n\n .icon-section {\n @apply flex items-center justify-center shrink-0;\n padding: var(--padding-y) var(--padding-x);\n border-radius: 0 var(--inner-radius) var(--inner-radius) 0;\n }\n\n .icon {\n @apply flex items-center justify-center w-4 h-4 opacity-70;\n }\n\n .trigger[aria-expanded=\"true\"] .icon,\n .trigger[data-open=\"true\"] .icon {\n transform: rotate(180deg);\n }\n\n .value {\n @apply flex items-center flex-1 min-w-0 gap-2 bg-transparent border-none p-0;\n cursor: inherit;\n }\n\n .value-icon {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n color: var(--foreground);\n }\n\n .value-text {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .content,\n .sub-content {\n --padding-x: calc(var(--spacing) * 1.5);\n --padding-y: var(--spacing);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n overflow: hidden;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n }\n\n .content-root,\n .sub-content-root {\n position: absolute;\n }\n\n .content {\n &[data-state=\"open\"][data-placement=\"bottom\"] { animation: slide-in-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"open\"][data-placement=\"top\"] { animation: slide-in-from-bottom 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"bottom\"] { animation: slide-out-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"top\"] { animation: slide-out-from-bottom 0.15s var(--ease-snappy-pop); }\n }\n\n .list {\n @apply space-y-1;\n }\n\n .item {\n --item-padding-x: var(--padding-x);\n --item-padding-y: calc(var(--padding-y) * 1.15);\n\n @apply flex items-center gap-2 outline-none cursor-default select-none;\n padding: var(--item-padding-y) var(--item-padding-x);\n\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n\n &[data-selected=\"true\"] {\n color: var(--item-foreground);\n }\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n }\n &[data-highlighted=\"true\"] {\n background-color: var(--item-background-hover);\n }\n }\n\n .item-content {\n @apply flex flex-col flex-1 min-w-0;\n }\n\n .item-text {\n @apply min-w-0 overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-description-color);\n @apply min-w-0 whitespace-normal break-words;\n }\n\n .item-icon, .item-indicator {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n }\n\n .item-icon { color: var(--item-icon-color); }\n .item-indicator { color: var(--item-indicator-color); margin-left: auto; }\n\n .item-with-description { @apply items-start py-2; }\n .item-icon-with-description, .item-indicator-with-description { @apply mt-0.5; }\n\n .separator {\n @apply my-1 -mx-1 h-px;\n background-color: var(--content-border); /* Reuses content border var */\n }\n\n .placeholder {\n color: var(--placeholder-color);\n }\n\n .icon-prefix {\n @apply inline-flex items-center shrink-0;\n }\n\n .select[data-mode=\"multiple\"] .item { gap: 0.5rem; }\n\n .search-trigger {\n @apply flex items-stretch relative bg-transparent cursor-text overflow-hidden;\n border-radius: var(--inner-radius);\n transition: box-shadow 150ms var(--ease-snappy-pop), border-color 150ms var(--ease-snappy-pop);\n\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n\n &:focus-within {\n @apply outline-none;\n box-shadow: 0 0 0 1px var(--search-focus-ring);\n z-index: 1;\n }\n }\n\n .search-value-section {\n @apply p-0;\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n }\n\n .input {\n padding: var(--padding-y) calc(var(--padding-x) * 1.50);\n padding-right: calc(var(--padding-x) * 2 + 1rem);\n @apply border-none rounded-none shadow-none bg-transparent;\n\n &[data-active], &[data-focus-visible] {\n @apply border-none shadow-none;\n }\n }\n\n .search-content-input {\n padding-inline: calc(var(--padding-x) * 1.50);\n @apply border-none rounded-none bg-transparent;\n }\n\n .search-icon-section {\n @apply absolute right-0 top-0 bottom-0 flex items-center justify-center bg-transparent pointer-events-none;\n padding-inline: var(--padding-x);\n }\n\n\n .search-wrapper {\n @apply overflow-hidden;\n border-bottom: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .content[data-placement=\"top\"] .search-wrapper {\n border-radius: 0;\n border-bottom: none;\n border-top: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .sub-trigger {\n --subtrigger-padding-x: var(--padding-x);\n --subtrigger-padding-y: var(--padding-y);\n\n @apply flex items-center gap-2 cursor-default select-none outline-none;\n padding: var(--subtrigger-padding-y) var(--subtrigger-padding-x);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--subtrigger-foreground);\n\n &[data-highlighted=\"true\"],\n &[data-state=\"open\"]:not([data-highlighted=\"true\"]) {\n background-color: var(--subtrigger-background-hover);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply shrink-0 ml-auto w-4 h-4 opacity-60;\n }\n\n .sub-content {\n min-width: 160px;\n max-width: 320px;\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-in-from-bottom { from { opacity: 0; translate: 0 2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-from-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n @keyframes slide-out-from-bottom { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 2px; } }\n}\n",
|
|
4284
|
+
"cssTypes": "declare const styles: {\n select: string;\n trigger: string;\n input: string;\n \"search-trigger\": string;\n \"search-value-section\": string;\n \"search-content-input\": string;\n \"search-icon-section\": string;\n \"search-wrapper\": string;\n \"value-section\": string;\n \"icon-section\": string;\n icon: string;\n value: string;\n \"value-icon\": string;\n \"value-text\": string;\n \"value-chevron\": string;\n \"content-root\": string;\n content: string;\n viewport: string;\n list: string;\n item: string;\n \"item-icon\": string;\n \"item-indicator\": string;\n \"item-text\": string;\n \"item-content\": string;\n \"item-description\": string;\n \"item-with-description\": string;\n \"item-icon-with-description\": string;\n \"item-indicator-with-description\": string;\n separator: string;\n \"scroll-button\": string;\n placeholder: string;\n \"icon-prefix\": string;\n \"sub-trigger\": string;\n \"sub-trigger-chevron\": string;\n \"sub-content-root\": string;\n \"sub-content\": string;\n};\n\nexport default styles;\n"
|
|
5019
4285
|
},
|
|
5020
4286
|
"slider": {
|
|
5021
4287
|
"tsx": "\"use client\"\n\nimport * as React from 'react';\n\nimport { useFocusRing } from '@react-aria/focus';\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport { asElementProps } from '@/lib/react-aria';\n\nimport css from \"./Slider.module.css\";\n\ntype SliderSize = 'sm' | 'md' | 'lg';\n\nexport interface SliderStyleSlots {\n root?: StyleValue;\n track?: StyleValue;\n range?: StyleValue;\n thumb?: StyleValue;\n}\n\nexport type SliderStylesProp = StylesProp<SliderStyleSlots>;\n\nconst resolveSliderBaseStyles = createStylesResolver(['root', 'track', 'range', 'thumb'] as const);\n\ninterface SliderRootProps {\n /** Size of the slider track and thumb */\n size?: SliderSize;\n /** Whether the slider is disabled */\n disabled?: boolean;\n /** Additional CSS class for the slider container */\n className?: string;\n /** Inline styles for the slider container */\n style?: React.CSSProperties;\n /** Minimum value of the slider range */\n min?: number;\n /** Maximum value of the slider range */\n max?: number;\n /** Step increment between values */\n step?: number;\n /** Initial value(s) for uncontrolled usage */\n defaultValue?: number | number[];\n /** Controlled value(s) for the slider thumb(s) */\n value?: number | number[];\n /** Called when the value changes */\n onValueChange?: (value: number[]) => void;\n /** Orientation of the slider track */\n orientation?: 'horizontal' | 'vertical';\n /** Accessible label for the slider */\n 'aria-label'?: string;\n /** ID of an element that labels the slider */\n 'aria-labelledby'?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SliderStylesProp;\n}\n\nconst SliderContext = React.createContext<{\n size: SliderSize;\n disabled?: boolean;\n} | null>(null);\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction snapToStep(value: number, min: number, max: number, step: number): number {\n const snapped = Math.round((value - min) / step) * step + min;\n return clamp(snapped, min, max);\n}\n\ninterface ThumbProps {\n index: number;\n value: number;\n min: number;\n max: number;\n step: number;\n disabled?: boolean;\n trackRef: React.RefObject<HTMLDivElement | null>;\n onValueChange: (index: number, value: number) => void;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n}\n\nfunction SliderThumbInternal({\n index,\n value,\n min,\n max,\n step,\n disabled,\n trackRef,\n onValueChange,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n className,\n}: ThumbProps) {\n const thumbRef = React.useRef<HTMLDivElement>(null);\n const [isDragging, setIsDragging] = React.useState(false);\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const percent = ((value - min) / (max - min)) * 100;\n\n const getValueFromPointer = React.useCallback((clientX: number) => {\n const track = trackRef.current;\n if (!track) return value;\n\n const rect = track.getBoundingClientRect();\n const percent = clamp((clientX - rect.left) / rect.width, 0, 1);\n const rawValue = percent * (max - min) + min;\n return snapToStep(rawValue, min, max, step);\n }, [trackRef, min, max, step, value]);\n\n const handlePointerDown = (e: React.PointerEvent) => {\n if (disabled) return;\n e.preventDefault();\n setIsDragging(true);\n thumbRef.current?.setPointerCapture(e.pointerId);\n thumbRef.current?.focus();\n };\n\n const handlePointerMove = (e: React.PointerEvent) => {\n if (!isDragging || disabled) return;\n const newValue = getValueFromPointer(e.clientX);\n if (newValue !== value) {\n onValueChange(index, newValue);\n }\n };\n\n const handlePointerUp = (e: React.PointerEvent) => {\n if (isDragging) {\n setIsDragging(false);\n thumbRef.current?.releasePointerCapture(e.pointerId);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n let newValue = value;\n const largeStep = step * 10;\n\n switch (e.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n newValue = clamp(value + step, min, max);\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n newValue = clamp(value - step, min, max);\n break;\n case 'PageUp':\n newValue = clamp(value + largeStep, min, max);\n break;\n case 'PageDown':\n newValue = clamp(value - largeStep, min, max);\n break;\n case 'Home':\n newValue = min;\n break;\n case 'End':\n newValue = max;\n break;\n default:\n return;\n }\n\n e.preventDefault();\n if (newValue !== value) {\n onValueChange(index, newValue);\n }\n };\n\n return (\n <div\n ref={thumbRef}\n role=\"slider\"\n tabIndex={disabled ? -1 : 0}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={value}\n aria-disabled={disabled || undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={cn('slider thumb', css.thumb, className)}\n style={{ left: `${percent}%` }}\n data-dragging={isDragging || undefined}\n data-focus-visible={isFocusVisible || undefined}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n onKeyDown={handleKeyDown}\n {...asElementProps<\"div\">(focusProps)}\n />\n );\n}\n\n/** Horizontal slider for selecting a value within a range */\nconst Root = React.forwardRef<HTMLDivElement, SliderRootProps>(\n (\n {\n className,\n styles,\n size = 'md',\n disabled,\n style,\n defaultValue,\n value: controlledValue,\n onValueChange,\n min = 0,\n max = 100,\n step = 1,\n orientation = 'horizontal',\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n },\n ref\n ) => {\n const trackRef = React.useRef<HTMLDivElement>(null);\n\n // Normalize to arrays\n const normalizeValue = (v: number | number[] | undefined): number[] | undefined => {\n if (v === undefined) return undefined;\n return Array.isArray(v) ? v : [v];\n };\n\n const [internalValues, setInternalValues] = React.useState<number[]>(() => {\n return normalizeValue(defaultValue) ?? normalizeValue(controlledValue) ?? [min];\n });\n\n const isControlled = controlledValue !== undefined;\n const values = isControlled ? normalizeValue(controlledValue)! : internalValues;\n\n const resolved = resolveSliderBaseStyles(styles);\n\n const handleValueChange = React.useCallback((index: number, newValue: number) => {\n const newValues = [...values];\n newValues[index] = newValue;\n\n if (!isControlled) {\n setInternalValues(newValues);\n }\n onValueChange?.(newValues);\n }, [values, isControlled, onValueChange]);\n\n const handleTrackClick = (e: React.PointerEvent) => {\n if (disabled) return;\n // Only handle clicks directly on the track, not on thumbs\n if (e.target !== trackRef.current) return;\n\n const track = trackRef.current;\n if (!track) return;\n\n const rect = track.getBoundingClientRect();\n const percent = clamp((e.clientX - rect.left) / rect.width, 0, 1);\n const rawValue = percent * (max - min) + min;\n const newValue = snapToStep(rawValue, min, max, step);\n\n // Find the closest thumb and update it\n let closestIndex = 0;\n let closestDistance = Math.abs(values[0] - newValue);\n for (let i = 1; i < values.length; i++) {\n const distance = Math.abs(values[i] - newValue);\n if (distance < closestDistance) {\n closestDistance = distance;\n closestIndex = i;\n }\n }\n\n handleValueChange(closestIndex, newValue);\n };\n\n return (\n <SliderContext.Provider value={{ size, disabled }}>\n <div\n ref={ref}\n data-size={size}\n data-disabled={disabled || undefined}\n data-orientation={orientation}\n style={style}\n className={cn('slider', css.slider, className, resolved.root)}\n {...props}\n >\n <div\n ref={trackRef}\n className={cn('slider track', css.track, resolved.track)}\n onPointerDown={handleTrackClick}\n >\n <div\n className={cn('slider range', css.range, resolved.range)}\n style={{\n left: `${values.length === 1 ? 0 : ((values[0] - min) / (max - min)) * 100}%`,\n right: `${values.length === 1 ? 100 - ((values[0] - min) / (max - min)) * 100 : 100 - ((values[values.length - 1] - min) / (max - min)) * 100}%`,\n }}\n />\n {values.map((value, index) => (\n <SliderThumbInternal\n key={index}\n index={index}\n value={value}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n trackRef={trackRef}\n onValueChange={handleValueChange}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={resolved.thumb}\n />\n ))}\n </div>\n </div>\n </SliderContext.Provider>\n );\n }\n);\nRoot.displayName = 'SliderRoot';\n\nexport { Root };\n",
|
|
@@ -5023,7 +4289,7 @@ export const generatedSourceCode = {
|
|
|
5023
4289
|
"cssTypes": "declare const styles: {\n readonly slider: string;\n readonly track: string;\n readonly range: string;\n readonly thumb: string;\n};\n\nexport default styles;\n"
|
|
5024
4290
|
},
|
|
5025
4291
|
"switch": {
|
|
5026
|
-
"tsx": "\"use client\";\n\nimport React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useSwitch } from \"@react-aria/switch\";\n\nimport { useToggleState } from \"react-stately\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\n\nimport styles from \"./Switch.module.css\";\n\n\n\
|
|
4292
|
+
"tsx": "\"use client\";\n\nimport React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useSwitch } from \"@react-aria/switch\";\n\nimport { useToggleState } from \"react-stately\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\n\nimport styles from \"./Switch.module.css\";\n\n\n\ninterface SwitchStyleSlots {\n root?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n}\n\ntype SwitchStylesProp = StylesProp<SwitchStyleSlots>;\n\nconst resolveSwitchBaseStyles = createStylesResolver(['root', 'track', 'thumb'] as const);\n\nexport interface SwitchProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\" | \"onChange\" | \"checked\" | \"defaultChecked\"> {\n /** Controlled selected (on) state */\n isSelected?: boolean;\n /** Called when the switch is toggled */\n onChange?: (isSelected: boolean) => void;\n /** Initial selected state for uncontrolled usage */\n defaultSelected?: boolean;\n\n /** Whether the switch is disabled */\n isDisabled?: boolean;\n /** Size of the switch */\n size?: \"default\" | \"sm\";\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SwitchStylesProp;\n}\n\n\nconst Switch = React.forwardRef<HTMLInputElement, SwitchProps>(\n ({\n className,\n styles: stylesProp,\n isDisabled = false,\n isSelected: controlledSelected,\n onChange,\n defaultSelected,\n size = \"default\",\n ...props\n },\n ref\n ) => {\n const state = useToggleState({\n isSelected: controlledSelected,\n defaultSelected: defaultSelected ?? false,\n onChange,\n });\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n // Extract aria-label from props if provided\n const { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledby, ...otherProps } = props;\n\n const { inputProps, isSelected } = useSwitch(\n {\n isDisabled,\n ...(ariaLabel && { \"aria-label\": ariaLabel }),\n ...(ariaLabelledby && { \"aria-labelledby\": ariaLabelledby }),\n },\n state,\n inputRef\n );\n const { focusProps, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled });\n\n\n\n React.useImperativeHandle(ref, () => inputRef.current!);\n\n const resolved = resolveSwitchBaseStyles(stylesProp);\n\n return (\n <div\n className={cn(\n 'switch',\n styles.switch,\n size === \"sm\" && styles[\"switch-sm\"],\n className,\n resolved.root\n )}\n data-selected={isSelected || undefined}\n data-disabled={isDisabled || undefined}\n data-focus-visible={isFocusVisible || undefined}\n data-hovered={isHovered || undefined}\n >\n <div\n className={cn(\n 'switch-track',\n styles[\"switch-track\"],\n resolved.track\n )}\n />\n <div\n className={cn(\n 'switch-thumb',\n styles[\"switch-thumb\"],\n resolved.thumb\n )}\n />\n <input\n ref={inputRef}\n type=\"checkbox\"\n className=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\"\n aria-checked={isSelected}\n {...mergeProps(inputProps, focusProps, hoverProps)}\n {...otherProps}\n />\n </div>\n );\n }\n);\n\nSwitch.displayName = \"Switch\";\n\nexport { Switch };\n",
|
|
5027
4293
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .switch {\n --radius: 9999px;\n --inner-radius: calc(var(--radius) - var(--border-width-base));\n\n --width: 2.75rem;\n --height: 1.5rem;\n --thumb-size: 1rem;\n --thumb-offset: 0.25rem;\n\n --disabled-opacity: 0.6;\n\n @apply relative inline-flex cursor-pointer items-center;\n user-select: none;\n width: var(--width);\n height: var(--height);\n }\n\n .switch-track {\n @apply absolute inset-0;\n transition: background-color 180ms var(--ease-snappy-pop), border-color 180ms var(--ease-snappy-pop), transform 180ms var(--ease-snappy-pop);\n background-color: var(--track-background-unchecked);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius);\n }\n\n .switch:active:not([data-disabled]) .switch-track {\n transform: scale(0.98);\n }\n\n .switch-thumb {\n @apply absolute top-0 bottom-0 my-auto;\n left: var(--thumb-offset);\n width: var(--thumb-size);\n height: var(--thumb-size);\n transition: left 180ms var(--ease-snappy-pop), background-color 180ms var(--ease-snappy-pop);\n background-color: var(--thumb-background-unchecked);\n border-radius: var(--inner-radius);\n z-index: 1;\n pointer-events: none;\n }\n\n .switch[data-selected] .switch-track {\n background-color: var(--track-background-checked);\n border-color: var(--border-checked);\n }\n\n .switch[data-selected] .switch-thumb {\n background-color: var(--thumb-background-checked);\n left: calc(var(--width) - var(--thumb-size) - var(--thumb-offset));\n }\n\n @media (hover: hover) {\n .switch[data-selected]:not([data-disabled]):hover .switch-track {\n border-color: var(--border-hover);\n }\n }\n\n .switch[data-selected]:not([data-disabled]):active .switch-track {\n border-color: var(--border-active);\n }\n\n .switch[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n\n\n .switch[data-focus-visible] {\n box-shadow: var(--focus-ring);\n }\n\n .switch-sm {\n --width: 1.75rem;\n --height: 1rem;\n --thumb-size: 0.625rem;\n --thumb-offset: 0.1875rem;\n }\n}\n",
|
|
5028
4294
|
"cssTypes": "export interface Styles {\n switch: string;\n \"switch-track\": string;\n \"switch-thumb\": string;\n \"switch-sm\": string;\n\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
5029
4295
|
},
|
|
@@ -5033,23 +4299,23 @@ export const generatedSourceCode = {
|
|
|
5033
4299
|
"cssTypes": ""
|
|
5034
4300
|
},
|
|
5035
4301
|
"tabs": {
|
|
5036
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { useFocusRing } from \"react-aria\"\nimport { cn } from \"./utils\"\nimport { StyleValue } from \"./utils\"\nimport { asElementProps } from \"@/lib/react-aria\"\nimport { StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport css from \"./Tabs.module.css\"\n\ntype TabsVariant = \"underline\"\ntype TabsOrientation = \"horizontal\" | \"vertical\"\n\ninterface IndicatorPosition {\n left: number\n top: number\n width: number\n height: number\n}\n\ninterface ListDimensions {\n width: number\n height: number\n}\n\ninterface TabsContextValue {\n selectedValue: string\n setSelectedValue: (value: string) => void\n variant?: TabsVariant\n orientation: TabsOrientation\n isDisabledTab: (value: string) => boolean\n indicatorReady: boolean\n setIndicatorReady: (ready: boolean) => void\n}\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null)\n\nfunction useTabsContext() {\n const context = React.useContext(TabsContext)\n if (!context) {\n throw new Error(\"Tabs component must be used within Tabs root\")\n }\n return context\n}\n\ninterface TabsStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsProps {\n /** Optional alternate visual style of the tab list indicator */\n variant?: TabsVariant\n /** Direction of the tab list layout */\n orientation?: TabsOrientation\n /** Initially selected tab value for uncontrolled usage */\n defaultValue?: string\n /** Controlled selected tab value */\n value?: string\n /** Called when the selected tab changes */\n onValueChange?: (value: string) => void\n /** Additional CSS class for the tabs root */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsBaseStyles = createStylesResolver(['root'] as const)\n\nconst Tabs = React.forwardRef<HTMLDivElement, TabsProps>(\n (\n {\n variant,\n orientation = \"horizontal\",\n defaultValue,\n value: controlledValue,\n onValueChange,\n className,\n styles: stylesProp,\n children,\n },\n ref\n ) => {\n const { root } = resolveTabsBaseStyles(stylesProp)\n\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue || \"\")\n const [disabledTabs, setDisabledTabs] = React.useState<Set<string>>(new Set())\n\n const selectedValue = controlledValue !== undefined ? controlledValue : uncontrolledValue\n const isDisabledTab = React.useCallback(\n (value: string) => disabledTabs.has(value),\n [disabledTabs]\n )\n\n const setSelectedValue = React.useCallback(\n (newValue: string) => {\n if (!isDisabledTab(newValue)) {\n if (controlledValue === undefined) {\n setUncontrolledValue(newValue)\n }\n onValueChange?.(newValue)\n }\n },\n [controlledValue, isDisabledTab, onValueChange]\n )\n\n const registerDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => new Set(prev).add(value))\n }, [])\n\n const unregisterDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => {\n const newSet = new Set(prev)\n newSet.delete(value)\n return newSet\n })\n }, [])\n\n const [indicatorReady, setIndicatorReady] = React.useState(false)\n\n return (\n <TabsContext.Provider\n value={{\n selectedValue,\n setSelectedValue,\n variant,\n orientation,\n isDisabledTab,\n indicatorReady,\n setIndicatorReady,\n }}\n >\n <div\n ref={ref}\n className={cn(\"tabs\", css.tabs, root, className)}\n data-orientation={orientation}\n >\n {React.Children.map(children, (child) =>\n React.isValidElement(child) && child.type === TabsTrigger\n ? React.cloneElement(child, {\n _registerDisabled: registerDisabledTab,\n _unregisterDisabled: unregisterDisabledTab,\n } as any)\n : child\n )}\n </div>\n </TabsContext.Provider>\n )\n }\n)\nTabs.displayName = \"Tabs\"\n\ninterface TabsListStyleSlots {\n root?: StyleValue\n indicator?: StyleValue\n}\n\ninterface TabsListProps {\n /** Additional CSS class names */\n className?: string\n children?: React.ReactNode\n /** Accessible label for the tab list */\n \"aria-label\"?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsListStyleSlots>\n}\n\nconst resolveTabsListBaseStyles = createStylesResolver(['root', 'indicator'] as const);\n\n/** Container for the row of tab trigger buttons */\nconst TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(\n ({ className, children, \"aria-label\": ariaLabel, styles: stylesProp }, ref) => {\n const { selectedValue, variant, orientation, setIndicatorReady } = useTabsContext()\n const { root, indicator } = resolveTabsListBaseStyles(stylesProp);\n const listRef = React.useRef<HTMLDivElement>(null)\n const [indicatorPosition, setIndicatorPosition] = React.useState<IndicatorPosition>({\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n })\n const [listDimensions, setListDimensions] = React.useState<ListDimensions>({\n width: 0,\n height: 0,\n })\n\n\n const measureTrigger = React.useCallback((element: HTMLElement | null) => {\n if (!element) return null\n\n const rect = element.getBoundingClientRect()\n const listRect = listRef.current?.getBoundingClientRect()\n\n if (!listRect) return null\n\n const relativeLeft = rect.left - listRect.left\n const relativeTop = rect.top - listRect.top\n\n return {\n left: relativeLeft,\n top: relativeTop,\n width: rect.width,\n height: rect.height,\n }\n }, [])\n\n const measureList = React.useCallback(() => {\n if (!listRef.current) return\n const rect = listRef.current.getBoundingClientRect()\n setListDimensions({\n width: rect.width,\n height: rect.height,\n })\n }, [])\n\n const updateIndicator = React.useCallback(\n (value: string) => {\n if (!listRef.current) return\n\n const trigger = listRef.current.querySelector(\n `[data-tabs-value=\"${value}\"]`\n ) as HTMLElement | null\n\n if (trigger) {\n const position = measureTrigger(trigger)\n if (position) {\n setIndicatorPosition(position)\n }\n }\n },\n [measureTrigger]\n )\n\n React.useLayoutEffect(() => {\n measureList()\n updateIndicator(selectedValue)\n setIndicatorReady(true)\n }, [selectedValue, updateIndicator, measureList, setIndicatorReady])\n\n React.useEffect(() => {\n if (!listRef.current) return\n\n const resizeObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n })\n\n resizeObserver.observe(listRef.current)\n return () => resizeObserver.disconnect()\n }, [selectedValue, updateIndicator, measureList])\n\n React.useEffect(() => {\n const handleWindowResize = () => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n }\n\n window.addEventListener(\"resize\", handleWindowResize)\n return () => window.removeEventListener(\"resize\", handleWindowResize)\n }, [selectedValue, updateIndicator, measureList])\n\n const getIndicatorStyle = React.useMemo<React.CSSProperties>(() => {\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n transition: \"all 0.2s cubic-bezier(0.4, 0, 0.2, 1)\",\n willChange: \"transform\",\n pointerEvents: \"none\",\n opacity: indicatorPosition.width === 0 && indicatorPosition.height === 0 ? 0 : 1,\n }\n\n if (indicatorPosition.width === 0 && indicatorPosition.height === 0) {\n return baseStyle\n }\n\n if (orientation === \"vertical\") {\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: 0,\n top: indicatorPosition.top,\n width: 2,\n height: indicatorPosition.height,\n }\n }\n // Apply horizontal padding to indicator for vertical orientation\n const horizontalPadding = 4\n const adjustedWidth = Math.max(0, listDimensions.width - horizontalPadding * 2)\n return {\n ...baseStyle,\n left: horizontalPadding,\n top: indicatorPosition.top,\n width: adjustedWidth,\n height: indicatorPosition.height,\n }\n }\n\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: indicatorPosition.top + indicatorPosition.height - 2,\n width: indicatorPosition.width,\n height: 2,\n }\n }\n\n // Apply vertical padding to indicator (matches --indicator-padding CSS variable)\n const verticalPadding = 4\n const adjustedHeight = Math.max(0, listDimensions.height - verticalPadding * 2)\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: verticalPadding,\n width: indicatorPosition.width,\n height: adjustedHeight,\n }\n }, [indicatorPosition, listDimensions, variant, orientation])\n\n const mergedRef = React.useCallback(\n (el: HTMLDivElement | null) => {\n listRef.current = el\n if (typeof ref === \"function\") ref(el)\n else if (ref) ref.current = el\n },\n [ref]\n )\n\n return (\n <div\n ref={mergedRef}\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation={orientation}\n className={cn(\"tabs\", \"list\", css.list, root, className)}\n data-variant={variant}\n data-orientation={orientation}\n style={{ position: \"relative\" }}\n >\n {indicatorPosition.width > 0 && (\n <div\n className={cn(\"tabs\", \"indicator\",\n variant === \"underline\" && \"underline\",\n variant === \"underline\" && \"indicator-underline\",\n css.indicator, {\n [css[\"indicator-underline\"]]: variant === \"underline\",\n }, indicator)}\n style={getIndicatorStyle}\n />\n )}\n {children}\n </div>\n )\n }\n)\nTabsList.displayName = \"TabsList\"\n\ninterface TabsTriggerStyleSlots {\n root?: StyleValue\n icon?: StyleValue\n}\n\ninterface TabsTriggerProps {\n /** Unique identifier matching the associated TabsContent value */\n value: string\n /** Whether the tab trigger is disabled */\n disabled?: boolean\n /** Icon element displayed before the tab label */\n icon?: React.ReactNode\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsTriggerStyleSlots>\n children?: React.ReactNode\n _registerDisabled?: (value: string) => void\n _unregisterDisabled?: (value: string) => void\n}\n\nconst resolveTabsTriggerBaseStyles = createStylesResolver(['root', 'icon'] as const);\n\n/** A tab button that activates its associated content panel */\nconst TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(\n (\n {\n value,\n disabled = false,\n icon,\n className,\n styles: stylesProp,\n children,\n _registerDisabled,\n _unregisterDisabled,\n },\n ref\n ) => {\n const { selectedValue, setSelectedValue, indicatorReady } = useTabsContext()\n const { root, icon: iconStyles } = resolveTabsTriggerBaseStyles(stylesProp);\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const isSelected = value === selectedValue\n\n const { focusProps, isFocusVisible } = useFocusRing()\n\n React.useEffect(() => {\n if (disabled) {\n _registerDisabled?.(value)\n } else {\n _unregisterDisabled?.(value)\n }\n }, [disabled, value, _registerDisabled, _unregisterDisabled])\n\n const handleClick = React.useCallback(() => {\n if (!disabled) {\n setSelectedValue(value)\n }\n }, [disabled, value, setSelectedValue])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return\n\n const listElement = buttonRef.current?.parentElement\n if (!listElement) return\n\n const triggers = Array.from(\n listElement.querySelectorAll('[data-tabs-value]')\n ) as HTMLButtonElement[]\n const currentIndex = triggers.findIndex((el) => el.getAttribute(\"data-tabs-value\") === value)\n\n let nextValue: string | null = null\n\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n e.preventDefault()\n const nextIndex = (currentIndex + 1) % triggers.length\n nextValue = triggers[nextIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n e.preventDefault()\n const prevIndex = currentIndex === 0 ? triggers.length - 1 : currentIndex - 1\n nextValue = triggers[prevIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"Home\") {\n e.preventDefault()\n nextValue = triggers[0].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"End\") {\n e.preventDefault()\n nextValue = triggers[triggers.length - 1].getAttribute(\"data-tabs-value\")\n }\n\n if (nextValue) {\n setSelectedValue(nextValue)\n setTimeout(() => {\n const nextTrigger = listElement.querySelector(\n `[data-tabs-value=\"${nextValue}\"]`\n ) as HTMLButtonElement | null\n nextTrigger?.focus()\n }, 0)\n }\n },\n [value, disabled, setSelectedValue]\n )\n\n const mergedRef = React.useCallback(\n (el: HTMLButtonElement | null) => {\n buttonRef.current = el\n if (typeof ref === \"function\") ref(el)\n else if (ref) ref.current = el\n },\n [ref]\n )\n\n return (\n <button\n {...asElementProps<\"button\">(focusProps)}\n ref={mergedRef}\n id={`${value}-trigger`}\n role=\"tab\"\n aria-selected={isSelected}\n aria-controls={`${value}-content`}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n data-tabs-value={value}\n className={cn(\"tabs\", \"trigger\", css.trigger, root, className)}\n data-selected={isSelected ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-indicator-ready={isSelected && indicatorReady ? \"true\" : undefined}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n {icon && <span className={cn(css[\"trigger-icon\"], iconStyles)}>{icon}</span>}\n {children}\n </button>\n )\n }\n)\nTabsTrigger.displayName = \"Tab\"\n\ninterface TabsContentStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsContentProps {\n /** Unique identifier matching the associated TabsTrigger value */\n value: string\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsContentStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsContentBaseStyles = createStylesResolver(['root'] as const);\n\n/** Content panel shown when its corresponding tab is active */\nconst TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(\n ({ value, className, children, styles: stylesProp }, ref) => {\n const { selectedValue, orientation } = useTabsContext()\n const { root } = resolveTabsContentBaseStyles(stylesProp);\n const isVisible = value === selectedValue\n\n return (\n <div\n ref={ref}\n role=\"tabpanel\"\n aria-labelledby={`${value}-trigger`}\n id={`${value}-content`}\n className={cn(\"tabs\", \"content\", css.content, root, className)}\n data-orientation={orientation}\n hidden={!isVisible}\n >\n {isVisible && children}\n </div>\n )\n }\n)\nTabsContent.displayName = \"TabsContent\"\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\nexport type { TabsProps, TabsListProps, TabsTriggerProps, TabsContentProps }\n",
|
|
5037
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n --indicator-padding: 2px;\n\n @apply absolute;\n background-color: var(--indicator-background);\n border-radius: var(--radius-xs);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n font-size: var(--text-
|
|
4302
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { useFocusRing } from \"react-aria\"\nimport { cn } from \"./utils\"\nimport { StyleValue } from \"./utils\"\nimport { asElementProps } from \"@/lib/react-aria\"\nimport { StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport css from \"./Tabs.module.css\"\n\ntype TabsVariant = \"underline\"\ntype TabsOrientation = \"horizontal\" | \"vertical\"\n\ninterface IndicatorPosition {\n left: number\n top: number\n width: number\n height: number\n}\n\ninterface ListDimensions {\n width: number\n height: number\n}\n\ninterface TabsContextValue {\n selectedValue: string\n setSelectedValue: (value: string) => void\n variant?: TabsVariant\n orientation: TabsOrientation\n isDisabledTab: (value: string) => boolean\n indicatorReady: boolean\n setIndicatorReady: (ready: boolean) => void\n}\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null)\n\nfunction useTabsContext() {\n const context = React.useContext(TabsContext)\n if (!context) {\n throw new Error(\"Tabs component must be used within Tabs root\")\n }\n return context\n}\n\ninterface TabsStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsProps {\n /** Optional alternate visual style of the tab list indicator */\n variant?: TabsVariant\n /** Direction of the tab list layout */\n orientation?: TabsOrientation\n /** Initially selected tab value for uncontrolled usage */\n defaultValue?: string\n /** Controlled selected tab value */\n value?: string\n /** Called when the selected tab changes */\n onValueChange?: (value: string) => void\n /** Additional CSS class for the tabs root */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsBaseStyles = createStylesResolver(['root'] as const)\n\nconst TabsRoot = React.forwardRef<HTMLDivElement, TabsProps>(\n (\n {\n variant,\n orientation = \"horizontal\",\n defaultValue,\n value: controlledValue,\n onValueChange,\n className,\n styles: stylesProp,\n children,\n },\n ref\n ) => {\n const { root } = resolveTabsBaseStyles(stylesProp)\n\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue || \"\")\n const [disabledTabs, setDisabledTabs] = React.useState<Set<string>>(new Set())\n\n const selectedValue = controlledValue !== undefined ? controlledValue : uncontrolledValue\n const isDisabledTab = React.useCallback(\n (value: string) => disabledTabs.has(value),\n [disabledTabs]\n )\n\n const setSelectedValue = React.useCallback(\n (newValue: string) => {\n if (!isDisabledTab(newValue)) {\n if (controlledValue === undefined) {\n setUncontrolledValue(newValue)\n }\n onValueChange?.(newValue)\n }\n },\n [controlledValue, isDisabledTab, onValueChange]\n )\n\n const registerDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => new Set(prev).add(value))\n }, [])\n\n const unregisterDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => {\n const newSet = new Set(prev)\n newSet.delete(value)\n return newSet\n })\n }, [])\n\n const [indicatorReady, setIndicatorReady] = React.useState(false)\n\n return (\n <TabsContext.Provider\n value={{\n selectedValue,\n setSelectedValue,\n variant,\n orientation,\n isDisabledTab,\n indicatorReady,\n setIndicatorReady,\n }}\n >\n <div\n ref={ref}\n className={cn(\"tabs\", css.tabs, root, className)}\n data-orientation={orientation}\n >\n {React.Children.map(children, (child) =>\n React.isValidElement(child) && child.type === TabsTrigger\n ? React.cloneElement(child, {\n _registerDisabled: registerDisabledTab,\n _unregisterDisabled: unregisterDisabledTab,\n } as any)\n : child\n )}\n </div>\n </TabsContext.Provider>\n )\n }\n)\nTabsRoot.displayName = \"Tabs\"\n\ninterface TabsListStyleSlots {\n root?: StyleValue\n indicator?: StyleValue\n}\n\ninterface TabsListProps {\n /** Additional CSS class names */\n className?: string\n children?: React.ReactNode\n /** Accessible label for the tab list */\n \"aria-label\"?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsListStyleSlots>\n}\n\nconst resolveTabsListBaseStyles = createStylesResolver(['root', 'indicator'] as const);\n\n/** Container for the row of tab trigger buttons */\nconst TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(\n ({ className, children, \"aria-label\": ariaLabel, styles: stylesProp }, ref) => {\n const { selectedValue, variant, orientation, setIndicatorReady } = useTabsContext()\n const { root, indicator } = resolveTabsListBaseStyles(stylesProp);\n const listRef = React.useRef<HTMLDivElement>(null)\n const [indicatorPosition, setIndicatorPosition] = React.useState<IndicatorPosition>({\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n })\n const [listDimensions, setListDimensions] = React.useState<ListDimensions>({\n width: 0,\n height: 0,\n })\n\n\n const measureTrigger = React.useCallback((element: HTMLElement | null) => {\n if (!element) return null\n\n const rect = element.getBoundingClientRect()\n const listRect = listRef.current?.getBoundingClientRect()\n\n if (!listRect) return null\n\n const relativeLeft = rect.left - listRect.left\n const relativeTop = rect.top - listRect.top\n\n return {\n left: relativeLeft,\n top: relativeTop,\n width: rect.width,\n height: rect.height,\n }\n }, [])\n\n const measureList = React.useCallback(() => {\n if (!listRef.current) return\n const rect = listRef.current.getBoundingClientRect()\n setListDimensions({\n width: rect.width,\n height: rect.height,\n })\n }, [])\n\n const updateIndicator = React.useCallback(\n (value: string) => {\n if (!listRef.current) return\n\n const trigger = listRef.current.querySelector(\n `[data-tabs-value=\"${value}\"]`\n ) as HTMLElement | null\n\n if (trigger) {\n const position = measureTrigger(trigger)\n if (position) {\n setIndicatorPosition(position)\n }\n }\n },\n [measureTrigger]\n )\n\n React.useLayoutEffect(() => {\n measureList()\n updateIndicator(selectedValue)\n setIndicatorReady(true)\n }, [selectedValue, updateIndicator, measureList, setIndicatorReady])\n\n React.useEffect(() => {\n if (!listRef.current) return\n\n const resizeObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n })\n\n resizeObserver.observe(listRef.current)\n return () => resizeObserver.disconnect()\n }, [selectedValue, updateIndicator, measureList])\n\n React.useEffect(() => {\n const handleWindowResize = () => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n }\n\n window.addEventListener(\"resize\", handleWindowResize)\n return () => window.removeEventListener(\"resize\", handleWindowResize)\n }, [selectedValue, updateIndicator, measureList])\n\n const getIndicatorStyle = React.useMemo<React.CSSProperties>(() => {\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n transition: \"all 0.2s cubic-bezier(0.4, 0, 0.2, 1)\",\n willChange: \"transform\",\n pointerEvents: \"none\",\n opacity: indicatorPosition.width === 0 && indicatorPosition.height === 0 ? 0 : 1,\n }\n\n if (indicatorPosition.width === 0 && indicatorPosition.height === 0) {\n return baseStyle\n }\n\n if (orientation === \"vertical\") {\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: 0,\n top: indicatorPosition.top,\n width: 2,\n height: indicatorPosition.height,\n }\n }\n // Apply horizontal padding to indicator for vertical orientation\n const horizontalPadding = 4\n const adjustedWidth = Math.max(0, listDimensions.width - horizontalPadding * 2)\n return {\n ...baseStyle,\n left: horizontalPadding,\n top: indicatorPosition.top,\n width: adjustedWidth,\n height: indicatorPosition.height,\n }\n }\n\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: indicatorPosition.top + indicatorPosition.height - 2,\n width: indicatorPosition.width,\n height: 2,\n }\n }\n\n // Apply vertical padding to indicator (matches --indicator-padding CSS variable)\n const verticalPadding = 4\n const adjustedHeight = Math.max(0, listDimensions.height - verticalPadding * 2)\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: verticalPadding,\n width: indicatorPosition.width,\n height: adjustedHeight,\n }\n }, [indicatorPosition, listDimensions, variant, orientation])\n\n const mergedRef = React.useCallback(\n (el: HTMLDivElement | null) => {\n listRef.current = el\n if (typeof ref === \"function\") ref(el)\n else if (ref) ref.current = el\n },\n [ref]\n )\n\n return (\n <div\n ref={mergedRef}\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation={orientation}\n className={cn(\"tabs\", \"list\", css.list, root, className)}\n data-variant={variant}\n data-orientation={orientation}\n style={{ position: \"relative\" }}\n >\n {indicatorPosition.width > 0 && (\n <div\n className={cn(\"tabs\", \"indicator\",\n variant === \"underline\" && \"underline\",\n variant === \"underline\" && \"indicator-underline\",\n css.indicator, {\n [css[\"indicator-underline\"]]: variant === \"underline\",\n }, indicator)}\n style={getIndicatorStyle}\n />\n )}\n {children}\n </div>\n )\n }\n)\nTabsList.displayName = \"TabsList\"\n\ninterface TabsTriggerStyleSlots {\n root?: StyleValue\n icon?: StyleValue\n}\n\ninterface TabsTriggerProps {\n /** Unique identifier matching the associated TabsContent value */\n value: string\n /** Whether the tab trigger is disabled */\n disabled?: boolean\n /** Icon element displayed before the tab label */\n icon?: React.ReactNode\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsTriggerStyleSlots>\n children?: React.ReactNode\n _registerDisabled?: (value: string) => void\n _unregisterDisabled?: (value: string) => void\n}\n\nconst resolveTabsTriggerBaseStyles = createStylesResolver(['root', 'icon'] as const);\n\n/** A tab button that activates its associated content panel */\nconst TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(\n (\n {\n value,\n disabled = false,\n icon,\n className,\n styles: stylesProp,\n children,\n _registerDisabled,\n _unregisterDisabled,\n },\n ref\n ) => {\n const { selectedValue, setSelectedValue, indicatorReady } = useTabsContext()\n const { root, icon: iconStyles } = resolveTabsTriggerBaseStyles(stylesProp);\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const isSelected = value === selectedValue\n\n const { focusProps, isFocusVisible } = useFocusRing()\n\n React.useEffect(() => {\n if (disabled) {\n _registerDisabled?.(value)\n } else {\n _unregisterDisabled?.(value)\n }\n }, [disabled, value, _registerDisabled, _unregisterDisabled])\n\n const handleClick = React.useCallback(() => {\n if (!disabled) {\n setSelectedValue(value)\n }\n }, [disabled, value, setSelectedValue])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return\n\n const listElement = buttonRef.current?.parentElement\n if (!listElement) return\n\n const triggers = Array.from(\n listElement.querySelectorAll('[data-tabs-value]')\n ) as HTMLButtonElement[]\n const currentIndex = triggers.findIndex((el) => el.getAttribute(\"data-tabs-value\") === value)\n\n let nextValue: string | null = null\n\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n e.preventDefault()\n const nextIndex = (currentIndex + 1) % triggers.length\n nextValue = triggers[nextIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n e.preventDefault()\n const prevIndex = currentIndex === 0 ? triggers.length - 1 : currentIndex - 1\n nextValue = triggers[prevIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"Home\") {\n e.preventDefault()\n nextValue = triggers[0].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"End\") {\n e.preventDefault()\n nextValue = triggers[triggers.length - 1].getAttribute(\"data-tabs-value\")\n }\n\n if (nextValue) {\n setSelectedValue(nextValue)\n setTimeout(() => {\n const nextTrigger = listElement.querySelector(\n `[data-tabs-value=\"${nextValue}\"]`\n ) as HTMLButtonElement | null\n nextTrigger?.focus()\n }, 0)\n }\n },\n [value, disabled, setSelectedValue]\n )\n\n const mergedRef = React.useCallback(\n (el: HTMLButtonElement | null) => {\n buttonRef.current = el\n if (typeof ref === \"function\") ref(el)\n else if (ref) ref.current = el\n },\n [ref]\n )\n\n return (\n <button\n {...asElementProps<\"button\">(focusProps)}\n ref={mergedRef}\n id={`${value}-trigger`}\n role=\"tab\"\n aria-selected={isSelected}\n aria-controls={`${value}-content`}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n data-tabs-value={value}\n className={cn(\"tabs\", \"trigger\", css.trigger, root, className)}\n data-selected={isSelected ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-indicator-ready={isSelected && indicatorReady ? \"true\" : undefined}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n {icon && <span className={cn(css[\"trigger-icon\"], iconStyles)}>{icon}</span>}\n {children}\n </button>\n )\n }\n)\nTabsTrigger.displayName = \"Tab\"\n\ninterface TabsContentStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsContentProps {\n /** Unique identifier matching the associated TabsTrigger value */\n value: string\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsContentStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsContentBaseStyles = createStylesResolver(['root'] as const);\n\n/** Content panel shown when its corresponding tab is active */\nconst TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(\n ({ value, className, children, styles: stylesProp }, ref) => {\n const { selectedValue, orientation } = useTabsContext()\n const { root } = resolveTabsContentBaseStyles(stylesProp);\n const isVisible = value === selectedValue\n\n return (\n <div\n ref={ref}\n role=\"tabpanel\"\n aria-labelledby={`${value}-trigger`}\n id={`${value}-content`}\n className={cn(\"tabs\", \"content\", css.content, root, className)}\n data-orientation={orientation}\n hidden={!isVisible}\n >\n {isVisible && children}\n </div>\n )\n }\n)\nTabsContent.displayName = \"TabsContent\"\n\nconst Tabs = Object.assign(TabsRoot, {\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent,\n})\n\nexport { Tabs }\nexport type { TabsProps, TabsListProps, TabsTriggerProps, TabsContentProps }\n",
|
|
4303
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n --indicator-padding: 2px;\n\n @apply absolute;\n background-color: var(--indicator-background);\n border-radius: var(--radius-xs);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--trigger-color);\n outline: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]) {\n &:hover {\n color: var(--trigger-hover-color);\n }\n\n &:active {\n color: var(--trigger-active-color);\n }\n }\n\n &[data-selected=\"true\"] {\n color: var(--trigger-selected-color);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n .list & {\n background-color: var(--trigger-selected-background);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--trigger-underline-color);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--trigger-underline-color);\n }\n }\n\n &[data-focus-visible] {\n background: var(--trigger-focus-background);\n outline: none;\n }\n\n &[data-disabled=\"true\"] {\n --disabled-opacity: 0.5;\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom: none;\n border-left: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n border-left-color: var(--trigger-underline-color);\n border-bottom: none;\n }\n }\n\n .trigger-icon {\n @apply flex h-4 w-4 shrink-0 items-center justify-center;\n }\n\n .content {\n @apply w-full p-0 outline-none;\n flex: 1;\n padding-top: 1rem;\n\n &[data-orientation=\"vertical\"] {\n flex: 1;\n width: 100%;\n }\n\n &:focus-visible {\n outline: 2px solid var(--content-outline-color);\n outline-offset: 2px;\n }\n }\n\n @media (max-width: 640px) {\n .list {\n padding: 0.125rem;\n\n &[data-variant=\"underline\"] {\n padding: 0;\n }\n }\n\n .trigger {\n @apply px-1 py-1;\n font-size: var(--text-sm);\n\n .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
|
|
5038
4304
|
"cssTypes": "declare const styles: {\n tabs: string;\n list: string;\n indicator: string;\n \"indicator-underline\": string;\n trigger: string;\n \"trigger-icon\": string;\n content: string;\n};\n\nexport default styles;\n"
|
|
5039
4305
|
},
|
|
5040
4306
|
"textarea": {
|
|
5041
|
-
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState, type ComponentPropsWithoutRef } from \"react\";\n\nimport { mergeProps } from \"@react-aria/utils\";\nimport { useFocusRing } from \"@react-aria/focus\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Scroll } from \"@/components/Scroll\";\nimport css from \"./Textarea.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\ntype ResizeAxis = \"both\" | \"x\" | \"y\" | \"none\";\n\nexport interface TextAreaStyleSlots {\n root?: StyleValue;\n characterCount?: StyleValue;\n}\n\nexport type TextAreaStylesProp = StylesProp<TextAreaStyleSlots>;\n\nconst resolveTextAreaBaseStyles = createStylesResolver(['root', 'characterCount'] as const);\n\nexport interface TextAreaProps extends Omit<ComponentPropsWithoutRef<\"textarea\">, \"size\"> {\n /** Size of the textarea */\n size?: Size;\n /** Whether to apply error styling */\n error?: boolean;\n /** Whether the textarea can be manually resized by the user. When enabled, `className` may include Tailwind `resize`, `resize-x`, `resize-y`, or `resize-none` to select the resize axis. */\n resizable?: boolean;\n /** Whether to display a character count below the textarea */\n showCharacterCount?: boolean;\n /** Maximum number of characters allowed */\n maxCharacters?: number;\n /** Maximum height before the custom scrollbar activates */\n maxHeight?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TextAreaStylesProp;\n}\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return React.useCallback((value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n }, refs);\n}\n\nconst resizeClassMap: Record<string, ResizeAxis> = {\n resize: \"both\",\n \"resize-x\": \"x\",\n \"resize-y\": \"y\",\n \"resize-none\": \"none\",\n};\n\nfunction resolveResizeAxis(className: string | undefined, resizable: boolean): ResizeAxis {\n if (!resizable) return \"none\";\n\n let axis: ResizeAxis | undefined;\n for (const token of className?.split(/\\s+/) ?? []) {\n const nextAxis = resizeClassMap[token];\n if (nextAxis) axis = nextAxis;\n }\n\n return axis ?? \"both\";\n}\n\nfunction stripResizeClasses(className: string | undefined) {\n if (!className) return className;\n\n const filtered = className\n .split(/\\s+/)\n .filter((token) => token && !resizeClassMap[token])\n .join(\" \");\n\n return filtered || undefined;\n}\n\nexport const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n className,\n size = \"md\",\n error = false,\n disabled,\n resizable = true,\n showCharacterCount = false,\n maxCharacters,\n maxHeight,\n value: controlledValue,\n defaultValue,\n onChange,\n onFocus,\n onBlur,\n style: propStyle,\n styles,\n ...props\n },\n ref\n ) => {\n const [internalValue, setInternalValue] = useState(controlledValue ?? defaultValue ?? \"\");\n const [isFocused, setIsFocused] = React.useState(false);\n const [internalHeight, setInternalHeight] = React.useState<number | null>(null);\n const [internalWidth, setInternalWidth] = React.useState<number | null>(null);\n\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n const surfaceRef = React.useRef<HTMLDivElement>(null);\n const scrollWrapperRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRef(ref, textareaRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const currentValue = controlledValue !== undefined ? controlledValue : internalValue;\n const charCount = typeof currentValue === \"string\" ? currentValue.length : 0;\n const isOverLimit = maxCharacters ? charCount > maxCharacters : false;\n\n const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n onBlur?.(e);\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = e.target.value;\n\n if (maxCharacters && newValue.length > maxCharacters) {\n const truncated = newValue.slice(0, maxCharacters);\n if (controlledValue === undefined) {\n setInternalValue(truncated);\n }\n onChange?.({\n ...e,\n target: { ...e.target, value: truncated },\n } as React.ChangeEvent<HTMLTextAreaElement>);\n } else {\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n }\n onChange?.(e);\n }\n };\n\n const resolved = resolveTextAreaBaseStyles(styles);\n const resizeAxis = resolveResizeAxis(className, resizable);\n const canResize = resizeAxis !== \"none\" && !disabled;\n const textareaClassName = stripResizeClasses(className);\n\n const handleResizeMouseDown = React.useCallback((e: React.MouseEvent) => {\n if (!canResize) return;\n\n e.preventDefault();\n const textarea = textareaRef.current;\n const surface = surfaceRef.current;\n const scrollWrapper = scrollWrapperRef.current;\n if (!textarea || !surface) return;\n\n const computed = window.getComputedStyle(textarea);\n const minHeight = Number.parseFloat(computed.minHeight) || 60;\n const minWidth = Number.parseFloat(computed.minWidth) || 160;\n const startX = e.clientX;\n const startY = e.clientY;\n const startWidth = surface.getBoundingClientRect().width;\n const startHeight = maxHeight\n ? (scrollWrapper?.getBoundingClientRect().height ?? surface.getBoundingClientRect().height)\n : surface.getBoundingClientRect().height;\n\n const onMouseMove = (ev: MouseEvent) => {\n if (resizeAxis === \"x\" || resizeAxis === \"both\") {\n const deltaX = ev.clientX - startX;\n setInternalWidth(Math.max(minWidth, startWidth + deltaX));\n }\n\n if (resizeAxis === \"y\" || resizeAxis === \"both\") {\n const deltaY = ev.clientY - startY;\n setInternalHeight(Math.max(minHeight, startHeight + deltaY));\n }\n };\n\n const onMouseUp = () => {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"\";\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"none\";\n }, [canResize, maxHeight, resizeAxis]);\n\n const effectiveMaxHeight = internalHeight !== null ? `${internalHeight}px` : maxHeight;\n\n const autoResize = React.useCallback(() => {\n const el = textareaRef.current;\n if (!el || !maxHeight) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n }, [maxHeight]);\n\n React.useLayoutEffect(() => {\n autoResize();\n }, [autoResize, currentValue]);\n\n const surfaceStyle: React.CSSProperties = {\n ...(internalWidth !== null ? { width: `${internalWidth}px`, maxWidth: \"100%\" } : {}),\n ...(maxHeight === undefined && internalHeight !== null ? { height: `${internalHeight}px` } : {}),\n };\n\n const textareaStyle: React.CSSProperties = {\n ...propStyle,\n resize: \"none\",\n ...(maxHeight === undefined && internalHeight !== null ? { height: \"100%\" } : {}),\n };\n\n const textareaEl = (\n <textarea\n ref={mergedRef}\n disabled={disabled}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-active={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-size={size}\n data-resize-axis={resizeAxis}\n data-scroll={maxHeight ? \"true\" : undefined}\n className={cn('textarea', css.textarea, textareaClassName, resolved.root)}\n style={textareaStyle}\n value={currentValue}\n {...mergeProps(focusProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n onChange: handleChange,\n ...props,\n })}\n />\n );\n\n return (\n <div className={css.container}>\n <div\n ref={surfaceRef}\n className={css.surface}\n data-resize-axis={resizeAxis}\n style={surfaceStyle}\n >\n {maxHeight ? (\n <div\n ref={scrollWrapperRef}\n className={cn('textarea', 'scroll-wrapper', css[\"scroll-wrapper\"])}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-active={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-size={size}\n >\n <Scroll maxHeight={effectiveMaxHeight} style={{ height: \"auto\" }}>\n {textareaEl}\n </Scroll>\n </div>\n ) : (\n textareaEl\n )}\n {canResize && (\n <div\n aria-hidden=\"true\"\n data-axis={resizeAxis}\n data-slot=\"resize-handle\"\n className={cn('textarea', 'resize-handle', css[\"resize-handle\"])}\n onMouseDown={handleResizeMouseDown}\n />\n )}\n </div>\n {showCharacterCount && (\n <div\n className={cn('textarea', 'character-count', css[\"character-count\"], resolved.characterCount)}\n data-over-limit={isOverLimit || undefined}\n >\n {charCount}{maxCharacters ? ` / ${maxCharacters}` : \"\"} characters\n </div>\n )}\n </div>\n );\n }\n);\n\nTextArea.displayName = \"TextArea\";\n",
|
|
5042
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n --disabled-opacity: 0.6;\n\n @apply block w-full px-3 py-2;\n font-family: inherit;\n font-size: var(--text-
|
|
4307
|
+
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState, type ComponentPropsWithoutRef } from \"react\";\n\nimport { mergeProps } from \"@react-aria/utils\";\nimport { useFocusRing } from \"@react-aria/focus\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Scroll } from \"@/components/Scroll\";\nimport css from \"./Textarea.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\ntype ResizeAxis = \"both\" | \"x\" | \"y\" | \"none\";\n\ninterface TextAreaStyleSlots {\n root?: StyleValue;\n characterCount?: StyleValue;\n}\n\ntype TextAreaStylesProp = StylesProp<TextAreaStyleSlots>;\n\nconst resolveTextAreaBaseStyles = createStylesResolver(['root', 'characterCount'] as const);\n\nexport interface TextAreaProps extends Omit<ComponentPropsWithoutRef<\"textarea\">, \"size\"> {\n /** Size of the textarea */\n size?: Size;\n /** Whether to apply error styling */\n error?: boolean;\n /** Whether the textarea can be manually resized by the user. When enabled, `className` may include Tailwind `resize`, `resize-x`, `resize-y`, or `resize-none` to select the resize axis. */\n resizable?: boolean;\n /** Whether to display a character count below the textarea */\n showCharacterCount?: boolean;\n /** Maximum number of characters allowed */\n maxCharacters?: number;\n /** Maximum height before the custom scrollbar activates */\n maxHeight?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TextAreaStylesProp;\n}\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return React.useCallback((value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n }, refs);\n}\n\nconst resizeClassMap: Record<string, ResizeAxis> = {\n resize: \"both\",\n \"resize-x\": \"x\",\n \"resize-y\": \"y\",\n \"resize-none\": \"none\",\n};\n\nfunction resolveResizeAxis(className: string | undefined, resizable: boolean): ResizeAxis {\n if (!resizable) return \"none\";\n\n let axis: ResizeAxis | undefined;\n for (const token of className?.split(/\\s+/) ?? []) {\n const nextAxis = resizeClassMap[token];\n if (nextAxis) axis = nextAxis;\n }\n\n return axis ?? \"both\";\n}\n\nfunction stripResizeClasses(className: string | undefined) {\n if (!className) return className;\n\n const filtered = className\n .split(/\\s+/)\n .filter((token) => token && !resizeClassMap[token])\n .join(\" \");\n\n return filtered || undefined;\n}\n\nexport const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n className,\n size = \"md\",\n error = false,\n disabled,\n resizable = true,\n showCharacterCount = false,\n maxCharacters,\n maxHeight,\n value: controlledValue,\n defaultValue,\n onChange,\n onFocus,\n onBlur,\n style: propStyle,\n styles,\n ...props\n },\n ref\n ) => {\n const [internalValue, setInternalValue] = useState(controlledValue ?? defaultValue ?? \"\");\n const [isFocused, setIsFocused] = React.useState(false);\n const [internalHeight, setInternalHeight] = React.useState<number | null>(null);\n const [internalWidth, setInternalWidth] = React.useState<number | null>(null);\n\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n const surfaceRef = React.useRef<HTMLDivElement>(null);\n const scrollWrapperRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRef(ref, textareaRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const currentValue = controlledValue !== undefined ? controlledValue : internalValue;\n const charCount = typeof currentValue === \"string\" ? currentValue.length : 0;\n const isOverLimit = maxCharacters ? charCount > maxCharacters : false;\n\n const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n onBlur?.(e);\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = e.target.value;\n\n if (maxCharacters && newValue.length > maxCharacters) {\n const truncated = newValue.slice(0, maxCharacters);\n if (controlledValue === undefined) {\n setInternalValue(truncated);\n }\n onChange?.({\n ...e,\n target: { ...e.target, value: truncated },\n } as React.ChangeEvent<HTMLTextAreaElement>);\n } else {\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n }\n onChange?.(e);\n }\n };\n\n const resolved = resolveTextAreaBaseStyles(styles);\n const resizeAxis = resolveResizeAxis(className, resizable);\n const canResize = resizeAxis !== \"none\" && !disabled;\n const textareaClassName = stripResizeClasses(className);\n\n const handleResizeMouseDown = React.useCallback((e: React.MouseEvent) => {\n if (!canResize) return;\n\n e.preventDefault();\n const textarea = textareaRef.current;\n const surface = surfaceRef.current;\n const scrollWrapper = scrollWrapperRef.current;\n if (!textarea || !surface) return;\n\n const computed = window.getComputedStyle(textarea);\n const minHeight = Number.parseFloat(computed.minHeight) || 60;\n const minWidth = Number.parseFloat(computed.minWidth) || 160;\n const startX = e.clientX;\n const startY = e.clientY;\n const startWidth = surface.getBoundingClientRect().width;\n const startHeight = maxHeight\n ? (scrollWrapper?.getBoundingClientRect().height ?? surface.getBoundingClientRect().height)\n : surface.getBoundingClientRect().height;\n\n const onMouseMove = (ev: MouseEvent) => {\n if (resizeAxis === \"x\" || resizeAxis === \"both\") {\n const deltaX = ev.clientX - startX;\n setInternalWidth(Math.max(minWidth, startWidth + deltaX));\n }\n\n if (resizeAxis === \"y\" || resizeAxis === \"both\") {\n const deltaY = ev.clientY - startY;\n setInternalHeight(Math.max(minHeight, startHeight + deltaY));\n }\n };\n\n const onMouseUp = () => {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"\";\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"none\";\n }, [canResize, maxHeight, resizeAxis]);\n\n const effectiveMaxHeight = internalHeight !== null ? `${internalHeight}px` : maxHeight;\n\n const autoResize = React.useCallback(() => {\n const el = textareaRef.current;\n if (!el || !maxHeight) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n }, [maxHeight]);\n\n React.useLayoutEffect(() => {\n autoResize();\n }, [autoResize, currentValue]);\n\n const surfaceStyle: React.CSSProperties = {\n ...(internalWidth !== null ? { width: `${internalWidth}px`, maxWidth: \"100%\" } : {}),\n ...(maxHeight === undefined && internalHeight !== null ? { height: `${internalHeight}px` } : {}),\n };\n\n const textareaStyle: React.CSSProperties = {\n ...propStyle,\n resize: \"none\",\n ...(maxHeight === undefined && internalHeight !== null ? { height: \"100%\" } : {}),\n };\n\n const textareaEl = (\n <textarea\n ref={mergedRef}\n disabled={disabled}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-active={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-size={size}\n data-resize-axis={resizeAxis}\n data-scroll={maxHeight ? \"true\" : undefined}\n className={cn('textarea', css.textarea, textareaClassName, resolved.root)}\n style={textareaStyle}\n value={currentValue}\n {...mergeProps(focusProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n onChange: handleChange,\n ...props,\n })}\n />\n );\n\n return (\n <div className={css.container}>\n <div\n ref={surfaceRef}\n className={css.surface}\n data-resize-axis={resizeAxis}\n style={surfaceStyle}\n >\n {maxHeight ? (\n <div\n ref={scrollWrapperRef}\n className={cn('textarea', 'scroll-wrapper', css[\"scroll-wrapper\"])}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-active={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-size={size}\n >\n <Scroll maxHeight={effectiveMaxHeight} style={{ height: \"auto\" }}>\n {textareaEl}\n </Scroll>\n </div>\n ) : (\n textareaEl\n )}\n {canResize && (\n <div\n aria-hidden=\"true\"\n data-axis={resizeAxis}\n data-slot=\"resize-handle\"\n className={cn('textarea', 'resize-handle', css[\"resize-handle\"])}\n onMouseDown={handleResizeMouseDown}\n />\n )}\n </div>\n {showCharacterCount && (\n <div\n className={cn('textarea', 'character-count', css[\"character-count\"], resolved.characterCount)}\n data-over-limit={isOverLimit || undefined}\n >\n {charCount}{maxCharacters ? ` / ${maxCharacters}` : \"\"} characters\n </div>\n )}\n </div>\n );\n }\n);\n\nTextArea.displayName = \"TextArea\";\n",
|
|
4308
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n --disabled-opacity: 0.6;\n\n @apply block w-full px-3 py-2;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-sizing: border-box;\n resize: none;\n outline: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n outline: none;\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n color: var(--disabled-foreground);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n\n &[data-error] {\n border-color: var(--border);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n\n &[data-resize-axis=\"x\"],\n &[data-resize-axis=\"both\"] {\n padding-inline-end: calc(var(--textarea-padding-x) + 1rem);\n }\n\n &[data-resize-axis=\"y\"],\n &[data-resize-axis=\"both\"] {\n padding-block-end: calc(var(--textarea-padding-y) + 1rem);\n }\n\n &[data-scroll=\"true\"] {\n overflow: hidden;\n resize: none;\n background: transparent;\n border: none;\n border-radius: 0;\n box-shadow: none;\n\n &[data-active],\n &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n\n &[data-disabled] {\n background-color: transparent;\n opacity: 1;\n }\n\n &[data-error] {\n border-color: transparent;\n\n &[data-active] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n }\n }\n\n .surface {\n @apply relative w-full;\n }\n\n .resize-handle {\n position: absolute;\n z-index: 1;\n touch-action: none;\n user-select: none;\n\n &::before {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--resize-handle-color);\n transition: background-color 150ms;\n }\n\n &::after {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--resize-handle-color);\n transition: background-color 150ms;\n }\n\n &:hover::before,\n &:hover::after {\n background-color: var(--resize-handle-color-hover);\n }\n\n &[data-axis=\"both\"] {\n right: 3px;\n bottom: 3px;\n width: 1.5rem;\n height: 1.5rem;\n cursor: nwse-resize;\n\n &::before {\n bottom: 0.35rem;\n right: 0.15rem;\n width: 0.5rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n\n &::after {\n bottom: 0.6rem;\n right: 0.2rem;\n width: 1.0rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n }\n\n &[data-axis=\"x\"] {\n top: 50%;\n right: 0;\n width: 0.75rem;\n height: 2rem;\n cursor: ew-resize;\n transform: translateY(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 0.125rem;\n height: 1.5rem;\n transform: translate(-50%, -50%);\n }\n }\n\n &[data-axis=\"y\"] {\n left: 50%;\n bottom: 0;\n width: 2rem;\n height: 0.75rem;\n cursor: ns-resize;\n transform: translateX(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 1.5rem;\n height: 0.125rem;\n transform: translate(-50%, -50%);\n }\n }\n }\n\n .scroll-wrapper {\n --disabled-opacity: 0.6;\n\n @apply w-full overflow-hidden;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n outline: none;\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n opacity: var(--disabled-opacity);\n }\n\n &[data-error] {\n border-color: var(--border);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n }\n\n .textarea[data-size=\"sm\"] {\n min-height: 5rem;\n --textarea-padding-x: 0.5rem;\n --textarea-padding-y: 0.25rem;\n font-size: var(--text-sm);\n @apply px-2 py-1;\n }\n\n .textarea[data-size=\"md\"] {\n min-height: 6rem;\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n font-size: var(--text-sm);\n @apply px-3 py-2;\n }\n\n .textarea[data-size=\"lg\"] {\n min-height: 8rem;\n --textarea-padding-x: 1rem;\n --textarea-padding-y: 0.75rem;\n font-size: var(--text-md);\n @apply px-4 py-3;\n }\n\n .container {\n @apply w-full;\n }\n\n .character-count {\n font-size: var(--text-sm);\n color: var(--character-count-color);\n @apply mt-1;\n transition: color 0.15s var(--ease-snappy-pop);\n }\n\n .character-count[data-over-limit] {\n color: var(--character-count-over-limit-color);\n }\n}\n",
|
|
5043
4309
|
"cssTypes": "declare const styles: {\n textarea: string;\n container: string;\n surface: string;\n \"character-count\": string;\n \"scroll-wrapper\": string;\n \"resize-handle\": string;\n};\n\nexport default styles;\n"
|
|
5044
4310
|
},
|
|
5045
4311
|
"toast": {
|
|
5046
|
-
"tsx": "\"use client\";\n\nimport React, { forwardRef, useImperativeHandle, useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport { useGSAP } from \"@gsap/react\";\nimport { cn } from \"./utils\";\nimport { createStylesResolver } from '@/lib/styles';\nimport css from './Toast.module.css';\nimport { ToastProps as ToastData, dispatch } from \"./Toast.Store\";\n\nimport { Info, CircleCheck, TriangleAlert, CircleAlert } from \"lucide-react\";\n\nimport { X } from 'lucide-react';\n\nconst DRAG_DISMISS_THRESHOLD = 100;\nconst DRAG_LEFT_RESISTANCE = 20;\n\nconst resolveToastBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'title',\n 'description',\n 'close',\n 'icon'\n] as const);\n\nconst toastIcons = {\n danger: <TriangleAlert className={css.icon} />,\n success: <CircleCheck className={css.icon} />,\n info: <Info className={css.icon} />,\n warning: <CircleAlert className={css.icon} />,\n default: null,\n};\n\ninterface ToastComponentProps {\n /** Toast data object containing content and display options */\n toast: ToastData;\n /** Whether the auto-dismiss timer pauses on mouse hover */\n pauseOnHover?: boolean;\n onDragStart?: () => void;\n onDragEnd?: () => void;\n onDismissStart?: () => void;\n onDismissEnd?: () => void;\n}\n\nexport const Toast = forwardRef<HTMLDivElement, ToastComponentProps>(function Toast(\n { toast, pauseOnHover = false, onDragStart, onDragEnd, onDismissStart, onDismissEnd },\n ref\n) {\n const innerRef = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => innerRef.current!);\n\n const {\n id,\n title,\n description,\n jsx,\n variant = 'default',\n duration = 5000,\n onDismiss,\n position = 'bottom-right',\n styles,\n } = toast;\n\n const resolved = resolveToastBaseStyles(styles);\n const isTop = position.startsWith('top');\n\n // Time tracking refs\n const elapsedRef = useRef(0);\n const lastTimeRef = useRef<number>(Date.now());\n const animationFrameRef = useRef<number | null>(null);\n\n // Use a ref for paused state to avoid restarting the effect on every hover change\n const isPausedRef = useRef(pauseOnHover);\n useEffect(() => {\n isPausedRef.current = pauseOnHover;\n }, [pauseOnHover]);\n\n // Drag state refs\n const dragStartXRef = useRef(0);\n const currentDeltaRef = useRef(0);\n const dragPausedRef = useRef(false);\n\n const handleDismiss = useCallback(() => {\n // Change absolute numbers to relative strings\n const yOffset = isTop ? \"-=20\" : \"+=20\";\n\n if (innerRef.current) {\n innerRef.current.dataset.dismissing = \"true\";\n onDismissStart?.();\n dispatch({ type: 'CLOSE_TOAST', toastId: id });\n gsap.killTweensOf(innerRef.current);\n gsap.to(innerRef.current, {\n opacity: 0,\n y: yOffset, // Animates relative to its current layout position\n scale: 0.9,\n duration: 0.3,\n onComplete: () => {\n onDismissEnd?.();\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n },\n });\n } else {\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n }\n }, [id, isTop, onDismiss]);\n\n useGSAP(() => {\n if (!innerRef.current) return;\n\n const spawnDir = toast.spawnDirection || 'bottom';\n const fromY = spawnDir === 'top' ? (isTop ?
|
|
5047
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .toast {\n @apply flex w-full max-w-[28rem] items-start gap-3 px-4 py-2.5 select-none;\n background: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow);\n font-family: inherit;\n font-size: var(--text-
|
|
4312
|
+
"tsx": "\"use client\";\n\nimport React, { forwardRef, useImperativeHandle, useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport { useGSAP } from \"@gsap/react\";\nimport { cn } from \"./utils\";\nimport { createStylesResolver } from '@/lib/styles';\nimport css from './Toast.module.css';\nimport { ToastProps as ToastData, dispatch } from \"./Toast.Store\";\n\nimport { Info, CircleCheck, TriangleAlert, CircleAlert } from \"lucide-react\";\n\nimport { X } from 'lucide-react';\n\nconst DRAG_DISMISS_THRESHOLD = 100;\nconst DRAG_LEFT_RESISTANCE = 20;\n\nconst resolveToastBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'title',\n 'description',\n 'close',\n 'icon'\n] as const);\n\nconst toastIcons = {\n danger: <TriangleAlert className={css.icon} />,\n success: <CircleCheck className={css.icon} />,\n info: <Info className={css.icon} />,\n warning: <CircleAlert className={css.icon} />,\n default: null,\n};\n\ninterface ToastComponentProps {\n /** Toast data object containing content and display options */\n toast: ToastData;\n /** Whether the auto-dismiss timer pauses on mouse hover */\n pauseOnHover?: boolean;\n onDragStart?: () => void;\n onDragEnd?: () => void;\n onDismissStart?: () => void;\n onDismissEnd?: () => void;\n}\n\nexport const Toast = forwardRef<HTMLDivElement, ToastComponentProps>(function Toast(\n { toast, pauseOnHover = false, onDragStart, onDragEnd, onDismissStart, onDismissEnd },\n ref\n) {\n const innerRef = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => innerRef.current!);\n\n const {\n id,\n title,\n description,\n jsx,\n variant = 'default',\n duration = 5000,\n onDismiss,\n position = 'bottom-right',\n styles,\n } = toast;\n\n const resolved = resolveToastBaseStyles(styles);\n const isTop = position.startsWith('top');\n\n // Time tracking refs\n const elapsedRef = useRef(0);\n const lastTimeRef = useRef<number>(Date.now());\n const animationFrameRef = useRef<number | null>(null);\n\n // Use a ref for paused state to avoid restarting the effect on every hover change\n const isPausedRef = useRef(pauseOnHover);\n useEffect(() => {\n isPausedRef.current = pauseOnHover;\n }, [pauseOnHover]);\n\n // Drag state refs\n const dragStartXRef = useRef(0);\n const currentDeltaRef = useRef(0);\n const dragPausedRef = useRef(false);\n\n const handleDismiss = useCallback(() => {\n // Change absolute numbers to relative strings\n const yOffset = isTop ? \"-=20\" : \"+=20\";\n\n if (innerRef.current) {\n innerRef.current.dataset.dismissing = \"true\";\n onDismissStart?.();\n dispatch({ type: 'CLOSE_TOAST', toastId: id });\n gsap.killTweensOf(innerRef.current);\n gsap.to(innerRef.current, {\n opacity: 0,\n y: yOffset, // Animates relative to its current layout position\n scale: 0.9,\n duration: 0.3,\n onComplete: () => {\n onDismissEnd?.();\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n },\n });\n } else {\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n }\n }, [id, isTop, onDismiss]);\n\n useGSAP(() => {\n if (!innerRef.current) return;\n\n const spawnDir = toast.spawnDirection || 'bottom';\n const fromY = spawnDir === 'top' ? (isTop ? 25 : -25) : (isTop ? -25 : 25);\n\n gsap.from(innerRef.current, {\n opacity: 1,\n y: fromY,\n duration: 0.35,\n ease: \"power3.out\",\n });\n }, { scope: innerRef });\n\n const handlePointerDown = useCallback((e: React.PointerEvent) => {\n if (innerRef.current?.dataset.dismissing) return;\n dragStartXRef.current = e.clientX;\n currentDeltaRef.current = 0;\n dragPausedRef.current = true;\n onDragStart?.();\n gsap.killTweensOf(innerRef.current);\n\n const onMove = (ev: PointerEvent) => {\n if (!innerRef.current) return;\n const delta = ev.clientX - dragStartXRef.current;\n currentDeltaRef.current = delta;\n\n if (delta >= 0) {\n const progress = Math.min(delta / DRAG_DISMISS_THRESHOLD, 1);\n gsap.set(innerRef.current, { x: delta, opacity: 1 - progress * 0.5 });\n } else {\n const x = -DRAG_LEFT_RESISTANCE * (1 - Math.exp(delta / DRAG_LEFT_RESISTANCE));\n gsap.set(innerRef.current, { x, opacity: 1 });\n }\n };\n\n const onUp = () => {\n dragPausedRef.current = false;\n onDragEnd?.();\n document.removeEventListener('pointermove', onMove);\n document.removeEventListener('pointerup', onUp);\n document.removeEventListener('pointercancel', onUp);\n\n const delta = currentDeltaRef.current;\n currentDeltaRef.current = 0;\n\n if (delta >= DRAG_DISMISS_THRESHOLD) {\n if (innerRef.current) {\n innerRef.current.dataset.dismissing = \"true\";\n onDismissStart?.();\n // Dispatch CLOSE_TOAST immediately to signal stack adjustment\n dispatch({ type: 'CLOSE_TOAST', toastId: id });\n gsap.killTweensOf(innerRef.current);\n gsap.to(innerRef.current, {\n x: \"+=200\",\n opacity: 0,\n duration: 0.25,\n ease: \"power2.in\",\n onComplete: () => {\n onDismissEnd?.();\n onDismiss?.();\n // Dispatch DISMISS_TOAST after animation completes\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n },\n });\n } else {\n // If innerRef.current is null, just dismiss immediately\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n }\n } else if (innerRef.current) {\n gsap.to(innerRef.current, {\n x: 0,\n opacity: 1,\n duration: 0.55,\n ease: \"elastic.out(1, 0.55)\",\n });\n }\n };\n\n document.addEventListener('pointermove', onMove);\n document.addEventListener('pointerup', onUp);\n document.addEventListener('pointercancel', onUp);\n }, [id, isTop, onDismiss, onDragStart, onDragEnd, onDismissStart, onDismissEnd]);\n\n // Animation Frame Timer Logic\n useEffect(() => {\n if (duration === Infinity || duration <= 0) return;\n lastTimeRef.current = Date.now();\n\n const loop = () => {\n const now = Date.now();\n const delta = now - lastTimeRef.current;\n lastTimeRef.current = now;\n\n if (!isPausedRef.current && !dragPausedRef.current) {\n elapsedRef.current += delta;\n\n if (elapsedRef.current >= duration) {\n handleDismiss();\n return;\n }\n }\n\n animationFrameRef.current = requestAnimationFrame(loop);\n };\n\n animationFrameRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }, [duration, handleDismiss]);\n\n const icon = toastIcons[variant as keyof typeof toastIcons];\n\n return (\n <div\n ref={innerRef}\n className={cn('toast', css.toast, variant, resolved.root)}\n role=\"alert\"\n onPointerDown={handlePointerDown}\n >\n {icon && <div className={cn(\"toast-icon\", resolved.icon)}>{icon}</div>}\n <div className={cn('toast-content', css.content, resolved.content)}>\n {jsx || (\n <>\n {title && <h4 className={cn('toast-title', css.title, resolved.title)}>{title}</h4>}\n {description && <p className={cn('toast-description', css.description, resolved.description)}>{description}</p>}\n </>\n )}\n {toast.action}\n </div>\n <button\n onClick={handleDismiss}\n className={cn('toast-close', css.close, resolved.close)}\n aria-label=\"Close\"\n >\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n );\n});\n",
|
|
4313
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .toast {\n @apply flex w-full max-w-[28rem] items-start gap-3 px-4 py-2.5 select-none;\n background: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow);\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n touch-action: pan-y;\n }\n\n .icon {\n @apply mr-4 mt-2 h-5 w-5 shrink-0;\n color: var(--icon-color);\n }\n\n .content {\n @apply min-w-0 flex-1;\n }\n\n .title {\n @apply m-0;\n font-weight: var(--font-weight-semibold);\n font-size: var(--text-sm);\n line-height: var(--leading-tight);\n color: var(--title-color);\n }\n\n .description {\n @apply mt-1 mb-0;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n color: var(--description-color);\n }\n\n .close {\n @apply flex shrink-0 items-center justify-center p-2 cursor-pointer;\n background: transparent;\n border: none;\n border-radius: var(--radius-sm);\n color: var(--close-color);\n opacity: 0.6;\n transition: opacity 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n\n @media (hover: hover) {\n &:hover {\n opacity: 1;\n background: var(--close-hover-background);\n }\n }\n }\n}\n",
|
|
5048
4314
|
"cssTypes": "declare const styles: {\n toast: string;\n icon: string;\n content: string;\n title: string;\n description: string;\n close: string;\n default: string;\n danger: string;\n success: string;\n info: string;\n warning: string;\n};\n\nexport default styles;\n"
|
|
5049
4315
|
},
|
|
5050
4316
|
"tooltip": {
|
|
5051
|
-
"tsx": "\"use client\";\n\nimport React, { useRef, useState, useEffect, useCallback } from \"react\";\n\nimport { createPortal } from \"react-dom\";\nimport { useTooltipTrigger, useTooltip, mergeProps } from \"react-aria\";\nimport { useFloating, offset, flip, shift, autoUpdate } from \"@floating-ui/react-dom\";\nimport { cn } from \"./utils\";\nimport { useTooltipTriggerState } from \"react-stately\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { Frame } from \"../Frame\";\nimport { Badge } from \"../Badge\";\nimport css from \"./Tooltip.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { type StyleValue } from \"./utils\";\n\nconst ARROW_PATH = \"M 0 0 C 3 0 4 -9 6 -9 C 8 -9 9 0 12 0\";\nconst ARROW_WIDTH = 12;\nconst TOOLTIP_GAP = 4;\nconst ARROW_POSITIONING_SIZE = 6;\nconst DEFAULT_SHOW_DELAY_MS = 600;\nconst SWAP_WINDOW_MS = 150;\nconst EXIT_ANIMATION_MS = 160;\n\nlet lastCloseTime = 0;\nlet lastOpenTime = 0;\nlet pendingExit: (() => void) | null = null;\n\nfunction useTimeout() {\n const idRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const start = useCallback((fn: () => void, ms: number) => {\n clearTimeout(idRef.current);\n idRef.current = setTimeout(fn, ms);\n }, []);\n\n const clear = useCallback(() => {\n clearTimeout(idRef.current);\n }, []);\n\n useEffect(() => () => clearTimeout(idRef.current), []);\n\n return [start, clear] as const;\n}\n\ntype TooltipPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: TooltipPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\nexport interface TooltipStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n frame?: StyleValue;\n hintBadge?: StyleValue;\n}\n\nexport type TooltipStylesProp = StylesProp<TooltipStyleSlots>;\n\nconst resolveTooltipStyles = createStylesResolver([\n 'root',\n 'trigger',\n 'content',\n 'frame',\n 'hintBadge',\n] as const);\n\nexport interface TooltipProps {\n children: React.ReactNode;\n /** Content to display inside the tooltip */\n content: React.ReactNode;\n /** Preferred side of the trigger where the tooltip appears */\n position?: TooltipPosition;\n /** Additional CSS class for the trigger wrapper */\n className?: string;\n /** Milliseconds before the tooltip appears after hover */\n delay?: number;\n /** Whether the tooltip is disabled */\n isDisabled?: boolean;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the tooltip opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the tooltip */\n hint?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TooltipStylesProp;\n}\n\nconst Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(\n (\n {\n children,\n content,\n position = \"top\",\n className,\n delay = DEFAULT_SHOW_DELAY_MS,\n isDisabled = false,\n isOpen: controlledIsOpen,\n onOpenChange,\n showArrow = false,\n hint,\n styles,\n },\n _ref\n ) => {\n const triggerRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const [shouldRender, setShouldRender] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n const [isInstant, setIsInstant] = useState(false);\n const wasOpenRef = useRef(false);\n const [startSwapTimer, clearSwapTimer] = useTimeout();\n const [startUnmountTimer, clearUnmountTimer] = useTimeout();\n\n const resolved = resolveTooltipStyles(styles);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n\n const state = useTooltipTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange: useCallback((open: boolean) => {\n if (open) lastOpenTime = Date.now();\n else lastCloseTime = Date.now();\n onOpenChangeRef.current?.(open);\n }, []),\n delay,\n isDisabled,\n });\n\n const { triggerProps, tooltipProps } = useTooltipTrigger(\n { isDisabled },\n state,\n triggerRef\n );\n const { tooltipProps: ariaTooltipProps } = useTooltip({}, state);\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: position,\n // Tooltips are commonly used in fixed headers; fixed positioning avoids scroll drift.\n strategy: \"fixed\",\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(TOOLTIP_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n useEffect(() => {\n if (state.isOpen) {\n wasOpenRef.current = true;\n const elapsed = Date.now() - lastCloseTime;\n const isSwap = lastCloseTime > 0 && elapsed < SWAP_WINDOW_MS;\n\n if (pendingExit) {\n pendingExit();\n pendingExit = null;\n }\n\n setIsInstant(isSwap);\n setShouldRender(true);\n } else if (wasOpenRef.current) {\n wasOpenRef.current = false;\n\n if (lastOpenTime > 0 && lastOpenTime >= lastCloseTime) {\n setIsVisible(false);\n setShouldRender(false);\n return;\n }\n\n // Non-batched: delay exit to allow cross-frame swap detection.\n // If another tooltip opens within the window, pendingExit cancels.\n startSwapTimer(() => {\n setIsVisible(false);\n startUnmountTimer(() => {\n setShouldRender(false);\n pendingExit = null;\n }, EXIT_ANIMATION_MS);\n }, SWAP_WINDOW_MS);\n\n pendingExit = () => {\n clearSwapTimer();\n clearUnmountTimer();\n setIsVisible(false);\n setShouldRender(false);\n };\n }\n }, [state.isOpen]);\n\n useEffect(() => {\n if (shouldRender && state.isOpen && isPositioned) {\n if (isInstant) {\n setIsVisible(true);\n const frame = requestAnimationFrame(() => setIsInstant(false));\n return () => cancelAnimationFrame(frame);\n } else {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setIsVisible(true);\n });\n });\n }\n }\n }, [shouldRender, state.isOpen, isPositioned, isInstant]);\n\n const mergedTriggerRef = useCallback((el: HTMLDivElement | null) => {\n (triggerRef as React.MutableRefObject<HTMLDivElement | null>).current = el;\n refs.setReference(el);\n }, [refs]);\n\n const mergedFloatingRef = useCallback((el: HTMLDivElement | null) => {\n (tooltipRef as React.MutableRefObject<HTMLDivElement | null>).current = el;\n refs.setFloating(el);\n }, [refs]);\n\n const trigger = triggerRef.current;\n const isTriggerVisible = !!(trigger && (trigger.offsetWidth > 0 || trigger.offsetHeight > 0));\n\n return (\n <>\n <div\n ref={mergedTriggerRef}\n {...asElementProps<\"div\">(mergeProps(triggerProps))}\n className={cn(css.trigger, className, resolved.trigger)}\n >\n {children}\n </div>\n\n {shouldRender &&\n createPortal(\n <div\n ref={mergedFloatingRef}\n {...asElementProps<\"div\">(mergeProps(tooltipProps, ariaTooltipProps))}\n className={cn(css.root, resolved.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn('tooltip', 'content', css.content, resolved.content)}\n data-visible={(isVisible && isTriggerVisible) ? \"true\" : \"false\"}\n data-instant={(isInstant || !isTriggerVisible) ? \"true\" : undefined}\n style={{\n transform: (isVisible && isTriggerVisible) ? \"scale(1)\" : getInitialTransform(placement),\n }}\n >\n <Frame\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n cornerRadius={8}\n padding=\"none\"\n >\n <div className={cn('tooltip', 'frame', css.frame, resolved.frame)} data-hint={hint ? \"\" : undefined}>\n {content}\n {hint && <Badge variant=\"secondary\" size=\"sm\" className={cn(resolved.hintBadge)}>{hint}</Badge>}\n </div>\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nTooltip.displayName = \"Tooltip\";\n\nexport { Tooltip };\n",
|
|
5052
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-
|
|
4317
|
+
"tsx": "\"use client\";\n\nimport React, { useRef, useState, useEffect, useCallback } from \"react\";\n\nimport { createPortal } from \"react-dom\";\nimport { useTooltipTrigger, useTooltip, mergeProps } from \"react-aria\";\nimport { useFloating } from \"../../hooks/useFloat/react/useFloating\";\nimport { flip } from \"../../hooks/useFloat/core/middleware/flip\";\nimport { offset } from \"../../hooks/useFloat/core/middleware/offset\";\nimport { shift } from \"../../hooks/useFloat/core/middleware/shift\";\nimport { autoUpdate } from \"../../hooks/useFloat/dom/autoUpdate\";\nimport { cn } from \"./utils\";\nimport { useTooltipTriggerState } from \"react-stately\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { Frame } from \"../Frame\";\nimport { Badge } from \"../Badge\";\nimport css from \"./Tooltip.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { type StyleValue } from \"./utils\";\n\nconst ARROW_PATH = \"M 0 0 C 3 0 4 -9 6 -9 C 8 -9 9 0 12 0\";\nconst ARROW_WIDTH = 12;\nconst TOOLTIP_GAP = 4;\nconst ARROW_POSITIONING_SIZE = 6;\nconst DEFAULT_SHOW_DELAY_MS = 600;\nconst SWAP_WINDOW_MS = 150;\nconst EXIT_ANIMATION_MS = 160;\n\nlet lastCloseTime = 0;\nlet lastOpenTime = 0;\nlet pendingExit: (() => void) | null = null;\n\nfunction useTimeout() {\n const idRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const start = useCallback((fn: () => void, ms: number) => {\n clearTimeout(idRef.current);\n idRef.current = setTimeout(fn, ms);\n }, []);\n\n const clear = useCallback(() => {\n clearTimeout(idRef.current);\n }, []);\n\n useEffect(() => () => clearTimeout(idRef.current), []);\n\n return [start, clear] as const;\n}\n\ntype TooltipPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: TooltipPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\ninterface TooltipStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n frame?: StyleValue;\n hintBadge?: StyleValue;\n}\n\ntype TooltipStylesProp = StylesProp<TooltipStyleSlots>;\n\nconst resolveTooltipStyles = createStylesResolver([\n 'root',\n 'trigger',\n 'content',\n 'frame',\n 'hintBadge',\n] as const);\n\nexport interface TooltipProps {\n children: React.ReactNode;\n /** Content to display inside the tooltip */\n content: React.ReactNode;\n /** Preferred side of the trigger where the tooltip appears */\n position?: TooltipPosition;\n /** Additional CSS class for the trigger wrapper */\n className?: string;\n /** Milliseconds before the tooltip appears after hover */\n delay?: number;\n /** Whether the tooltip is disabled */\n isDisabled?: boolean;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the tooltip opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the tooltip */\n hint?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TooltipStylesProp;\n}\n\nconst Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(\n (\n {\n children,\n content,\n position = \"top\",\n className,\n delay = DEFAULT_SHOW_DELAY_MS,\n isDisabled = false,\n isOpen: controlledIsOpen,\n onOpenChange,\n showArrow = false,\n hint,\n styles,\n },\n _ref\n ) => {\n const triggerRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const [shouldRender, setShouldRender] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n const [isInstant, setIsInstant] = useState(false);\n const wasOpenRef = useRef(false);\n const [startSwapTimer, clearSwapTimer] = useTimeout();\n const [startUnmountTimer, clearUnmountTimer] = useTimeout();\n\n const resolved = resolveTooltipStyles(styles);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n\n const state = useTooltipTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange: useCallback((open: boolean) => {\n if (open) lastOpenTime = Date.now();\n else lastCloseTime = Date.now();\n onOpenChangeRef.current?.(open);\n }, []),\n delay,\n isDisabled,\n });\n\n const { triggerProps, tooltipProps } = useTooltipTrigger(\n { isDisabled },\n state,\n triggerRef\n );\n const { tooltipProps: ariaTooltipProps } = useTooltip({}, state);\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: position,\n // Tooltips are commonly used in fixed headers; fixed positioning avoids scroll drift.\n strategy: \"fixed\",\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(TOOLTIP_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n useEffect(() => {\n if (state.isOpen) {\n wasOpenRef.current = true;\n const elapsed = Date.now() - lastCloseTime;\n const isSwap = lastCloseTime > 0 && elapsed < SWAP_WINDOW_MS;\n\n if (pendingExit) {\n pendingExit();\n pendingExit = null;\n }\n\n setIsInstant(isSwap);\n setShouldRender(true);\n } else if (wasOpenRef.current) {\n wasOpenRef.current = false;\n\n if (lastOpenTime > 0 && lastOpenTime >= lastCloseTime) {\n setIsVisible(false);\n setShouldRender(false);\n return;\n }\n\n // Non-batched: delay exit to allow cross-frame swap detection.\n // If another tooltip opens within the window, pendingExit cancels.\n startSwapTimer(() => {\n setIsVisible(false);\n startUnmountTimer(() => {\n setShouldRender(false);\n pendingExit = null;\n }, EXIT_ANIMATION_MS);\n }, SWAP_WINDOW_MS);\n\n pendingExit = () => {\n clearSwapTimer();\n clearUnmountTimer();\n setIsVisible(false);\n setShouldRender(false);\n };\n }\n }, [state.isOpen]);\n\n useEffect(() => {\n if (shouldRender && state.isOpen && isPositioned) {\n if (isInstant) {\n setIsVisible(true);\n const frame = requestAnimationFrame(() => setIsInstant(false));\n return () => cancelAnimationFrame(frame);\n } else {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setIsVisible(true);\n });\n });\n }\n }\n }, [shouldRender, state.isOpen, isPositioned, isInstant]);\n\n const mergedTriggerRef = useCallback((el: HTMLDivElement | null) => {\n (triggerRef as React.MutableRefObject<HTMLDivElement | null>).current = el;\n refs.setReference(el);\n }, [refs]);\n\n const mergedFloatingRef = useCallback((el: HTMLDivElement | null) => {\n (tooltipRef as React.MutableRefObject<HTMLDivElement | null>).current = el;\n refs.setFloating(el);\n }, [refs]);\n\n const trigger = triggerRef.current;\n const isTriggerVisible = !!(trigger && (trigger.offsetWidth > 0 || trigger.offsetHeight > 0));\n\n return (\n <>\n <div\n ref={mergedTriggerRef}\n {...asElementProps<\"div\">(mergeProps(triggerProps))}\n className={cn(css.trigger, className, resolved.trigger)}\n >\n {children}\n </div>\n\n {shouldRender &&\n createPortal(\n <div\n ref={mergedFloatingRef}\n {...asElementProps<\"div\">(mergeProps(tooltipProps, ariaTooltipProps))}\n className={cn(css.root, resolved.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn('tooltip', 'content', css.content, resolved.content)}\n data-visible={(isVisible && isTriggerVisible) ? \"true\" : \"false\"}\n data-instant={(isInstant || !isTriggerVisible) ? \"true\" : undefined}\n style={{\n transform: (isVisible && isTriggerVisible) ? \"scale(1)\" : getInitialTransform(placement),\n }}\n >\n <Frame\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n cornerRadius={8}\n padding=\"none\"\n >\n <div className={cn('tooltip', 'frame', css.frame, resolved.frame)} data-hint={hint ? \"\" : undefined}>\n {content}\n {hint && <Badge variant=\"secondary\" size=\"sm\" className={cn(resolved.hintBadge)}>{hint}</Badge>}\n </div>\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nTooltip.displayName = \"Tooltip\";\n\nexport { Tooltip };\n",
|
|
4318
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n\n .frame[data-hint] {\n @apply pr-1;\n }\n}\n",
|
|
5053
4319
|
"cssTypes": "export interface Styles {\n trigger: string;\n root: string;\n content: string;\n frame: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
5054
4320
|
}
|
|
5055
4321
|
};
|
|
@@ -5285,6 +4551,6 @@ export const generatedCorePeerDependencies = [
|
|
|
5285
4551
|
"react-dom"
|
|
5286
4552
|
];
|
|
5287
4553
|
export const packageMetadata = {
|
|
5288
|
-
"version": "0.3.
|
|
4554
|
+
"version": "0.3.2"
|
|
5289
4555
|
};
|
|
5290
4556
|
//# sourceMappingURL=generated-data.js.map
|