ui-lab-registry 0.3.2 → 0.3.4
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/components/Button/examples/01-variants.d.ts.map +1 -1
- package/dist/components/Button/examples/01-variants.js.map +1 -1
- package/dist/components/Button/examples/02-multi-actions.d.ts +6 -0
- package/dist/components/Button/examples/02-multi-actions.d.ts.map +1 -0
- package/dist/components/Button/examples/02-multi-actions.js +12 -0
- package/dist/components/Button/examples/02-multi-actions.js.map +1 -0
- package/dist/components/Button/examples/03-joined-toggle.d.ts +6 -0
- package/dist/components/Button/examples/03-joined-toggle.d.ts.map +1 -0
- package/dist/components/Button/examples/03-joined-toggle.js +15 -0
- package/dist/components/Button/examples/03-joined-toggle.js.map +1 -0
- package/dist/components/Button/examples/04-sub-stack-actions.d.ts +6 -0
- package/dist/components/Button/examples/04-sub-stack-actions.d.ts.map +1 -0
- package/dist/components/Button/examples/04-sub-stack-actions.js +15 -0
- package/dist/components/Button/examples/04-sub-stack-actions.js.map +1 -0
- package/dist/components/Button/examples/05-split-action-button.d.ts +6 -0
- package/dist/components/Button/examples/05-split-action-button.d.ts.map +1 -0
- package/dist/components/Button/examples/05-split-action-button.js +42 -0
- package/dist/components/Button/examples/05-split-action-button.js.map +1 -0
- package/dist/components/Button/examples/index.d.ts +8 -0
- package/dist/components/Button/examples/index.d.ts.map +1 -1
- package/dist/components/Button/examples/index.js +8 -0
- package/dist/components/Button/examples/index.js.map +1 -1
- package/dist/components/Button/examples.json +27 -1
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +8 -0
- package/dist/components/Button/index.js.map +1 -1
- package/dist/generated-data.d.ts.map +1 -1
- package/dist/generated-data.js +101 -140
- package/dist/generated-data.js.map +1 -1
- package/dist/generated-styles.d.ts.map +1 -1
- package/dist/generated-styles.js +142 -76
- package/dist/generated-styles.js.map +1 -1
- package/dist/generated-styles.json +142 -76
- package/dist/patterns/layout/media-object/metadata.json +1 -1
- package/dist/patterns/layout/media-object/variations/lg/index.js +1 -1
- package/dist/patterns/layout/media-object/variations/lg/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +38 -12
- package/dist/registry.js.map +1 -1
- package/dist/sections/CTA/index.js +1 -1
- package/dist/sections/CTA/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Anchor/metadata.json +1 -1
- package/src/components/Button/examples/01-variants.tsx +1 -1
- package/src/components/Button/examples/02-multi-actions.tsx +20 -0
- package/src/components/Button/examples/03-joined-toggle.tsx +32 -0
- package/src/components/Button/examples/04-sub-stack-actions.tsx +29 -0
- package/src/components/Button/examples/05-split-action-button.tsx +108 -0
- package/src/components/Button/examples/index.ts +8 -0
- package/src/components/Button/examples.json +27 -1
- package/src/components/Button/index.tsx +8 -0
- package/src/components/Color/metadata.json +1 -1
- package/src/components/Date/metadata.json +1 -1
- package/src/components/Expand/metadata.json +1 -1
- package/src/components/Frame/metadata.json +1 -1
- package/src/components/Gallery/metadata.json +1 -1
- package/src/components/Grid/metadata.json +1 -1
- package/src/components/Mask/metadata.json +1 -1
- package/src/components/Page/metadata.json +1 -1
- package/src/components/Path/metadata.json +1 -1
- package/src/components/Scroll/metadata.json +1 -1
- package/src/generated-data.ts +101 -140
- package/src/generated-styles.ts +142 -76
- package/src/patterns/layout/media-object/metadata.json +1 -1
- package/src/patterns/layout/media-object/variations/lg/index.tsx +1 -1
- package/src/registry.ts +38 -12
- package/src/sections/CTA/index.tsx +1 -1
package/dist/generated-data.js
CHANGED
|
@@ -161,7 +161,33 @@ export const generatedAPI = {
|
|
|
161
161
|
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
162
162
|
}
|
|
163
163
|
],
|
|
164
|
-
"examples": [
|
|
164
|
+
"examples": [
|
|
165
|
+
{
|
|
166
|
+
"title": "Button Variants",
|
|
167
|
+
"description": "All available button variants including primary, default, secondary, outline, and ghost styles.",
|
|
168
|
+
"code": "import React from 'react'\nimport { Button } from 'ui-lab-components'\n\nexport default function Example() {\n return (\n <div className=\"p-4 space-y-8\">\n <div>\n <h3 className=\"text-sm font-semibold text-foreground-200 mb-3\">Primary Variant</h3>\n <div className=\"flex gap-2 flex-wrap\">\n <Button variant=\"primary\">Primary Button</Button>\n <Button variant=\"primary\" disabled>Disabled</Button>\n </div>\n </div>\n\n <div>\n <h3 className=\"text-sm font-semibold text-foreground-200 mb-3\">Default Variant</h3>\n <div className=\"flex gap-2 flex-wrap\">\n <Button variant=\"default\">Default Button</Button>\n <Button variant=\"default\" disabled>Disabled</Button>\n </div>\n </div>\n\n <div>\n <h3 className=\"text-sm font-semibold text-foreground-200 mb-3\">Secondary Variant</h3>\n <div className=\"flex gap-2 flex-wrap\">\n <Button variant=\"secondary\">Secondary Button</Button>\n <Button variant=\"secondary\" disabled>Disabled</Button>\n </div>\n </div>\n\n <div>\n <h3 className=\"text-sm font-semibold text-foreground-200 mb-3\">Outline Variant</h3>\n <div className=\"flex gap-2 flex-wrap\">\n <Button variant=\"outline\">Outline Button</Button>\n <Button variant=\"outline\" disabled>Disabled</Button>\n </div>\n </div>\n\n <div>\n <h3 className=\"text-sm font-semibold text-foreground-200 mb-3\">Ghost Variant</h3>\n <div className=\"flex gap-2 flex-wrap\">\n <Button variant=\"ghost\">Ghost Button</Button>\n <Button variant=\"ghost\" disabled>Disabled</Button>\n </div>\n </div>\n\n <div>\n <h3 className=\"text-sm font-semibold text-foreground-200 mb-3\">Sizes</h3>\n <div className=\"flex gap-2 flex-wrap items-center\">\n <Button size=\"sm\">Small</Button>\n <Button size=\"md\">Medium</Button>\n <Button size=\"lg\">Large</Button>\n </div>\n </div>\n </div>\n )\n}"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"title": "Multiple Actions",
|
|
172
|
+
"description": "A primary action button grouped with secondary actions and an options menu.",
|
|
173
|
+
"code": "\"use client\";\n\nimport React, { useState } from 'react'\nimport { Button, Flex } from 'ui-lab-components'\nimport { FaEllipsisVertical } from \"react-icons/fa6\";\n\nexport default function Example() {\n return (\n <Flex gap=\"xs\" className=\"w-110 *:not-last:flex-1\">\n <Button size=\"sm\" variant=\"primary\" >Subscribe</Button>\n <Button size=\"sm\" >Message</Button>\n <Button size=\"icon\" variant=\"outline\" icon={<FaEllipsisVertical />} />\n </Flex>\n );\n}"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"title": "Joined Toggle Buttons",
|
|
177
|
+
"description": "Multiple buttons grouped together for view/mode selection with active state indication.",
|
|
178
|
+
"code": "\"use client\";\n\nimport React, { useState } from 'react'\nimport { Button, Group, Divider, Input, Flex } from 'ui-lab-components'\nimport { FaList, FaGrip, FaTable, FaPlus } from \"react-icons/fa6\";\nimport { LuSearch } from \"react-icons/lu\";\n\nexport default function Example() {\n const [viewMode, setViewMode] = useState(\"list\");\n return (\n <Flex className=\"w-110\" gap=\"xs\" align=\"center\">\n <Input\n placeholder=\"Search items...\"\n icon={<LuSearch />}\n className=\"w-full\"\n />\n <Group orientation=\"horizontal\" value={viewMode} onChange={setViewMode}>\n <Group.Button size=\"icon\" value=\"list\"><FaList /></Group.Button>\n <Divider orientation=\"vertical\" />\n <Group.Button size=\"icon\" value=\"grid\"><FaGrip /></Group.Button>\n <Divider orientation=\"vertical\" />\n <Group.Button size=\"icon\" value=\"table\"><FaTable /></Group.Button>\n </Group>\n <Button size=\"sm\" icon={{ left: <FaPlus size={12} /> }} >New</Button>\n </Flex>\n );\n}"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"title": "Sub Stack Actions",
|
|
182
|
+
"description": "A collection of buttons and inputs arranged horizontally for grouped actions and filtering.",
|
|
183
|
+
"code": "\"use client\";\n\nimport React, { useState } from 'react'\nimport { Button, Group, Input, Badge, Flex } from 'ui-lab-components'\nimport { FaList, FaGrip, FaPlus } from \"react-icons/fa6\";\nimport { LuSearch } from \"react-icons/lu\";\n\nexport default function Example() {\n const [viewMode, setViewMode] = useState(\"list\");\n return (\n <Flex align=\"center\" gap=\"xs\" className=\"w-110\">\n <Group orientation=\"horizontal\" spacing=\"xs\" value={viewMode} onChange={setViewMode}>\n <Group.Button size=\"icon\" value=\"list\"><FaList /></Group.Button>\n <Group.Button size=\"icon\" value=\"grid\"><FaGrip /></Group.Button>\n </Group>\n <Input\n placeholder=\"Search...\"\n icon={<LuSearch />}\n hint={<Badge size=\"sm\" variant=\"secondary\" >Ctrl+K</Badge>}\n />\n <Button size=\"sm\" icon={{ right: <FaPlus size={12} /> }} >Upload</Button>\n </Flex>\n );\n}"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"title": "Split Action Button",
|
|
187
|
+
"description": "A primary action button joined with a Select dropdown that changes what the button does — useful for publish workflows with multiple delivery options.",
|
|
188
|
+
"code": "\"use client\";\n\nimport React, { useState } from 'react'\nimport { Button, Divider, Select, Searchable, Flex, Menu } from 'ui-lab-components'\nimport { FaSpinner, FaCheck, FaEllipsisVertical, FaHashtag, FaLock } from \"react-icons/fa6\";\n\ntype Channel = { value: string; label: string; description: string; private?: boolean };\n\nconst channels: Channel[] = [\n { value: \"general\", label: \"general\", description: \"General team discussion\" },\n { value: \"announcements\", label: \"announcements\", description: \"Important team updates\", private: true },\n { value: \"design\", label: \"design\", description: \"Design work and feedback\" },\n { value: \"engineering\", label: \"engineering\", description: \"Engineering discussions\" },\n { value: \"marketing\", label: \"marketing\", description: \"Marketing campaigns\" },\n { value: \"product\", label: \"product\", description: \"Product roadmap and planning\" },\n { value: \"random\", label: \"random\", description: \"Off-topic conversations\" },\n { value: \"releases\", label: \"releases\", description: \"Release announcements\", private: true },\n];\n\ntype PublishAction = \"publish\" | \"draft\" | \"schedule\";\n\nconst publishActions: Record<PublishAction, { label: string; variant: \"ghost\" | \"default\" | \"outline\" }> = {\n publish: { label: \"Publish Now\", variant: \"ghost\" },\n draft: { label: \"Save as Draft\", variant: \"ghost\" },\n schedule: { label: \"Schedule for Later\", variant: \"outline\" },\n};\n\nexport default function Example() {\n const [action, setAction] = useState<PublishAction>(\"publish\");\n const [status, setStatus] = useState<\"idle\" | \"loading\" | \"done\">(\"idle\");\n const [channel, setChannel] = useState<string | number | null>(\"general\");\n const selectedChannel = channels.find((c) => c.value === channel);\n const cfg = publishActions[action];\n\n const handleExecute = () => {\n setStatus(\"loading\");\n setTimeout(() => {\n setStatus(\"done\");\n setTimeout(() => setStatus(\"idle\"), 2000);\n }, 1500);\n };\n\n const leftIcon = status === \"loading\" ? <FaSpinner className=\"animate-spin\" /> : status === \"done\" ? <FaCheck /> : undefined;\n const label = status === \"loading\" ? \"Saving...\" : status === \"done\" ? \"Done!\" : cfg.label;\n\n return (\n <Flex gap=\"xs\" className=\"w-110\" align=\"center\">\n <Select\n selectedKey={channel}\n valueLabel={selectedChannel ? `#${selectedChannel.label}` : undefined}\n onSelectionChange={setChannel}\n className=\"flex h-10\"\n isDisabled={status !== \"idle\"}\n >\n <Searchable.Input className=\"w-50\" icon={<FaHashtag />} placeholder=\"Select channel...\" />\n <Select.Content>\n <Select.List>\n {channels.map((c) => (\n <Select.Item\n key={c.value}\n value={c.value}\n textValue={c.label}\n icon={c.private ? <FaLock className=\"h-3 w-3\" /> : <FaHashtag className=\"h-3 w-3\" />}\n description={c.description}\n >\n {c.label}\n </Select.Item>\n ))}\n </Select.List>\n </Select.Content>\n </Select>\n <Select className=\"flex h-10\" selectedKey={action} onSelectionChange={(key) => setAction(key as PublishAction)} isDisabled={status !== \"idle\"}>\n <Button\n onPress={handleExecute}\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full\"\n isDisabled={status !== \"idle\"}\n icon={leftIcon}\n >\n {label}\n </Button>\n <Divider orientation=\"vertical\" spacing=\"none\" />\n <Select.Trigger />\n <Select.Content>\n <Select.Item value=\"publish\" textValue=\"Publish Now\">Publish Now</Select.Item>\n <Select.Item value=\"draft\" textValue=\"Save as Draft\">Save as Draft</Select.Item>\n <Select.Item value=\"schedule\" textValue=\"Schedule for Later\">Schedule for Later</Select.Item>\n </Select.Content>\n </Select>\n <Menu type=\"pop-over\">\n <Menu.Trigger>\n <Button size=\"icon\" variant=\"outline\" icon={<FaEllipsisVertical />} />\n </Menu.Trigger>\n <Menu.Content offset={8} side=\"bottom\" align=\"end\">\n <Menu.Item>Preview</Menu.Item>\n <Menu.Item>View History</Menu.Item>\n <Menu.Item>Discard Changes</Menu.Item>\n </Menu.Content>\n </Menu>\n </Flex>\n );\n}"
|
|
189
|
+
}
|
|
190
|
+
]
|
|
165
191
|
},
|
|
166
192
|
"card": {
|
|
167
193
|
"props": [
|
|
@@ -318,48 +344,41 @@ export const generatedAPI = {
|
|
|
318
344
|
{
|
|
319
345
|
"name": "value",
|
|
320
346
|
"type": "string",
|
|
321
|
-
"required": false
|
|
322
|
-
"description": "Controlled color value as a CSS color string"
|
|
347
|
+
"required": false
|
|
323
348
|
},
|
|
324
349
|
{
|
|
325
350
|
"name": "defaultValue",
|
|
326
351
|
"type": "string",
|
|
327
352
|
"required": false,
|
|
328
|
-
"defaultValue": "#000000"
|
|
329
|
-
"description": "Initial color value for uncontrolled usage"
|
|
353
|
+
"defaultValue": "#000000"
|
|
330
354
|
},
|
|
331
355
|
{
|
|
332
356
|
"name": "onChange",
|
|
333
357
|
"type": "((color: string) => void)",
|
|
334
|
-
"required": false
|
|
335
|
-
"description": "Called continuously while the user drags the color picker"
|
|
358
|
+
"required": false
|
|
336
359
|
},
|
|
337
360
|
{
|
|
338
361
|
"name": "onChangeComplete",
|
|
339
362
|
"type": "((color: string) => void)",
|
|
340
|
-
"required": false
|
|
341
|
-
"description": "Called once when the user finishes a drag interaction"
|
|
363
|
+
"required": false
|
|
342
364
|
},
|
|
343
365
|
{
|
|
344
366
|
"name": "showOpacity",
|
|
345
367
|
"type": "boolean",
|
|
346
368
|
"required": false,
|
|
347
|
-
"defaultValue": "false"
|
|
348
|
-
"description": "Whether to show the opacity/alpha slider"
|
|
369
|
+
"defaultValue": "false"
|
|
349
370
|
},
|
|
350
371
|
{
|
|
351
372
|
"name": "showPreview",
|
|
352
373
|
"type": "boolean",
|
|
353
374
|
"required": false,
|
|
354
|
-
"defaultValue": "false"
|
|
355
|
-
"description": "Whether to show a color preview swatch next to the input"
|
|
375
|
+
"defaultValue": "false"
|
|
356
376
|
},
|
|
357
377
|
{
|
|
358
378
|
"name": "format",
|
|
359
379
|
"type": "hex | rgb",
|
|
360
380
|
"required": false,
|
|
361
381
|
"defaultValue": "hex",
|
|
362
|
-
"description": "Output format of the color value string",
|
|
363
382
|
"enumValues": [
|
|
364
383
|
"hex",
|
|
365
384
|
"rgb"
|
|
@@ -369,15 +388,13 @@ export const generatedAPI = {
|
|
|
369
388
|
"name": "disabled",
|
|
370
389
|
"type": "boolean",
|
|
371
390
|
"required": false,
|
|
372
|
-
"defaultValue": "false"
|
|
373
|
-
"description": "Whether the color picker is disabled"
|
|
391
|
+
"defaultValue": "false"
|
|
374
392
|
},
|
|
375
393
|
{
|
|
376
394
|
"name": "size",
|
|
377
395
|
"type": "sm | md | lg",
|
|
378
396
|
"required": false,
|
|
379
397
|
"defaultValue": "md",
|
|
380
|
-
"description": "Size of the color picker",
|
|
381
398
|
"enumValues": [
|
|
382
399
|
"sm",
|
|
383
400
|
"md",
|
|
@@ -387,14 +404,7 @@ export const generatedAPI = {
|
|
|
387
404
|
{
|
|
388
405
|
"name": "className",
|
|
389
406
|
"type": "string",
|
|
390
|
-
"required": false
|
|
391
|
-
"description": "Additional CSS class for the root element"
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
"name": "styles",
|
|
395
|
-
"type": "ColorStylesProp",
|
|
396
|
-
"required": false,
|
|
397
|
-
"description": "Classes applied to the root or named slots"
|
|
407
|
+
"required": false
|
|
398
408
|
}
|
|
399
409
|
],
|
|
400
410
|
"subComponents": {
|
|
@@ -481,19 +491,16 @@ export const generatedAPI = {
|
|
|
481
491
|
]
|
|
482
492
|
},
|
|
483
493
|
"ColorInput": {
|
|
484
|
-
"description": "Text input for entering a color value directly",
|
|
485
494
|
"props": [
|
|
486
495
|
{
|
|
487
496
|
"name": "value",
|
|
488
497
|
"type": "string",
|
|
489
|
-
"required": true
|
|
490
|
-
"description": "Current color value string displayed in the text input"
|
|
498
|
+
"required": true
|
|
491
499
|
},
|
|
492
500
|
{
|
|
493
501
|
"name": "format",
|
|
494
502
|
"type": "hex | rgb",
|
|
495
503
|
"required": true,
|
|
496
|
-
"description": "Active color format controlling the input placeholder and value representation",
|
|
497
504
|
"enumValues": [
|
|
498
505
|
"hex",
|
|
499
506
|
"rgb"
|
|
@@ -502,27 +509,23 @@ export const generatedAPI = {
|
|
|
502
509
|
{
|
|
503
510
|
"name": "onFormatChange",
|
|
504
511
|
"type": "((format: \"hex\" | \"rgb\") => void)",
|
|
505
|
-
"required": false
|
|
506
|
-
"description": "Called when the user selects a different color format from the dropdown"
|
|
512
|
+
"required": false
|
|
507
513
|
},
|
|
508
514
|
{
|
|
509
515
|
"name": "onValueChange",
|
|
510
516
|
"type": "((value: string) => void)",
|
|
511
|
-
"required": false
|
|
512
|
-
"description": "Called when the user types a valid color string into the input"
|
|
517
|
+
"required": false
|
|
513
518
|
},
|
|
514
519
|
{
|
|
515
520
|
"name": "disabled",
|
|
516
521
|
"type": "boolean",
|
|
517
|
-
"required": false
|
|
518
|
-
"description": "Disables the text input and format selector"
|
|
522
|
+
"required": false
|
|
519
523
|
},
|
|
520
524
|
{
|
|
521
525
|
"name": "size",
|
|
522
526
|
"type": "sm | md | lg",
|
|
523
527
|
"required": false,
|
|
524
528
|
"defaultValue": "md",
|
|
525
|
-
"description": "Size of the input group",
|
|
526
529
|
"enumValues": [
|
|
527
530
|
"sm",
|
|
528
531
|
"md",
|
|
@@ -533,14 +536,12 @@ export const generatedAPI = {
|
|
|
533
536
|
"name": "showPreview",
|
|
534
537
|
"type": "boolean",
|
|
535
538
|
"required": false,
|
|
536
|
-
"defaultValue": "false"
|
|
537
|
-
"description": "Whether to show a color preview swatch beside the input"
|
|
539
|
+
"defaultValue": "false"
|
|
538
540
|
},
|
|
539
541
|
{
|
|
540
542
|
"name": "previewColor",
|
|
541
543
|
"type": "string",
|
|
542
|
-
"required": false
|
|
543
|
-
"description": "RGB color string used to fill the preview swatch"
|
|
544
|
+
"required": false
|
|
544
545
|
}
|
|
545
546
|
]
|
|
546
547
|
},
|
|
@@ -867,14 +868,15 @@ export const generatedAPI = {
|
|
|
867
868
|
},
|
|
868
869
|
{
|
|
869
870
|
"name": "size",
|
|
870
|
-
"type": "sm | md | lg",
|
|
871
|
+
"type": "sm | md | lg | auto",
|
|
871
872
|
"required": false,
|
|
872
|
-
"defaultValue": "
|
|
873
|
+
"defaultValue": "auto",
|
|
873
874
|
"description": "Size of the divider thickness",
|
|
874
875
|
"enumValues": [
|
|
875
876
|
"sm",
|
|
876
877
|
"md",
|
|
877
|
-
"lg"
|
|
878
|
+
"lg",
|
|
879
|
+
"auto"
|
|
878
880
|
]
|
|
879
881
|
},
|
|
880
882
|
{
|
|
@@ -960,18 +962,6 @@ export const generatedAPI = {
|
|
|
960
962
|
"required": false,
|
|
961
963
|
"description": "Compound sub-components or content nodes"
|
|
962
964
|
},
|
|
963
|
-
{
|
|
964
|
-
"name": "triggerClassName",
|
|
965
|
-
"type": "string",
|
|
966
|
-
"required": false,
|
|
967
|
-
"description": "Additional CSS class for the trigger button"
|
|
968
|
-
},
|
|
969
|
-
{
|
|
970
|
-
"name": "contentClassName",
|
|
971
|
-
"type": "string",
|
|
972
|
-
"required": false,
|
|
973
|
-
"description": "Additional CSS class for the content area"
|
|
974
|
-
},
|
|
975
965
|
{
|
|
976
966
|
"name": "styles",
|
|
977
967
|
"type": "ExpandStylesProp",
|
|
@@ -1708,13 +1698,14 @@ export const generatedAPI = {
|
|
|
1708
1698
|
},
|
|
1709
1699
|
{
|
|
1710
1700
|
"name": "size",
|
|
1711
|
-
"type": "sm | md | lg",
|
|
1701
|
+
"type": "sm | md | lg | auto",
|
|
1712
1702
|
"required": false,
|
|
1713
1703
|
"description": "Size of the divider thickness",
|
|
1714
1704
|
"enumValues": [
|
|
1715
1705
|
"sm",
|
|
1716
1706
|
"md",
|
|
1717
|
-
"lg"
|
|
1707
|
+
"lg",
|
|
1708
|
+
"auto"
|
|
1718
1709
|
]
|
|
1719
1710
|
},
|
|
1720
1711
|
{
|
|
@@ -2604,18 +2595,6 @@ export const generatedAPI = {
|
|
|
2604
2595
|
"required": false,
|
|
2605
2596
|
"description": "Additional class for the modal panel"
|
|
2606
2597
|
},
|
|
2607
|
-
{
|
|
2608
|
-
"name": "contentClassName",
|
|
2609
|
-
"type": "string",
|
|
2610
|
-
"required": false,
|
|
2611
|
-
"description": "Additional class for the inner content area"
|
|
2612
|
-
},
|
|
2613
|
-
{
|
|
2614
|
-
"name": "overlayClassName",
|
|
2615
|
-
"type": "string",
|
|
2616
|
-
"required": false,
|
|
2617
|
-
"description": "Additional class for the backdrop overlay"
|
|
2618
|
-
},
|
|
2619
2598
|
{
|
|
2620
2599
|
"name": "styles",
|
|
2621
2600
|
"type": "ModalStylesProp",
|
|
@@ -2829,13 +2808,7 @@ export const generatedAPI = {
|
|
|
2829
2808
|
"name": "className",
|
|
2830
2809
|
"type": "string",
|
|
2831
2810
|
"required": false,
|
|
2832
|
-
"description": "Additional CSS class for the trigger element.
|
|
2833
|
-
},
|
|
2834
|
-
{
|
|
2835
|
-
"name": "contentClassName",
|
|
2836
|
-
"type": "string",
|
|
2837
|
-
"required": false,
|
|
2838
|
-
"description": "Additional CSS class for the popover content panel. Merged with `styles.content`."
|
|
2811
|
+
"description": "Additional CSS class for the trigger element."
|
|
2839
2812
|
},
|
|
2840
2813
|
{
|
|
2841
2814
|
"name": "isOpen",
|
|
@@ -3010,32 +2983,23 @@ export const generatedAPI = {
|
|
|
3010
2983
|
},
|
|
3011
2984
|
"scroll": {
|
|
3012
2985
|
"props": [
|
|
3013
|
-
{
|
|
3014
|
-
"name": "children",
|
|
3015
|
-
"type": "ReactNode",
|
|
3016
|
-
"required": true,
|
|
3017
|
-
"description": "Content to render inside the scroll container"
|
|
3018
|
-
},
|
|
3019
2986
|
{
|
|
3020
2987
|
"name": "maxHeight",
|
|
3021
2988
|
"type": "string",
|
|
3022
2989
|
"required": false,
|
|
3023
|
-
"defaultValue": "100%"
|
|
3024
|
-
"description": "Maximum height before scrolling becomes active"
|
|
2990
|
+
"defaultValue": "100%"
|
|
3025
2991
|
},
|
|
3026
2992
|
{
|
|
3027
2993
|
"name": "maxWidth",
|
|
3028
2994
|
"type": "string",
|
|
3029
2995
|
"required": false,
|
|
3030
|
-
"defaultValue": "100%"
|
|
3031
|
-
"description": "Maximum width before scrolling becomes active"
|
|
2996
|
+
"defaultValue": "100%"
|
|
3032
2997
|
},
|
|
3033
2998
|
{
|
|
3034
2999
|
"name": "direction",
|
|
3035
3000
|
"type": "vertical | horizontal",
|
|
3036
3001
|
"required": false,
|
|
3037
3002
|
"defaultValue": "vertical",
|
|
3038
|
-
"description": "Scroll direction",
|
|
3039
3003
|
"enumValues": [
|
|
3040
3004
|
"vertical",
|
|
3041
3005
|
"horizontal"
|
|
@@ -3045,56 +3009,53 @@ export const generatedAPI = {
|
|
|
3045
3009
|
"name": "paddingY",
|
|
3046
3010
|
"type": "string | number",
|
|
3047
3011
|
"required": false,
|
|
3048
|
-
"defaultValue": "4"
|
|
3049
|
-
"description": "Padding on the top and bottom of the scrollbar track in pixels"
|
|
3012
|
+
"defaultValue": "4"
|
|
3050
3013
|
},
|
|
3051
3014
|
{
|
|
3052
3015
|
"name": "fade-y",
|
|
3053
3016
|
"type": "boolean",
|
|
3054
3017
|
"required": false,
|
|
3055
|
-
"defaultValue": "false"
|
|
3056
|
-
"description": "Whether to apply a fade mask at the top and bottom scroll edges"
|
|
3018
|
+
"defaultValue": "false"
|
|
3057
3019
|
},
|
|
3058
3020
|
{
|
|
3059
3021
|
"name": "fadeDistance",
|
|
3060
3022
|
"type": "number",
|
|
3061
3023
|
"required": false,
|
|
3062
|
-
"defaultValue": "5"
|
|
3063
|
-
"description": "Pixels scrolled before the fade mask begins to appear"
|
|
3024
|
+
"defaultValue": "5"
|
|
3064
3025
|
},
|
|
3065
3026
|
{
|
|
3066
3027
|
"name": "fadeSize",
|
|
3067
3028
|
"type": "number",
|
|
3068
3029
|
"required": false,
|
|
3069
|
-
"defaultValue": "4"
|
|
3070
|
-
"description": "Percentage of container height used for the fade gradient"
|
|
3030
|
+
"defaultValue": "4"
|
|
3071
3031
|
},
|
|
3072
3032
|
{
|
|
3073
3033
|
"name": "enabled",
|
|
3074
3034
|
"type": "boolean",
|
|
3075
3035
|
"required": false,
|
|
3076
|
-
"defaultValue": "true"
|
|
3077
|
-
"description": "Whether to render the custom scrollbar; when false, renders children without scroll"
|
|
3036
|
+
"defaultValue": "true"
|
|
3078
3037
|
},
|
|
3079
3038
|
{
|
|
3080
3039
|
"name": "hide",
|
|
3081
3040
|
"type": "boolean",
|
|
3082
3041
|
"required": false,
|
|
3083
|
-
"defaultValue": "true"
|
|
3084
|
-
"description": "Whether to hide the scrollbar when not actively scrolling"
|
|
3042
|
+
"defaultValue": "true"
|
|
3085
3043
|
},
|
|
3086
3044
|
{
|
|
3087
|
-
"name": "
|
|
3045
|
+
"name": "inline",
|
|
3088
3046
|
"type": "boolean",
|
|
3089
3047
|
"required": false,
|
|
3090
|
-
"defaultValue": "false"
|
|
3091
|
-
"description": "When true, the scrollbar sits inline displacing content; when false (default), it overlays the content"
|
|
3048
|
+
"defaultValue": "false"
|
|
3092
3049
|
},
|
|
3093
3050
|
{
|
|
3094
3051
|
"name": "styles",
|
|
3095
3052
|
"type": "ScrollStylesProp",
|
|
3096
|
-
"required": false
|
|
3097
|
-
|
|
3053
|
+
"required": false
|
|
3054
|
+
},
|
|
3055
|
+
{
|
|
3056
|
+
"name": "storageKey",
|
|
3057
|
+
"type": "string",
|
|
3058
|
+
"required": false
|
|
3098
3059
|
}
|
|
3099
3060
|
],
|
|
3100
3061
|
"examples": [
|
|
@@ -4085,13 +4046,13 @@ export const generatedAPI = {
|
|
|
4085
4046
|
};
|
|
4086
4047
|
export const generatedStyles = {
|
|
4087
4048
|
"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
|
|
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-
|
|
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
|
|
4049
|
+
"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 height: fit-content;\n width: fit-content;\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",
|
|
4050
|
+
"banner": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\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}\n\n.banner.md {\n @apply px-4 py-3;\n}\n\n.banner.lg {\n @apply px-6 py-4;\n}\n",
|
|
4051
|
+
"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 /* height: fit-content; */\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",
|
|
4091
4052
|
"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",
|
|
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-
|
|
4053
|
+
"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-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",
|
|
4093
4054
|
"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",
|
|
4094
|
-
"color": "@reference \"tailwindcss\";\n\n@layer components {\n .color {\n --
|
|
4055
|
+
"color": "@reference \"tailwindcss\";\n\n@layer components {\n .color {\n --background: color-mix(in srgb, var(--background-800) 30%, transparent);;\n --border: var(--background-700);\n --ring-color: var(--accent-500);\n display: flex;\n flex-direction: column;\n gap: 1rem;\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: 0.5;\n pointer-events: none;\n }\n\n .colorControls {\n @apply pb-3 px-3 space-y-3;\n }\n\n /* Input styles */\n .inputGroup {\n width: 100%;\n }\n\n .inputGroup > div:first-child {\n flex: 1;\n min-width: 0;\n }\n\n .colorInput {\n width: 100%;\n }\n\n .formatSelect {\n flex-shrink: 0;\n width: 85px;\n }\n\n .color[data-size=\"sm\"] .formatSelect {\n width: 75px;\n }\n\n /* Canvas Styles */\n .canvasContainer {\n position: relative;\n width: 96%;\n @apply mx-auto mt-2;\n cursor: crosshair;\n touch-action: none;\n display: flex;\n flex-direction: column;\n }\n\n .canvasContainer[data-focus-visible=\"true\"] {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .canvas {\n position: relative;\n width: 100%;\n flex: 1;\n border-radius: none;\n /* clip-path: inset(0 round var(--radius-sm)); */\n overflow: hidden;\n }\n\n .canvasGradientHue {\n position: absolute;\n inset: 0;\n overflow: hidden;\n /* border-radius: var(--radius-sm); */\n }\n\n .canvasGradientSaturation {\n position: absolute;\n inset: 0;\n background: linear-gradient(to right, rgb(255, 255, 255), transparent);\n border-radius: none;\n }\n\n .canvasGradientLightness {\n position: absolute;\n inset: 0;\n background: linear-gradient(to top, rgb(0, 0, 0), transparent);\n border-radius: none\n }\n\n .canvasPointer {\n position: absolute;\n width: 12px;\n height: 12px;\n border-radius: var(--radius-full);\n border: 2px solid color-mix(in srgb, var(--foreground-200) 50%, transparent);\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 display: flex;\n align-items: center;\n height: 16px;\n border-radius: var(--radius-full);\n position: relative;\n cursor: pointer;\n touch-action: none;\n overflow: hidden;\n }\n\n .hueTrack {\n position: relative;\n width: 100%;\n height: 100%;\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 position: absolute;\n width: 12px;\n height: 12px;\n border-radius: var(--radius-full);\n border: 2px solid white;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n top: 50%;\n transform: translateY(-50%) translateX(-50%);\n background: white;\n pointer-events: none;\n }\n\n .hueSlider[data-focus-visible=\"true\"] .hueThumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .hueThumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .hueThumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Opacity Slider Styles */\n .opacitySlider {\n display: flex;\n align-items: center;\n height: 12px;\n border-radius: var(--radius-full);\n position: relative;\n cursor: pointer;\n touch-action: none;\n overflow: hidden;\n }\n\n .opacityTrack {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: var(--radius-full);\n background-image: repeating-linear-gradient(\n 45deg,\n var(--background-800),\n var(--background-800) 10px,\n var(--background-700) 10px,\n var(--background-700) 20px\n );\n border: var(--border-width-base) solid var(--border);\n overflow: hidden;\n }\n\n .opacityThumb {\n position: absolute;\n width: 10px;\n height: 10px;\n border-radius: var(--radius-full);\n border: 2px solid white;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n top: 50%;\n transform: translateY(-50%) translateX(-50%);\n background: white;\n pointer-events: none;\n }\n\n .opacitySlider[data-focus-visible=\"true\"] .opacityThumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .opacityThumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .opacityThumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Recent Colors Styles */\n .recentColors {\n display: flex;\n gap: 0.5rem;\n overflow-x: auto;\n padding-bottom: 0.25rem;\n }\n\n .recentColorSwatch {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n cursor: pointer;\n background: none;\n padding: 0;\n outline: none;\n }\n\n .recentColorSwatch:hover {\n transform: scale(1.1);\n box-shadow: 0 0 0 2px var(--ring-color);\n }\n\n .recentColorSwatch:active {\n transform: scale(0.95);\n }\n\n .recentColorSwatch:focus-visible {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n\n /* Preview Container - deprecated, use previewSwatch instead */\n .previewContainer {\n display: flex;\n justify-content: center;\n padding: 0.5rem 0;\n }\n\n /* Preview Swatch - inline with input */\n .previewSwatch {\n position: relative;\n width: 36px;\n height: 36px;\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 overflow: hidden;\n flex-shrink: 0;\n }\n\n .previewSwatch::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--background-700),\n var(--background-700) 6px,\n var(--background-800) 6px,\n var(--background-800) 12px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .previewSwatch::after {\n content: \"\";\n position: absolute;\n 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 .preview {\n position: relative;\n width: 64px;\n height: 64px;\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 overflow: hidden;\n }\n\n .preview::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--background-700),\n var(--background-700) 10px,\n var(--background-800) 10px,\n var(--background-800) 20px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .canvasContainer {\n min-height: 160px;\n }\n}\n",
|
|
4095
4056
|
"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",
|
|
4096
4057
|
"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",
|
|
4097
4058
|
"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",
|
|
@@ -4100,12 +4061,12 @@ export const generatedStyles = {
|
|
|
4100
4061
|
"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",
|
|
4101
4062
|
"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",
|
|
4102
4063
|
"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",
|
|
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-
|
|
4104
|
-
"input": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n
|
|
4064
|
+
"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 --group-border-width: var(--border-width-base, 1px);\n --inner-radius: calc(var(--radius) - var(--group-border-width));\n \n --control-height: calc((0.80em * var(--leading-tight, 1.25)) + (var(--spacing) * 4) + (var(--group-border-width) * 2));\n --item-height: max(calc(var(--control-height) - (var(--padding) * 2) - (var(--group-border-width) * 2)), 0px);\n \n --button-padding-x: max(calc(var(--spacing) * 2.5 - var(--padding)), 0px);\n --button-padding-y: max(calc(var(--spacing) * 2 - var(--padding)), 0px);\n --input-padding-x: max(calc(var(--spacing) * 3 - var(--padding)), 0px);\n --input-padding-y: max(calc(var(--spacing) * 1.5 - var(--padding)), 0px);\n\n @apply flex overflow-hidden shrink-0 box-border;\n color: var(--foreground);\n background-color: var(--background);\n border: var(--group-border-width) solid var(--border);\n border-radius: var(--radius);\n padding: var(--padding);\n\n &.horizontal {\n @apply flex-row items-stretch;\n height: var(--control-height);\n\n /* Fixed: Use margin-block (top/bottom) for horizontal layouts */\n .item.divider { margin-block: calc(var(--padding) * -1); }\n .item.divider > [role=\"separator\"] { height: 100%; }\n }\n\n &.vertical {\n @apply flex-col;\n \n .item .group-item { @apply w-full; }\n /* Fixed: Use margin-inline (left/right) for vertical layouts */\n .item.divider { margin-inline: calc(var(--padding) * -1); }\n .item.divider > [role=\"separator\"] { width: 100%; }\n }\n\n &.none {\n --padding: 0;\n @apply gap-0;\n }\n \n &.xs {\n --radius-basis: calc(var(--spacing) * 0.875);\n @apply space-x-0.5;\n }\n \n &.sm {\n --radius-basis: calc(var(--spacing) * 1.25);\n @apply space-x-1;\n }\n\n /* --- Ghost Variant --- */\n &.ghost {\n @apply gap-1 border-none overflow-visible;\n \n &.none { @apply gap-0; }\n }\n }\n\n .item {\n @apply flex items-stretch;\n\n &.grow { flex: 1; }\n &.divider { \n @apply p-0 shrink-0 flex-none; \n > [role=\"separator\"] { flex: 0 0 auto; }\n }\n }\n\n /* Shared Height Logic */\n :is(.group-item, .group-input-wrapper, .group-select-wrapper) {\n height: 100%;\n min-height: var(--item-height);\n }\n\n .group-item {\n @apply flex box-border;\n padding: var(--button-padding-y) var(--button-padding-x);\n\n &.active {\n @apply relative;\n background-color: var(--active-background);\n color: var(--active-foreground);\n }\n }\n\n .group-input-wrapper {\n @apply flex flex-1 items-stretch overflow-visible;\n input { \n @apply h-full; \n padding: var(--input-padding-y) var(--input-padding-x); \n }\n }\n\n .group-select-wrapper {\n @apply flex items-stretch p-0 bg-transparent border-none;\n .select { @apply h-full w-full; }\n }\n\n .trigger { border: none; }\n\n .group:not(.ghost) {\n .item :is(.group-item, .group-select-wrapper) { border: none; }\n \n .group-item.active { font-weight: 500; }\n \n .group-input-wrapper {\n --input-border-color: transparent;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n &.none {\n :is(.group-item, .trigger, .group-select-wrapper) { border-radius: 0; --radius: 0; --inner-radius: 0; }\n .group-input-wrapper { --input-border-radius: 0; }\n\n &.horizontal {\n .item:first-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n .item:last-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n }\n &.vertical {\n .item:first-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n .item:last-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n }\n }\n\n &:is(.xs, .sm) {\n :is(.group-item, .trigger, .group-select-wrapper > *) { border-radius: var(--inner-radius); }\n .group-input-wrapper { --input-border-radius: var(--inner-radius); }\n }\n }\n\n /* Ghost overrides */\n .group.ghost {\n .group-item.active { border-radius: var(--inner-radius); }\n \n &:not(.none) .item .group-item:not(.active) {\n border-radius: var(--inner-radius);\n border: var(--border-width-base) solid transparent;\n }\n\n &.none {\n :is(.group-item, .group-select-wrapper) { border: none; border-radius: 0; --radius: 0; --inner-radius: 0; }\n .group-input-wrapper { --input-border-color: transparent; --input-border-radius: 0; }\n }\n }\n\n :is(.group-item, .trigger, .group-input-wrapper > [data-focus-visible]):focus-visible {\n @apply relative outline-none z-10;\n }\n\n .group:not(.ghost) :is(.group-item, .trigger, .group-input-wrapper > [data-focus-visible]):focus-visible {\n box-shadow: inset 0 0 0 1px var(--focus-ring-color);\n }\n\n .group.ghost :is(.group-item, .trigger, .group-input-wrapper > [data-focus-visible]):focus-visible {\n box-shadow: 0 0 0 1px var(--focus-ring-color);\n }\n}\n",
|
|
4065
|
+
"input": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n height: fit-content;\n flex: 1;\n min-width: 0;\n @apply py-1.5 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.5;\n }\n\n .end-adornments {\n @apply pr-1.5;\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
4066
|
"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
4067
|
"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",
|
|
4107
4068
|
"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",
|
|
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
|
|
4069
|
+
"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 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 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 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 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
4070
|
"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",
|
|
4110
4071
|
"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",
|
|
4111
4072
|
"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",
|
|
@@ -4113,14 +4074,14 @@ export const generatedStyles = {
|
|
|
4113
4074
|
"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",
|
|
4114
4075
|
"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",
|
|
4115
4076
|
"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",
|
|
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-
|
|
4117
|
-
"select": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n
|
|
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
|
|
4077
|
+
"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-inline=\"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-inline=\"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",
|
|
4078
|
+
"select": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\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 font-size: var(--font-size);\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\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 font-size: var(--font-size);\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-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(--font-size);\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(--font-size);\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(--font-size);\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",
|
|
4079
|
+
"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 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",
|
|
4119
4080
|
"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",
|
|
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
|
|
4081
|
+
"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 @apply absolute;\n background-color: var(--indicator-background);\n box-sizing: border-box;\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-fallback {\n z-index: -1;\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-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\"]):not([data-indicator-fallback=\"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\"]):not([data-indicator-fallback=\"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 .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
|
|
4121
4082
|
"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
4083
|
"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
|
|
4084
|
+
"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 whitespace-nowrap;;\n color: var(--foreground);\n }\n\n .frame[data-hint] {\n @apply pr-1;\n }\n}\n"
|
|
4124
4085
|
};
|
|
4125
4086
|
export const generatedSourceCode = {
|
|
4126
4087
|
"anchor": {
|
|
@@ -4130,17 +4091,17 @@ export const generatedSourceCode = {
|
|
|
4130
4091
|
},
|
|
4131
4092
|
"badge": {
|
|
4132
4093
|
"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
|
|
4094
|
+
"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 height: fit-content;\n width: fit-content;\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",
|
|
4134
4095
|
"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"
|
|
4135
4096
|
},
|
|
4136
4097
|
"banner": {
|
|
4137
4098
|
"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-
|
|
4099
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\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}\n\n.banner.md {\n @apply px-4 py-3;\n}\n\n.banner.lg {\n @apply px-6 py-4;\n}\n",
|
|
4139
4100
|
"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"
|
|
4140
4101
|
},
|
|
4141
4102
|
"button": {
|
|
4142
4103
|
"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
|
|
4104
|
+
"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 /* height: fit-content; */\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",
|
|
4144
4105
|
"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"
|
|
4145
4106
|
},
|
|
4146
4107
|
"card": {
|
|
@@ -4150,7 +4111,7 @@ export const generatedSourceCode = {
|
|
|
4150
4111
|
},
|
|
4151
4112
|
"checkbox": {
|
|
4152
4113
|
"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-
|
|
4114
|
+
"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-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",
|
|
4154
4115
|
"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"
|
|
4155
4116
|
},
|
|
4156
4117
|
"code": {
|
|
@@ -4159,12 +4120,12 @@ export const generatedSourceCode = {
|
|
|
4159
4120
|
"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"
|
|
4160
4121
|
},
|
|
4161
4122
|
"color": {
|
|
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",
|
|
4163
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .color {\n --
|
|
4123
|
+
"tsx": "\"use client\";\n\nimport React, { useState, useEffect, useCallback, useRef, useReducer } from \"react\";\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\";\nimport { cn } from \"./utils\";\n\ntype CanvasState = { s: number; v: number; h: number; hg: number };\ntype CanvasAction =\n | { type: 'SET_FROM_COLOR'; h: number; s: number; v: number }\n | { type: 'SET_CANVAS'; s: number; v: number }\n | { type: 'SET_HUE'; h: number; updateHg: boolean };\n\nfunction canvasReducer(state: CanvasState, action: CanvasAction): CanvasState {\n switch (action.type) {\n case 'SET_FROM_COLOR':\n if (action.s > 0) return { s: action.s, v: action.v, h: action.h, hg: action.h };\n return { ...state, s: action.s, v: action.v };\n case 'SET_CANVAS':\n return { ...state, s: action.s, v: action.v };\n case 'SET_HUE':\n return { ...state, h: action.h, hg: action.updateHg ? action.h : state.hg };\n default:\n return state;\n }\n}\n\nexport interface ColorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n value?: string;\n defaultValue?: string;\n onChange?: (color: string) => void;\n onChangeComplete?: (color: string) => void;\n showOpacity?: boolean;\n showPreview?: boolean;\n format?: \"hex\" | \"rgb\";\n disabled?: boolean;\n size?: \"sm\" | \"md\" | \"lg\";\n className?: string;\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 ...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 [canvasState, dispatchCanvas] = useReducer(canvasReducer, {\n s: initialState.s, v: initialState.v, h: initialState.h, hg: initialState.h,\n });\n const { s: canvasSaturation, v: canvasBrightness, h: hue, hg: hueWhenGrayscale } = canvasState;\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 dispatchCanvas({ type: 'SET_FROM_COLOR', h, s, v });\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 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 dispatchCanvas({ type: 'SET_CANVAS', s: saturation, v: 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 dispatchCanvas({ type: 'SET_HUE', h: newHue, updateHg: canvasSaturation > 0 });\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 dispatchCanvas({ type: 'SET_FROM_COLOR', h, s, v });\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 dispatchCanvas({ type: 'SET_FROM_COLOR', h, s, v });\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(styles.color, className)}\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={styles.colorControls}>\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",
|
|
4124
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .color {\n --background: color-mix(in srgb, var(--background-800) 30%, transparent);;\n --border: var(--background-700);\n --ring-color: var(--accent-500);\n display: flex;\n flex-direction: column;\n gap: 1rem;\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: 0.5;\n pointer-events: none;\n }\n\n .colorControls {\n @apply pb-3 px-3 space-y-3;\n }\n\n /* Input styles */\n .inputGroup {\n width: 100%;\n }\n\n .inputGroup > div:first-child {\n flex: 1;\n min-width: 0;\n }\n\n .colorInput {\n width: 100%;\n }\n\n .formatSelect {\n flex-shrink: 0;\n width: 85px;\n }\n\n .color[data-size=\"sm\"] .formatSelect {\n width: 75px;\n }\n\n /* Canvas Styles */\n .canvasContainer {\n position: relative;\n width: 96%;\n @apply mx-auto mt-2;\n cursor: crosshair;\n touch-action: none;\n display: flex;\n flex-direction: column;\n }\n\n .canvasContainer[data-focus-visible=\"true\"] {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .canvas {\n position: relative;\n width: 100%;\n flex: 1;\n border-radius: none;\n /* clip-path: inset(0 round var(--radius-sm)); */\n overflow: hidden;\n }\n\n .canvasGradientHue {\n position: absolute;\n inset: 0;\n overflow: hidden;\n /* border-radius: var(--radius-sm); */\n }\n\n .canvasGradientSaturation {\n position: absolute;\n inset: 0;\n background: linear-gradient(to right, rgb(255, 255, 255), transparent);\n border-radius: none;\n }\n\n .canvasGradientLightness {\n position: absolute;\n inset: 0;\n background: linear-gradient(to top, rgb(0, 0, 0), transparent);\n border-radius: none\n }\n\n .canvasPointer {\n position: absolute;\n width: 12px;\n height: 12px;\n border-radius: var(--radius-full);\n border: 2px solid color-mix(in srgb, var(--foreground-200) 50%, transparent);\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 display: flex;\n align-items: center;\n height: 16px;\n border-radius: var(--radius-full);\n position: relative;\n cursor: pointer;\n touch-action: none;\n overflow: hidden;\n }\n\n .hueTrack {\n position: relative;\n width: 100%;\n height: 100%;\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 position: absolute;\n width: 12px;\n height: 12px;\n border-radius: var(--radius-full);\n border: 2px solid white;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n top: 50%;\n transform: translateY(-50%) translateX(-50%);\n background: white;\n pointer-events: none;\n }\n\n .hueSlider[data-focus-visible=\"true\"] .hueThumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .hueThumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .hueThumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Opacity Slider Styles */\n .opacitySlider {\n display: flex;\n align-items: center;\n height: 12px;\n border-radius: var(--radius-full);\n position: relative;\n cursor: pointer;\n touch-action: none;\n overflow: hidden;\n }\n\n .opacityTrack {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: var(--radius-full);\n background-image: repeating-linear-gradient(\n 45deg,\n var(--background-800),\n var(--background-800) 10px,\n var(--background-700) 10px,\n var(--background-700) 20px\n );\n border: var(--border-width-base) solid var(--border);\n overflow: hidden;\n }\n\n .opacityThumb {\n position: absolute;\n width: 10px;\n height: 10px;\n border-radius: var(--radius-full);\n border: 2px solid white;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n top: 50%;\n transform: translateY(-50%) translateX(-50%);\n background: white;\n pointer-events: none;\n }\n\n .opacitySlider[data-focus-visible=\"true\"] .opacityThumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .opacityThumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .opacityThumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Recent Colors Styles */\n .recentColors {\n display: flex;\n gap: 0.5rem;\n overflow-x: auto;\n padding-bottom: 0.25rem;\n }\n\n .recentColorSwatch {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n cursor: pointer;\n background: none;\n padding: 0;\n outline: none;\n }\n\n .recentColorSwatch:hover {\n transform: scale(1.1);\n box-shadow: 0 0 0 2px var(--ring-color);\n }\n\n .recentColorSwatch:active {\n transform: scale(0.95);\n }\n\n .recentColorSwatch:focus-visible {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n\n /* Preview Container - deprecated, use previewSwatch instead */\n .previewContainer {\n display: flex;\n justify-content: center;\n padding: 0.5rem 0;\n }\n\n /* Preview Swatch - inline with input */\n .previewSwatch {\n position: relative;\n width: 36px;\n height: 36px;\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 overflow: hidden;\n flex-shrink: 0;\n }\n\n .previewSwatch::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--background-700),\n var(--background-700) 6px,\n var(--background-800) 6px,\n var(--background-800) 12px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .previewSwatch::after {\n content: \"\";\n position: absolute;\n 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 .preview {\n position: relative;\n width: 64px;\n height: 64px;\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 overflow: hidden;\n }\n\n .preview::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--background-700),\n var(--background-700) 10px,\n var(--background-800) 10px,\n var(--background-800) 20px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .canvasContainer {\n min-height: 160px;\n }\n}\n",
|
|
4164
4125
|
"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"
|
|
4165
4126
|
},
|
|
4166
4127
|
"command": {
|
|
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",
|
|
4128
|
+
"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, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\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\ninterface CommandStyleSlots {\n root?: StyleValue;\n overlay?: StyleValue;\n}\n\ntype CommandStylesProp = StylesProp<CommandStyleSlots>;\n\nconst resolveCommandBaseStyles = createStylesResolver([\n \"root\",\n \"overlay\",\n] as const);\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 /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: CommandStylesProp;\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, styles: commandStyles, 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 const resolved = resolveCommandBaseStyles(commandStyles);\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 resolved.overlay,\n )}\n onClick={handleOverlayClick}\n >\n <Card\n {...filterDOMProps(dialogProps)}\n ref={modalRef}\n className={cn(\"content\", styles[\"content\"], className, resolved.root)}\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
4129
|
"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
4130
|
"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"
|
|
4170
4131
|
},
|
|
@@ -4179,12 +4140,12 @@ export const generatedSourceCode = {
|
|
|
4179
4140
|
"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"
|
|
4180
4141
|
},
|
|
4181
4142
|
"divider": {
|
|
4182
|
-
"tsx": "import React from \"react\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { GroupContext } from \"../Group/Group\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\";\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: 3 },\n md: { thickness: 2, radius: 1, spacing: 6 },\n lg: { thickness: 4, radius: 2, spacing: 12 },\n} as const;\n\nfunction getDashedMaskSvg(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// --- CVA Variants ---\n\nconst dividerVariants = cva(\"shrink-0\", {\n variants: {\n variant: { solid: \"\", dashed: \"\", dotted: \"\" },\n orientation: { horizontal: \"w-full\", vertical: \"self-stretch\" },\n size: { sm: \"\", md: \"\", lg: \"\" },\n spacing: { none: \"\", sm: \"\", md: \"\", lg: \"\" },\n },\n compoundVariants: [\n // Size + Orientation → dimensions\n { orientation: \"horizontal\", size: \"sm\", class: \"h-px\" },\n { orientation: \"vertical\", size: \"sm\", class: \"w-px\" },\n { orientation: \"horizontal\", size: \"md\", class: \"h-0.5\" },\n { orientation: \"vertical\", size: \"md\", class: \"w-0.5\" },\n { orientation: \"horizontal\", size: \"lg\", class: \"h-1\" },\n { orientation: \"vertical\", size: \"lg\", class: \"w-1\" },\n { orientation: \"horizontal\", spacing: \"none\", class: \"my-0\" },\n { orientation: \"vertical\", spacing: \"none\", class: \"mx-0\" },\n { orientation: \"horizontal\", spacing: \"sm\", class: \"my-1\" },\n { orientation: \"vertical\", spacing: \"sm\", class: \"mx-1\" },\n { orientation: \"horizontal\", spacing: \"md\", class: \"my-2\" },\n { orientation: \"vertical\", spacing: \"md\", class: \"mx-2\" },\n { orientation: \"horizontal\", spacing: \"lg\", class: \"my-4\" },\n { orientation: \"vertical\", spacing: \"lg\", class: \"mx-4\" },\n ],\n defaultVariants: {\n variant: \"solid\",\n orientation: \"horizontal\",\n size: \"
|
|
4143
|
+
"tsx": "import React from \"react\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { GroupContext } from \"../Group/Group\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\" | \"auto\";\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: 3 },\n md: { thickness: 2, radius: 1, spacing: 6 },\n lg: { thickness: 4, radius: 2, spacing: 12 },\n} as const;\n\nfunction getDashedMaskSvg(orientation: Orientation, size: Size): string {\n const { thickness, dashLength, gapLength } = DASHED_DIMENSIONS[size === \"auto\" ? \"md\" : 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 === \"auto\" ? \"md\" : 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// --- CVA Variants ---\n\nconst dividerVariants = cva(\"shrink-0\", {\n variants: {\n variant: { solid: \"\", dashed: \"\", dotted: \"\" },\n orientation: { horizontal: \"w-full\", vertical: \"self-stretch\" },\n size: { sm: \"\", md: \"\", lg: \"\", auto: \"\" },\n spacing: { none: \"\", sm: \"\", md: \"\", lg: \"\" },\n },\n compoundVariants: [\n // Size + Orientation → dimensions\n { orientation: \"horizontal\", size: \"sm\", class: \"h-px\" },\n { orientation: \"vertical\", size: \"sm\", class: \"w-px\" },\n { orientation: \"horizontal\", size: \"md\", class: \"h-0.5\" },\n { orientation: \"vertical\", size: \"md\", class: \"w-0.5\" },\n { orientation: \"horizontal\", size: \"lg\", class: \"h-1\" },\n { orientation: \"vertical\", size: \"lg\", class: \"w-1\" },\n { orientation: \"horizontal\", spacing: \"none\", class: \"my-0\" },\n { orientation: \"vertical\", spacing: \"none\", class: \"mx-0\" },\n { orientation: \"horizontal\", spacing: \"sm\", class: \"my-1\" },\n { orientation: \"vertical\", spacing: \"sm\", class: \"mx-1\" },\n { orientation: \"horizontal\", spacing: \"md\", class: \"my-2\" },\n { orientation: \"vertical\", spacing: \"md\", class: \"mx-2\" },\n { orientation: \"horizontal\", spacing: \"lg\", class: \"my-4\" },\n { orientation: \"vertical\", spacing: \"lg\", class: \"mx-4\" },\n ],\n defaultVariants: {\n variant: \"solid\",\n orientation: \"horizontal\",\n size: \"auto\",\n spacing: \"md\",\n },\n});\n\nexport interface DividerStyleSlots {\n root?: StyleValue;\n}\n\nexport type DividerStylesProp = StylesProp<DividerStyleSlots>;\n\nconst resolveDividerStyles = createStylesResolver(['root'] as const);\n\nexport interface DividerProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof dividerVariants> {\n /** Controls the line style of the divider */\n variant?: \"solid\" | \"dashed\" | \"dotted\";\n /** Controls the axis the divider spans */\n orientation?: \"horizontal\" | \"vertical\";\n /** Size of the divider thickness */\n size?: \"sm\" | \"md\" | \"lg\" | \"auto\";\n /** Controls the margin around the divider */\n spacing?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: DividerStylesProp;\n}\n\nconst Divider = React.forwardRef<HTMLDivElement, DividerProps>(\n ({ className, styles, variant = \"solid\", orientation, size = \"auto\", spacing, style, ...props }, ref) => {\n const groupContext = React.useContext(GroupContext);\n\n const resolvedOrientation = (() => {\n if (orientation !== undefined) return orientation;\n if (!groupContext) return \"horizontal\";\n return groupContext.groupOrientation === \"horizontal\" ? \"vertical\" : \"horizontal\";\n })() as Orientation;\n\n const resolvedSpacing = (() => {\n if (spacing !== undefined) return spacing;\n if (!groupContext) return \"md\";\n return \"none\";\n })();\n const getMaskStyles = (): React.CSSProperties => {\n const baseStyles: React.CSSProperties = { backgroundColor: \"var(--color-background-700)\" };\n if (variant === \"solid\") return baseStyles\n\n const svgDataUri =\n variant === \"dashed\"\n ? getDashedMaskSvg(resolvedOrientation, size)\n : getDottedMaskSvg(resolvedOrientation, size);\n\n const maskRepeat = resolvedOrientation === \"horizontal\" ? \"repeat-x\" : \"repeat-y\";\n const encodedSvg = `url(\"data:image/svg+xml,${svgDataUri}\")`;\n\n return {\n ...baseStyles,\n WebkitMaskImage: encodedSvg,\n maskImage: encodedSvg,\n WebkitMaskRepeat: maskRepeat,\n maskRepeat: maskRepeat,\n } as React.CSSProperties;\n };\n\n const getAutoSizeStyle = (): React.CSSProperties => {\n if (size !== \"auto\") return {};\n return resolvedOrientation === \"horizontal\"\n ? { height: \"var(--border-width-base, 1px)\" }\n : { width: \"var(--border-width-base, 1px)\" };\n };\n\n const resolved = resolveDividerStyles(styles);\n\n return (\n <div\n ref={ref}\n className={cn(\n dividerVariants({ variant, orientation: resolvedOrientation, size, spacing: resolvedSpacing }),\n 'divider', className, resolved.root,\n )}\n style={{ ...getMaskStyles(), ...getAutoSizeStyle(), ...style }}\n role=\"separator\"\n aria-orientation={resolvedOrientation}\n {...props}\n />\n );\n },\n);\n\nDivider.displayName = \"Divider\";\n\nexport { Divider, dividerVariants };\n",
|
|
4183
4144
|
"css": "",
|
|
4184
4145
|
"cssTypes": ""
|
|
4185
4146
|
},
|
|
4186
4147
|
"expand": {
|
|
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 /**
|
|
4148
|
+
"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 /** 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, 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, ...rootProps } = 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={resolved.trigger}>{title}</ExpandTrigger>\n {customDivider || <ExpandDivider />}\n <ExpandContent className={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",
|
|
4188
4149
|
"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",
|
|
4189
4150
|
"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"
|
|
4190
4151
|
},
|
|
@@ -4209,13 +4170,13 @@ export const generatedSourceCode = {
|
|
|
4209
4170
|
"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"
|
|
4210
4171
|
},
|
|
4211
4172
|
"group": {
|
|
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
|
|
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-
|
|
4173
|
+
"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 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 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\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 // 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 ? value === context.groupValue : active\n const handlePress = value !== undefined && context.groupOnChange !== undefined ? () => context.groupOnChange!(value) : onPress\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('group-select-wrapper', 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",
|
|
4174
|
+
"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 --group-border-width: var(--border-width-base, 1px);\n --inner-radius: calc(var(--radius) - var(--group-border-width));\n \n --control-height: calc((0.80em * var(--leading-tight, 1.25)) + (var(--spacing) * 4) + (var(--group-border-width) * 2));\n --item-height: max(calc(var(--control-height) - (var(--padding) * 2) - (var(--group-border-width) * 2)), 0px);\n \n --button-padding-x: max(calc(var(--spacing) * 2.5 - var(--padding)), 0px);\n --button-padding-y: max(calc(var(--spacing) * 2 - var(--padding)), 0px);\n --input-padding-x: max(calc(var(--spacing) * 3 - var(--padding)), 0px);\n --input-padding-y: max(calc(var(--spacing) * 1.5 - var(--padding)), 0px);\n\n @apply flex overflow-hidden shrink-0 box-border;\n color: var(--foreground);\n background-color: var(--background);\n border: var(--group-border-width) solid var(--border);\n border-radius: var(--radius);\n padding: var(--padding);\n\n &.horizontal {\n @apply flex-row items-stretch;\n height: var(--control-height);\n\n /* Fixed: Use margin-block (top/bottom) for horizontal layouts */\n .item.divider { margin-block: calc(var(--padding) * -1); }\n .item.divider > [role=\"separator\"] { height: 100%; }\n }\n\n &.vertical {\n @apply flex-col;\n \n .item .group-item { @apply w-full; }\n /* Fixed: Use margin-inline (left/right) for vertical layouts */\n .item.divider { margin-inline: calc(var(--padding) * -1); }\n .item.divider > [role=\"separator\"] { width: 100%; }\n }\n\n &.none {\n --padding: 0;\n @apply gap-0;\n }\n \n &.xs {\n --radius-basis: calc(var(--spacing) * 0.875);\n @apply space-x-0.5;\n }\n \n &.sm {\n --radius-basis: calc(var(--spacing) * 1.25);\n @apply space-x-1;\n }\n\n /* --- Ghost Variant --- */\n &.ghost {\n @apply gap-1 border-none overflow-visible;\n \n &.none { @apply gap-0; }\n }\n }\n\n .item {\n @apply flex items-stretch;\n\n &.grow { flex: 1; }\n &.divider { \n @apply p-0 shrink-0 flex-none; \n > [role=\"separator\"] { flex: 0 0 auto; }\n }\n }\n\n /* Shared Height Logic */\n :is(.group-item, .group-input-wrapper, .group-select-wrapper) {\n height: 100%;\n min-height: var(--item-height);\n }\n\n .group-item {\n @apply flex box-border;\n padding: var(--button-padding-y) var(--button-padding-x);\n\n &.active {\n @apply relative;\n background-color: var(--active-background);\n color: var(--active-foreground);\n }\n }\n\n .group-input-wrapper {\n @apply flex flex-1 items-stretch overflow-visible;\n input { \n @apply h-full; \n padding: var(--input-padding-y) var(--input-padding-x); \n }\n }\n\n .group-select-wrapper {\n @apply flex items-stretch p-0 bg-transparent border-none;\n .select { @apply h-full w-full; }\n }\n\n .trigger { border: none; }\n\n .group:not(.ghost) {\n .item :is(.group-item, .group-select-wrapper) { border: none; }\n \n .group-item.active { font-weight: 500; }\n \n .group-input-wrapper {\n --input-border-color: transparent;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n &.none {\n :is(.group-item, .trigger, .group-select-wrapper) { border-radius: 0; --radius: 0; --inner-radius: 0; }\n .group-input-wrapper { --input-border-radius: 0; }\n\n &.horizontal {\n .item:first-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n .item:last-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n }\n &.vertical {\n .item:first-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n .item:last-child :is(.group-item, .trigger, .group-input-wrapper > *, .group-select-wrapper > *) {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n }\n }\n\n &:is(.xs, .sm) {\n :is(.group-item, .trigger, .group-select-wrapper > *) { border-radius: var(--inner-radius); }\n .group-input-wrapper { --input-border-radius: var(--inner-radius); }\n }\n }\n\n /* Ghost overrides */\n .group.ghost {\n .group-item.active { border-radius: var(--inner-radius); }\n \n &:not(.none) .item .group-item:not(.active) {\n border-radius: var(--inner-radius);\n border: var(--border-width-base) solid transparent;\n }\n\n &.none {\n :is(.group-item, .group-select-wrapper) { border: none; border-radius: 0; --radius: 0; --inner-radius: 0; }\n .group-input-wrapper { --input-border-color: transparent; --input-border-radius: 0; }\n }\n }\n\n :is(.group-item, .trigger, .group-input-wrapper > [data-focus-visible]):focus-visible {\n @apply relative outline-none z-10;\n }\n\n .group:not(.ghost) :is(.group-item, .trigger, .group-input-wrapper > [data-focus-visible]):focus-visible {\n box-shadow: inset 0 0 0 1px var(--focus-ring-color);\n }\n\n .group.ghost :is(.group-item, .trigger, .group-input-wrapper > [data-focus-visible]):focus-visible {\n box-shadow: 0 0 0 1px var(--focus-ring-color);\n }\n}\n",
|
|
4214
4175
|
"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"
|
|
4215
4176
|
},
|
|
4216
4177
|
"input": {
|
|
4217
4178
|
"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
|
|
4179
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n height: fit-content;\n flex: 1;\n min-width: 0;\n @apply py-1.5 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.5;\n }\n\n .end-adornments {\n @apply pr-1.5;\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
4180
|
"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"
|
|
4220
4181
|
},
|
|
4221
4182
|
"label": {
|
|
@@ -4235,7 +4196,7 @@ export const generatedSourceCode = {
|
|
|
4235
4196
|
},
|
|
4236
4197
|
"menu": {
|
|
4237
4198
|
"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
|
|
4199
|
+
"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 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 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 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 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",
|
|
4239
4200
|
"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"
|
|
4240
4201
|
},
|
|
4241
4202
|
"modal": {
|
|
@@ -4259,7 +4220,7 @@ export const generatedSourceCode = {
|
|
|
4259
4220
|
"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"
|
|
4260
4221
|
},
|
|
4261
4222
|
"popover": {
|
|
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.
|
|
4223
|
+
"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. */\n className?: 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, 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, resolved.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, 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
4224
|
"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",
|
|
4264
4225
|
"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"
|
|
4265
4226
|
},
|
|
@@ -4274,18 +4235,18 @@ export const generatedSourceCode = {
|
|
|
4274
4235
|
"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"
|
|
4275
4236
|
},
|
|
4276
4237
|
"scroll": {
|
|
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-
|
|
4238
|
+
"tsx": "\"use client\";\n\nimport React, {\n useRef,\n useLayoutEffect,\n useState,\n useCallback,\n} from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Scroll.module.css\";\nimport {\n SCROLL_RESTORE_AXIS_ATTR,\n SCROLL_RESTORE_DEBUG_ID_KEY,\n SCROLL_RESTORE_FLAG,\n SCROLL_RESTORE_STORAGE_KEY_ATTR,\n getBootstrapRestoredNode,\n getScrollRestoreDebugId,\n getScrollRestoreMetrics,\n getScrollPositionProperty,\n recordScrollRestoreTrace,\n} from \"./scripts/restore-scroll.constants\";\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 children: React.ReactNode;\n maxHeight?: string;\n maxWidth?: string;\n direction?: \"vertical\" | \"horizontal\";\n paddingY?: string | number;\n \"fade-y\"?: boolean;\n fadeDistance?: number;\n fadeSize?: number;\n enabled?: boolean;\n hide?: boolean;\n inline?: boolean;\n styles?: ScrollStylesProp;\n storageKey?: string;\n}\n\nconst resolveScrollBaseStyles = createStylesResolver([\n \"root\",\n \"content\",\n \"track\",\n \"thumb\",\n \"horizontal\",\n \"vertical\",\n] as const);\n\nfunction readStoredScrollOffset(storageKey: string): number | null {\n if (typeof window === \"undefined\") return null;\n\n try {\n const storedValue = window.sessionStorage.getItem(storageKey);\n if (storedValue === null) return null;\n\n const parsedValue = parseInt(storedValue, 10);\n return Number.isNaN(parsedValue) ? null : parsedValue;\n } catch {\n return null;\n }\n}\n\nfunction persistStoredScrollOffset(storageKey: string, scrollOffset: number): void {\n if (typeof window === \"undefined\") return;\n\n try {\n window.sessionStorage.setItem(storageKey, String(scrollOffset));\n } catch {\n // Ignore storage failures. The live scroll position is already updated.\n }\n}\n\nfunction hasPreHydrationScrollRestore(node: HTMLDivElement): boolean {\n return Boolean((node as HTMLDivElement & Record<string, unknown>)[SCROLL_RESTORE_FLAG]);\n}\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 inline = false,\n styles,\n storageKey,\n style: propsStyle,\n ...restProps\n },\n ref,\n ) => {\n const isHoriz = direction === \"horizontal\";\n\n // Axis-Agnostic property keys\n const clientSizeKey = isHoriz ? \"clientWidth\" : \"clientHeight\";\n const scrollSizeKey = isHoriz ? \"scrollWidth\" : \"scrollHeight\";\n const scrollPosKey = getScrollPositionProperty(direction);\n const clientPosKey = isHoriz ? \"clientX\" : \"clientY\";\n const trackSizeKey = isHoriz ? \"width\" : \"height\";\n const trackPosKey = isHoriz ? \"left\" : \"top\";\n\n const numPaddingY = typeof paddingY === \"number\" ? paddingY : parseInt(String(paddingY), 10) || 0;\n const strPaddingY = typeof paddingY === \"number\" ? `${paddingY}px` : String(paddingY);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const thumbRef = useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRef(ref, containerRef);\n\n const resolved = resolveScrollBaseStyles(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 [isScrolling, setIsScrolling] = useState(false);\n\n const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n const dragStartRef = useRef({ origin: 0, scrollOrigin: 0 });\n const thumbSizeRef = useRef(0);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const mutationObserverRef = useRef<MutationObserver | 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\n const containerSize = container[clientSizeKey];\n const contentSize = content[scrollSizeKey] || containerSize;\n const currentScroll = content[scrollPosKey];\n const trackSize = isHoriz ? containerSize : containerSize - numPaddingY * 2;\n\n const needs = contentSize > containerSize;\n setNeedsScrollbar(needs);\n\n const scrollRatio = trackSize / Math.max(1, contentSize);\n const newThumbSize = Math.max(20, Math.min(trackSize, trackSize * scrollRatio));\n const maxScroll = contentSize - containerSize;\n const scrollProgress = needs ? currentScroll / maxScroll : 0;\n const maxThumbPos = trackSize - newThumbSize;\n const newThumbPos = scrollProgress * maxThumbPos;\n\n setThumbSize(newThumbSize);\n thumbSizeRef.current = newThumbSize;\n setThumbPosition(newThumbPos);\n\n if (!isHoriz && fadeY && needs) {\n const topP = Math.min(1, Math.max(0, currentScroll / fadeDistance));\n const botP = Math.min(1, Math.max(0, (maxScroll - currentScroll) / 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 if (!isHoriz) {\n content.style.maskImage = \"\";\n content.style.webkitMaskImage = \"\";\n }\n }, [isHoriz, clientSizeKey, scrollSizeKey, scrollPosKey, numPaddingY, fadeY, fadeDistance, fadeSize]);\n\n const cleanupScrollTimeout = useCallback(() => {\n if (scrollTimeoutRef.current) {\n clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = null;\n }\n }, []);\n\n const cleanupObservers = useCallback(() => {\n resizeObserverRef.current?.disconnect();\n mutationObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n mutationObserverRef.current = null;\n }, []);\n\n const cleanupDragListeners = useCallback(() => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"\";\n }, []);\n\n const restoreStoredScrollPosition = useCallback(() => {\n if (!storageKey || !contentRef.current) return;\n\n const contentNode = contentRef.current;\n const bootstrapNode = getBootstrapRestoredNode(storageKey);\n const sameNodeAsBootstrap = Boolean(bootstrapNode) && bootstrapNode === contentNode;\n const currentNodeId = getScrollRestoreDebugId(contentNode);\n const bootstrapNodeId = getScrollRestoreDebugId(bootstrapNode);\n const beforeMetrics = getScrollRestoreMetrics(contentNode, direction);\n const hasPreHydrationRestore = hasPreHydrationScrollRestore(contentNode);\n\n recordScrollRestoreTrace(\"client:layout-effect\", {\n storageKey,\n hasPreHydrationRestore,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n clientSize: beforeMetrics.clientSize,\n scrollSize: beforeMetrics.scrollSize,\n maxScroll: beforeMetrics.maxScroll,\n scrollOffset: beforeMetrics.scrollOffset,\n });\n\n if (hasPreHydrationRestore) {\n recordScrollRestoreTrace(\"client:skip-prehydrated\", {\n storageKey,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n });\n return;\n }\n\n const savedOffset = readStoredScrollOffset(storageKey);\n if (savedOffset === null) {\n recordScrollRestoreTrace(\"client:no-stored-offset\", {\n storageKey,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n });\n return;\n }\n\n contentNode[scrollPosKey] = savedOffset;\n\n const afterMetrics = getScrollRestoreMetrics(contentNode, direction);\n recordScrollRestoreTrace(\"client:fallback-restore\", {\n storageKey,\n storedOffset: savedOffset,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n beforeScrollOffset: beforeMetrics.scrollOffset,\n afterScrollOffset: afterMetrics.scrollOffset,\n clientSize: afterMetrics.clientSize,\n scrollSize: afterMetrics.scrollSize,\n maxScroll: afterMetrics.maxScroll,\n clamped: savedOffset !== afterMetrics.scrollOffset,\n clampedToZero: savedOffset > 0 && afterMetrics.scrollOffset === 0,\n });\n }, [direction, scrollPosKey, storageKey]);\n\n const connectObservers = useCallback(() => {\n cleanupObservers();\n updateScrollbar();\n\n const resizeObserver = new ResizeObserver(() => requestAnimationFrame(updateScrollbar));\n const mutationObserver = new MutationObserver(() => requestAnimationFrame(updateScrollbar));\n\n if (containerRef.current) resizeObserver.observe(containerRef.current);\n if (contentRef.current) {\n resizeObserver.observe(contentRef.current);\n mutationObserver.observe(contentRef.current, { childList: true, subtree: true });\n }\n\n resizeObserverRef.current = resizeObserver;\n mutationObserverRef.current = mutationObserver;\n }, [cleanupObservers, updateScrollbar]);\n\n const handleContentRef = useCallback(\n (node: HTMLDivElement | null) => {\n contentRef.current = node;\n if (!node) {\n cleanupObservers();\n cleanupDragListeners();\n cleanupScrollTimeout();\n return;\n }\n\n recordScrollRestoreTrace(\"client:content-ref\", {\n storageKey: storageKey ?? null,\n currentNodeId: getScrollRestoreDebugId(node),\n preHydrationFlag: hasPreHydrationScrollRestore(node),\n bootstrapDebugId: storageKey ? getScrollRestoreDebugId(getBootstrapRestoredNode(storageKey)) : null,\n debugIdPropertyKey: SCROLL_RESTORE_DEBUG_ID_KEY,\n });\n connectObservers();\n },\n [cleanupDragListeners, cleanupObservers, cleanupScrollTimeout, connectObservers, storageKey]\n );\n\n const handleScroll = useCallback(() => {\n updateScrollbar();\n if (storageKey && contentRef.current) {\n persistStoredScrollOffset(storageKey, contentRef.current[scrollPosKey]);\n }\n setIsScrolling(true);\n if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = setTimeout(() => setIsScrolling(false), 1500);\n }, [updateScrollbar, storageKey, scrollPosKey]);\n\n const handleContainerMouseMove = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!containerRef.current) return;\n const rect = containerRef.current.getBoundingClientRect();\n const mousePos = isHoriz ? e.clientY - rect.top : e.clientX - rect.left;\n const rectSize = isHoriz ? rect.height : rect.width;\n\n const newIsHovered = mousePos > rectSize - 20;\n if (newIsHovered !== isHoveredRight) setIsHoveredRight(newIsHovered);\n },\n [isHoriz, isHoveredRight]\n );\n\n const handleContainerMouseLeave = useCallback(() => setIsHoveredRight(false), []);\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!contentRef.current || !containerRef.current) return;\n\n const delta = e[clientPosKey] - dragStartRef.current.origin;\n const containerSize = containerRef.current[clientSizeKey];\n const maxScroll = contentRef.current[scrollSizeKey] - containerSize;\n const scrollRatio = maxScroll / (containerSize - thumbSizeRef.current);\n\n contentRef.current[scrollPosKey] = Math.max(\n 0,\n Math.min(maxScroll, dragStartRef.current.scrollOrigin + delta * scrollRatio)\n );\n },\n [clientPosKey, clientSizeKey, scrollPosKey, scrollSizeKey]\n );\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n cleanupDragListeners();\n }, [cleanupDragListeners]);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!contentRef.current) return;\n e.preventDefault();\n\n dragStartRef.current = {\n origin: e[clientPosKey],\n scrollOrigin: contentRef.current[scrollPosKey],\n };\n setIsDragging(true);\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n },\n [clientPosKey, scrollPosKey, handleMouseMove, handleMouseUp]\n );\n\n const handleTrackClick = useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current || !contentRef.current || !thumbRef.current) return;\n\n const rect = containerRef.current.getBoundingClientRect();\n const thumbRect = thumbRef.current.getBoundingClientRect();\n const rectStartKey = isHoriz ? \"left\" : \"top\";\n const rectEndKey = isHoriz ? \"right\" : \"bottom\";\n const padOffset = isHoriz ? 0 : numPaddingY;\n\n const clickPos = e[clientPosKey] - rect[rectStartKey] - padOffset;\n const relThumbStart = thumbRect[rectStartKey] - rect[rectStartKey] - padOffset;\n const relThumbEnd = thumbRect[rectEndKey] - rect[rectStartKey] - padOffset;\n\n // Ignore clicks directly on the thumb (handled by handleMouseDown)\n if (clickPos >= relThumbStart && clickPos <= relThumbEnd) return;\n\n const containerSize = containerRef.current[clientSizeKey];\n const contentSize = contentRef.current[scrollSizeKey];\n const maxScroll = contentSize - containerSize;\n const trackSize = isHoriz ? containerSize : containerSize - numPaddingY * 2;\n\n const newThumbSize = Math.max(20, trackSize * (trackSize / contentSize));\n const targetThumbStart = clickPos - newThumbSize / 2;\n const maxThumbPos = trackSize - newThumbSize;\n const clampedThumbStart = Math.max(0, Math.min(maxThumbPos, targetThumbStart));\n\n const scrollProgress = clampedThumbStart / maxThumbPos;\n contentRef.current[scrollPosKey] = Math.max(0, Math.min(maxScroll, scrollProgress * maxScroll));\n\n dragStartRef.current = {\n origin: e[clientPosKey],\n scrollOrigin: contentRef.current[scrollPosKey],\n };\n setIsDragging(true);\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n },\n [isHoriz, numPaddingY, clientPosKey, clientSizeKey, scrollPosKey, scrollSizeKey, handleMouseMove, handleMouseUp]\n );\n\n const handleWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!contentRef.current || !isHoriz) return;\n e.preventDefault();\n\n const content = contentRef.current;\n const scrollAmount = e.deltaY || e.deltaX;\n const maxScroll = content.scrollWidth - content.clientWidth;\n\n content.scrollLeft = Math.max(0, Math.min(maxScroll, content.scrollLeft + scrollAmount));\n },\n [isHoriz]\n );\n\n useLayoutEffect(() => {\n restoreStoredScrollPosition();\n connectObservers();\n }, [restoreStoredScrollPosition, connectObservers, enabled]);\n\n if (!enabled) {\n return (\n <div\n ref={ref}\n className={cn(\"scroll\", css.root, resolved.root, className)}\n style={{\n [isHoriz ? \"width\" : \"height\"]: \"100%\",\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: isHoriz ? maxWidth : maxHeight,\n ...propsStyle,\n }}\n {...restProps}\n >\n {children}\n </div>\n );\n }\n\n const showOpacity = !hide || (needsScrollbar && (isHoveredRight || isDragging || isScrolling)) ? 1 : 0;\n\n return (\n <div\n ref={mergedRef}\n className={cn(\n \"scroll\",\n css.root,\n isHoriz ? css.horizontal : css.vertical,\n className,\n resolved.root,\n isHoriz ? resolved.horizontal : resolved.vertical\n )}\n style={{\n [isHoriz ? \"width\" : \"height\"]: \"100%\",\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: isHoriz ? maxWidth : maxHeight,\n ...(!isHoriz && strPaddingY ? { \"--scroll-padding-y\": strPaddingY } : {}),\n ...propsStyle,\n } as React.CSSProperties}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-dragging={String(isDragging)}\n data-inline={String(inline)}\n {...restProps}\n >\n <div\n ref={handleContentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n onWheel={isHoriz ? handleWheel : undefined}\n style={{ [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\" }}\n {...(storageKey\n ? {\n [SCROLL_RESTORE_STORAGE_KEY_ATTR]: storageKey,\n [SCROLL_RESTORE_AXIS_ATTR]: direction,\n }\n : {})}\n >\n {children}\n </div>\n\n <div\n className={cn(css.track, resolved.track)}\n data-hide={String(hide)}\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 [trackSizeKey]: `${thumbSize}px`,\n [trackPosKey]: `${thumbPosition}px`,\n }}\n onMouseDown={handleMouseDown}\n />\n )}\n </div>\n </div>\n );\n }\n);\n\nScroll.displayName = \"Scroll\";\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\nexport { Scroll };\n",
|
|
4239
|
+
"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-inline=\"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-inline=\"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",
|
|
4279
4240
|
"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"
|
|
4280
4241
|
},
|
|
4281
4242
|
"select": {
|
|
4282
4243
|
"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
|
|
4244
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\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 font-size: var(--font-size);\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\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 font-size: var(--font-size);\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-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(--font-size);\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(--font-size);\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(--font-size);\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
4245
|
"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"
|
|
4285
4246
|
},
|
|
4286
4247
|
"slider": {
|
|
4287
4248
|
"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",
|
|
4288
|
-
"css": "@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
|
|
4249
|
+
"css": "@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 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",
|
|
4289
4250
|
"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"
|
|
4290
4251
|
},
|
|
4291
4252
|
"switch": {
|
|
@@ -4299,9 +4260,9 @@ export const generatedSourceCode = {
|
|
|
4299
4260
|
"cssTypes": ""
|
|
4300
4261
|
},
|
|
4301
4262
|
"tabs": {
|
|
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
|
|
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"
|
|
4263
|
+
"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)\nconst TABS_INDICATOR_INSET = 4\nconst TABS_UNDERLINE_THICKNESS = 2\n\ninterface TabsListContextValue {\n indicatorClassName: string\n}\n\nconst TabsListContext = React.createContext<TabsListContextValue | 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\nfunction useTabsListContext() {\n return React.useContext(TabsListContext)\n}\n\nfunction getTabsIndicatorClassName(indicator: string, variant?: TabsVariant) {\n return cn(\n \"tabs\",\n \"indicator\",\n variant === \"underline\" && \"underline\",\n variant === \"underline\" && \"indicator-underline\",\n css.indicator,\n {\n [css[\"indicator-underline\"]]: variant === \"underline\",\n },\n indicator\n )\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 const indicatorClassName = React.useMemo(\n () => getTabsIndicatorClassName(indicator, variant),\n [indicator, variant]\n )\n const tabsListContext = React.useMemo(\n () => ({ indicatorClassName }),\n [indicatorClassName]\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 margin: 0,\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: TABS_UNDERLINE_THICKNESS,\n height: indicatorPosition.height,\n }\n }\n const horizontalPadding = TABS_INDICATOR_INSET\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 - TABS_UNDERLINE_THICKNESS,\n width: indicatorPosition.width,\n height: TABS_UNDERLINE_THICKNESS,\n }\n }\n\n const verticalPadding = TABS_INDICATOR_INSET\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 <TabsListContext.Provider value={tabsListContext}>\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 {children}\n {indicatorPosition.width > 0 && (\n <div\n aria-hidden=\"true\"\n className={indicatorClassName}\n style={getIndicatorStyle}\n />\n )}\n </div>\n </TabsListContext.Provider>\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, orientation, variant } = useTabsContext()\n const tabsListContext = useTabsListContext()\n const { root, icon: iconStyles } = resolveTabsTriggerBaseStyles(stylesProp)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const isSelected = value === selectedValue\n const showIndicatorFallback = isSelected && !indicatorReady && !!tabsListContext\n const fallbackIndicatorStyle = React.useMemo<React.CSSProperties>(() => {\n if (variant === \"underline\") {\n if (orientation === \"vertical\") {\n return {\n top: 0,\n bottom: 0,\n left: -TABS_UNDERLINE_THICKNESS,\n width: TABS_UNDERLINE_THICKNESS,\n height: \"100%\",\n margin: 0,\n }\n }\n\n return {\n left: 0,\n right: 0,\n bottom: -TABS_UNDERLINE_THICKNESS,\n width: \"100%\",\n height: TABS_UNDERLINE_THICKNESS,\n margin: 0,\n }\n }\n\n return {\n inset: 0,\n margin: 0,\n }\n }, [orientation, variant])\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 data-indicator-fallback={showIndicatorFallback ? \"true\" : undefined}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n {showIndicatorFallback && tabsListContext && (\n <span\n aria-hidden=\"true\"\n className={cn(tabsListContext.indicatorClassName, css[\"indicator-fallback\"])}\n style={fallbackIndicatorStyle}\n />\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",
|
|
4264
|
+
"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 @apply absolute;\n background-color: var(--indicator-background);\n box-sizing: border-box;\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-fallback {\n z-index: -1;\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-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\"]):not([data-indicator-fallback=\"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\"]):not([data-indicator-fallback=\"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 .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
|
|
4265
|
+
"cssTypes": "declare const styles: {\n tabs: string;\n list: string;\n indicator: string;\n \"indicator-fallback\": string;\n \"indicator-underline\": string;\n trigger: string;\n \"trigger-icon\": string;\n content: string;\n};\n\nexport default styles;\n"
|
|
4305
4266
|
},
|
|
4306
4267
|
"textarea": {
|
|
4307
4268
|
"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",
|
|
@@ -4315,7 +4276,7 @@ export const generatedSourceCode = {
|
|
|
4315
4276
|
},
|
|
4316
4277
|
"tooltip": {
|
|
4317
4278
|
"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
|
|
4279
|
+
"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 whitespace-nowrap;;\n color: var(--foreground);\n }\n\n .frame[data-hint] {\n @apply pr-1;\n }\n}\n",
|
|
4319
4280
|
"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"
|
|
4320
4281
|
}
|
|
4321
4282
|
};
|
|
@@ -4551,6 +4512,6 @@ export const generatedCorePeerDependencies = [
|
|
|
4551
4512
|
"react-dom"
|
|
4552
4513
|
];
|
|
4553
4514
|
export const packageMetadata = {
|
|
4554
|
-
"version": "0.3.
|
|
4515
|
+
"version": "0.3.4"
|
|
4555
4516
|
};
|
|
4556
4517
|
//# sourceMappingURL=generated-data.js.map
|