ui-lab-registry 0.3.3 → 0.3.41

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.
Files changed (67) hide show
  1. package/dist/components/Button/examples/01-variants.d.ts.map +1 -1
  2. package/dist/components/Button/examples/01-variants.js.map +1 -1
  3. package/dist/components/Button/examples/02-multi-actions.d.ts +6 -0
  4. package/dist/components/Button/examples/02-multi-actions.d.ts.map +1 -0
  5. package/dist/components/Button/examples/02-multi-actions.js +12 -0
  6. package/dist/components/Button/examples/02-multi-actions.js.map +1 -0
  7. package/dist/components/Button/examples/03-joined-toggle.d.ts +6 -0
  8. package/dist/components/Button/examples/03-joined-toggle.d.ts.map +1 -0
  9. package/dist/components/Button/examples/03-joined-toggle.js +15 -0
  10. package/dist/components/Button/examples/03-joined-toggle.js.map +1 -0
  11. package/dist/components/Button/examples/04-sub-stack-actions.d.ts +6 -0
  12. package/dist/components/Button/examples/04-sub-stack-actions.d.ts.map +1 -0
  13. package/dist/components/Button/examples/04-sub-stack-actions.js +15 -0
  14. package/dist/components/Button/examples/04-sub-stack-actions.js.map +1 -0
  15. package/dist/components/Button/examples/05-split-action-button.d.ts +6 -0
  16. package/dist/components/Button/examples/05-split-action-button.d.ts.map +1 -0
  17. package/dist/components/Button/examples/05-split-action-button.js +42 -0
  18. package/dist/components/Button/examples/05-split-action-button.js.map +1 -0
  19. package/dist/components/Button/examples/index.d.ts +8 -0
  20. package/dist/components/Button/examples/index.d.ts.map +1 -1
  21. package/dist/components/Button/examples/index.js +8 -0
  22. package/dist/components/Button/examples/index.js.map +1 -1
  23. package/dist/components/Button/examples.json +27 -1
  24. package/dist/components/Button/index.d.ts.map +1 -1
  25. package/dist/components/Button/index.js +8 -0
  26. package/dist/components/Button/index.js.map +1 -1
  27. package/dist/generated-data.d.ts.map +1 -1
  28. package/dist/generated-data.js +126 -149
  29. package/dist/generated-data.js.map +1 -1
  30. package/dist/generated-styles.d.ts.map +1 -1
  31. package/dist/generated-styles.js +192 -126
  32. package/dist/generated-styles.js.map +1 -1
  33. package/dist/generated-styles.json +192 -126
  34. package/dist/patterns/layout/media-object/metadata.json +1 -1
  35. package/dist/patterns/layout/media-object/variations/lg/index.js +1 -1
  36. package/dist/patterns/layout/media-object/variations/lg/index.js.map +1 -1
  37. package/dist/registry.d.ts.map +1 -1
  38. package/dist/registry.js +38 -12
  39. package/dist/registry.js.map +1 -1
  40. package/dist/sections/CTA/index.js +1 -1
  41. package/dist/sections/CTA/index.js.map +1 -1
  42. package/package.json +2 -2
  43. package/src/components/Anchor/metadata.json +1 -1
  44. package/src/components/Button/examples/01-variants.tsx +1 -1
  45. package/src/components/Button/examples/02-multi-actions.tsx +20 -0
  46. package/src/components/Button/examples/03-joined-toggle.tsx +32 -0
  47. package/src/components/Button/examples/04-sub-stack-actions.tsx +29 -0
  48. package/src/components/Button/examples/05-split-action-button.tsx +108 -0
  49. package/src/components/Button/examples/index.ts +8 -0
  50. package/src/components/Button/examples.json +27 -1
  51. package/src/components/Button/index.tsx +8 -0
  52. package/src/components/Color/metadata.json +1 -1
  53. package/src/components/Date/metadata.json +1 -1
  54. package/src/components/Expand/metadata.json +1 -1
  55. package/src/components/Frame/metadata.json +1 -1
  56. package/src/components/Gallery/metadata.json +1 -1
  57. package/src/components/Grid/metadata.json +1 -1
  58. package/src/components/Mask/metadata.json +1 -1
  59. package/src/components/Page/metadata.json +1 -1
  60. package/src/components/Path/metadata.json +1 -1
  61. package/src/components/Scroll/metadata.json +1 -1
  62. package/src/generated-data.ts +126 -149
  63. package/src/generated-styles.ts +192 -126
  64. package/src/patterns/layout/media-object/metadata.json +1 -1
  65. package/src/patterns/layout/media-object/variations/lg/index.tsx +1 -1
  66. package/src/registry.ts +38 -12
  67. package/src/sections/CTA/index.tsx +1 -1
@@ -25,8 +25,19 @@ export const generatedAPI = {
25
25
  "type": "AnchorStylesProp",
26
26
  "required": false,
27
27
  "description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
28
+ },
29
+ {
30
+ "name": "preview",
31
+ "type": "ReactNode",
32
+ "required": false,
33
+ "description": "Preview content to show in a tooltip on hover. Use this in server components instead of <Anchor.Preview>."
28
34
  }
29
35
  ],
36
+ "subComponents": {
37
+ "AnchorPreview": {
38
+ "props": []
39
+ }
40
+ },
30
41
  "examples": [
31
42
  {
32
43
  "title": "Basic Anchor",
@@ -161,7 +172,33 @@ export const generatedAPI = {
161
172
  "description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
162
173
  }
163
174
  ],
164
- "examples": []
175
+ "examples": [
176
+ {
177
+ "title": "Button Variants",
178
+ "description": "All available button variants including primary, default, secondary, outline, and ghost styles.",
179
+ "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}"
180
+ },
181
+ {
182
+ "title": "Multiple Actions",
183
+ "description": "A primary action button grouped with secondary actions and an options menu.",
184
+ "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}"
185
+ },
186
+ {
187
+ "title": "Joined Toggle Buttons",
188
+ "description": "Multiple buttons grouped together for view/mode selection with active state indication.",
189
+ "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}"
190
+ },
191
+ {
192
+ "title": "Sub Stack Actions",
193
+ "description": "A collection of buttons and inputs arranged horizontally for grouped actions and filtering.",
194
+ "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}"
195
+ },
196
+ {
197
+ "title": "Split Action Button",
198
+ "description": "A primary action button joined with a Select dropdown that changes what the button does — useful for publish workflows with multiple delivery options.",
199
+ "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}"
200
+ }
201
+ ]
165
202
  },
166
203
  "card": {
167
204
  "props": [
@@ -318,48 +355,41 @@ export const generatedAPI = {
318
355
  {
319
356
  "name": "value",
320
357
  "type": "string",
321
- "required": false,
322
- "description": "Controlled color value as a CSS color string"
358
+ "required": false
323
359
  },
324
360
  {
325
361
  "name": "defaultValue",
326
362
  "type": "string",
327
363
  "required": false,
328
- "defaultValue": "#000000",
329
- "description": "Initial color value for uncontrolled usage"
364
+ "defaultValue": "#000000"
330
365
  },
331
366
  {
332
367
  "name": "onChange",
333
368
  "type": "((color: string) => void)",
334
- "required": false,
335
- "description": "Called continuously while the user drags the color picker"
369
+ "required": false
336
370
  },
337
371
  {
338
372
  "name": "onChangeComplete",
339
373
  "type": "((color: string) => void)",
340
- "required": false,
341
- "description": "Called once when the user finishes a drag interaction"
374
+ "required": false
342
375
  },
343
376
  {
344
377
  "name": "showOpacity",
345
378
  "type": "boolean",
346
379
  "required": false,
347
- "defaultValue": "false",
348
- "description": "Whether to show the opacity/alpha slider"
380
+ "defaultValue": "false"
349
381
  },
350
382
  {
351
383
  "name": "showPreview",
352
384
  "type": "boolean",
353
385
  "required": false,
354
- "defaultValue": "false",
355
- "description": "Whether to show a color preview swatch next to the input"
386
+ "defaultValue": "false"
356
387
  },
357
388
  {
358
389
  "name": "format",
359
390
  "type": "hex | rgb",
360
391
  "required": false,
361
392
  "defaultValue": "hex",
362
- "description": "Output format of the color value string",
363
393
  "enumValues": [
364
394
  "hex",
365
395
  "rgb"
@@ -369,15 +399,13 @@ export const generatedAPI = {
369
399
  "name": "disabled",
370
400
  "type": "boolean",
371
401
  "required": false,
372
- "defaultValue": "false",
373
- "description": "Whether the color picker is disabled"
402
+ "defaultValue": "false"
374
403
  },
375
404
  {
376
405
  "name": "size",
377
406
  "type": "sm | md | lg",
378
407
  "required": false,
379
408
  "defaultValue": "md",
380
- "description": "Size of the color picker",
381
409
  "enumValues": [
382
410
  "sm",
383
411
  "md",
@@ -387,14 +415,7 @@ export const generatedAPI = {
387
415
  {
388
416
  "name": "className",
389
417
  "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"
418
+ "required": false
398
419
  }
399
420
  ],
400
421
  "subComponents": {
@@ -481,19 +502,16 @@ export const generatedAPI = {
481
502
  ]
482
503
  },
483
504
  "ColorInput": {
484
- "description": "Text input for entering a color value directly",
485
505
  "props": [
486
506
  {
487
507
  "name": "value",
488
508
  "type": "string",
489
- "required": true,
490
- "description": "Current color value string displayed in the text input"
509
+ "required": true
491
510
  },
492
511
  {
493
512
  "name": "format",
494
513
  "type": "hex | rgb",
495
514
  "required": true,
496
- "description": "Active color format controlling the input placeholder and value representation",
497
515
  "enumValues": [
498
516
  "hex",
499
517
  "rgb"
@@ -502,27 +520,23 @@ export const generatedAPI = {
502
520
  {
503
521
  "name": "onFormatChange",
504
522
  "type": "((format: \"hex\" | \"rgb\") => void)",
505
- "required": false,
506
- "description": "Called when the user selects a different color format from the dropdown"
523
+ "required": false
507
524
  },
508
525
  {
509
526
  "name": "onValueChange",
510
527
  "type": "((value: string) => void)",
511
- "required": false,
512
- "description": "Called when the user types a valid color string into the input"
528
+ "required": false
513
529
  },
514
530
  {
515
531
  "name": "disabled",
516
532
  "type": "boolean",
517
- "required": false,
518
- "description": "Disables the text input and format selector"
533
+ "required": false
519
534
  },
520
535
  {
521
536
  "name": "size",
522
537
  "type": "sm | md | lg",
523
538
  "required": false,
524
539
  "defaultValue": "md",
525
- "description": "Size of the input group",
526
540
  "enumValues": [
527
541
  "sm",
528
542
  "md",
@@ -533,14 +547,12 @@ export const generatedAPI = {
533
547
  "name": "showPreview",
534
548
  "type": "boolean",
535
549
  "required": false,
536
- "defaultValue": "false",
537
- "description": "Whether to show a color preview swatch beside the input"
550
+ "defaultValue": "false"
538
551
  },
539
552
  {
540
553
  "name": "previewColor",
541
554
  "type": "string",
542
- "required": false,
543
- "description": "RGB color string used to fill the preview swatch"
555
+ "required": false
544
556
  }
545
557
  ]
546
558
  },
@@ -867,14 +879,15 @@ export const generatedAPI = {
867
879
  },
868
880
  {
869
881
  "name": "size",
870
- "type": "sm | md | lg",
882
+ "type": "sm | md | lg | auto",
871
883
  "required": false,
872
- "defaultValue": "md",
884
+ "defaultValue": "auto",
873
885
  "description": "Size of the divider thickness",
874
886
  "enumValues": [
875
887
  "sm",
876
888
  "md",
877
- "lg"
889
+ "lg",
890
+ "auto"
878
891
  ]
879
892
  },
880
893
  {
@@ -960,18 +973,6 @@ export const generatedAPI = {
960
973
  "required": false,
961
974
  "description": "Compound sub-components or content nodes"
962
975
  },
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
976
  {
976
977
  "name": "styles",
977
978
  "type": "ExpandStylesProp",
@@ -1708,13 +1709,14 @@ export const generatedAPI = {
1708
1709
  },
1709
1710
  {
1710
1711
  "name": "size",
1711
- "type": "sm | md | lg",
1712
+ "type": "sm | md | lg | auto",
1712
1713
  "required": false,
1713
1714
  "description": "Size of the divider thickness",
1714
1715
  "enumValues": [
1715
1716
  "sm",
1716
1717
  "md",
1717
- "lg"
1718
+ "lg",
1719
+ "auto"
1718
1720
  ]
1719
1721
  },
1720
1722
  {
@@ -2077,7 +2079,14 @@ export const generatedAPI = {
2077
2079
  ]
2078
2080
  },
2079
2081
  "mask": {
2080
- "props": [],
2082
+ "props": [
2083
+ {
2084
+ "name": "asChild",
2085
+ "type": "boolean",
2086
+ "required": false,
2087
+ "defaultValue": "false"
2088
+ }
2089
+ ],
2081
2090
  "examples": [
2082
2091
  {
2083
2092
  "title": "Mask - Read More Effect",
@@ -2604,18 +2613,6 @@ export const generatedAPI = {
2604
2613
  "required": false,
2605
2614
  "description": "Additional class for the modal panel"
2606
2615
  },
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
2616
  {
2620
2617
  "name": "styles",
2621
2618
  "type": "ModalStylesProp",
@@ -2829,13 +2826,7 @@ export const generatedAPI = {
2829
2826
  "name": "className",
2830
2827
  "type": "string",
2831
2828
  "required": false,
2832
- "description": "Additional CSS class for the trigger element. Merged with `styles.root`."
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`."
2829
+ "description": "Additional CSS class for the trigger element."
2839
2830
  },
2840
2831
  {
2841
2832
  "name": "isOpen",
@@ -3010,32 +3001,21 @@ export const generatedAPI = {
3010
3001
  },
3011
3002
  "scroll": {
3012
3003
  "props": [
3013
- {
3014
- "name": "children",
3015
- "type": "ReactNode",
3016
- "required": true,
3017
- "description": "Content to render inside the scroll container"
3018
- },
3019
3004
  {
3020
3005
  "name": "maxHeight",
3021
3006
  "type": "string",
3022
- "required": false,
3023
- "defaultValue": "100%",
3024
- "description": "Maximum height before scrolling becomes active"
3007
+ "required": false
3025
3008
  },
3026
3009
  {
3027
3010
  "name": "maxWidth",
3028
3011
  "type": "string",
3029
- "required": false,
3030
- "defaultValue": "100%",
3031
- "description": "Maximum width before scrolling becomes active"
3012
+ "required": false
3032
3013
  },
3033
3014
  {
3034
3015
  "name": "direction",
3035
3016
  "type": "vertical | horizontal",
3036
3017
  "required": false,
3037
3018
  "defaultValue": "vertical",
3038
- "description": "Scroll direction",
3039
3019
  "enumValues": [
3040
3020
  "vertical",
3041
3021
  "horizontal"
@@ -3045,56 +3025,53 @@ export const generatedAPI = {
3045
3025
  "name": "paddingY",
3046
3026
  "type": "string | number",
3047
3027
  "required": false,
3048
- "defaultValue": "4",
3049
- "description": "Padding on the top and bottom of the scrollbar track in pixels"
3028
+ "defaultValue": "4"
3050
3029
  },
3051
3030
  {
3052
3031
  "name": "fade-y",
3053
3032
  "type": "boolean",
3054
3033
  "required": false,
3055
- "defaultValue": "false",
3056
- "description": "Whether to apply a fade mask at the top and bottom scroll edges"
3034
+ "defaultValue": "false"
3057
3035
  },
3058
3036
  {
3059
3037
  "name": "fadeDistance",
3060
3038
  "type": "number",
3061
3039
  "required": false,
3062
- "defaultValue": "5",
3063
- "description": "Pixels scrolled before the fade mask begins to appear"
3040
+ "defaultValue": "5"
3064
3041
  },
3065
3042
  {
3066
3043
  "name": "fadeSize",
3067
3044
  "type": "number",
3068
3045
  "required": false,
3069
- "defaultValue": "4",
3070
- "description": "Percentage of container height used for the fade gradient"
3046
+ "defaultValue": "4"
3071
3047
  },
3072
3048
  {
3073
3049
  "name": "enabled",
3074
3050
  "type": "boolean",
3075
3051
  "required": false,
3076
- "defaultValue": "true",
3077
- "description": "Whether to render the custom scrollbar; when false, renders children without scroll"
3052
+ "defaultValue": "true"
3078
3053
  },
3079
3054
  {
3080
3055
  "name": "hide",
3081
3056
  "type": "boolean",
3082
3057
  "required": false,
3083
- "defaultValue": "true",
3084
- "description": "Whether to hide the scrollbar when not actively scrolling"
3058
+ "defaultValue": "true"
3085
3059
  },
3086
3060
  {
3087
- "name": "inset",
3061
+ "name": "inline",
3088
3062
  "type": "boolean",
3089
3063
  "required": false,
3090
- "defaultValue": "false",
3091
- "description": "When true, the scrollbar sits inline displacing content; when false (default), it overlays the content"
3064
+ "defaultValue": "false"
3092
3065
  },
3093
3066
  {
3094
3067
  "name": "styles",
3095
3068
  "type": "ScrollStylesProp",
3096
- "required": false,
3097
- "description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
3069
+ "required": false
3070
+ },
3071
+ {
3072
+ "name": "storageKey",
3073
+ "type": "string",
3074
+ "required": false
3098
3075
  }
3099
3076
  ],
3100
3077
  "examples": [
@@ -4084,14 +4061,14 @@ export const generatedAPI = {
4084
4061
  }
4085
4062
  };
4086
4063
  export const generatedStyles = {
4087
- "anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n @apply inline;\n }\n\n .preview {\n @apply inline;\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover);\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background);\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n}\n",
4088
- "badge": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2 px-3 py-0.5;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--background-border);\n border-radius: var(--radius-sm);\n }\n\n .badge.sm {\n @apply px-1.5 py-px;\n gap: 0.25rem;\n font-size: var(--text-sm);\n }\n\n .badge.dismissible {\n @apply pr-0.5;\n }\n\n .badge.md {\n @apply px-3.5 py-1;\n font-size: var(--text-sm);\n }\n\n .badge.lg {\n @apply px-4 py-2.5;\n font-size: var(--text-sm);\n }\n\n .pill { border-radius: 9999px; }\n\n .icon {\n @apply flex items-center shrink-0;\n }\n\n .dismiss {\n @apply ml-1 flex items-center justify-center p-1 cursor-pointer;\n border-radius: var(--radius-xs);\n background: transparent;\n border: none;\n color: var(--dismiss-color);\n transition: opacity 150ms var(--ease-snappy-pop), transform 150ms var(--ease-snappy-pop);\n outline: none;\n }\n\n .dismiss-button[data-hovered=\"true\"] {\n background: var(--dismiss-hover-background);\n }\n\n .dismiss-button[data-pressed=\"true\"] {\n background: var(--dismiss-pressed-background);\n transform: scale(0.95);\n }\n\n .dismiss-button[data-focus-visible=\"true\"] {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n}\n",
4089
- "banner": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n line-height: var(--leading-normal);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out, border-color 0.15s ease-out;\n }\n\n .content {\n @apply flex flex-col gap-2;\n }\n\n .icon-container {\n @apply flex shrink-0 items-center justify-center self-start;\n }\n\n .icon {\n @apply mr-4 h-5 w-5;\n color: var(--icon-color, currentColor);\n }\n\n .dismiss {\n @apply flex h-8 w-8 shrink-0 items-center justify-center p-0 cursor-pointer;\n background-color: transparent;\n color: currentColor;\n border: none;\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n }\n\n .title {\n font-weight: var(--font-weight-semibold);\n font-size: inherit;\n line-height: var(--leading-tight);\n @apply my-0;\n }\n\n .body {\n font-weight: var(--font-weight-medium);\n font-size: inherit;\n line-height: var(--leading-normal);\n @apply my-0;\n }\n}\n\n\n.banner.sm {\n @apply px-3 py-2;\n font-size: var(--text-sm);\n}\n\n.banner.md {\n @apply px-4 py-3;\n font-size: var(--text-sm);\n}\n\n.banner.lg {\n @apply px-6 py-4;\n font-size: var(--text-sm);\n}\n",
4090
- "button": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:active:not(:disabled) {\n filter: brightness(0.8);\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 1.5px var(--focus-visible);\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n filter: grayscale(0.5);\n }\n }\n}\n",
4064
+ "anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: 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",
4065
+ "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",
4066
+ "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",
4067
+ "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
4068
  "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-pressed=\"true\"] {\n transform: scale(0.92);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n /* Checkmark and Indeterminate styles - combined */\n .checkbox-checkmark,\n .checkbox-indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--checkmark-color);\n pointer-events: none;\n }\n\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .label-sm {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-md {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-lg {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-disabled {\n @apply opacity-60 cursor-not-allowed;\n }\n\n .helper-text {\n @apply text-sm ml-8;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .helper-text-normal { color: inherit; }\n\n .helper-text-error { color: var(--error-color); }\n}\n",
4069
+ "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
4070
  "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 --disabled-opacity: 0.5;\n @apply flex flex-col gap-4;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n width: 260px;\n }\n\n .color[data-disabled=\"true\"] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n .colorControls,\n .color-controls {\n @apply pb-3 px-3 space-y-3;\n }\n\n /* Input styles */\n .inputGroup,\n .input-group {\n @apply flex w-full;\n }\n\n .inputGroup > div:nth-child(1),\n .input-group > div:nth-child(1) {\n flex: 1;\n @apply min-w-0;\n }\n\n .colorInput,\n .color-input { @apply w-full; }\n\n .formatSelect,\n .format-select { @apply shrink-0; width: 85px; }\n\n .color[data-size=\"sm\"] .formatSelect,\n .color[data-size=\"sm\"] .format-select { width: 75px; }\n\n /* Canvas Styles */\n .canvasContainer,\n .canvas-container {\n @apply relative mx-auto mt-2 flex flex-col;\n width: 96%;\n cursor: crosshair;\n touch-action: none;\n min-height: 160px;\n }\n\n .canvasContainer[data-focus-visible=\"true\"],\n .canvas-container[data-focus-visible=\"true\"] {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .canvas {\n @apply relative w-full flex-1 overflow-hidden;\n flex: 1;\n border-radius: none;\n }\n\n .canvasGradientHue,\n .canvas-gradient-hue {\n @apply absolute inset-0 overflow-hidden;\n }\n\n .canvasGradientSaturation,\n .canvasGradientLightness,\n .canvas-gradient-saturation,\n .canvas-gradient-lightness {\n @apply absolute inset-0;\n border-radius: none;\n }\n\n .canvas-gradient-saturation {\n background: linear-gradient(to right, rgb(255, 255, 255), transparent);\n }\n\n .canvas-gradient-lightness {\n background: linear-gradient(to top, rgb(0, 0, 0), transparent);\n }\n\n .canvasPointer,\n .canvas-pointer {\n @apply absolute h-3 w-3;\n border-radius: var(--radius-full);\n border: 2px solid var(--pointer-border);\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n transform: translate(-50%, -50%);\n z-index: 10;\n }\n\n /* Hue Slider Styles */\n .hueSlider,\n .hue-slider {\n @apply relative flex items-center overflow-hidden;\n height: 16px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .hueTrack,\n .hue-track {\n @apply relative h-full w-full;\n border-radius: var(--radius-full);\n background: linear-gradient(\n to right,\n hsl(0, 100%, 50%),\n hsl(60, 100%, 50%),\n hsl(120, 100%, 50%),\n hsl(180, 100%, 50%),\n hsl(240, 100%, 50%),\n hsl(300, 100%, 50%),\n hsl(360, 100%, 50%)\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .hueThumb,\n .opacityThumb,\n .hue-thumb,\n .opacity-thumb {\n @apply absolute;\n border-radius: var(--radius-full);\n border: 2px solid var(--thumb-border);\n top: 50%;\n background: var(--thumb-background);\n pointer-events: none;\n }\n\n .hueThumb,\n .hue-thumb {\n @apply h-3 w-3;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .hueSlider[data-focus-visible=\"true\"] .hueThumb,\n .hue-slider[data-focus-visible=\"true\"] .hue-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .hue-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .hue-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Opacity Slider Styles */\n .opacitySlider,\n .opacity-slider {\n @apply relative flex items-center overflow-hidden;\n height: 12px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .opacityTrack,\n .opacity-track {\n @apply relative h-full w-full overflow-hidden;\n border-radius: var(--radius-full);\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-dark),\n var(--checkerboard-dark) 10px,\n var(--checkerboard-light) 10px,\n var(--checkerboard-light) 20px\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .opacityThumb,\n .opacity-thumb {\n @apply h-2.5 w-2.5;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .opacitySlider[data-focus-visible=\"true\"] .opacityThumb,\n .opacity-slider[data-focus-visible=\"true\"] .opacity-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .opacity-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .opacity-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Recent Colors Styles */\n .recentColors,\n .recent-colors {\n @apply flex gap-2 overflow-x-auto pb-1;\n }\n\n .recentColorSwatch,\n .recent-color-swatch {\n @apply h-8 w-8 shrink-0 cursor-pointer p-0;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n background: none;\n outline: none;\n }\n\n .recentColorSwatch:hover,\n .recent-color-swatch:hover {\n transform: scale(1.1);\n box-shadow: 0 0 0 2px var(--ring-color);\n }\n\n .recentColorSwatch:active,\n .recent-color-swatch:active {\n transform: scale(0.95);\n }\n\n .recentColorSwatch:focus-visible,\n .recent-color-swatch:focus-visible {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n\n /* Preview Container - deprecated, use preview-swatch instead */\n .previewContainer,\n .preview-container {\n @apply flex justify-center py-2;\n }\n\n /* Preview Swatch - inline with input */\n .previewSwatch,\n .preview-swatch {\n @apply relative h-9 w-9 shrink-0 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n\n .previewSwatch::before,\n .preview-swatch::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 6px,\n var(--checkerboard-dark) 6px,\n var(--checkerboard-dark) 12px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .preview {\n @apply relative h-16 w-16 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n }\n\n .preview::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 10px,\n var(--checkerboard-dark) 10px,\n var(--checkerboard-dark) 20px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .previewSwatch::after,\n .preview-swatch::after,\n .preview::after {\n content: \"\";\n @apply absolute inset-0;\n background-color: var(--preview-color, transparent);\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 2;\n }\n}\n",
4071
+ "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
4072
  "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
4073
  "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
4074
  "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 +4077,12 @@ export const generatedStyles = {
4100
4077
  "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
4078
  "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
4079
  "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-base, 1px));\n\n @apply flex overflow-hidden;\n width: fit-content;\n flex-shrink: 0;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n\n border-radius: var(--radius);\n padding: var(--padding);\n }\n\n /* Orientations */\n .group.horizontal {\n @apply flex-row items-stretch;\n }\n\n .group.vertical {\n @apply flex-col;\n }\n\n /* Spacing */\n .group.none {\n --padding: 0;\n @apply gap-0;\n }\n\n .group.xs {\n --radius-basis: calc(var(--spacing) * 0.875);\n --padding: var(--radius-basis);\n @apply space-x-0.5;\n }\n\n .group.sm {\n --radius-basis: calc(var(--spacing) * 1.25);\n --padding: var(--radius-basis);\n @apply space-x-1;\n }\n\n /* Variants */\n .group.ghost {\n border: none;\n overflow: visible;\n @apply gap-1;\n }\n\n .item {\n @apply flex min-w-0 items-stretch;\n }\n\n .item.grow {\n flex: 1;\n }\n\n .group:not(.ghost) .item .group-item,\n .group:not(.ghost) .item .group-select-wrapper {\n border: none;\n }\n\n .group:not(.ghost) .group-input-wrapper {\n --input-border-color: transparent;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .group.none:not(.ghost) .item .group-item,\n .group.none:not(.ghost) .item .group-select-wrapper,\n .group.none:not(.ghost) .item .trigger {\n border-radius: 0;\n }\n\n .group.none:not(.ghost) .group-input-wrapper {\n --input-border-radius: 0;\n }\n\n .group.none:not(.ghost) .item .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n }\n\n .group.xs:not(.ghost) .item .group-item,\n .group.xs:not(.ghost) .item .trigger,\n .group.xs:not(.ghost) .group-select-wrapper .group-item,\n .group.xs:not(.ghost) .group-select-wrapper .trigger,\n .group.sm:not(.ghost) .item .group-item,\n .group.sm:not(.ghost) .item .trigger,\n .group.sm:not(.ghost) .group-select-wrapper .group-item,\n .group.sm:not(.ghost) .group-select-wrapper .trigger {\n border-radius: var(--inner-radius);\n }\n\n .group.xs:not(.ghost) .group-input-wrapper,\n .group.sm:not(.ghost) .group-input-wrapper {\n --input-border-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .group-item {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .group-item {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .item.divider {\n @apply flex items-stretch p-0;\n }\n\n .item.divider > [role=\"separator\"] {\n @apply h-full w-full;\n }\n\n .group.horizontal .item.divider {\n margin-top: calc(var(--padding) * -1);\n margin-bottom: calc(var(--padding) * -1);\n }\n\n .group.vertical .item.divider {\n margin-left: calc(var(--padding) * -1);\n margin-right: calc(var(--padding) * -1);\n }\n\n .group.ghost:not(.none) .item .group-item:not(.active) {\n border-radius: var(--inner-radius);\n border: var(--border-width-base) solid transparent;\n }\n\n /* ghost + none: flat children — no borders or radius */\n .group.ghost.none {\n @apply gap-0;\n }\n\n .group.ghost.none .item .group-item,\n .group.ghost.none .group-item.active {\n border: none;\n border-radius: 0;\n }\n\n .group.ghost.none .group-input-wrapper {\n --input-border-color: transparent;\n --input-border-radius: 0;\n }\n\n .group.ghost.none .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n border: none;\n border-radius: 0;\n }\n\n .group:not(.ghost) .item .group-item:focus-visible,\n .group:not(.ghost) .item .trigger:focus-visible,\n .group:not(.ghost) .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group.ghost .item .group-item:focus-visible,\n .group.ghost .item .trigger:focus-visible,\n .group.ghost .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group-input-wrapper {\n @apply flex h-full items-stretch;\n flex: 1;\n overflow: visible;\n }\n\n .group-input-wrapper input {\n height: 100%;\n }\n\n .item .group-item {\n @apply flex h-full;\n }\n\n .group.vertical .item .group-item {\n @apply w-full;\n }\n\n .group.xs .item button.group-item {\n padding: calc(var(--spacing) * 1.00) calc(var(--spacing) * 1.50);\n }\n\n .group.sm .item button.group-item {\n padding: calc(var(--spacing) * 1.50) calc(var(--spacing) * 2.00);\n }\n\n .group.none .item button.group-item {\n padding: calc(var(--spacing) * 2.00) calc(var(--spacing) * 2.50);\n }\n\n .group-select-wrapper {\n @apply flex items-stretch p-0;\n border: none;\n background-color: transparent;\n }\n\n .group.none:not(.ghost) .trigger {\n border-radius: 0;\n }\n\n .trigger {\n border: none;\n }\n\n .group-select-wrapper .select {\n @apply h-full w-full;\n }\n\n .group-item.active {\n @apply relative;\n }\n\n .group.ghost .group-item.active {\n border-radius: var(--inner-radius);\n }\n\n .group:not(.ghost) .group-item.active {\n background-color: var(--active-background);\n font-weight: 500;\n }\n}\n",
4104
- "input": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n\n flex: 1;\n min-width: 0;\n @apply py-2 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n color: var(--icon-color);\n pointer-events: none;\n }\n\n .prefix-icon {\n @apply relative;\n }\n\n .suffix-icon {\n @apply relative;\n }\n\n .container {\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--input-border-color, var(--border));\n border-radius: var(--input-border-radius, var(--radius-sm));\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-active] {\n border-color: var(--input-active-border-color, var(--ring-color));\n box-shadow: var(--input-active-box-shadow, 0 0 0 1px mix(var(--ring-color) 20%, transparent));\n }\n\n &[data-focus-visible] {\n @apply ring-0;\n border-color: var(--input-active-border-color, var(--ring-color));\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n &[data-error] {\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n\n &[data-variant=\"ghost\"] {\n background-color: transparent;\n border-color: transparent;\n &[data-active], &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n }\n\n .start-adornments,\n .end-adornments {\n @apply flex items-center gap-1;\n flex-shrink: 0;\n pointer-events: none;\n }\n\n .start-adornments {\n @apply pl-2;\n }\n\n .end-adornments {\n @apply pr-2;\n }\n\n .actions {\n @apply flex items-center gap-1;\n pointer-events: auto;\n }\n\n .action {\n @apply flex items-center justify-center p-2;\n border-radius: 0.25rem;\n color: var(--action-color);\n }\n\n .action:hover {\n background-color: var(--background-hover);\n color: var(--action-hover-color);\n }\n\n .number-controls {\n @apply flex w-6 flex-col;\n pointer-events: auto;\n }\n\n .number-controls.disabled {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .spin-button {\n @apply flex w-full flex-1 items-center justify-center p-0 cursor-pointer;\n flex: 1;\n background-color: transparent;\n border: none;\n color: var(--spin-color);\n transition: color 150ms ease-out, background-color 150ms ease-out;\n\n &:hover:not(:disabled) {\n background-color: var(--spin-hover-background);\n color: var(--spin-hover-color);\n }\n\n &:active:not(:disabled) {\n background-color: var(--spin-active-background);\n color: var(--spin-active-color);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n }\n}\n",
4080
+ "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",
4081
+ "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
4082
  "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
4083
  "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
4084
  "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 font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n cursor: default;\n user-select: none;\n outline: none;\n color: var(--item-foreground);\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .item,\n .sub-trigger {\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .item-indicator {\n @apply ml-auto flex h-4 w-4 shrink-0 items-center justify-center;\n color: var(--item-indicator-color);\n }\n\n .sub-trigger {\n @apply flex min-w-0 items-center gap-2;\n padding: var(--padding);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n cursor: default;\n user-select: none;\n outline: none;\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-state=\"open\"]:not([data-highlighted]) {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply ml-auto h-4 w-4 shrink-0;\n }\n\n .sub-content {\n @apply absolute min-w-40 max-w-80 overflow-hidden;\n z-index: 50000;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n\n &[data-state=\"open\"] {\n animation: var(--menu-animation, slide-in-from-top 0.15s var(--ease-snappy-pop));\n }\n\n &[data-state=\"closed\"] {\n animation: var(--menu-animation, slide-out-to-top 0.15s var(--ease-snappy-pop));\n }\n }\n\n .label {\n padding: var(--padding);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--label-foreground);\n\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .separator {\n @apply -mx-1 my-1 h-px;\n background-color: var(--separator-background);\n }\n\n .shortcut {\n margin-left: auto;\n font-size: var(--text-sm);\n letter-spacing: 0.1em;\n color: var(--shortcut-foreground);\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-to-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n}\n",
4085
+ "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
4086
  "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
4087
  "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
4088
  "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,34 +4090,34 @@ export const generatedStyles = {
4113
4090
  "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
4091
  "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
4092
  "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-inset=\"true\"] .content {\n padding-right: 16px;\n }\n\n .horizontal .content {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .horizontal[data-inset=\"true\"] .content {\n padding-bottom: 16px;\n }\n\n .vertical .content::-webkit-scrollbar,\n .horizontal .content::-webkit-scrollbar { display: none; }\n\n .track {\n @apply absolute;\n z-index: 10;\n }\n\n .track[data-hide=\"true\"] {\n transition-property: opacity;\n transition-duration: 200ms;\n }\n\n .vertical .track {\n right: 4px;\n top: var(--scroll-padding-y, 0);\n width: 12px;\n height: calc(100% - 2 * var(--scroll-padding-y, 0));\n background-color: var(--track-background);\n box-sizing: border-box;\n }\n\n .horizontal .track {\n bottom: 2px;\n left: 0;\n height: 12px;\n width: 100%;\n background-color: var(--track-background);\n }\n\n .thumb {\n position: absolute;\n border-radius: calc(var(--radius-xs) * 0.80);\n background-color: var(--thumb-background);\n transition-property: background-color, width, height;\n transition-duration: 150ms;\n }\n\n .thumb:hover { background-color: var(--thumb-hover-background); }\n\n .root[data-dragging=\"true\"] .thumb {\n background-color: var(--thumb-dragging-background);\n }\n\n .vertical .thumb {\n width: 6px;\n margin-left: 6px;\n transition-property: background-color, width, margin-left;\n transition-duration: 150ms;\n }\n\n .vertical .thumb:hover,\n .vertical[data-dragging=\"true\"] .thumb {\n width: 8px;\n margin-left: 4px;\n }\n\n .horizontal .thumb {\n height: 6px;\n margin-top: 6px;\n transition-property: background-color, height, margin-top;\n transition-duration: 150ms;\n }\n\n .horizontal .thumb:hover,\n .horizontal[data-dragging=\"true\"] .thumb {\n height: 8px;\n margin-top: 4px;\n }\n}\n",
4117
- "select": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n\n --padding-x: calc(var(--spacing) * 2.00);\n --padding-y: calc(var(--spacing) * 1.75);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--border-color);\n border-radius: var(--radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed]:not([data-disabled]) {\n background-color: var(--pressed-background);\n }\n\n &[aria-expanded=\"true\"] {\n background-color: var(--hover-background);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not(:disabled):hover .icon-section,\n &:not(:disabled):hover .value-section:not(:empty) {\n background-color: var(--hover-background);\n }\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 2px var(--focus-ring-background), 0 0 0 4px var(--ring-color);\n @apply outline-none;\n }\n\n :global(.group) &:focus-visible {\n @apply outline-none;\n }\n }\n\n button.trigger { @apply p-0; }\n\n .value-section {\n @apply flex items-center flex-1 min-w-0 gap-0.5;\n\n padding: var(--padding-y) var(--padding-x);\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n\n &:only-child {\n border-radius: var(--inner-radius);\n justify-content: center;\n }\n &:empty {\n flex: 0;\n padding: 0;\n min-width: auto;\n }\n }\n\n .icon-section {\n @apply flex items-center justify-center shrink-0;\n padding: var(--padding-y) var(--padding-x);\n border-radius: 0 var(--inner-radius) var(--inner-radius) 0;\n }\n\n .icon {\n @apply flex items-center justify-center w-4 h-4 opacity-70;\n }\n\n .trigger[aria-expanded=\"true\"] .icon,\n .trigger[data-open=\"true\"] .icon {\n transform: rotate(180deg);\n }\n\n .value {\n @apply flex items-center flex-1 min-w-0 gap-2 bg-transparent border-none p-0;\n cursor: inherit;\n }\n\n .value-icon {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n color: var(--foreground);\n }\n\n .value-text {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .content,\n .sub-content {\n --padding-x: calc(var(--spacing) * 1.5);\n --padding-y: var(--spacing);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n overflow: hidden;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n }\n\n .content-root,\n .sub-content-root {\n position: absolute;\n }\n\n .content {\n &[data-state=\"open\"][data-placement=\"bottom\"] { animation: slide-in-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"open\"][data-placement=\"top\"] { animation: slide-in-from-bottom 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"bottom\"] { animation: slide-out-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"top\"] { animation: slide-out-from-bottom 0.15s var(--ease-snappy-pop); }\n }\n\n .list {\n @apply space-y-1;\n }\n\n .item {\n --item-padding-x: var(--padding-x);\n --item-padding-y: calc(var(--padding-y) * 1.15);\n\n @apply flex items-center gap-2 outline-none cursor-default select-none;\n padding: var(--item-padding-y) var(--item-padding-x);\n\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n\n &[data-selected=\"true\"] {\n color: var(--item-foreground);\n }\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n }\n &[data-highlighted=\"true\"] {\n background-color: var(--item-background-hover);\n }\n }\n\n .item-content {\n @apply flex flex-col flex-1 min-w-0;\n }\n\n .item-text {\n @apply min-w-0 overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-description-color);\n @apply min-w-0 whitespace-normal break-words;\n }\n\n .item-icon, .item-indicator {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n }\n\n .item-icon { color: var(--item-icon-color); }\n .item-indicator { color: var(--item-indicator-color); margin-left: auto; }\n\n .item-with-description { @apply items-start py-2; }\n .item-icon-with-description, .item-indicator-with-description { @apply mt-0.5; }\n\n .separator {\n @apply my-1 -mx-1 h-px;\n background-color: var(--content-border); /* Reuses content border var */\n }\n\n .placeholder {\n color: var(--placeholder-color);\n }\n\n .icon-prefix {\n @apply inline-flex items-center shrink-0;\n }\n\n .select[data-mode=\"multiple\"] .item { gap: 0.5rem; }\n\n .search-trigger {\n @apply flex items-stretch relative bg-transparent cursor-text overflow-hidden;\n border-radius: var(--inner-radius);\n transition: box-shadow 150ms var(--ease-snappy-pop), border-color 150ms var(--ease-snappy-pop);\n\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n\n &:focus-within {\n @apply outline-none;\n box-shadow: 0 0 0 1px var(--search-focus-ring);\n z-index: 1;\n }\n }\n\n .search-value-section {\n @apply p-0;\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n }\n\n .input {\n padding: var(--padding-y) calc(var(--padding-x) * 1.50);\n padding-right: calc(var(--padding-x) * 2 + 1rem);\n @apply border-none rounded-none shadow-none bg-transparent;\n\n &[data-active], &[data-focus-visible] {\n @apply border-none shadow-none;\n }\n }\n\n .search-content-input {\n padding-inline: calc(var(--padding-x) * 1.50);\n @apply border-none rounded-none bg-transparent;\n }\n\n .search-icon-section {\n @apply absolute right-0 top-0 bottom-0 flex items-center justify-center bg-transparent pointer-events-none;\n padding-inline: var(--padding-x);\n }\n\n\n .search-wrapper {\n @apply overflow-hidden;\n border-bottom: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .content[data-placement=\"top\"] .search-wrapper {\n border-radius: 0;\n border-bottom: none;\n border-top: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .sub-trigger {\n --subtrigger-padding-x: var(--padding-x);\n --subtrigger-padding-y: var(--padding-y);\n\n @apply flex items-center gap-2 cursor-default select-none outline-none;\n padding: var(--subtrigger-padding-y) var(--subtrigger-padding-x);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--subtrigger-foreground);\n\n &[data-highlighted=\"true\"],\n &[data-state=\"open\"]:not([data-highlighted=\"true\"]) {\n background-color: var(--subtrigger-background-hover);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply shrink-0 ml-auto w-4 h-4 opacity-60;\n }\n\n .sub-content {\n min-width: 160px;\n max-width: 320px;\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-in-from-bottom { from { opacity: 0; translate: 0 2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-from-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n @keyframes slide-out-from-bottom { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 2px; } }\n}\n",
4118
- "slider": "@reference \"tailwindcss\";\n\n@layer components {\n .slider {\n --disabled-opacity: 0.6;\n\n @apply relative flex w-full items-center;\n touch-action: none;\n user-select: none;\n }\n\n .slider[data-size=\"sm\"] { @apply h-6; }\n .slider[data-size=\"md\"] { @apply h-8; }\n .slider[data-size=\"lg\"] { @apply h-10; }\n\n .slider[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .track {\n --track-height-sm: 0.25rem;\n --track-height-md: 0.375rem;\n --track-height-lg: 0.5rem;\n\n @apply relative flex grow items-center;\n flex-grow: 1;\n overflow: visible;\n border-radius: var(--radius-xs);\n background-color: var(--slider-track-background);\n }\n\n .slider[data-size=\"sm\"] .track { height: var(--track-height-sm); }\n .slider[data-size=\"md\"] .track { height: var(--track-height-md); }\n .slider[data-size=\"lg\"] .track { height: var(--track-height-lg); }\n\n .range {\n @apply absolute h-full;\n background-color: var(--slider-range-background-default);\n transition: background-color 200ms var(--ease-snappy-pop);\n border-radius: var(--radius-xs);\n }\n\n .slider[data-disabled] .range { background-color: var(--slider-range-background-disabled); }\n\n .thumb {\n --thumb-size-sm: 0.75rem;\n --thumb-size-md: 1rem;\n --thumb-size-lg: 1.25rem;\n\n @apply absolute block;\n background-color: var(--slider-thumb-background-default);\n border-radius: 9999px;\n cursor: grab;\n outline: none;\n top: 50%;\n transform: translate(-50%, -50%);\n }\n\n .slider[data-size=\"sm\"] .thumb {\n width: var(--thumb-size-sm);\n height: var(--thumb-size-sm);\n }\n\n .slider[data-size=\"md\"] .thumb {\n width: var(--thumb-size-md);\n height: var(--thumb-size-md);\n }\n\n .slider[data-size=\"lg\"] .thumb {\n width: var(--thumb-size-lg);\n height: var(--thumb-size-lg);\n }\n\n .slider[data-disabled] .thumb {\n background-color: var(--slider-thumb-background-disabled);\n cursor: not-allowed;\n }\n\n .thumb[data-focus-visible] {\n background-color: var(--slider-thumb-background-focus);\n box-shadow: 0 0 0 3px var(--slider-thumb-ring);\n }\n\n .thumb[data-dragging] {\n cursor: grabbing;\n transform: translate(-50%, -50%) scale(1.1);\n }\n}\n",
4093
+ "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",
4094
+ "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",
4095
+ "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
4096
  "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 --indicator-padding: 2px;\n\n @apply absolute;\n background-color: var(--indicator-background);\n border-radius: var(--radius-xs);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--trigger-color);\n outline: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]) {\n &:hover {\n color: var(--trigger-hover-color);\n }\n\n &:active {\n color: var(--trigger-active-color);\n }\n }\n\n &[data-selected=\"true\"] {\n color: var(--trigger-selected-color);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n .list & {\n background-color: var(--trigger-selected-background);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--trigger-underline-color);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--trigger-underline-color);\n }\n }\n\n &[data-focus-visible] {\n background: var(--trigger-focus-background);\n outline: none;\n }\n\n &[data-disabled=\"true\"] {\n --disabled-opacity: 0.5;\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom: none;\n border-left: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n border-left-color: var(--trigger-underline-color);\n border-bottom: none;\n }\n }\n\n .trigger-icon {\n @apply flex h-4 w-4 shrink-0 items-center justify-center;\n }\n\n .content {\n @apply w-full p-0 outline-none;\n flex: 1;\n padding-top: 1rem;\n\n &[data-orientation=\"vertical\"] {\n flex: 1;\n width: 100%;\n }\n\n &:focus-visible {\n outline: 2px solid var(--content-outline-color);\n outline-offset: 2px;\n }\n }\n\n @media (max-width: 640px) {\n .list {\n padding: 0.125rem;\n\n &[data-variant=\"underline\"] {\n padding: 0;\n }\n }\n\n .trigger {\n @apply px-1 py-1;\n font-size: var(--text-sm);\n\n .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
4097
+ "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 height: 100%;\n background-color: transparent;\n border: none;\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
4098
  "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
4099
  "toast": "@reference \"tailwindcss\";\n\n@layer components {\n .toast {\n @apply flex w-full max-w-[28rem] items-start gap-3 px-4 py-2.5 select-none;\n background: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow);\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n touch-action: pan-y;\n }\n\n .icon {\n @apply mr-4 mt-2 h-5 w-5 shrink-0;\n color: var(--icon-color);\n }\n\n .content {\n @apply min-w-0 flex-1;\n }\n\n .title {\n @apply m-0;\n font-weight: var(--font-weight-semibold);\n font-size: var(--text-sm);\n line-height: var(--leading-tight);\n color: var(--title-color);\n }\n\n .description {\n @apply mt-1 mb-0;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n color: var(--description-color);\n }\n\n .close {\n @apply flex shrink-0 items-center justify-center p-2 cursor-pointer;\n background: transparent;\n border: none;\n border-radius: var(--radius-sm);\n color: var(--close-color);\n opacity: 0.6;\n transition: opacity 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n\n @media (hover: hover) {\n &:hover {\n opacity: 1;\n background: var(--close-hover-background);\n }\n }\n }\n}\n",
4123
- "tooltip": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n\n .frame[data-hint] {\n @apply pr-1;\n }\n}\n"
4100
+ "tooltip": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n display: contents;\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
4101
  };
4125
4102
  export const generatedSourceCode = {
4126
4103
  "anchor": {
4127
- "tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Popover } from \"@/components/Popover\";\nimport css from \"./Anchor.module.css\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface AnchorStyleSlots {\n root?: StyleValue;\n underline?: StyleValue;\n preview?: StyleValue;\n}\n\ntype AnchorStylesProp = StylesProp<AnchorStyleSlots>;\n\nconst resolveAnchorBaseStyles = createStylesResolver(['root', 'underline', 'preview'] as const);\n\nconst DASHED_DIMENSIONS = {\n sm: { thickness: 1, dashLength: 8, gapLength: 4 },\n md: { thickness: 2, dashLength: 8, gapLength: 4 },\n lg: { thickness: 4, dashLength: 10, gapLength: 6 },\n} as const;\n\nconst DOTTED_DIMENSIONS = {\n sm: { thickness: 1, radius: 0.5, spacing: 6 },\n md: { thickness: 2, radius: 1, spacing: 8 },\n lg: { thickness: 4, radius: 2, spacing: 12 },\n} as const;\n\nfunction getPath(orientation: Orientation, size: Size): string {\n const { thickness, dashLength, gapLength } = DASHED_DIMENSIONS[size];\n const totalLength = dashLength + gapLength;\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${totalLength}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${dashLength}' height='${thickness}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${totalLength}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${thickness}' height='${dashLength}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\nfunction getDottedMaskSvg(orientation: Orientation, size: Size): string {\n const { thickness, radius, spacing } = DOTTED_DIMENSIONS[size];\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${spacing}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${spacing}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\n// --- Sub-components ---\n\nexport interface AnchorPreviewProps\n extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nconst AnchorPreview = React.forwardRef<HTMLSpanElement, AnchorPreviewProps>(\n ({ children }, ref) => {\n return <span ref={ref as React.Ref<HTMLSpanElement>} style={{ display: \"none\" }}>{children}</span>;\n },\n);\nAnchorPreview.displayName = \"Anchor.Preview\";\n\ninterface AnchorUnderlineProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controls the line style of the underline */\n variant?: \"solid\" | \"dashed\" | \"dotted\";\n}\n\nconst AnchorUnderline = React.forwardRef<HTMLDivElement, AnchorUnderlineProps>(\n ({ className, variant = \"solid\", style, ...props }, ref) => {\n const getMaskStyles = (): React.CSSProperties => {\n if (variant === \"solid\") return {}\n\n const orientation = \"horizontal\";\n const size = \"sm\";\n\n const svgDataUri = variant === \"dashed\" ? getPath(orientation, size) : getDottedMaskSvg(orientation, size);\n const maskRepeat = \"repeat-x\";\n const encodedSvg = `url(\"data:image/svg+xml,${svgDataUri}\")`;\n\n return {\n WebkitMaskImage: encodedSvg,\n maskImage: encodedSvg,\n WebkitMaskRepeat: maskRepeat,\n maskRepeat: maskRepeat,\n } as React.CSSProperties;\n };\n\n return (\n <span\n ref={ref}\n className={cn(css.underline, className)}\n style={{ ...getMaskStyles(), ...style }}\n {...props}\n />\n );\n }\n);\nAnchorUnderline.displayName = \"Anchor.Underline\";\n\n// --- Main Anchor Component ---\n\nexport interface AnchorProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n children?: React.ReactNode;\n /** Additional CSS class for the anchor element */\n className?: string;\n /** URL the anchor navigates to */\n href?: string;\n /** Browsing context for the link (e.g. \"_blank\") */\n target?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: AnchorStylesProp;\n}\n\nconst AnchorRoot = React.forwardRef<HTMLAnchorElement | HTMLSpanElement, AnchorProps>(\n ({ className, children, href, target = \"_blank\", styles, ...props }, ref) => {\n const [isOpen, setIsOpen] = React.useState(false);\n let previewContent: React.ReactNode = null;\n let hasUnderline = false;\n\n const childrenArray = React.Children.toArray(children);\n const resolved = resolveAnchorBaseStyles(styles);\n\n let filteredChildren: React.ReactNode[] = [];\n\n // Extract preview content and filter it out from rendered children\n React.Children.forEach(childrenArray, (child) => {\n if (React.isValidElement(child)) {\n if (child.type === AnchorPreview) {\n previewContent = (child.props as any).children;\n // Don't add to filteredChildren\n } else if (child.type === AnchorUnderline) {\n hasUnderline = true;\n // Clone AnchorUnderline to inject resolved.underline\n const underlineChild = child as React.ReactElement<AnchorUnderlineProps>;\n filteredChildren.push(React.cloneElement(underlineChild, {\n className: cn(underlineChild.props.className, resolved.underline),\n }));\n } else {\n filteredChildren.push(child);\n }\n } else {\n filteredChildren.push(child);\n }\n });\n\n // Inject default underline if none provided\n if (!hasUnderline) {\n filteredChildren.push(<AnchorUnderline key=\"__default_underline\" className={resolved.underline} />);\n }\n\n const triggerElement = href ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n className={cn('anchor', 'trigger', css.trigger, resolved.root)}\n >\n {filteredChildren}\n </a>\n ) : (\n <span ref={ref as React.Ref<HTMLSpanElement>} className={cn('anchor', 'trigger', css.trigger, resolved.root)}>{filteredChildren}</span>\n );\n\n // If no preview content, render trigger directly without popover\n if (!previewContent) {\n return triggerElement;\n }\n\n return (\n <Popover\n content={previewContent}\n isOpen={isOpen}\n onOpenChange={setIsOpen}\n position=\"bottom\"\n className={cn('preview', css.preview, className, resolved.preview)}\n {...props}\n >\n {triggerElement}\n </Popover>\n );\n },\n);\nAnchorRoot.displayName = \"Anchor\";\n\n// Compound component with attached sub-components\nconst Anchor = React.forwardRef<HTMLDivElement, AnchorProps & { Preview: typeof AnchorPreview; Underline: typeof AnchorUnderline }>((props, ref) => {\n return <AnchorRoot ref={ref} {...props} />;\n}) as React.ForwardRefExoticComponent<AnchorProps & React.RefAttributes<HTMLDivElement>> & { Preview: typeof AnchorPreview; Underline: typeof AnchorUnderline };\n\nAnchor.displayName = \"Anchor\";\nAnchor.Preview = AnchorPreview;\nAnchor.Underline = AnchorUnderline;\n\nexport { Anchor };\n",
4128
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n @apply inline;\n }\n\n .preview {\n @apply inline;\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover);\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-ring);\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background);\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n}\n",
4104
+ "tsx": "import * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Tooltip } from \"@/components/Tooltip\";\nimport css from \"./Anchor.module.css\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface AnchorStyleSlots {\n root?: StyleValue;\n underline?: StyleValue;\n preview?: StyleValue;\n}\n\ntype AnchorStylesProp = StylesProp<AnchorStyleSlots>;\n\nconst resolveAnchorBaseStyles = createStylesResolver(['root', 'underline', 'preview'] as const);\nconst ANCHOR_PREVIEW_DISPLAY_NAME = \"Anchor.Preview\";\nconst ANCHOR_UNDERLINE_DISPLAY_NAME = \"Anchor.Underline\";\n\nconst DASHED_DIMENSIONS = {\n sm: { thickness: 1, dashLength: 8, gapLength: 4 },\n md: { thickness: 2, dashLength: 8, gapLength: 4 },\n lg: { thickness: 4, dashLength: 10, gapLength: 6 },\n} as const;\n\nconst DOTTED_DIMENSIONS = {\n sm: { thickness: 1, radius: 0.5, spacing: 6 },\n md: { thickness: 2, radius: 1, spacing: 8 },\n lg: { thickness: 4, radius: 2, spacing: 12 },\n} as const;\n\nfunction getPath(orientation: Orientation, size: Size): string {\n const { thickness, dashLength, gapLength } = DASHED_DIMENSIONS[size];\n const totalLength = dashLength + gapLength;\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${totalLength}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${dashLength}' height='${thickness}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${totalLength}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${thickness}' height='${dashLength}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\nfunction getDottedMaskSvg(orientation: Orientation, size: Size): string {\n const { thickness, radius, spacing } = DOTTED_DIMENSIONS[size];\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${spacing}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${spacing}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\ntype CompoundComponentType = {\n displayName?: string;\n name?: string;\n render?: {\n displayName?: string;\n name?: string;\n };\n};\n\nfunction matchesCompoundComponent(\n childType: React.JSXElementConstructor<any> | string | undefined,\n component: React.JSXElementConstructor<any>,\n displayName: string,\n): boolean {\n if (!childType) {\n return false;\n }\n\n if (childType === component) {\n return true;\n }\n\n if (typeof childType === \"string\") {\n return false;\n }\n\n const componentType = childType as CompoundComponentType;\n\n return (\n componentType.displayName === displayName ||\n componentType.name === displayName ||\n componentType.render?.displayName === displayName ||\n componentType.render?.name === displayName\n );\n}\n\n// --- Sub-components ---\n\nexport interface AnchorPreviewProps\n extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nexport function AnchorPreview({ children }: AnchorPreviewProps) {\n return null;\n}\nAnchorPreview.displayName = ANCHOR_PREVIEW_DISPLAY_NAME;\n\ninterface AnchorUnderlineProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controls the line style of the underline */\n variant?: \"solid\" | \"dashed\" | \"dotted\";\n}\n\nconst AnchorUnderline = React.forwardRef<HTMLDivElement, AnchorUnderlineProps>(\n ({ className, variant = \"solid\", style, ...props }, ref) => {\n const getMaskStyles = (): React.CSSProperties => {\n if (variant === \"solid\") return {}\n\n const orientation = \"horizontal\";\n const size = \"sm\";\n\n const svgDataUri = variant === \"dashed\" ? getPath(orientation, size) : getDottedMaskSvg(orientation, size);\n const maskRepeat = \"repeat-x\";\n const encodedSvg = `url(\"data:image/svg+xml,${svgDataUri}\")`;\n\n return {\n WebkitMaskImage: encodedSvg,\n maskImage: encodedSvg,\n WebkitMaskRepeat: maskRepeat,\n maskRepeat: maskRepeat,\n } as React.CSSProperties;\n };\n\n return (\n <span\n ref={ref}\n className={cn(css.underline, className)}\n style={{ ...getMaskStyles(), ...style }}\n {...props}\n />\n );\n }\n);\nAnchorUnderline.displayName = ANCHOR_UNDERLINE_DISPLAY_NAME;\n\n// --- Main Anchor Component ---\n\nexport interface AnchorProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"onChange\"> {\n children?: React.ReactNode;\n /** Additional CSS class for the anchor element */\n className?: string;\n /** URL the anchor navigates to */\n href?: string;\n /** Browsing context for the link (e.g. \"_blank\") */\n target?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: AnchorStylesProp;\n /** Preview content to show in a tooltip on hover. Use this in server components instead of <Anchor.Preview>. */\n preview?: React.ReactNode;\n}\n\nconst AnchorRoot = React.forwardRef<HTMLAnchorElement | HTMLSpanElement, AnchorProps>(\n ({ className, children, href, target = \"_blank\", styles, preview: previewProp, ...props }, ref) => {\n let previewContent: React.ReactNode = previewProp ?? null;\n let hasUnderline = false;\n\n const childrenArray = React.Children.toArray(children);\n const resolved = resolveAnchorBaseStyles(styles);\n\n let filteredChildren: React.ReactNode[] = [];\n\n // Extract preview content and filter it out from rendered children\n React.Children.forEach(childrenArray, (child) => {\n if (React.isValidElement(child)) {\n if (matchesCompoundComponent(child.type, AnchorPreview, ANCHOR_PREVIEW_DISPLAY_NAME)) {\n if (!previewProp) previewContent = (child.props as any).children;\n // Don't add to filteredChildren\n } else if (matchesCompoundComponent(child.type, AnchorUnderline, ANCHOR_UNDERLINE_DISPLAY_NAME)) {\n hasUnderline = true;\n // Clone AnchorUnderline to inject resolved.underline\n const underlineChild = child as React.ReactElement<AnchorUnderlineProps>;\n filteredChildren.push(React.cloneElement(underlineChild, {\n className: cn(underlineChild.props.className, resolved.underline),\n }));\n } else {\n filteredChildren.push(child);\n }\n } else {\n filteredChildren.push(child);\n }\n });\n\n // Inject default underline if none provided\n if (!hasUnderline) {\n filteredChildren.push(<AnchorUnderline key=\"__default_underline\" className={resolved.underline} />);\n }\n\n const triggerElement = href ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n className={cn(\"anchor\", \"trigger\", css.trigger, className, resolved.root)}\n {...props}\n >\n {filteredChildren}\n </a>\n ) : (\n <span\n ref={ref as React.Ref<HTMLSpanElement>}\n className={cn(\"anchor\", \"trigger\", css.trigger, className, resolved.root)}\n {...props}\n >\n {filteredChildren}\n </span>\n );\n\n // If no preview content, render trigger directly without a tooltip wrapper.\n if (!previewContent) {\n return triggerElement;\n }\n\n return (\n <Tooltip\n content={previewContent}\n showArrow\n position=\"top\"\n className={cn(\"preview\", css.preview)}\n styles={{ content: resolved.preview }}\n >\n {triggerElement}\n </Tooltip>\n );\n },\n);\nAnchorRoot.displayName = \"Anchor\";\n\n// Compound component with attached sub-components\nconst Anchor = Object.assign(AnchorRoot, {\n Preview: AnchorPreview,\n Underline: AnchorUnderline,\n});\n\nexport { Anchor };\n",
4105
+ "css": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: 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",
4129
4106
  "cssTypes": "export const anchor: string;\nexport const preview: string;\nexport const trigger: string;\nexport const underline: string;"
4130
4107
  },
4131
4108
  "badge": {
4132
4109
  "tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Badge.module.css\";\n\nimport { X } from \"lucide-react\";\n\ntype BadgeSize = \"sm\" | \"md\" | \"lg\";\n\ninterface BadgeStyleSlots {\n root?: StyleValue;\n icon?: StyleValue;\n dismiss?: StyleValue;\n}\n\ntype BadgeStylesProp = StylesProp<BadgeStyleSlots>;\n\nexport interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Visual color style of the badge */\n variant?: string;\n /** Size of the badge */\n size?: BadgeSize;\n /** Icon element displayed before the badge label */\n icon?: React.ReactNode;\n /** Whether to show a dismiss button */\n dismissible?: boolean;\n /** Called when the dismiss button is clicked */\n onDismiss?: () => void;\n /** Whether to render with a fully rounded pill shape */\n pill?: boolean;\n /** Numeric count to display; replaces children when provided */\n count?: number;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: BadgeStylesProp;\n}\n\nconst sizeMap = {\n sm: css[\"sm\"],\n md: css[\"md\"],\n lg: css[\"lg\"],\n} as const;\n\ninterface DismissButtonProps {\n onDismiss?: () => void;\n size: BadgeSize;\n className?: StyleValue;\n}\n\nfunction DismissButton({ onDismiss, size, className }: DismissButtonProps) {\n const buttonRef = React.useRef<HTMLDivElement>(null);\n\n const { buttonProps, isPressed } = useButton(\n {\n \"aria-label\": \"Dismiss\",\n onPress: onDismiss,\n },\n buttonRef\n );\n\n const { focusProps, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({});\n\n return (\n <div\n {...mergeProps(buttonProps, focusProps, hoverProps)}\n ref={buttonRef}\n role=\"button\"\n tabIndex={0}\n className={cn(css.dismiss, className)}\n data-pressed={isPressed || undefined}\n data-hovered={isHovered || undefined}\n data-focus-visible={isFocusVisible || undefined}\n >\n <X size={14} />\n </div>\n );\n}\n\nconst resolveBadgeBaseStyles = createStylesResolver(['root', 'icon', 'dismiss'] as const);\n\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n (\n {\n variant = \"default\",\n size = \"sm\",\n icon,\n dismissible = false,\n onDismiss,\n pill = false,\n count,\n children,\n className,\n styles,\n ...props\n },\n ref\n ) => {\n const resolved = resolveBadgeBaseStyles(styles);\n return (\n <span\n ref={ref}\n className={cn(\n \"badge\",\n variant,\n size,\n css.badge,\n sizeMap[size],\n pill && css.pill,\n dismissible && css.dismissible,\n className,\n resolved.root\n )}\n data-variant={variant}\n data-size={size}\n data-pill={pill ? \"true\" : undefined}\n data-dismissible={dismissible || undefined}\n {...props}\n >\n {icon && (\n <span className={cn(css.icon, resolved.icon)} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n {count !== undefined ? count : children}\n {dismissible && <DismissButton onDismiss={onDismiss} size={size} className={resolved.dismiss} />}\n </span>\n );\n }\n);\n\nBadge.displayName = \"Badge\";\n\nexport { Badge };\n",
4133
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2 px-3 py-0.5;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--background-border);\n border-radius: var(--radius-sm);\n }\n\n .badge.sm {\n @apply px-1.5 py-px;\n gap: 0.25rem;\n font-size: var(--text-sm);\n }\n\n .badge.dismissible {\n @apply pr-0.5;\n }\n\n .badge.md {\n @apply px-3.5 py-1;\n font-size: var(--text-sm);\n }\n\n .badge.lg {\n @apply px-4 py-2.5;\n font-size: var(--text-sm);\n }\n\n .pill { border-radius: 9999px; }\n\n .icon {\n @apply flex items-center shrink-0;\n }\n\n .dismiss {\n @apply ml-1 flex items-center justify-center p-1 cursor-pointer;\n border-radius: var(--radius-xs);\n background: transparent;\n border: none;\n color: var(--dismiss-color);\n transition: opacity 150ms var(--ease-snappy-pop), transform 150ms var(--ease-snappy-pop);\n outline: none;\n }\n\n .dismiss-button[data-hovered=\"true\"] {\n background: var(--dismiss-hover-background);\n }\n\n .dismiss-button[data-pressed=\"true\"] {\n background: var(--dismiss-pressed-background);\n transform: scale(0.95);\n }\n\n .dismiss-button[data-focus-visible=\"true\"] {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n}\n",
4110
+ "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
4111
  "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
4112
  },
4136
4113
  "banner": {
4137
4114
  "tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { useHover, mergeProps } from \"react-aria\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Banner.module.css\";\nimport { Info, CircleCheck, TriangleAlert, CircleAlert } from \"lucide-react\";\n\ntype BannerSize = \"sm\" | \"md\" | \"lg\";\n\ninterface BannerStyleSlots {\n root?: StyleValue;\n \"icon-container\"?: StyleValue;\n content?: StyleValue;\n dismiss?: StyleValue;\n}\n\ntype BannerStylesProp = StylesProp<BannerStyleSlots>;\n\nexport interface BannerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Variant class appended to the root element. Accepts any string. */\n variant?: string;\n /** Controls the padding and font size of the banner */\n size?: BannerSize;\n /** When true, renders a dismiss button that hides the banner on click */\n isDismissible?: boolean;\n /** Called when the dismiss button is clicked */\n onDismiss?: () => void;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: BannerStylesProp;\n}\n\ninterface BannerTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: StyleValue;\n}\n\ninterface BannerBodyProps extends React.HTMLAttributes<HTMLParagraphElement> {\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: StyleValue;\n}\n\nconst bannerIcons = {\n note: Info,\n info: Info,\n success: CircleCheck,\n warning: TriangleAlert,\n danger: CircleAlert,\n} as const;\n\ntype PresetBannerVariant = keyof typeof bannerIcons;\n\nfunction isPresetBannerVariant(variant: string): variant is PresetBannerVariant {\n return Object.prototype.hasOwnProperty.call(bannerIcons, variant);\n}\n\nconst getBannerIcon = (variant: string) => {\n const Icon = bannerIcons[isPresetBannerVariant(variant) ? variant : \"note\"];\n return <Icon className={css.icon} />;\n};\n\nconst sizeMap = {\n sm: css[\"sm\"],\n md: css[\"md\"],\n lg: css[\"lg\"],\n} as const;\n\nconst resolveBannerBaseStyles = createStylesResolver(['root', 'icon-container', 'content', 'dismiss'] as const);\n\n/** Heading text for the banner message */\nconst BannerTitle = React.forwardRef<HTMLHeadingElement, BannerTitleProps>(\n ({ className, styles, ...props }, ref) => (\n <h3\n ref={ref}\n className={cn(\"title\", css.title, className, styles)}\n {...props}\n />\n )\n);\n\nBannerTitle.displayName = \"Banner.Title\";\n\n/** Body text content of the banner */\nconst BannerBody = React.forwardRef<HTMLParagraphElement, BannerBodyProps>(\n ({ className, styles, ...props }, ref) => (\n <p\n ref={ref}\n className={cn(\"body\", css.body, className, styles)}\n {...props}\n />\n )\n);\n\nBannerBody.displayName = \"Banner.Body\";\n\n/** Full-width notification strip for system messages and alerts */\nconst BannerRoot = React.forwardRef<HTMLDivElement, BannerProps>(\n (\n {\n className,\n styles,\n variant = \"note\",\n size = \"md\",\n isDismissible = false,\n onDismiss,\n children,\n ...props\n },\n ref\n ) => {\n const [isVisible, setIsVisible] = React.useState(true);\n const { hoverProps, isHovered } = useHover({});\n\n const handleDismiss = () => {\n setIsVisible(false);\n onDismiss?.();\n };\n\n if (!isVisible) {\n return null;\n }\n\n const icon = getBannerIcon(variant);\n const resolved = resolveBannerBaseStyles(styles);\n\n return (\n <div\n {...mergeProps(hoverProps, props)}\n ref={ref}\n className={cn(\"banner\", variant, css.banner, sizeMap[size], className, resolved.root)}\n data-variant={variant}\n data-size={size}\n data-hovered={isHovered ? \"true\" : \"false\"}\n >\n {icon && <div className={cn(\"icon\", css.icon, resolved[\"icon-container\"])}>{icon}</div>}\n <div className={cn(\"content\", css.content, resolved.content)}>\n {children}\n </div>\n {isDismissible && (\n <button\n onClick={handleDismiss}\n className={cn(\"dismiss\", css.dismiss, resolved.dismiss)}\n aria-label=\"Dismiss banner\"\n type=\"button\"\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <line x1=\"4\" y1=\"4\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"4\" x2=\"4\" y2=\"12\" />\n </svg>\n </button>\n )}\n </div>\n );\n }\n);\n\nBannerRoot.displayName = \"Banner\";\n\ninterface BannerComponent extends React.ForwardRefExoticComponent<BannerProps & React.RefAttributes<HTMLDivElement>> {\n Title: typeof BannerTitle;\n Body: typeof BannerBody;\n}\n\nconst Banner = Object.assign(BannerRoot, {\n Title: BannerTitle,\n Body: BannerBody,\n}) as BannerComponent;\n\nexport { Banner };\n",
4138
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n line-height: var(--leading-normal);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out, border-color 0.15s ease-out;\n }\n\n .content {\n @apply flex flex-col gap-2;\n }\n\n .icon-container {\n @apply flex shrink-0 items-center justify-center self-start;\n }\n\n .icon {\n @apply mr-4 h-5 w-5;\n color: var(--icon-color, currentColor);\n }\n\n .dismiss {\n @apply flex h-8 w-8 shrink-0 items-center justify-center p-0 cursor-pointer;\n background-color: transparent;\n color: currentColor;\n border: none;\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n }\n\n .title {\n font-weight: var(--font-weight-semibold);\n font-size: inherit;\n line-height: var(--leading-tight);\n @apply my-0;\n }\n\n .body {\n font-weight: var(--font-weight-medium);\n font-size: inherit;\n line-height: var(--leading-normal);\n @apply my-0;\n }\n}\n\n\n.banner.sm {\n @apply px-3 py-2;\n font-size: var(--text-sm);\n}\n\n.banner.md {\n @apply px-4 py-3;\n font-size: var(--text-sm);\n}\n\n.banner.lg {\n @apply px-6 py-4;\n font-size: var(--text-sm);\n}\n",
4115
+ "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
4116
  "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
4117
  },
4141
4118
  "button": {
4142
- "tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Button.module.css\";\n\ntype ButtonSize = \"sm\" | \"md\" | \"lg\" | (string & {});\ntype ButtonIconSlots = {\n left?: React.ReactNode;\n right?: React.ReactNode;\n};\n\ninterface ButtonIconStyles {\n left?: StyleValue;\n right?: StyleValue;\n}\n\nexport interface ButtonStyleSlots {\n root?: StyleValue;\n icon?: StyleValue | ButtonIconStyles;\n}\n\nexport type ButtonStylesProp = StylesProp<ButtonStyleSlots>;\n\nconst resolveButtonBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight'] as const);\n\nfunction resolveButtonStyles(styles: ButtonStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveButtonBaseStyles(styles)\n const { root, icon } = styles;\n\n let iconLeft: StyleValue | undefined;\n let iconRight: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon;\n iconRight = icon;\n } else {\n iconLeft = icon.left;\n iconRight = icon.right;\n }\n }\n\n return resolveButtonBaseStyles({ root, iconLeft, iconRight });\n}\n\nexport interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"href\" | \"target\"> {\n /** Variant class appended to the root element. Accepts any string. */\n variant?: string;\n /** Size class appended to the root element. Accepts any string. */\n size?: ButtonSize;\n /** Disables interaction and applies disabled styling */\n isDisabled?: boolean;\n /** React Aria press handler — preferred over onClick for accessibility */\n onPress?: (e: { target: EventTarget | null }) => void;\n /** Icon slots rendered before (left) or after (right) the button label */\n icon?: React.ReactNode | ButtonIconSlots;\n /** Renders the button as an anchor element when provided */\n href?: string;\n /** Browsing context for the anchor variant (e.g. \"_blank\") */\n target?: React.HTMLAttributeAnchorTarget;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ButtonStylesProp;\n}\n\nfunction isButtonIconSlots(icon: ButtonProps[\"icon\"]): icon is ButtonIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon);\n}\n\nfunction resolveButtonIcon(icon: ButtonProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isButtonIconSlots(icon)) {\n return icon;\n }\n\n return { left: icon };\n}\n\nfunction resolveButtonIconSizeClass(size: ButtonSize | undefined) {\n if (!size) {\n return undefined;\n }\n\n return (css as unknown as Record<string, string | undefined>)[`icon-${size}`];\n}\n\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n ({ className, styles, variant = \"default\", size = \"md\", children, onClick, onPress, isDisabled, disabled, icon, href, target, rel, ...props }, ref) => {\n const buttonRef = React.useRef<HTMLButtonElement | HTMLAnchorElement>(null);\n const mergedRef = useMergedRef(ref, buttonRef);\n const isButtonDisabled = isDisabled ?? disabled ?? false;\n const [isPressed, setIsPressed] = React.useState(false);\n const isAnchor = !!href;\n\n const handlePress = React.useCallback((e: any) => {\n if (onPress) onPress({ target: e.target });\n if (onClick) onClick(e as unknown as React.MouseEvent<HTMLButtonElement>);\n }, [onPress, onClick]);\n\n const handleMouseDown = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n if (!isButtonDisabled) {\n setIsPressed(true);\n }\n props.onMouseDown?.(e as any);\n }, [isButtonDisabled, props]);\n\n const handleMouseUp = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsPressed(false);\n props.onMouseUp?.(e as any);\n }, [props]);\n\n const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsPressed(false);\n props.onMouseLeave?.(e as any);\n }, [props]);\n\n const { buttonProps } = useButton({\n isDisabled: isButtonDisabled,\n onPress: handlePress,\n }, buttonRef as React.RefObject<HTMLButtonElement>);\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing({ autoFocus: props.autoFocus });\n const { hoverProps, isHovered } = useHover({ isDisabled: isButtonDisabled });\n\n const resolved = resolveButtonStyles(styles);\n const resolvedIcon = resolveButtonIcon(icon);\n const iconSizeClassName = resolveButtonIconSizeClass(size);\n const buttonClassName = cn(\"button\", variant, size, css.button, className, resolved.root);\n\n if (isAnchor) {\n return (\n <a\n {...mergeProps(focusProps, hoverProps, props as any)}\n ref={mergedRef as unknown as React.RefObject<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={rel ?? (target === \"_blank\" ? \"noopener noreferrer\" : undefined)}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n className={buttonClassName}\n data-disabled={isButtonDisabled ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : \"false\"}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {resolvedIcon?.left && <span className={cn(iconSizeClassName, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(iconSizeClassName, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </a>\n );\n }\n\n return (\n <button\n {...mergeProps(buttonProps, focusProps, hoverProps, props)}\n ref={mergedRef as unknown as React.RefObject<HTMLButtonElement>}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n className={buttonClassName}\n data-disabled={isButtonDisabled ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : \"false\"}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {resolvedIcon?.left && <span className={cn(iconSizeClassName, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(iconSizeClassName, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </button>\n );\n }\n);\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return (value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n };\n}\n\nButton.displayName = \"Button\";\n\nexport { Button };\n",
4143
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:active:not(:disabled) {\n filter: brightness(0.8);\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 1.5px var(--focus-visible);\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n filter: grayscale(0.5);\n }\n }\n}\n",
4119
+ "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 = (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",
4120
+ "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
4121
  "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
4122
  },
4146
4123
  "card": {
@@ -4150,7 +4127,7 @@ export const generatedSourceCode = {
4150
4127
  },
4151
4128
  "checkbox": {
4152
4129
  "tsx": "\"use client\";\n\nimport React, { forwardRef, useState } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Checkbox.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface CheckboxStyleSlots {\n root?: StyleValue;\n label?: StyleValue;\n helperText?: StyleValue;\n}\n\ntype CheckboxStylesProp = StylesProp<CheckboxStyleSlots>;\n\nconst resolveCheckboxBaseStyles = createStylesResolver(['root', 'label', 'helperText'] as const);\n\nexport interface CheckboxProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\"> {\n /** Size of the checkbox */\n size?: Size;\n /** Label text or element displayed next to the checkbox */\n label?: React.ReactNode;\n /** Helper text shown below the checkbox */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to show an indeterminate (partial selection) state */\n isIndeterminate?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: CheckboxStylesProp;\n}\n\nconst sizeMap: Record<Size, string> = {\n sm: css[\"size-sm\"],\n md: css[\"size-md\"],\n lg: css[\"size-lg\"],\n};\n\nconst labelSizeMap: Record<Size, string> = {\n sm: css[\"label-sm\"],\n md: css[\"label-md\"],\n lg: css[\"label-lg\"],\n};\n\nexport const Checkbox = forwardRef<HTMLDivElement, CheckboxProps>(\n (\n {\n className,\n size = \"md\",\n label,\n helperText,\n helperTextError = false,\n id,\n disabled = false,\n checked,\n defaultChecked,\n onChange,\n isIndeterminate = false,\n styles,\n ...props\n },\n ref\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null);\n const [isFocused, setIsFocused] = useState(false);\n // Track pressed state for tactile feedback animation (data-pressed attribute)\n const [isPressed, setIsPressed] = useState(false);\n const [internalChecked, setInternalChecked] = useState(() =>\n checked !== undefined ? checked : (defaultChecked ?? false)\n );\n\n const handleFocus = () => setIsFocused(true);\n const handleBlur = () => setIsFocused(false);\n\n // React Aria press state handlers for tactile scale animation (mouse)\n const handleMouseDown = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n if (!disabled) {\n setIsPressed(true);\n }\n props.onMouseDown?.(e);\n }, [disabled, props]);\n\n const handleMouseUp = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n setIsPressed(false);\n props.onMouseUp?.(e);\n }, [props]);\n\n const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n setIsPressed(false);\n props.onMouseLeave?.(e);\n }, [props]);\n\n // React Aria press state handlers for keyboard interactions (Space/Enter)\n const handleKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {\n if (!disabled && (e.key === \" \" || e.key === \"Enter\")) {\n setIsPressed(true);\n }\n props.onKeyDown?.(e);\n }, [disabled, props]);\n\n const handleKeyUp = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n setIsPressed(false);\n }\n props.onKeyUp?.(e);\n }, [props]);\n\n React.useEffect(() => {\n if (checked !== undefined) {\n setInternalChecked(checked);\n }\n }, [checked]);\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n // Update internal state (needed for uncontrolled mode)\n setInternalChecked(e.target.checked);\n // Call parent handler if provided\n onChange?.(e);\n };\n\n // Filter out boolean props to avoid DOM attribute warnings\n const domProps = Object.fromEntries(\n Object.entries(props).filter(([, value]) => typeof value !== 'boolean')\n );\n\n // Determine if this is a controlled component\n const isControlled = checked !== undefined;\n const displayChecked = isControlled ? checked : internalChecked;\n\n const resolved = resolveCheckboxBaseStyles(styles);\n\n return (\n <div ref={ref} className={cn(\"checkbox-root\", css['checkbox-root'], resolved.root)}>\n <div className={cn((css as any)['checkbox-container'], sizeMap[size])}>\n <input\n ref={inputRef}\n type=\"checkbox\"\n id={id}\n disabled={disabled}\n {...(isControlled ? { checked } : { defaultChecked: internalChecked })}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n className={cn(\n 'checkbox',\n css.checkbox,\n isIndeterminate && css.indeterminate,\n className\n )}\n data-size={size}\n data-selected={displayChecked ? \"true\" : undefined}\n data-disabled={disabled ? \"true\" : undefined}\n data-indeterminate={isIndeterminate ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : undefined}\n {...domProps}\n />\n {displayChecked && !isIndeterminate && (\n <svg\n className={cn('checkbox-root', 'checkbox-checkmark', (css as any)['checkbox-checkmark'])}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n )}\n {isIndeterminate && (\n <svg\n className={cn('checkbox-root', 'checkbox-indeterminate', css['checkbox-indeterminate'])}\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n )}\n </div>\n {label && (\n <label\n htmlFor={id}\n className={cn(\n css.label,\n labelSizeMap[size],\n disabled && css[\"label-disabled\"],\n resolved.label\n )}\n >\n {label}\n </label>\n )}\n {helperText && (\n <p\n className={cn(\n 'checkbox-root',\n 'helper-text',\n css[\"helper-text\"],\n helperTextError && 'helper-text-error',\n helperTextError\n ? css[\"helper-text-error\"]\n : css[\"helper-text-normal\"],\n resolved.helperText\n )}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nCheckbox.displayName = \"Checkbox\";\n",
4153
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n /* Hidden input element positioned behind visual checkbox */\n .checkbox-input {\n @apply absolute inset-0 h-full w-full cursor-pointer;\n opacity: 0;\n }\n\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .checkbox-container {\n @apply relative inline-flex items-center justify-center;\n }\n\n /* Visual checkbox */\n .checkbox {\n --disabled-opacity: 0.6;\n\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-xs);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n /* Interactive States */\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:focus-visible {\n outline: 2px solid transparent;\n box-shadow: 0 0 0 3px var(--ring-color);\n }\n\n &[data-pressed=\"true\"] {\n transform: scale(0.92);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background);\n border-color: var(--border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n /* Checkmark and Indeterminate styles - combined */\n .checkbox-checkmark,\n .checkbox-indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--checkmark-color);\n pointer-events: none;\n }\n\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .label-sm {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-md {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-lg {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium)\n }\n\n .label-disabled {\n @apply opacity-60 cursor-not-allowed;\n }\n\n .helper-text {\n @apply text-sm ml-8;\n transition: color 200ms var(--ease-snappy-pop);\n }\n\n .helper-text-normal { color: inherit; }\n\n .helper-text-error { color: var(--error-color); }\n}\n",
4130
+ "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
4131
  "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
4132
  },
4156
4133
  "code": {
@@ -4159,12 +4136,12 @@ export const generatedSourceCode = {
4159
4136
  "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
4137
  },
4161
4138
  "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 --disabled-opacity: 0.5;\n @apply flex flex-col gap-4;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n width: 260px;\n }\n\n .color[data-disabled=\"true\"] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n\n .colorControls,\n .color-controls {\n @apply pb-3 px-3 space-y-3;\n }\n\n /* Input styles */\n .inputGroup,\n .input-group {\n @apply flex w-full;\n }\n\n .inputGroup > div:nth-child(1),\n .input-group > div:nth-child(1) {\n flex: 1;\n @apply min-w-0;\n }\n\n .colorInput,\n .color-input { @apply w-full; }\n\n .formatSelect,\n .format-select { @apply shrink-0; width: 85px; }\n\n .color[data-size=\"sm\"] .formatSelect,\n .color[data-size=\"sm\"] .format-select { width: 75px; }\n\n /* Canvas Styles */\n .canvasContainer,\n .canvas-container {\n @apply relative mx-auto mt-2 flex flex-col;\n width: 96%;\n cursor: crosshair;\n touch-action: none;\n min-height: 160px;\n }\n\n .canvasContainer[data-focus-visible=\"true\"],\n .canvas-container[data-focus-visible=\"true\"] {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .canvas {\n @apply relative w-full flex-1 overflow-hidden;\n flex: 1;\n border-radius: none;\n }\n\n .canvasGradientHue,\n .canvas-gradient-hue {\n @apply absolute inset-0 overflow-hidden;\n }\n\n .canvasGradientSaturation,\n .canvasGradientLightness,\n .canvas-gradient-saturation,\n .canvas-gradient-lightness {\n @apply absolute inset-0;\n border-radius: none;\n }\n\n .canvas-gradient-saturation {\n background: linear-gradient(to right, rgb(255, 255, 255), transparent);\n }\n\n .canvas-gradient-lightness {\n background: linear-gradient(to top, rgb(0, 0, 0), transparent);\n }\n\n .canvasPointer,\n .canvas-pointer {\n @apply absolute h-3 w-3;\n border-radius: var(--radius-full);\n border: 2px solid var(--pointer-border);\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);\n pointer-events: none;\n transform: translate(-50%, -50%);\n z-index: 10;\n }\n\n /* Hue Slider Styles */\n .hueSlider,\n .hue-slider {\n @apply relative flex items-center overflow-hidden;\n height: 16px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .hueTrack,\n .hue-track {\n @apply relative h-full w-full;\n border-radius: var(--radius-full);\n background: linear-gradient(\n to right,\n hsl(0, 100%, 50%),\n hsl(60, 100%, 50%),\n hsl(120, 100%, 50%),\n hsl(180, 100%, 50%),\n hsl(240, 100%, 50%),\n hsl(300, 100%, 50%),\n hsl(360, 100%, 50%)\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .hueThumb,\n .opacityThumb,\n .hue-thumb,\n .opacity-thumb {\n @apply absolute;\n border-radius: var(--radius-full);\n border: 2px solid var(--thumb-border);\n top: 50%;\n background: var(--thumb-background);\n pointer-events: none;\n }\n\n .hueThumb,\n .hue-thumb {\n @apply h-3 w-3;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .hueSlider[data-focus-visible=\"true\"] .hueThumb,\n .hue-slider[data-focus-visible=\"true\"] .hue-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .hue-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .hue-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Opacity Slider Styles */\n .opacitySlider,\n .opacity-slider {\n @apply relative flex items-center overflow-hidden;\n height: 12px;\n border-radius: var(--radius-full);\n cursor: pointer;\n touch-action: none;\n }\n\n .opacityTrack,\n .opacity-track {\n @apply relative h-full w-full overflow-hidden;\n border-radius: var(--radius-full);\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-dark),\n var(--checkerboard-dark) 10px,\n var(--checkerboard-light) 10px,\n var(--checkerboard-light) 20px\n );\n border: var(--border-width-base) solid var(--border);\n }\n\n .opacityThumb,\n .opacity-thumb {\n @apply h-2.5 w-2.5;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n transform: translateY(-50%) translateX(-50%);\n }\n\n .opacitySlider[data-focus-visible=\"true\"] .opacityThumb,\n .opacity-slider[data-focus-visible=\"true\"] .opacity-thumb {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n .opacity-thumb:hover {\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n }\n\n .opacity-thumb:active {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* Recent Colors Styles */\n .recentColors,\n .recent-colors {\n @apply flex gap-2 overflow-x-auto pb-1;\n }\n\n .recentColorSwatch,\n .recent-color-swatch {\n @apply h-8 w-8 shrink-0 cursor-pointer p-0;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n background: none;\n outline: none;\n }\n\n .recentColorSwatch:hover,\n .recent-color-swatch:hover {\n transform: scale(1.1);\n box-shadow: 0 0 0 2px var(--ring-color);\n }\n\n .recentColorSwatch:active,\n .recent-color-swatch:active {\n transform: scale(0.95);\n }\n\n .recentColorSwatch:focus-visible,\n .recent-color-swatch:focus-visible {\n outline: 2px solid var(--ring-color);\n outline-offset: 2px;\n }\n\n\n /* Preview Container - deprecated, use preview-swatch instead */\n .previewContainer,\n .preview-container {\n @apply flex justify-center py-2;\n }\n\n /* Preview Swatch - inline with input */\n .previewSwatch,\n .preview-swatch {\n @apply relative h-9 w-9 shrink-0 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n\n .previewSwatch::before,\n .preview-swatch::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 6px,\n var(--checkerboard-dark) 6px,\n var(--checkerboard-dark) 12px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .preview {\n @apply relative h-16 w-16 overflow-hidden;\n border-radius: var(--radius-sm);\n border: var(--border-width-base) solid var(--border);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n }\n\n .preview::before {\n content: \"\";\n @apply absolute inset-0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-light),\n var(--checkerboard-light) 10px,\n var(--checkerboard-dark) 10px,\n var(--checkerboard-dark) 20px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .previewSwatch::after,\n .preview-swatch::after,\n .preview::after {\n content: \"\";\n @apply absolute inset-0;\n background-color: var(--preview-color, transparent);\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 2;\n }\n}\n",
4139
+ "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",
4140
+ "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
4141
  "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
4142
  },
4166
4143
  "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",
4144
+ "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
4145
  "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
4146
  "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
4147
  },
@@ -4179,12 +4156,12 @@ export const generatedSourceCode = {
4179
4156
  "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
4157
  },
4181
4158
  "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: \"md\",\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\";\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 = \"md\", 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 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(), ...style }}\n role=\"separator\"\n aria-orientation={resolvedOrientation}\n {...props}\n />\n );\n },\n);\n\nDivider.displayName = \"Divider\";\n\nexport { Divider, dividerVariants };\n",
4159
+ "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
4160
  "css": "",
4184
4161
  "cssTypes": ""
4185
4162
  },
4186
4163
  "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 /** Additional CSS class for the trigger button */\n triggerClassName?: string;\n /** Additional CSS class for the content area */\n contentClassName?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: ExpandStylesProp;\n}\n\nconst ExpandRoot = React.forwardRef<HTMLDivElement, ExpandProps>(\n (\n {\n isExpanded,\n defaultExpanded = false,\n onExpandedChange,\n onChange,\n isDisabled = false,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const state = useToggleState({\n isSelected: isExpanded,\n defaultSelected: defaultExpanded,\n onChange: onExpandedChange || onChange,\n });\n\n const { title, triggerClassName, contentClassName, styles: expandStyles, ...divProps } = props;\n const resolved = resolveExpandBaseStyles(expandStyles);\n\n return (\n <ExpandContext.Provider value={{ state, isDisabled }}>\n <div\n ref={ref}\n className={cn(\"expand\", styles.expand, className, resolved.root)}\n data-disabled={isDisabled || undefined}\n {...divProps}\n >\n {children}\n </div>\n </ExpandContext.Provider>\n );\n },\n);\nExpandRoot.displayName = \"Expand\";\n\n// Compatibility wrapper to support both old API and new Compound API\nconst Expand = React.forwardRef<\n HTMLDivElement,\n ExpandProps & {\n Trigger?: typeof ExpandTrigger;\n Content?: typeof ExpandContent;\n Divider?: typeof ExpandDivider;\n Icon?: typeof ExpandIcon;\n }\n>((props, ref) => {\n const { title, children, triggerClassName, contentClassName, ...rootProps } =\n props;\n const resolved = resolveExpandBaseStyles(props.styles);\n\n // If title is provided, use the \"Preset\" structure (Backward Compatibility)\n if (title !== undefined) {\n const childrenArray = React.Children.toArray(children);\n const customDivider = childrenArray.find(\n (child) => React.isValidElement(child) && child.type === ExpandDivider,\n );\n const filteredChildren = childrenArray.filter(\n (child) => !(React.isValidElement(child) && child.type === ExpandDivider),\n );\n\n return (\n <ExpandRoot ref={ref} {...rootProps}>\n <ExpandTrigger className={cn(triggerClassName, resolved.trigger)}>{title}</ExpandTrigger>\n {customDivider || <ExpandDivider />}\n <ExpandContent className={cn(contentClassName, resolved.content)}>\n {filteredChildren}\n </ExpandContent>\n </ExpandRoot>\n );\n }\n\n // Otherwise, use Compound structure (children are expected to include Trigger/Content/Divider)\n return (\n <ExpandRoot ref={ref} {...rootProps}>\n {children}\n </ExpandRoot>\n );\n}) as React.ForwardRefExoticComponent<\n ExpandProps & React.RefAttributes<HTMLDivElement>\n> & {\n Trigger: typeof ExpandTrigger;\n Content: typeof ExpandContent;\n Divider: typeof ExpandDivider;\n Icon: typeof ExpandIcon;\n};\n\nExpand.displayName = \"Expand\";\n\n// Attach sub-components\nExpand.Trigger = ExpandTrigger;\nExpand.Content = ExpandContent;\nExpand.Divider = ExpandDivider;\nExpand.Icon = ExpandIcon;\n\nexport { Expand };\n",
4164
+ "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
4165
  "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
4166
  "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
4167
  },
@@ -4209,13 +4186,13 @@ export const generatedSourceCode = {
4209
4186
  "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
4187
  },
4211
4188
  "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 { SelectTriggerContext } from \"../Select/Select.Trigger\"\nimport styles from \"./Group.module.css\"\n\ntype Orientation = \"horizontal\" | \"vertical\"\ntype Spacing = \"none\" | \"xs\" | \"sm\"\ntype Variant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\"\n\nexport interface GroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Controls the axis that children are arranged along */\n orientation?: Orientation\n /** Controls the gap between group items */\n spacing?: Spacing\n /** Controls the shared visual style applied to group items */\n variant?: Variant\n /** Whether all items in the group are non-interactive */\n isDisabled?: boolean\n /** The currently active button value for toggle group behavior */\n value?: string\n /** Called when a button with a value prop is pressed */\n onChange?: (value: string) => void\n}\n\ninterface GroupContextValue {\n isInGroup: boolean\n groupVariant: Variant\n groupOrientation: Orientation\n groupSpacing: Spacing\n groupIsDisabled: boolean\n groupValue?: string\n groupOnChange?: (value: string) => void\n}\n\n// Context\nconst GroupContext = React.createContext<GroupContextValue | null>(null)\n\nfunction useGroupContext() {\n const context = React.useContext(GroupContext)\n if (!context) {\n throw new Error(\"Group sub-components must be used within Group\")\n }\n return context\n}\n\n// Variant and orientation maps\nconst orientationMap: Record<Orientation, string> = {\n horizontal: styles.horizontal,\n vertical: styles.vertical,\n}\n\nconst spacingMap: Record<Spacing, string> = {\n none: styles.none,\n xs: styles.xs,\n sm: styles.sm,\n}\n\nconst variantMap: Record<Variant, string | undefined> = {\n primary: undefined,\n secondary: undefined,\n outline: undefined,\n ghost: styles.ghost,\n}\n\n// Detect Divider elements by checking for separator role or orientation prop\nfunction isDivider(child: React.ReactNode): boolean {\n if (!React.isValidElement(child)) return false\n const props = (child.props || {}) as Record<string, unknown>\n return props.role === \"separator\" || \"orientation\" in props\n}\n\n// Root component\n/** Button group that groups related buttons together */\nconst GroupRoot = React.forwardRef<HTMLDivElement, GroupProps>(\n (\n {\n className,\n orientation = \"horizontal\",\n spacing = \"none\",\n variant = \"primary\",\n children,\n isDisabled = false,\n value,\n onChange,\n ...props\n },\n ref\n ) => {\n const isVertical = orientation === \"vertical\"\n\n const childrenArray = React.Children.toArray(children).filter(\n (child) => child !== null && child !== undefined\n )\n\n const contextValue: GroupContextValue = {\n isInGroup: true,\n groupVariant: variant,\n groupOrientation: orientation,\n groupSpacing: spacing,\n groupIsDisabled: isDisabled,\n groupValue: value,\n groupOnChange: onChange,\n }\n\n return (\n <GroupContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn(\n 'group',\n orientation,\n variant,\n styles.group,\n orientationMap[orientation],\n spacingMap[spacing],\n variantMap[variant],\n className\n )}\n role=\"group\"\n aria-disabled={isDisabled || undefined}\n {...props}\n >\n {childrenArray.map((child, index) => {\n const isFirst = index === 0\n const isLast = index === childrenArray.length - 1\n const isDividerChild = isDivider(child)\n \n // Extract layout-related classes from child to apply to the item wrapper\n const childProps = React.isValidElement(child) ? (child.props as any) : {}\n const childClassName = childProps.className || \"\"\n const shouldGrow = childClassName.includes('w-full') || childClassName.includes('flex-1')\n\n return (\n <div\n key={`item-${index}`}\n className={cn(\n 'item',\n styles.item,\n isVertical ? styles.vertical : styles.horizontal,\n isFirst && styles.first,\n isLast && styles.last,\n isDividerChild && styles.divider,\n shouldGrow && styles.grow\n )}\n >\n {child}\n </div>\n )\n })}\n </div>\n </GroupContext.Provider>\n )\n }\n)\nGroupRoot.displayName = \"Group\"\n\n// Group.Button component\ninterface GroupButtonProps extends ButtonProps {\n /** Whether this button is in an active/pressed state */\n active?: boolean\n /** Identifier used for toggle group behavior when Group has value/onChange */\n value?: string\n}\n\ntype GroupButtonIconSlots = {\n left?: React.ReactNode\n right?: React.ReactNode\n}\n\nfunction isGroupButtonIconSlots(icon: ButtonProps[\"icon\"]): icon is GroupButtonIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon)\n}\n\nfunction resolveGroupButtonIcon(icon: ButtonProps[\"icon\"]): GroupButtonIconSlots | undefined {\n if (!icon) return undefined\n if (isGroupButtonIconSlots(icon)) {\n return icon\n }\n\n return { left: icon as React.ReactNode, right: undefined }\n}\n\n/** Button styled to merge seamlessly with adjacent group items */\nconst GroupButton = React.forwardRef<HTMLButtonElement, GroupButtonProps>(\n ({ active, value, variant, className, onPress, ...restProps }, ref) => {\n const context = useGroupContext()\n const isInSelectTrigger = React.useContext(SelectTriggerContext)\n\n // Merge disabled state from group context\n const isDisabled = restProps.isDisabled ?? context.groupIsDisabled\n\n // Derive active and onPress from toggle group context when value is provided\n const isActive = value !== undefined && context.groupValue !== undefined\n ? value === context.groupValue\n : active\n const handlePress = value !== undefined && context.groupOnChange !== undefined\n ? () => context.groupOnChange!(value)\n : onPress\n\n if (isInSelectTrigger) {\n const icon = resolveGroupButtonIcon(restProps.icon)\n\n return (\n <span className={cn(styles['group-item'], className)}>\n {icon?.left}\n {restProps.children}\n {icon?.right}\n </span>\n )\n }\n\n let buttonVariant = variant\n if (variant === undefined) {\n if (context.groupVariant === \"ghost\") {\n buttonVariant = isActive ? \"default\" : \"ghost\"\n } else {\n buttonVariant = \"ghost\"\n }\n }\n\n const buttonProps = {\n ...restProps,\n onPress: handlePress,\n variant: buttonVariant,\n isDisabled,\n className: cn(\n styles['group-item'],\n isActive && styles.active,\n className\n ),\n }\n\n return <Button ref={ref} {...buttonProps} />\n }\n)\nGroupButton.displayName = \"Group.Button\"\n\n// Group.Input component\ninterface GroupInputProps extends InputProps { }\n\n/** Input field integrated into the button group */\nconst GroupInput = React.forwardRef<HTMLInputElement, GroupInputProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div className={cn(styles['group-input-wrapper'], className)}>\n <Input\n ref={ref}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInput.displayName = \"Group.Input\"\n\n// Group.InputWrapper component - preserves Input styling (for use with ghost variant)\ninterface GroupInputWrapperProps extends InputProps { }\n\n/** Input variant that preserves Input styling within the group */\nconst GroupInputWrapper = React.forwardRef<HTMLInputElement, GroupInputWrapperProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div className={cn(styles['group-input-wrapper'], className)}>\n <Input\n ref={ref}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInputWrapper.displayName = \"Group.InputWrapper\"\n\n// Group.Select component\ninterface GroupSelectProps extends SelectProps<any> { }\n\n/** Select dropdown integrated into the button group */\nconst GroupSelect = React.forwardRef<HTMLDivElement, GroupSelectProps>(\n ({ className, isDisabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const disabled = isDisabled ?? context.groupIsDisabled\n\n return (\n <Select\n ref={ref}\n {...props}\n isDisabled={disabled}\n className={cn('groupSelectWrapper', styles['group-select-wrapper'], className)}\n />\n )\n }\n)\nGroupSelect.displayName = \"Group.Select\"\n\n// Assemble compound component\nconst Group = Object.assign(GroupRoot, {\n Button: GroupButton,\n Input: GroupInput,\n InputWrapper: GroupInputWrapper,\n Select: GroupSelect,\n})\n\nexport { Group, GroupContext }\n",
4213
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --radius-basis: calc(var(--spacing) * 1.5);\n --padding: var(--radius-basis);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply flex overflow-hidden;\n width: fit-content;\n flex-shrink: 0;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n\n border-radius: var(--radius);\n padding: var(--padding);\n }\n\n /* Orientations */\n .group.horizontal {\n @apply flex-row items-stretch;\n }\n\n .group.vertical {\n @apply flex-col;\n }\n\n /* Spacing */\n .group.none {\n --padding: 0;\n @apply gap-0;\n }\n\n .group.xs {\n --radius-basis: calc(var(--spacing) * 0.875);\n --padding: var(--radius-basis);\n @apply space-x-0.5;\n }\n\n .group.sm {\n --radius-basis: calc(var(--spacing) * 1.25);\n --padding: var(--radius-basis);\n @apply space-x-1;\n }\n\n /* Variants */\n .group.ghost {\n border: none;\n overflow: visible;\n @apply gap-1;\n }\n\n .item {\n @apply flex min-w-0 items-stretch;\n }\n\n .item.grow {\n flex: 1;\n }\n\n .group:not(.ghost) .item .group-item,\n .group:not(.ghost) .item .group-select-wrapper {\n border: none;\n }\n\n .group:not(.ghost) .group-input-wrapper {\n --input-border-color: transparent;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .group.none:not(.ghost) .item .group-item,\n .group.none:not(.ghost) .item .group-select-wrapper,\n .group.none:not(.ghost) .item .trigger {\n border-radius: 0;\n }\n\n .group.none:not(.ghost) .group-input-wrapper {\n --input-border-radius: 0;\n }\n\n .group.none:not(.ghost) .item .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n }\n\n .group.xs:not(.ghost) .item .group-item,\n .group.xs:not(.ghost) .item .trigger,\n .group.xs:not(.ghost) .group-select-wrapper .group-item,\n .group.xs:not(.ghost) .group-select-wrapper .trigger,\n .group.sm:not(.ghost) .item .group-item,\n .group.sm:not(.ghost) .item .trigger,\n .group.sm:not(.ghost) .group-select-wrapper .group-item,\n .group.sm:not(.ghost) .group-select-wrapper .trigger {\n border-radius: var(--inner-radius);\n }\n\n .group.xs:not(.ghost) .group-input-wrapper,\n .group.sm:not(.ghost) .group-input-wrapper {\n --input-border-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .group-item {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .group-item {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-input-wrapper > * {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-input-wrapper > * {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child .group-select-wrapper .group-item {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child .group-select-wrapper .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-bottom-left-radius: var(--inner-radius);\n }\n\n .group.none.horizontal:not(.ghost) .item:last-child > .trigger {\n border-top-right-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:first-child > .trigger {\n border-top-left-radius: var(--inner-radius);\n border-top-right-radius: var(--inner-radius);\n }\n\n .group.none.vertical:not(.ghost) .item:last-child > .trigger {\n border-bottom-left-radius: var(--inner-radius);\n border-bottom-right-radius: var(--inner-radius);\n }\n\n .item.divider {\n @apply flex items-stretch p-0;\n }\n\n .item.divider > [role=\"separator\"] {\n @apply h-full w-full;\n }\n\n .group.horizontal .item.divider {\n margin-top: calc(var(--padding) * -1);\n margin-bottom: calc(var(--padding) * -1);\n }\n\n .group.vertical .item.divider {\n margin-left: calc(var(--padding) * -1);\n margin-right: calc(var(--padding) * -1);\n }\n\n .group.ghost:not(.none) .item .group-item:not(.active) {\n border-radius: var(--inner-radius);\n border: var(--border-width-base) solid transparent;\n }\n\n /* ghost + none: flat children — no borders or radius */\n .group.ghost.none {\n @apply gap-0;\n }\n\n .group.ghost.none .item .group-item,\n .group.ghost.none .group-item.active {\n border: none;\n border-radius: 0;\n }\n\n .group.ghost.none .group-input-wrapper {\n --input-border-color: transparent;\n --input-border-radius: 0;\n }\n\n .group.ghost.none .group-select-wrapper {\n --radius: 0;\n --inner-radius: 0;\n border: none;\n border-radius: 0;\n }\n\n .group:not(.ghost) .item .group-item:focus-visible,\n .group:not(.ghost) .item .trigger:focus-visible,\n .group:not(.ghost) .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group.ghost .item .group-item:focus-visible,\n .group.ghost .item .trigger:focus-visible,\n .group.ghost .group-input-wrapper > [data-focus-visible] {\n outline: none;\n box-shadow: 0 0 0 1px var(--focus-ring-color);\n position: relative;\n z-index: 1;\n }\n\n .group-input-wrapper {\n @apply flex h-full items-stretch;\n flex: 1;\n overflow: visible;\n }\n\n .group-input-wrapper input {\n height: 100%;\n }\n\n .item .group-item {\n @apply flex h-full;\n }\n\n .group.vertical .item .group-item {\n @apply w-full;\n }\n\n .group.xs .item button.group-item {\n padding: calc(var(--spacing) * 1.00) calc(var(--spacing) * 1.50);\n }\n\n .group.sm .item button.group-item {\n padding: calc(var(--spacing) * 1.50) calc(var(--spacing) * 2.00);\n }\n\n .group.none .item button.group-item {\n padding: calc(var(--spacing) * 2.00) calc(var(--spacing) * 2.50);\n }\n\n .group-select-wrapper {\n @apply flex items-stretch p-0;\n border: none;\n background-color: transparent;\n }\n\n .group.none:not(.ghost) .trigger {\n border-radius: 0;\n }\n\n .trigger {\n border: none;\n }\n\n .group-select-wrapper .select {\n @apply h-full w-full;\n }\n\n .group-item.active {\n @apply relative;\n }\n\n .group.ghost .group-item.active {\n border-radius: var(--inner-radius);\n }\n\n .group:not(.ghost) .group-item.active {\n background-color: var(--active-background);\n font-weight: 500;\n }\n}\n",
4189
+ "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",
4190
+ "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
4191
  "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
4192
  },
4216
4193
  "input": {
4217
4194
  "tsx": "\"use client\";\n\nimport React, { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { mergeProps, } from \"@react-aria/utils\";\n\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Tooltip } from \"@/components/Tooltip\";\nimport css from \"./Input.module.css\";\n\ntype Variant = \"default\" | \"ghost\";\n\nexport interface InputStyleSlots {\n root?: StyleValue;\n}\n\nexport type InputStylesProp = StylesProp<InputStyleSlots>;\n\nexport type InputAction = InputActionDef | React.ReactNode;\ntype InputIconSlots = {\n prefix?: React.ReactNode;\n suffix?: React.ReactNode;\n};\n\nexport type InputActionDef = {\n icon: React.ReactNode;\n title: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n};\n\ntype InputActionSlots = {\n left?: InputAction[];\n right?: InputAction[];\n};\n\nconst resolveInputStyles = createStylesResolver(['root'] as const);\n\nexport interface InputProps extends Omit<ComponentPropsWithoutRef<\"input\">, \"size\"> {\n /** Controls the visual style of the input */\n variant?: Variant;\n /** Whether the input is in an error state */\n error?: boolean;\n /** Icon displayed before the input value by default, or in named prefix/suffix slots */\n icon?: React.ReactNode | InputIconSlots;\n /** Inline actions rendered on the left or right side of the input. Passing an array keeps the existing right-side behavior. */\n actions?: InputAction[] | InputActionSlots;\n /** Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts. */\n hint?: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: InputStylesProp;\n}\n\nfunction isInputIconSlots(icon: InputProps[\"icon\"]): icon is InputIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && (\"prefix\" in icon || \"suffix\" in icon);\n}\n\nfunction resolveInputIcon(icon: InputProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isInputIconSlots(icon)) {\n return icon;\n }\n\n return { prefix: icon };\n}\n\nfunction isInputActionSlots(actions: InputProps[\"actions\"]): actions is InputActionSlots {\n return typeof actions === \"object\" && actions !== null && !Array.isArray(actions) && !React.isValidElement(actions) && (\"left\" in actions || \"right\" in actions);\n}\n\nfunction resolveInputActions(actions: InputProps[\"actions\"]): InputActionSlots {\n if (!actions) {\n return {};\n }\n\n if (isInputActionSlots(actions)) {\n return actions;\n }\n\n return { right: actions };\n}\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return React.useCallback((value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n }, refs);\n}\n\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n variant = \"default\",\n error = false,\n disabled,\n icon,\n actions,\n hint,\n type = \"text\",\n onFocus,\n onBlur,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const resolvedActions = resolveInputActions(actions);\n const resolvedIcon = resolveInputIcon(icon);\n const leftActions = resolvedActions.left ?? [];\n const rightActions = resolvedActions.right ?? [];\n const hasPrefix = !!resolvedIcon?.prefix;\n const hasSuffix = !!resolvedIcon?.suffix;\n const hasLeftActions = leftActions.length > 0;\n const hasRightActions = rightActions.length > 0;\n const hasHint = hint !== undefined && hint !== null;\n const hasStartAdornment = hasPrefix || hasLeftActions;\n const isNumberType = type === \"number\";\n const [isFocused, setIsFocused] = React.useState(false);\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const mergedRef = useMergedRef(ref, inputRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n onBlur?.(e);\n };\n\n const handleSpinClick = (direction: \"up\" | \"down\") => {\n if (!inputRef.current || disabled) return;\n\n const input = inputRef.current;\n\n if (direction === \"up\") {\n input.stepUp();\n } else {\n input.stepDown();\n }\n\n // Dispatch native input event to trigger React onChange handlers\n const event = new Event(\"input\", { bubbles: true });\n input.dispatchEvent(event);\n };\n\n const resolved = resolveInputStyles(stylesProp);\n const hasEndAdornment = hasSuffix || hasRightActions || hasHint || isNumberType;\n const inputPaddingStyle: React.CSSProperties = {\n ...(hasStartAdornment ? { paddingLeft: '8px' } : {}),\n ...(hasEndAdornment ? { paddingRight: '8px' } : {}),\n };\n\n const renderAction = (action: InputAction, index: number) => {\n const key = React.isValidElement(action) ? index : ((action as InputActionDef).title || index);\n\n return React.isValidElement(action) ? (\n <React.Fragment key={key}>{action}</React.Fragment>\n ) : (\n <Tooltip key={key} content={(action as InputActionDef).title} position=\"top\">\n <button\n type=\"button\"\n className={css.action}\n aria-label={(action as InputActionDef).title}\n onClick={(action as InputActionDef).onClick}\n >\n {(action as InputActionDef).icon}\n </button>\n </Tooltip>\n );\n };\n\n return (\n <div\n className={cn('input', css.container)}\n data-active={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n >\n {hasStartAdornment && (\n <div className={css['start-adornments']} data-start-adornments>\n {hasPrefix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], css['prefix-icon'])}>\n {resolvedIcon?.prefix}\n </div>\n )}\n {hasLeftActions && (\n <div className={css.actions} data-actions data-actions-position=\"left\">\n {leftActions.map(renderAction)}\n </div>\n )}\n </div>\n )}\n <input\n ref={mergedRef}\n type={type}\n disabled={disabled}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-active={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n className={cn(\n 'input',\n css.input,\n className,\n resolved.root\n )}\n style={inputPaddingStyle}\n {...mergeProps(focusProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n ...props,\n })}\n />\n {hasEndAdornment && (\n <div className={css['end-adornments']} data-end-adornments>\n {hasSuffix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], css['suffix-icon'])}>\n {resolvedIcon?.suffix}\n </div>\n )}\n {hasRightActions && (\n <div className={css.actions} data-actions data-actions-position=\"right\">\n {rightActions.map(renderAction)}\n </div>\n )}\n {hasHint && <span data-hint>{hint}</span>}\n {isNumberType && (\n <div\n className={cn(css['number-controls'], disabled && css.disabled)}\n data-disabled={disabled || undefined}\n >\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'])}\n onClick={() => handleSpinClick(\"up\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Increment\"\n >\n <ChevronUp size={12} />\n </button>\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'])}\n onClick={() => handleSpinClick(\"down\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Decrement\"\n >\n <ChevronDown size={12} />\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n }\n);\n\nInput.displayName = \"Input\";\n",
4218
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n --disabled-opacity: 0.5;\n\n flex: 1;\n min-width: 0;\n @apply py-2 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n color: var(--icon-color);\n pointer-events: none;\n }\n\n .prefix-icon {\n @apply relative;\n }\n\n .suffix-icon {\n @apply relative;\n }\n\n .container {\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--input-border-color, var(--border));\n border-radius: var(--input-border-radius, var(--radius-sm));\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-active] {\n border-color: var(--input-active-border-color, var(--ring-color));\n box-shadow: var(--input-active-box-shadow, 0 0 0 1px mix(var(--ring-color) 20%, transparent));\n }\n\n &[data-focus-visible] {\n @apply ring-0;\n border-color: var(--input-active-border-color, var(--ring-color));\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n &[data-error] {\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n\n &[data-variant=\"ghost\"] {\n background-color: transparent;\n border-color: transparent;\n &[data-active], &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n }\n\n .start-adornments,\n .end-adornments {\n @apply flex items-center gap-1;\n flex-shrink: 0;\n pointer-events: none;\n }\n\n .start-adornments {\n @apply pl-2;\n }\n\n .end-adornments {\n @apply pr-2;\n }\n\n .actions {\n @apply flex items-center gap-1;\n pointer-events: auto;\n }\n\n .action {\n @apply flex items-center justify-center p-2;\n border-radius: 0.25rem;\n color: var(--action-color);\n }\n\n .action:hover {\n background-color: var(--background-hover);\n color: var(--action-hover-color);\n }\n\n .number-controls {\n @apply flex w-6 flex-col;\n pointer-events: auto;\n }\n\n .number-controls.disabled {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .spin-button {\n @apply flex w-full flex-1 items-center justify-center p-0 cursor-pointer;\n flex: 1;\n background-color: transparent;\n border: none;\n color: var(--spin-color);\n transition: color 150ms ease-out, background-color 150ms ease-out;\n\n &:hover:not(:disabled) {\n background-color: var(--spin-hover-background);\n color: var(--spin-hover-color);\n }\n\n &:active:not(:disabled) {\n background-color: var(--spin-active-background);\n color: var(--spin-active-color);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n }\n}\n",
4195
+ "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
4196
  "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
4197
  },
4221
4198
  "label": {
@@ -4229,13 +4206,13 @@ export const generatedSourceCode = {
4229
4206
  "cssTypes": "declare const styles: {\n readonly container: string;\n readonly header: string;\n readonly sticky: string;\n readonly item: string;\n readonly actionGroup: string;\n readonly actions: string;\n readonly action: string;\n readonly checkbox: string;\n readonly control: string;\n readonly media: string;\n readonly desc: string;\n readonly footer: string;\n};\n\nexport default styles;\n"
4230
4207
  },
4231
4208
  "mask": {
4232
- "tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"./utils\";\nimport styles from \"./Mask.module.css\";\n\ninterface MaskContextValue {\n maskFilters: string[];\n clipPath?: string;\n}\n\nconst MaskContext = React.createContext<MaskContextValue | undefined>(undefined);\n\nconst useMaskContext = () => {\n const context = React.useContext(MaskContext);\n if (!context) {\n throw new Error(\"Mask sub-components must be used within a Mask component\");\n }\n return context;\n};\n\nexport interface MaskProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nconst MaskRoot = React.forwardRef<HTMLDivElement, MaskProps>(\n ({ className, children, style, ...props }, ref) => {\n const childArray = React.Children.toArray(children);\n const maskFilters: string[] = [];\n let clipPath: string | undefined;\n let hasFixedFade = false;\n let contentChildren: React.ReactNode[] = [];\n\n childArray.forEach((child) => {\n if (React.isValidElement(child)) {\n if (child.type === MaskFade) {\n const fadeChild = child as React.ReactElement<MaskFadeProps>;\n if (fadeChild.props.fixed) hasFixedFade = true;\n maskFilters.push(generateFadeMask(fadeChild.props.direction, fadeChild.props.intensity, fadeChild.props.fixed));\n } else if (child.type === MaskClip) {\n const clipChild = child as React.ReactElement<MaskClipProps>;\n clipPath = clipChild.props.shape;\n } else {\n contentChildren.push(child);\n }\n } else {\n contentChildren.push(child);\n }\n });\n\n const contextValue: MaskContextValue = { maskFilters, clipPath };\n\n const maskStyles = {\n ...style,\n ...(hasFixedFade ? { maxHeight: \"inherit\", overflow: \"hidden\" as const } : {}),\n ...(clipPath ? { \"--mask-clip-path\": clipPath } as Record<string, string> : {}),\n ...(maskFilters.length > 0 ? {\n WebkitMaskImage: maskFilters.join(\", \"),\n maskImage: maskFilters.join(\", \"),\n WebkitMaskComposite: maskFilters.length > 1 ? \"source-in\" : \"source-over\",\n maskComposite: maskFilters.length > 1 ? \"intersect\" : \"add\",\n } : {}),\n } as React.CSSProperties;\n\n return (\n <MaskContext.Provider value={contextValue}>\n <div\n {...props}\n ref={ref}\n className={cn(\"mask\", styles.mask, className)}\n style={maskStyles}\n >\n {contentChildren}\n </div>\n </MaskContext.Provider>\n );\n }\n);\n\nMaskRoot.displayName = \"Mask\";\n\ninterface MaskGradientProps extends React.HTMLAttributes<HTMLDivElement> {\n /** CSS gradient string applied as the mask image */\n gradient: string;\n}\n\nconst MaskGradient = React.forwardRef<HTMLDivElement, MaskGradientProps>(\n ({ className, gradient, style, children, ...props }, ref) => {\n const maskStyles = {\n ...style,\n \"--mask-gradient\": gradient,\n } as React.CSSProperties;\n\n return (\n <div\n {...props}\n ref={ref}\n className={cn(styles.mask, styles[\"mask-gradient\"], className)}\n style={maskStyles}\n >\n {children}\n </div>\n );\n }\n);\n\nMaskGradient.displayName = \"MaskGradient\";\n\ninterface MaskFadeProps {\n /** Edge of the container where the fade starts */\n direction?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /** Controls the size of the fade — higher values produce a longer fade */\n intensity?: number;\n /** Uses percentage-based fade size instead of pixel-based, for fixed-height containers */\n fixed?: boolean;\n}\n\nconst MaskFade: React.FC<MaskFadeProps> = () => null;\nMaskFade.displayName = \"MaskFade\";\n\nfunction generateFadeMask(direction: string = \"bottom\", intensity: number = 1, fixed?: boolean): string {\n const fadeSize = fixed ? `${Math.min(50, 15 * intensity)}%` : `${Math.min(200, 40 * intensity)}px`;\n const directionMap = {\n top: `linear-gradient(to bottom, transparent 0, black ${fadeSize})`,\n bottom: `linear-gradient(to bottom, black calc(100% - ${fadeSize}), transparent 100%)`,\n left: `linear-gradient(to right, transparent 0, black ${fadeSize})`,\n right: `linear-gradient(to right, black calc(100% - ${fadeSize}), transparent 100%)`,\n };\n return directionMap[direction as keyof typeof directionMap] || directionMap.bottom;\n}\n\n\ninterface MaskClipProps {\n /** CSS clip-path value applied to the container (e.g. polygon, circle) */\n shape: string;\n}\n\nconst MaskClip: React.FC<MaskClipProps> = () => null;\nMaskClip.displayName = \"MaskClip\";\n\nconst Mask = Object.assign(MaskRoot, {\n Gradient: MaskGradient,\n Fade: MaskFade,\n Clip: MaskClip,\n});\n\nexport { Mask };\n",
4209
+ "tsx": "import * as React from \"react\";\nimport { cn } from \"./utils\";\nimport styles from \"./Mask.module.css\";\n\nexport interface MaskProps extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n children: React.ReactNode;\n}\n\ntype MaskFilter =\n | {\n kind: \"fade\";\n direction: NonNullable<MaskFadeProps[\"direction\"]>;\n intensity: number;\n fixed?: boolean;\n }\n | {\n kind: \"scroll-fade\";\n };\n\nconst MaskRoot = React.forwardRef<HTMLDivElement, MaskProps>(\n ({ asChild = false, className, children, style, ...props }, ref) => {\n const childArray = React.Children.toArray(children);\n const maskFilters: MaskFilter[] = [];\n let clipPath: string | undefined;\n let hasFixedFade = false;\n let contentChildren: React.ReactNode[] = [];\n const supportsScrollFade = hasScrollFadeVariables(style);\n\n childArray.forEach((child) => {\n if (React.isValidElement(child)) {\n if (child.type === MaskFade) {\n const fadeChild = child as React.ReactElement<MaskFadeProps>;\n if (isScrollFade(fadeChild.props) && supportsScrollFade) {\n maskFilters.push({ kind: \"scroll-fade\" });\n } else {\n if (fadeChild.props.fixed) hasFixedFade = true;\n maskFilters.push({\n kind: \"fade\",\n direction: fadeChild.props.direction ?? \"bottom\",\n intensity: fadeChild.props.intensity ?? 1,\n fixed: fadeChild.props.fixed,\n });\n }\n } else if (child.type === MaskClip) {\n const clipChild = child as React.ReactElement<MaskClipProps>;\n clipPath = clipChild.props.shape;\n } else {\n contentChildren.push(child);\n }\n } else {\n contentChildren.push(child);\n }\n });\n\n const resolvedMaskFilters = maskFilters.map((maskFilter) => generateMaskFilter(maskFilter));\n\n const maskStyles = {\n ...style,\n ...(hasFixedFade ? { maxHeight: \"inherit\", overflow: \"hidden\" as const } : {}),\n ...(clipPath ? { \"--mask-clip-path\": clipPath } as Record<string, string> : {}),\n ...(resolvedMaskFilters.length > 0 ? {\n WebkitMaskImage: resolvedMaskFilters.join(\", \"),\n maskImage: resolvedMaskFilters.join(\", \"),\n WebkitMaskComposite: resolvedMaskFilters.length > 1 ? \"source-in\" : \"source-over\",\n maskComposite: resolvedMaskFilters.length > 1 ? \"intersect\" : \"add\",\n } : {}),\n } as React.CSSProperties;\n\n if (asChild) {\n if (contentChildren.length !== 1 || !React.isValidElement(contentChildren[0])) {\n throw new Error(\"Mask with asChild expects exactly one valid React element child.\");\n }\n\n const child = contentChildren[0] as React.ReactElement<{\n className?: string;\n style?: React.CSSProperties;\n ref?: React.Ref<HTMLDivElement>;\n }>;\n\n return React.cloneElement(child, {\n ...props,\n ref: mergeRefs(ref, child.props.ref),\n className: cn(\"mask\", styles.mask, className, child.props.className),\n style: {\n ...child.props.style,\n ...maskStyles,\n },\n });\n }\n\n return (\n <div\n {...props}\n ref={ref}\n className={cn(\"mask\", styles.mask, className)}\n style={maskStyles}\n >\n {contentChildren}\n </div>\n );\n }\n);\n\nMaskRoot.displayName = \"Mask\";\n\ninterface MaskGradientProps extends React.HTMLAttributes<HTMLDivElement> {\n /** CSS gradient string applied as the mask image */\n gradient: string;\n}\n\nconst MaskGradient = React.forwardRef<HTMLDivElement, MaskGradientProps>(\n ({ className, gradient, style, children, ...props }, ref) => {\n const maskStyles = {\n ...style,\n \"--mask-gradient\": gradient,\n } as React.CSSProperties;\n\n return (\n <div\n {...props}\n ref={ref}\n className={cn(styles.mask, styles[\"mask-gradient\"], className)}\n style={maskStyles}\n >\n {children}\n </div>\n );\n }\n);\n\nMaskGradient.displayName = \"MaskGradient\";\n\ninterface MaskFadeProps {\n /** Edge of the container where the fade starts. Omit to use the variable-driven vertical scroll fade preset. */\n direction?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /** Controls the size of the fade — higher values produce a longer fade */\n intensity?: number;\n /** Uses percentage-based fade size instead of pixel-based, for fixed-height containers */\n fixed?: boolean;\n}\n\nconst MaskFade: React.FC<MaskFadeProps> = () => null;\nMaskFade.displayName = \"MaskFade\";\n\nfunction mergeRefs<T>(\n ...refs: Array<React.Ref<T> | undefined>\n): React.RefCallback<T> {\n return (value) => {\n refs.forEach((ref) => {\n if (!ref) return;\n\n if (typeof ref === \"function\") {\n ref(value);\n return;\n }\n\n (ref as React.MutableRefObject<T | null>).current = value;\n });\n };\n}\n\nfunction isScrollFade(props: MaskFadeProps): boolean {\n return props.direction === undefined && props.intensity === undefined && props.fixed === undefined;\n}\n\nfunction hasScrollFadeVariables(style: React.CSSProperties | undefined): boolean {\n if (!style) return false;\n\n const styleEntries = style as Record<string, unknown>;\n return \"--mask-top-fade\" in styleEntries || \"--mask-bottom-fade\" in styleEntries;\n}\n\nfunction generateMaskFilter(maskFilter: MaskFilter): string {\n if (maskFilter.kind === \"scroll-fade\") {\n return \"linear-gradient(to bottom, transparent 0%, black var(--mask-top-fade, 0%), black calc(100% - var(--mask-bottom-fade, 0%)), transparent 100%)\";\n }\n\n return generateFadeMask(maskFilter.direction, maskFilter.intensity, maskFilter.fixed);\n}\n\nfunction generateFadeMask(direction: string = \"bottom\", intensity: number = 1, fixed?: boolean): string {\n const fadeSize = fixed ? `${Math.min(50, 15 * intensity)}%` : `${Math.min(200, 40 * intensity)}px`;\n const directionMap = {\n top: `linear-gradient(to bottom, transparent 0, black ${fadeSize})`,\n bottom: `linear-gradient(to bottom, black calc(100% - ${fadeSize}), transparent 100%)`,\n left: `linear-gradient(to right, transparent 0, black ${fadeSize})`,\n right: `linear-gradient(to right, black calc(100% - ${fadeSize}), transparent 100%)`,\n };\n return directionMap[direction as keyof typeof directionMap] || directionMap.bottom;\n}\n\n\ninterface MaskClipProps {\n /** CSS clip-path value applied to the container (e.g. polygon, circle) */\n shape: string;\n}\n\nconst MaskClip: React.FC<MaskClipProps> = () => null;\nMaskClip.displayName = \"MaskClip\";\n\nconst Mask = Object.assign(MaskRoot, {\n Gradient: MaskGradient,\n Fade: MaskFade,\n Clip: MaskClip,\n});\n\nexport { Mask };\n",
4233
4210
  "css": "@reference \"tailwindcss\";\n\n@layer components {\n .mask {\n @apply relative h-full w-full;\n }\n}\n\n.mask[style*=\"mask-image\"],\n.mask[style*=\"-webkit-mask-image\"] {\n -webkit-mask-size: 100% 100%;\n mask-size: 100% 100%;\n}\n\n.mask[style*=\"--mask-clip-path\"] {\n clip-path: var(--mask-clip-path);\n}\n\n.mask-gradient {\n background: var(--mask-gradient);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n color: transparent;\n}\n",
4234
4211
  "cssTypes": "declare const styles: {\n mask: string;\n \"mask-gradient\": string;\n};\n\nexport default styles;\n"
4235
4212
  },
4236
4213
  "menu": {
4237
4214
  "tsx": "import * as React from \"react\"\nimport type { Key } from \"react-aria\"\nimport { useListNavigation } from \"../../utils/list-navigation\"\nimport type {\n MenuContextValue,\n MenuSubmenuContextValue,\n RadioGroupContextValue,\n MenuProps,\n MenuPortalProps,\n MenuItemExtras,\n} from \"./menu.types\"\n\nconst MenuContext = React.createContext<MenuContextValue | null>(null)\n\nexport function useMenuContext() {\n const context = React.useContext(MenuContext)\n if (!context) {\n throw new Error(\"Menu component must be used within Menu root\")\n }\n return context\n}\n\nexport const MenuSubmenuContext = React.createContext<MenuSubmenuContextValue | null>(null)\n\nexport function useMenuSubmenuContext() {\n return React.useContext(MenuSubmenuContext)\n}\n\nexport const RadioGroupContext = React.createContext<RadioGroupContextValue | null>(null)\n\nexport function useRadioGroupContext() {\n return React.useContext(RadioGroupContext)\n}\n\nconst MenuPortal = ({ children }: MenuPortalProps) => {\n return <>{children}</>\n}\nMenuPortal.displayName = \"MenuPortal\"\nconst Menu = ({\n children,\n type = \"context-menu\",\n selectionMode = \"none\",\n selectedKeys: controlledSelectedKeys,\n defaultSelectedKeys,\n onSelectionChange,\n}: MenuProps) => {\n const [isOpen, setIsOpen] = React.useState(false)\n const [uncontrolledSelectedKeys, setUncontrolledSelectedKeys] = React.useState<Set<Key>>(\n defaultSelectedKeys ?? new Set()\n )\n const [radioGroups, setRadioGroups] = React.useState<Map<string, Key | null>>(new Map())\n const [activeSubmenuKey, setActiveSubmenuKey] = React.useState<Key | null>(null)\n\n const selectedKeys = controlledSelectedKeys !== undefined ? controlledSelectedKeys : uncontrolledSelectedKeys\n\n const nav = useListNavigation({ isOpen })\n const itemExtrasRef = React.useRef<Map<Key, MenuItemExtras>>(new Map())\n const mouseMoveDetectedRef = React.useRef(true)\n const clickPositionRef = React.useRef({ x: 0, y: 0 })\n const triggerRef = React.useRef<HTMLDivElement | null>(null)\n\n const registerItem = React.useCallback((key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => {\n nav.registerItem(key, textValue, isDisabled)\n itemExtrasRef.current.set(key, { onSelect, isSubmenuTrigger })\n }, [nav.registerItem])\n\n const unregisterItem = React.useCallback((key: Key) => {\n nav.unregisterItem(key)\n itemExtrasRef.current.delete(key)\n }, [nav.unregisterItem])\n\n const handleSelectionChange = React.useCallback((keys: Set<Key>) => {\n if (controlledSelectedKeys === undefined) {\n setUncontrolledSelectedKeys(keys)\n }\n onSelectionChange?.(keys)\n }, [controlledSelectedKeys, onSelectionChange])\n\n const toggleSelection = React.useCallback((key: Key) => {\n const newKeys = new Set(selectedKeys)\n if (selectionMode === \"single\") {\n newKeys.clear()\n newKeys.add(key)\n } else if (selectionMode === \"multiple\") {\n if (newKeys.has(key)) {\n newKeys.delete(key)\n } else {\n newKeys.add(key)\n }\n }\n handleSelectionChange(newKeys)\n }, [selectedKeys, selectionMode, handleSelectionChange])\n\n const close = React.useCallback(() => {\n setIsOpen(false)\n nav.setFocusedKey(null)\n }, [nav.setFocusedKey])\n\n const selectFocusedItem = React.useCallback(() => {\n if (nav.focusedKey === null) return\n const item = nav.items.find(i => i.key === nav.focusedKey)\n if (item?.isDisabled) return\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n extras?.onSelect?.()\n }, [nav.focusedKey, nav.items])\n\n const isFocusedItemSubmenu = React.useCallback(() => {\n if (nav.focusedKey === null) return false\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n return extras?.isSubmenuTrigger ?? false\n }, [nav.focusedKey])\n\n const setRadioGroupValue = React.useCallback((groupName: string, value: Key | null) => {\n setRadioGroups(prev => {\n const next = new Map(prev)\n next.set(groupName, value)\n return next\n })\n }, [])\n\n const getRadioGroupValue = React.useCallback((groupName: string) => {\n return radioGroups.get(groupName) ?? null\n }, [radioGroups])\n\n React.useEffect(() => {\n if (isOpen && nav.focusedKey === null && nav.enabledFilteredItems.length > 0) {\n nav.setFocusedKey(nav.enabledFilteredItems[0].key)\n }\n }, [isOpen, nav.enabledFilteredItems, nav.focusedKey, nav.setFocusedKey])\n\n const contextValue = React.useMemo(() => ({\n isOpen,\n setIsOpen,\n type,\n close,\n selectionMode,\n selectedKeys,\n onSelectionChange: handleSelectionChange,\n toggleSelection,\n items: nav.items,\n registerItem,\n unregisterItem,\n focusedKey: nav.focusedKey,\n setFocusedKey: nav.setFocusedKey,\n navigateToNextItem: nav.navigateToNextItem,\n navigateToPrevItem: nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n radioGroups,\n setRadioGroupValue,\n getRadioGroupValue,\n triggerRef,\n mouseMoveDetectedRef,\n clickPositionRef,\n activeSubmenuKey,\n setActiveSubmenuKey,\n } satisfies MenuContextValue), [\n isOpen,\n setIsOpen,\n type,\n close,\n selectionMode,\n selectedKeys,\n handleSelectionChange,\n toggleSelection,\n nav.items,\n registerItem,\n unregisterItem,\n nav.focusedKey,\n nav.setFocusedKey,\n nav.navigateToNextItem,\n nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n radioGroups,\n setRadioGroupValue,\n getRadioGroupValue,\n activeSubmenuKey,\n setActiveSubmenuKey,\n ])\n\n return (\n <MenuContext.Provider value={contextValue}>\n {children}\n </MenuContext.Provider>\n )\n}\nMenu.displayName = \"Menu\"\n\nexport { Menu, MenuPortal }\nexport type {\n MenuProps,\n MenuTriggerProps,\n MenuPortalProps,\n MenuContentProps,\n MenuGroupProps,\n MenuItemProps,\n MenuCheckboxItemProps,\n MenuRadioGroupProps,\n MenuRadioItemProps,\n MenuLabelProps,\n MenuSeparatorProps,\n MenuShortcutProps,\n MenuSubProps,\n MenuSubTriggerProps,\n MenuSubContentProps,\n} from \"./menu.types\"\n",
4238
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .content,\n .sub-content {\n --padding: calc(var(--spacing) * 1.5);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n --menu-animation: none;\n --disabled-opacity: 0.5;\n }\n\n .trigger {\n &[data-type=\"pop-over\"][data-active] {\n opacity: 1;\n background-color: var(--trigger-active-background);\n border-radius: var(--radius-sm, 0.375rem);\n }\n }\n\n .content {\n @apply absolute min-w-40 max-w-80 overflow-hidden;\n z-index: 50000;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n\n &[data-state=\"open\"] {\n animation: var(--menu-animation, slide-in-from-top 0.15s var(--ease-snappy-pop));\n }\n\n &[data-state=\"closed\"] {\n animation: var(--menu-animation, slide-out-to-top 0.15s var(--ease-snappy-pop));\n }\n }\n\n .list {\n @apply space-y-1;\n max-height: 24rem;\n overflow-y: auto;\n }\n\n .item,\n .checkbox-item,\n .radio-item {\n @apply flex min-w-0 items-center gap-2;\n padding: var(--padding);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n cursor: default;\n user-select: none;\n outline: none;\n color: var(--item-foreground);\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .item,\n .sub-trigger {\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .item-indicator {\n @apply ml-auto flex h-4 w-4 shrink-0 items-center justify-center;\n color: var(--item-indicator-color);\n }\n\n .sub-trigger {\n @apply flex min-w-0 items-center gap-2;\n padding: var(--padding);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n cursor: default;\n user-select: none;\n outline: none;\n &[data-highlighted] {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-state=\"open\"]:not([data-highlighted]) {\n background-color: var(--item-highlighted-background);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply ml-auto h-4 w-4 shrink-0;\n }\n\n .sub-content {\n @apply absolute min-w-40 max-w-80 overflow-hidden;\n z-index: 50000;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n\n &[data-state=\"open\"] {\n animation: var(--menu-animation, slide-in-from-top 0.15s var(--ease-snappy-pop));\n }\n\n &[data-state=\"closed\"] {\n animation: var(--menu-animation, slide-out-to-top 0.15s var(--ease-snappy-pop));\n }\n }\n\n .label {\n padding: var(--padding);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--label-foreground);\n\n &[data-inset] {\n padding-left: calc(var(--padding) * 2.67);\n }\n }\n\n .separator {\n @apply -mx-1 my-1 h-px;\n background-color: var(--separator-background);\n }\n\n .shortcut {\n margin-left: auto;\n font-size: var(--text-sm);\n letter-spacing: 0.1em;\n color: var(--shortcut-foreground);\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-to-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n}\n",
4215
+ "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
4216
  "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
4217
  },
4241
4218
  "modal": {
@@ -4259,7 +4236,7 @@ export const generatedSourceCode = {
4259
4236
  "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
4237
  },
4261
4238
  "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. Merged with `styles.root`. */\n className?: string;\n /** Additional CSS class for the popover content panel. Merged with `styles.content`. */\n contentClassName?: string;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the popover opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n}\n\nconst Popover = React.forwardRef<HTMLDivElement, PopoverProps>(\n ({ children, content, position = \"bottom\", styles, className: externalClassName, contentClassName: externalContentClassName, isOpen: controlledIsOpen, onOpenChange, showArrow = false }, ref) => {\n\n const resolvePopoverBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'trigger',\n ] as const);\n\n const resolved = resolvePopoverBaseStyles(styles);\n\n const triggerRef = React.useRef<HTMLDivElement>(null);\n const popoverContentRef = React.useRef<HTMLDivElement>(null);\n const [isAnimating, setIsAnimating] = React.useState(false);\n const [isExiting, setIsExiting] = React.useState(false);\n\n const state = useOverlayTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange,\n });\n\n const { triggerProps, overlayProps } = useOverlayTrigger({ type: \"dialog\" }, state, triggerRef);\n const { dialogProps } = useDialog({}, popoverContentRef);\n\n const placementMap: Record<PopoverPosition, \"top\" | \"bottom\" | \"left\" | \"right\"> = {\n top: \"top\",\n bottom: \"bottom\",\n left: \"left\",\n right: \"right\",\n };\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: placementMap[position],\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(POPOVER_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n // Trigger animation when popover is opened and positioned\n React.useEffect(() => {\n if (state.isOpen && isPositioned) {\n setIsExiting(false);\n setIsAnimating(true);\n }\n }, [state.isOpen, isPositioned]);\n\n // Handle exit animation when closing\n React.useEffect(() => {\n if (!state.isOpen && isAnimating) {\n // First, enable exit mode so element stays in DOM\n setIsExiting(true);\n\n requestAnimationFrame(() => setIsAnimating(false));\n const timer = setTimeout(() => setIsExiting(false), 50)\n return () => clearTimeout(timer);\n }\n }, [state.isOpen, isAnimating]);\n\n React.useLayoutEffect(() => {\n refs.setReference(triggerRef.current);\n }, [refs]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n triggerRef.current &&\n !triggerRef.current.contains(target) &&\n popoverContentRef.current &&\n !popoverContentRef.current.contains(target)\n ) {\n state.close();\n }\n };\n document.addEventListener(\"click\", handleClickOutside);\n return () => document.removeEventListener(\"click\", handleClickOutside);\n }, [state.isOpen, state]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") state.close();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [state.isOpen, state]);\n\n const mergedRef = React.useCallback(\n (el: HTMLDivElement | null) => {\n (triggerRef as React.RefObject<HTMLDivElement | null>).current = el;\n refs.setReference(el);\n if (typeof ref === \"function\") ref(el);\n else if (ref) ref.current = el;\n },\n [refs, ref]\n );\n\n const mergedContentRef = React.useCallback(\n (el: HTMLDivElement | null) => {\n (popoverContentRef as React.RefObject<HTMLDivElement | null>).current = el;\n refs.setFloating(el);\n },\n [refs]\n );\n\n // Convert React Aria's onPress to onClick for native HTML elements\n const nativeProps = React.useMemo(() => {\n const props: any = { ...triggerProps };\n if (props.onPress && typeof props.onPress === 'function') {\n const onPress = props.onPress;\n props.onClick = (e: React.MouseEvent) => {\n onPress({ target: e.currentTarget, type: 'press', pointerType: 'mouse', ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey, altKey: e.altKey });\n };\n delete props.onPress;\n }\n return props;\n }, [triggerProps]);\n\n const triggerElement = React.isValidElement(children)\n ? React.cloneElement(children as React.ReactElement<{ className?: string; ref?: React.Ref<HTMLButtonElement | HTMLDivElement> }>, {\n ...nativeProps,\n className: cn((children as React.ReactElement<{ className?: string }>).props.className, externalClassName, css.trigger, resolved.trigger),\n ref: mergedRef,\n })\n : (\n <span ref={mergedRef} {...nativeProps} className={cn(css.trigger, externalClassName, resolved.trigger)}>\n {children}\n </span>\n );\n\n return (\n <>\n {triggerElement}\n {(state.isOpen || isExiting) &&\n createPortal(\n <div\n ref={mergedContentRef}\n {...asElementProps<\"div\">(mergeProps(overlayProps, dialogProps))}\n className={cn(css.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn('popover', 'content', css.content)}\n style={{\n opacity: isAnimating ? 1 : 0,\n transform: isAnimating ? \"scale(1)\" : getInitialTransform(placement),\n pointerEvents: isAnimating ? 'auto' : 'none',\n }}\n >\n <Frame\n role=\"dialog\"\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n cornerRadius={8}\n padding=\"none\"\n className={cn('popover', 'frame', css.frame, externalContentClassName, resolved.content)}\n >\n {content}\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nPopover.displayName = \"Popover\";\n\nexport { Popover };\n",
4239
+ "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
4240
  "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
4241
  "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
4242
  },
@@ -4274,18 +4251,18 @@ export const generatedSourceCode = {
4274
4251
  "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
4252
  },
4276
4253
  "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-inset=\"true\"] .content {\n padding-right: 16px;\n }\n\n .horizontal .content {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .horizontal[data-inset=\"true\"] .content {\n padding-bottom: 16px;\n }\n\n .vertical .content::-webkit-scrollbar,\n .horizontal .content::-webkit-scrollbar { display: none; }\n\n .track {\n @apply absolute;\n z-index: 10;\n }\n\n .track[data-hide=\"true\"] {\n transition-property: opacity;\n transition-duration: 200ms;\n }\n\n .vertical .track {\n right: 4px;\n top: var(--scroll-padding-y, 0);\n width: 12px;\n height: calc(100% - 2 * var(--scroll-padding-y, 0));\n background-color: var(--track-background);\n box-sizing: border-box;\n }\n\n .horizontal .track {\n bottom: 2px;\n left: 0;\n height: 12px;\n width: 100%;\n background-color: var(--track-background);\n }\n\n .thumb {\n position: absolute;\n border-radius: calc(var(--radius-xs) * 0.80);\n background-color: var(--thumb-background);\n transition-property: background-color, width, height;\n transition-duration: 150ms;\n }\n\n .thumb:hover { background-color: var(--thumb-hover-background); }\n\n .root[data-dragging=\"true\"] .thumb {\n background-color: var(--thumb-dragging-background);\n }\n\n .vertical .thumb {\n width: 6px;\n margin-left: 6px;\n transition-property: background-color, width, margin-left;\n transition-duration: 150ms;\n }\n\n .vertical .thumb:hover,\n .vertical[data-dragging=\"true\"] .thumb {\n width: 8px;\n margin-left: 4px;\n }\n\n .horizontal .thumb {\n height: 6px;\n margin-top: 6px;\n transition-property: background-color, height, margin-top;\n transition-duration: 150ms;\n }\n\n .horizontal .thumb:hover,\n .horizontal[data-dragging=\"true\"] .thumb {\n height: 8px;\n margin-top: 4px;\n }\n}\n",
4254
+ "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 { Mask } from \"../Mask\";\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 getInitialScrollFadeVars(\n direction: ScrollProps[\"direction\"],\n fadeY: boolean,\n fadeSize: number,\n): React.CSSProperties {\n if (direction !== \"vertical\" || !fadeY) {\n return {\n \"--mask-top-fade\": \"0%\",\n \"--mask-bottom-fade\": \"0%\",\n } as React.CSSProperties;\n }\n\n // SSR cannot know overflow or scroll position, so default to a bottom-only hint.\n return {\n \"--mask-top-fade\": \"0%\",\n \"--mask-bottom-fade\": `${fadeSize}%`,\n } as React.CSSProperties;\n}\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,\n maxWidth,\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 maskRef = 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 && maskRef.current) {\n const maskNode = maskRef.current;\n if (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 maskNode.style.setProperty(\"--mask-top-fade\", `${topP * fadeSize}%`);\n maskNode.style.setProperty(\"--mask-bottom-fade\", `${botP * fadeSize}%`);\n } else {\n maskNode.style.setProperty(\"--mask-top-fade\", \"0%\");\n maskNode.style.setProperty(\"--mask-bottom-fade\", \"0%\");\n }\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 const axisConstraintStyle = {\n ...(isHoriz\n ? (maxWidth ? { maxWidth } : {})\n : (maxHeight ? { maxHeight } : {})),\n };\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 ...axisConstraintStyle,\n ...propsStyle,\n }}\n {...restProps}\n >\n {children}\n </div>\n );\n }\n\n const showOpacity = needsScrollbar && (!hide || 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 ...axisConstraintStyle,\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 <Mask\n ref={maskRef}\n style={{\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\",\n overflow: \"hidden\",\n ...getInitialScrollFadeVars(direction, fadeY, fadeSize),\n } as React.CSSProperties}\n >\n {!isHoriz && fadeY ? <Mask.Fade /> : null}\n <div\n ref={handleContentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n onWheel={isHoriz ? handleWheel : undefined}\n style={{\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\",\n minHeight: 0,\n minWidth: 0,\n }}\n {...(storageKey\n ? {\n [SCROLL_RESTORE_STORAGE_KEY_ATTR]: storageKey,\n [SCROLL_RESTORE_AXIS_ATTR]: direction,\n }\n : {})}\n >\n {children}\n </div>\n </Mask>\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 && (\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",
4255
+ "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
4256
  "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
4257
  },
4281
4258
  "select": {
4282
4259
  "tsx": "import * as React from \"react\"\nimport { Key } from \"@react-types/shared\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport styles from \"./Select.module.css\"\nimport { useListNavigation, useMergedRef, handleListKeyDown, type ItemData } from \"./Select.shared\"\n\nexport type SelectItemData = ItemData\n\nexport type SelectTriggerMode = \"click\" | \"hover\"\nexport type SelectMode = \"single\" | \"multiple\"\n\ninterface SelectStyleSlots {\n root?: StyleValue;\n}\n\ntype SelectStylesProp = StylesProp<SelectStyleSlots>;\n\nexport interface SelectContextValue {\n isOpen: boolean\n setIsOpen: React.Dispatch<React.SetStateAction<boolean>>\n contentPlacement: \"top\" | \"bottom\"\n setContentPlacement: React.Dispatch<React.SetStateAction<\"top\" | \"bottom\">>\n triggerType: \"button\" | \"input\"\n mode: SelectMode\n selectedKey: Key | null\n selectedKeys?: Set<Key>\n selectedTextValue: string\n onSelect: (key: Key) => void\n onToggle?: (key: Key) => void\n triggerRef: React.MutableRefObject<HTMLElement | null>\n wrapperRef: React.MutableRefObject<HTMLElement | null>\n contentRef: React.MutableRefObject<HTMLElement | null>\n triggerProps: any\n isFocusVisible: boolean\n isPressed: boolean\n isHovered: boolean\n isDisabled: boolean\n items: SelectItemData[]\n registerItem: (key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => void\n unregisterItem: (key: Key) => void\n searchValue: string\n setSearchValue: React.Dispatch<React.SetStateAction<string>>\n filteredItems: SelectItemData[]\n visibleKeys: Set<Key>\n focusedKey: Key | null\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>\n navigateToNextItem: () => void\n navigateToPrevItem: () => void\n selectFocusedItem: () => void\n isFocusedItemSubmenu: () => boolean\n maxItems: number\n triggerMode: SelectTriggerMode\n handleHoverIntent: (isHovering: boolean) => void\n mouseMoveDetectedRef: React.MutableRefObject<boolean>\n filter?: (item: any) => boolean\n contentId: string\n}\n\nconst SelectContext = React.createContext<SelectContextValue | null>(null)\n\nexport function useSelectContext() {\n const context = React.useContext(SelectContext)\n if (!context) {\n throw new Error(\"Select component must be used within Select root\")\n }\n return context\n}\n\nexport interface SelectProps<T = any> extends React.PropsWithChildren {\n /** Selection mode: \"single\" for one item, \"multiple\" for multi-item selection */\n mode?: SelectMode\n /** External items array — used when items are provided as data rather than JSX */\n items?: Array<T>\n /** Controlled selected key for single-select mode */\n selectedKey?: Key | null\n /** Default selected key for uncontrolled single-select */\n defaultSelectedKey?: Key | null\n /** Controlled selected keys for multi-select mode */\n selectedKeys?: Key[]\n /** Default selected keys for uncontrolled multi-select */\n defaultSelectedKeys?: Key[]\n /** Default display text shown in the trigger when nothing is selected */\n defaultValue?: string | null\n /** Display text for the currently selected value — used for SSR/SSG to avoid\n * flash of placeholder before items register. Provide alongside selectedKey or\n * defaultSelectedKey so the correct label renders on the first pass. */\n valueLabel?: string\n /** Called when selection changes; receives a single key (single) or key array (multiple) */\n onSelectionChange?: (value: any) => void\n /** Disables the entire select and prevents interaction */\n isDisabled?: boolean\n /** Focuses the trigger automatically on mount */\n autoFocus?: boolean\n /** Maximum number of items visible before the dropdown scrolls */\n maxItems?: number\n /** Additional CSS class for the root wrapper */\n className?: string\n /** How the dropdown opens: \"click\" (default) or \"hover\" */\n trigger?: SelectTriggerMode\n /** Custom filter predicate applied to the items array */\n filter?: (item: T) => boolean\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SelectStylesProp;\n}\n\nconst resolveSelectBaseStyles = createStylesResolver(['root'] as const);\n\nconst Select = React.forwardRef<HTMLDivElement, SelectProps<any>>(\n (\n {\n mode = \"single\",\n items: propItems = [],\n selectedKey: controlledSelectedKey,\n defaultSelectedKey,\n selectedKeys: controlledSelectedKeys,\n defaultSelectedKeys = [],\n defaultValue,\n valueLabel,\n onSelectionChange,\n isDisabled = false,\n autoFocus = false,\n maxItems = 6,\n children,\n className,\n trigger: triggerMode = \"click\",\n filter,\n styles: stylesProp,\n },\n ref\n ) => {\n const triggerRef = React.useRef<HTMLElement>(null)\n const wrapperRef = React.useRef<HTMLElement>(null)\n const contentRef = React.useRef<HTMLElement>(null)\n const mouseMoveDetectedRef = React.useRef(true)\n const itemExtrasRef = React.useRef<Map<Key, { onSelect?: () => void; isSubmenuTrigger?: boolean }>>(new Map())\n const [isOpen, setIsOpen] = React.useState(false)\n const [contentPlacement, setContentPlacement] = React.useState<\"top\" | \"bottom\">(\"bottom\")\n const contentId = React.useId()\n const hoverTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const handleHoverIntent = React.useCallback((isHovering: boolean) => {\n if (triggerMode !== \"hover\" || isDisabled) return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n hoverTimeoutRef.current = null\n }\n\n if (isHovering) {\n setIsOpen(true)\n } else {\n hoverTimeoutRef.current = setTimeout(() => {\n setIsOpen(false)\n }, 100)\n }\n }, [triggerMode, isDisabled])\n\n React.useEffect(() => {\n if (!isOpen || triggerMode !== \"hover\" || isDisabled) return\n\n const handleMouseMove = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n const isOver = wrapperRef.current?.contains(target) ||\n contentRef.current?.contains(target)\n\n if (!isOver) {\n handleHoverIntent(false)\n } else {\n handleHoverIntent(true)\n }\n }\n\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [isOpen, triggerMode, isDisabled, handleHoverIntent])\n\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n const [uncontrolledSelectedKey, setUncontrolledSelectedKey] = React.useState<Key | null>(\n defaultSelectedKey ?? null\n )\n const [uncontrolledSelectedKeys, setUncontrolledSelectedKeys] = React.useState<Set<Key>>(\n new Set(defaultSelectedKeys)\n )\n const [selectedTextValue, setSelectedTextValue] = React.useState(valueLabel ?? defaultValue ?? \"\")\n const selectedKey = controlledSelectedKey !== undefined ? controlledSelectedKey : uncontrolledSelectedKey\n const selectedKeys = controlledSelectedKeys !== undefined ? new Set(controlledSelectedKeys) : uncontrolledSelectedKeys\n\n const nav = useListNavigation({\n isOpen,\n externalItems: propItems.length > 0 ? propItems : undefined,\n filter: filter ? (item: any) => filter({ ...item, label: item.textValue } as any) : undefined\n })\n\n const registerItem = React.useCallback((key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => {\n nav.registerItem(key, textValue, isDisabled)\n itemExtrasRef.current.set(key, { onSelect, isSubmenuTrigger })\n }, [nav.registerItem])\n\n const unregisterItem = React.useCallback((key: Key) => {\n nav.unregisterItem(key)\n itemExtrasRef.current.delete(key)\n }, [nav.unregisterItem])\n\n const isFocusedItemSubmenu = React.useCallback(() => {\n if (nav.focusedKey === null) return false\n return itemExtrasRef.current.get(nav.focusedKey)?.isSubmenuTrigger ?? false\n }, [nav.focusedKey])\n\n const onSelect = React.useCallback((key: Key) => {\n const item = nav.items.find(i => i.key === key)\n if (item) {\n setSelectedTextValue(item.textValue)\n }\n if (controlledSelectedKey === undefined) {\n setUncontrolledSelectedKey(key)\n }\n onSelectionChange?.(key)\n setIsOpen(false)\n nav.setSearchValue(\"\")\n }, [controlledSelectedKey, onSelectionChange, nav.items])\n\n const onToggle = React.useCallback((key: Key) => {\n const newKeys = new Set(selectedKeys)\n if (newKeys.has(key)) {\n newKeys.delete(key)\n } else {\n newKeys.add(key)\n }\n if (controlledSelectedKeys === undefined) {\n setUncontrolledSelectedKeys(newKeys)\n }\n onSelectionChange?.(Array.from(newKeys))\n }, [selectedKeys, controlledSelectedKeys, onSelectionChange])\n\n const selectFocusedItem = React.useCallback(() => {\n if (nav.focusedKey !== null) {\n const item = nav.enabledFilteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n if (extras?.onSelect) {\n extras.onSelect()\n } else if (mode === \"multiple\") {\n onToggle(nav.focusedKey)\n } else {\n onSelect(nav.focusedKey)\n }\n }\n }\n }, [nav.focusedKey, nav.enabledFilteredItems, onSelect, onToggle, mode])\n\n React.useEffect(() => {\n if (isOpen) {\n // Only initialize focusedKey if it's not already valid\n if (nav.focusedKey !== null && nav.visibleKeys.has(nav.focusedKey)) {\n const item = nav.filteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n return // Keep current keyboard focus, don't reset it\n }\n }\n\n const focusKey = mode === \"multiple\" && selectedKeys.size > 0\n ? Array.from(selectedKeys)[0]\n : selectedKey\n\n if (focusKey !== null && nav.visibleKeys.has(focusKey)) {\n const item = nav.filteredItems.find(item => item.key === focusKey)\n if (item && !item.isDisabled) {\n nav.setFocusedKey(focusKey)\n return\n }\n }\n if (nav.enabledFilteredItems.length > 0) {\n nav.setFocusedKey(nav.enabledFilteredItems[0].key)\n } else {\n nav.setFocusedKey(null)\n }\n }\n }, [isOpen, selectedKey, selectedKeys, nav.visibleKeys, nav.enabledFilteredItems, nav.filteredItems, mode, nav.focusedKey])\n\n const { buttonProps, isPressed } = useButton({\n isDisabled,\n onPress: (e) => {\n if (isDisabled) return\n // Keyboard interactions are handled by onKeyDown to prevent conflicts\n if (e.pointerType !== 'keyboard') {\n setIsOpen(prev => !prev)\n }\n },\n }, triggerRef)\n const { focusProps, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({\n isDisabled,\n onHoverStart: () => handleHoverIntent(true),\n onHoverEnd: () => handleHoverIntent(false),\n })\n\n const triggerProps = mergeProps(buttonProps, focusProps, hoverProps, {\n 'aria-haspopup': 'listbox' as const,\n 'aria-expanded': isOpen,\n 'aria-controls': isOpen ? contentId : undefined,\n 'aria-disabled': isDisabled || undefined,\n onKeyDown: (e: React.KeyboardEvent) => {\n if (!isOpen) {\n if (e.key === 'ArrowDown' || e.key === 'Enter' || (e.key === ' ' && !isDisabled)) {\n e.preventDefault()\n setIsOpen(true)\n }\n return\n }\n\n handleListKeyDown(e, {\n navigateNext: nav.navigateToNextItem,\n navigatePrev: nav.navigateToPrevItem,\n confirm: selectFocusedItem,\n close: () => {\n setIsOpen(false)\n nav.setSearchValue(\"\")\n triggerRef.current?.focus()\n },\n filteredItems: nav.filteredItems,\n setFocusedKey: nav.setFocusedKey,\n })\n },\n })\n\n React.useEffect(() => {\n if (autoFocus && triggerRef.current) {\n triggerRef.current.focus({ preventScroll: true })\n }\n }, [autoFocus])\n\n React.useEffect(() => {\n if (mode === \"single\") {\n if (selectedKey === null) {\n setSelectedTextValue(\"\")\n } else {\n const selectedItem = nav.items.find(item => item.key === selectedKey)\n if (selectedItem) {\n setSelectedTextValue(selectedItem.textValue)\n } else if (valueLabel !== undefined) {\n setSelectedTextValue(valueLabel)\n } else if (defaultValue !== undefined && defaultValue !== null) {\n setSelectedTextValue(defaultValue)\n }\n }\n }\n }, [selectedKey, nav.items, mode, defaultValue, valueLabel])\n\n const rootRef = useMergedRef<HTMLDivElement>(wrapperRef, ref)\n\n const childrenArray = React.Children.toArray(children)\n const trigger = childrenArray.find(child => React.isValidElement(child) && (\n (child.type as any)?.displayName === 'SelectTrigger' ||\n (child.type as any)?.displayName === 'SearchableTrigger'\n ))\n const contentItems = childrenArray.filter(child => React.isValidElement(child) && ((child.type as any)?.displayName === 'SelectContent' || (child.type as any)?.displayName === 'SearchableContent'))\n const otherContent = childrenArray.filter(child => !React.isValidElement(child) || (\n (child.type as any)?.displayName !== 'SelectTrigger' &&\n (child.type as any)?.displayName !== 'SearchableTrigger' &&\n (child.type as any)?.displayName !== 'SelectContent' &&\n (child.type as any)?.displayName !== 'SearchableContent'\n ))\n const triggerType = React.isValidElement(trigger) && (trigger.type as any)?.displayName === 'SearchableTrigger'\n ? 'input'\n : 'button'\n\n const resolvedStyles = resolveSelectBaseStyles(stylesProp);\n\n return (\n <SelectContext.Provider\n value={{\n isOpen,\n setIsOpen,\n contentPlacement,\n setContentPlacement,\n triggerType,\n mode,\n selectedKey,\n selectedKeys: mode === \"multiple\" ? selectedKeys : undefined,\n selectedTextValue,\n onSelect,\n onToggle: mode === \"multiple\" ? onToggle : undefined,\n triggerRef,\n wrapperRef,\n contentRef,\n triggerProps,\n isFocusVisible,\n isPressed,\n isHovered,\n isDisabled,\n items: nav.items,\n registerItem,\n unregisterItem,\n searchValue: nav.searchValue,\n setSearchValue: nav.setSearchValue,\n filteredItems: nav.filteredItems,\n visibleKeys: nav.visibleKeys,\n focusedKey: nav.focusedKey,\n setFocusedKey: nav.setFocusedKey,\n navigateToNextItem: nav.navigateToNextItem,\n navigateToPrevItem: nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n maxItems,\n triggerMode,\n handleHoverIntent,\n mouseMoveDetectedRef,\n filter,\n contentId,\n }}\n >\n <div\n ref={rootRef}\n className={cn('select', styles.select, className, resolvedStyles.root)}\n data-mode={mode}\n >\n {otherContent}\n {trigger}\n {contentItems}\n </div>\n </SelectContext.Provider>\n )\n }\n)\nSelect.displayName = \"Select\"\n\nexport { Select, SelectContext }\n",
4283
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n\n --padding-x: calc(var(--spacing) * 2.00);\n --padding-y: calc(var(--spacing) * 1.75);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--border-color);\n border-radius: var(--radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed]:not([data-disabled]) {\n background-color: var(--pressed-background);\n }\n\n &[aria-expanded=\"true\"] {\n background-color: var(--hover-background);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not(:disabled):hover .icon-section,\n &:not(:disabled):hover .value-section:not(:empty) {\n background-color: var(--hover-background);\n }\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 2px var(--focus-ring-background), 0 0 0 4px var(--ring-color);\n @apply outline-none;\n }\n\n :global(.group) &:focus-visible {\n @apply outline-none;\n }\n }\n\n button.trigger { @apply p-0; }\n\n .value-section {\n @apply flex items-center flex-1 min-w-0 gap-0.5;\n\n padding: var(--padding-y) var(--padding-x);\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n\n &:only-child {\n border-radius: var(--inner-radius);\n justify-content: center;\n }\n &:empty {\n flex: 0;\n padding: 0;\n min-width: auto;\n }\n }\n\n .icon-section {\n @apply flex items-center justify-center shrink-0;\n padding: var(--padding-y) var(--padding-x);\n border-radius: 0 var(--inner-radius) var(--inner-radius) 0;\n }\n\n .icon {\n @apply flex items-center justify-center w-4 h-4 opacity-70;\n }\n\n .trigger[aria-expanded=\"true\"] .icon,\n .trigger[data-open=\"true\"] .icon {\n transform: rotate(180deg);\n }\n\n .value {\n @apply flex items-center flex-1 min-w-0 gap-2 bg-transparent border-none p-0;\n cursor: inherit;\n }\n\n .value-icon {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n color: var(--foreground);\n }\n\n .value-text {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .content,\n .sub-content {\n --padding-x: calc(var(--spacing) * 1.5);\n --padding-y: var(--spacing);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n overflow: hidden;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--radius);\n }\n\n .content-root,\n .sub-content-root {\n position: absolute;\n }\n\n .content {\n &[data-state=\"open\"][data-placement=\"bottom\"] { animation: slide-in-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"open\"][data-placement=\"top\"] { animation: slide-in-from-bottom 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"bottom\"] { animation: slide-out-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"top\"] { animation: slide-out-from-bottom 0.15s var(--ease-snappy-pop); }\n }\n\n .list {\n @apply space-y-1;\n }\n\n .item {\n --item-padding-x: var(--padding-x);\n --item-padding-y: calc(var(--padding-y) * 1.15);\n\n @apply flex items-center gap-2 outline-none cursor-default select-none;\n padding: var(--item-padding-y) var(--item-padding-x);\n\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n\n &[data-selected=\"true\"] {\n color: var(--item-foreground);\n }\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n }\n &[data-highlighted=\"true\"] {\n background-color: var(--item-background-hover);\n }\n }\n\n .item-content {\n @apply flex flex-col flex-1 min-w-0;\n }\n\n .item-text {\n @apply min-w-0 overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--item-description-color);\n @apply min-w-0 whitespace-normal break-words;\n }\n\n .item-icon, .item-indicator {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n }\n\n .item-icon { color: var(--item-icon-color); }\n .item-indicator { color: var(--item-indicator-color); margin-left: auto; }\n\n .item-with-description { @apply items-start py-2; }\n .item-icon-with-description, .item-indicator-with-description { @apply mt-0.5; }\n\n .separator {\n @apply my-1 -mx-1 h-px;\n background-color: var(--content-border); /* Reuses content border var */\n }\n\n .placeholder {\n color: var(--placeholder-color);\n }\n\n .icon-prefix {\n @apply inline-flex items-center shrink-0;\n }\n\n .select[data-mode=\"multiple\"] .item { gap: 0.5rem; }\n\n .search-trigger {\n @apply flex items-stretch relative bg-transparent cursor-text overflow-hidden;\n border-radius: var(--inner-radius);\n transition: box-shadow 150ms var(--ease-snappy-pop), border-color 150ms var(--ease-snappy-pop);\n\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n\n &:focus-within {\n @apply outline-none;\n box-shadow: 0 0 0 1px var(--search-focus-ring);\n z-index: 1;\n }\n }\n\n .search-value-section {\n @apply p-0;\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n }\n\n .input {\n padding: var(--padding-y) calc(var(--padding-x) * 1.50);\n padding-right: calc(var(--padding-x) * 2 + 1rem);\n @apply border-none rounded-none shadow-none bg-transparent;\n\n &[data-active], &[data-focus-visible] {\n @apply border-none shadow-none;\n }\n }\n\n .search-content-input {\n padding-inline: calc(var(--padding-x) * 1.50);\n @apply border-none rounded-none bg-transparent;\n }\n\n .search-icon-section {\n @apply absolute right-0 top-0 bottom-0 flex items-center justify-center bg-transparent pointer-events-none;\n padding-inline: var(--padding-x);\n }\n\n\n .search-wrapper {\n @apply overflow-hidden;\n border-bottom: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .content[data-placement=\"top\"] .search-wrapper {\n border-radius: 0;\n border-bottom: none;\n border-top: var(--border-width-base, 1px) solid var(--content-border);\n }\n\n .sub-trigger {\n --subtrigger-padding-x: var(--padding-x);\n --subtrigger-padding-y: var(--padding-y);\n\n @apply flex items-center gap-2 cursor-default select-none outline-none;\n padding: var(--subtrigger-padding-y) var(--subtrigger-padding-x);\n border-radius: var(--inner-radius);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--subtrigger-foreground);\n\n &[data-highlighted=\"true\"],\n &[data-state=\"open\"]:not([data-highlighted=\"true\"]) {\n background-color: var(--subtrigger-background-hover);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n pointer-events: none;\n }\n }\n\n .sub-trigger-chevron {\n @apply shrink-0 ml-auto w-4 h-4 opacity-60;\n }\n\n .sub-content {\n min-width: 160px;\n max-width: 320px;\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-in-from-bottom { from { opacity: 0; translate: 0 2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-from-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n @keyframes slide-out-from-bottom { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 2px; } }\n}\n",
4260
+ "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
4261
  "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
4262
  },
4286
4263
  "slider": {
4287
4264
  "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 cursor: grab;\n outline: none;\n top: 50%;\n transform: translate(-50%, -50%);\n }\n\n .slider[data-size=\"sm\"] .thumb {\n width: var(--thumb-size-sm);\n height: var(--thumb-size-sm);\n }\n\n .slider[data-size=\"md\"] .thumb {\n width: var(--thumb-size-md);\n height: var(--thumb-size-md);\n }\n\n .slider[data-size=\"lg\"] .thumb {\n width: var(--thumb-size-lg);\n height: var(--thumb-size-lg);\n }\n\n .slider[data-disabled] .thumb {\n background-color: var(--slider-thumb-background-disabled);\n cursor: not-allowed;\n }\n\n .thumb[data-focus-visible] {\n background-color: var(--slider-thumb-background-focus);\n box-shadow: 0 0 0 3px var(--slider-thumb-ring);\n }\n\n .thumb[data-dragging] {\n cursor: grabbing;\n transform: translate(-50%, -50%) scale(1.1);\n }\n}\n",
4265
+ "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
4266
  "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
4267
  },
4291
4268
  "switch": {
@@ -4299,9 +4276,9 @@ export const generatedSourceCode = {
4299
4276
  "cssTypes": ""
4300
4277
  },
4301
4278
  "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 --indicator-padding: 2px;\n\n @apply absolute;\n background-color: var(--indicator-background);\n border-radius: var(--radius-xs);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--trigger-color);\n outline: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]) {\n &:hover {\n color: var(--trigger-hover-color);\n }\n\n &:active {\n color: var(--trigger-active-color);\n }\n }\n\n &[data-selected=\"true\"] {\n color: var(--trigger-selected-color);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n .list & {\n background-color: var(--trigger-selected-background);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--trigger-underline-color);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--trigger-underline-color);\n }\n }\n\n &[data-focus-visible] {\n background: var(--trigger-focus-background);\n outline: none;\n }\n\n &[data-disabled=\"true\"] {\n --disabled-opacity: 0.5;\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-radius: 0;\n border-bottom: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom: none;\n border-left: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]) {\n border-left-color: var(--trigger-underline-color);\n border-bottom: none;\n }\n }\n\n .trigger-icon {\n @apply flex h-4 w-4 shrink-0 items-center justify-center;\n }\n\n .content {\n @apply w-full p-0 outline-none;\n flex: 1;\n padding-top: 1rem;\n\n &[data-orientation=\"vertical\"] {\n flex: 1;\n width: 100%;\n }\n\n &:focus-visible {\n outline: 2px solid var(--content-outline-color);\n outline-offset: 2px;\n }\n }\n\n @media (max-width: 640px) {\n .list {\n padding: 0.125rem;\n\n &[data-variant=\"underline\"] {\n padding: 0;\n }\n }\n\n .trigger {\n @apply px-1 py-1;\n font-size: var(--text-sm);\n\n .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
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"
4279
+ "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",
4280
+ "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 height: 100%;\n background-color: transparent;\n border: none;\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",
4281
+ "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
4282
  },
4306
4283
  "textarea": {
4307
4284
  "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",
@@ -4314,8 +4291,8 @@ export const generatedSourceCode = {
4314
4291
  "cssTypes": "declare const styles: {\n toast: string;\n icon: string;\n content: string;\n title: string;\n description: string;\n close: string;\n default: string;\n danger: string;\n success: string;\n info: string;\n warning: string;\n};\n\nexport default styles;\n"
4315
4292
  },
4316
4293
  "tooltip": {
4317
- "tsx": "\"use client\";\n\nimport React, { useRef, useState, useEffect, useCallback } from \"react\";\n\nimport { createPortal } from \"react-dom\";\nimport { useTooltipTrigger, useTooltip, mergeProps } from \"react-aria\";\nimport { useFloating } from \"../../hooks/useFloat/react/useFloating\";\nimport { flip } from \"../../hooks/useFloat/core/middleware/flip\";\nimport { offset } from \"../../hooks/useFloat/core/middleware/offset\";\nimport { shift } from \"../../hooks/useFloat/core/middleware/shift\";\nimport { autoUpdate } from \"../../hooks/useFloat/dom/autoUpdate\";\nimport { cn } from \"./utils\";\nimport { useTooltipTriggerState } from \"react-stately\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { Frame } from \"../Frame\";\nimport { Badge } from \"../Badge\";\nimport css from \"./Tooltip.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { type StyleValue } from \"./utils\";\n\nconst ARROW_PATH = \"M 0 0 C 3 0 4 -9 6 -9 C 8 -9 9 0 12 0\";\nconst ARROW_WIDTH = 12;\nconst TOOLTIP_GAP = 4;\nconst ARROW_POSITIONING_SIZE = 6;\nconst DEFAULT_SHOW_DELAY_MS = 600;\nconst SWAP_WINDOW_MS = 150;\nconst EXIT_ANIMATION_MS = 160;\n\nlet lastCloseTime = 0;\nlet lastOpenTime = 0;\nlet pendingExit: (() => void) | null = null;\n\nfunction useTimeout() {\n const idRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const start = useCallback((fn: () => void, ms: number) => {\n clearTimeout(idRef.current);\n idRef.current = setTimeout(fn, ms);\n }, []);\n\n const clear = useCallback(() => {\n clearTimeout(idRef.current);\n }, []);\n\n useEffect(() => () => clearTimeout(idRef.current), []);\n\n return [start, clear] as const;\n}\n\ntype TooltipPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: TooltipPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\ninterface TooltipStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n frame?: StyleValue;\n hintBadge?: StyleValue;\n}\n\ntype TooltipStylesProp = StylesProp<TooltipStyleSlots>;\n\nconst resolveTooltipStyles = createStylesResolver([\n 'root',\n 'trigger',\n 'content',\n 'frame',\n 'hintBadge',\n] as const);\n\nexport interface TooltipProps {\n children: React.ReactNode;\n /** Content to display inside the tooltip */\n content: React.ReactNode;\n /** Preferred side of the trigger where the tooltip appears */\n position?: TooltipPosition;\n /** Additional CSS class for the trigger wrapper */\n className?: string;\n /** Milliseconds before the tooltip appears after hover */\n delay?: number;\n /** Whether the tooltip is disabled */\n isDisabled?: boolean;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the tooltip opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the tooltip */\n hint?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TooltipStylesProp;\n}\n\nconst Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(\n (\n {\n children,\n content,\n position = \"top\",\n className,\n delay = DEFAULT_SHOW_DELAY_MS,\n isDisabled = false,\n isOpen: controlledIsOpen,\n onOpenChange,\n showArrow = false,\n hint,\n styles,\n },\n _ref\n ) => {\n const triggerRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const [shouldRender, setShouldRender] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n const [isInstant, setIsInstant] = useState(false);\n const wasOpenRef = useRef(false);\n const [startSwapTimer, clearSwapTimer] = useTimeout();\n const [startUnmountTimer, clearUnmountTimer] = useTimeout();\n\n const resolved = resolveTooltipStyles(styles);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n\n const state = useTooltipTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange: useCallback((open: boolean) => {\n if (open) lastOpenTime = Date.now();\n else lastCloseTime = Date.now();\n onOpenChangeRef.current?.(open);\n }, []),\n delay,\n isDisabled,\n });\n\n const { triggerProps, tooltipProps } = useTooltipTrigger(\n { isDisabled },\n state,\n triggerRef\n );\n const { tooltipProps: ariaTooltipProps } = useTooltip({}, state);\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: position,\n // Tooltips are commonly used in fixed headers; fixed positioning avoids scroll drift.\n strategy: \"fixed\",\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(TOOLTIP_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n useEffect(() => {\n if (state.isOpen) {\n wasOpenRef.current = true;\n const elapsed = Date.now() - lastCloseTime;\n const isSwap = lastCloseTime > 0 && elapsed < SWAP_WINDOW_MS;\n\n if (pendingExit) {\n pendingExit();\n pendingExit = null;\n }\n\n setIsInstant(isSwap);\n setShouldRender(true);\n } else if (wasOpenRef.current) {\n wasOpenRef.current = false;\n\n if (lastOpenTime > 0 && lastOpenTime >= lastCloseTime) {\n setIsVisible(false);\n setShouldRender(false);\n return;\n }\n\n // Non-batched: delay exit to allow cross-frame swap detection.\n // If another tooltip opens within the window, pendingExit cancels.\n startSwapTimer(() => {\n setIsVisible(false);\n startUnmountTimer(() => {\n setShouldRender(false);\n pendingExit = null;\n }, EXIT_ANIMATION_MS);\n }, SWAP_WINDOW_MS);\n\n pendingExit = () => {\n clearSwapTimer();\n clearUnmountTimer();\n setIsVisible(false);\n setShouldRender(false);\n };\n }\n }, [state.isOpen]);\n\n useEffect(() => {\n if (shouldRender && state.isOpen && isPositioned) {\n if (isInstant) {\n setIsVisible(true);\n const frame = requestAnimationFrame(() => setIsInstant(false));\n return () => cancelAnimationFrame(frame);\n } else {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setIsVisible(true);\n });\n });\n }\n }\n }, [shouldRender, state.isOpen, isPositioned, isInstant]);\n\n const mergedTriggerRef = useCallback((el: HTMLDivElement | null) => {\n (triggerRef as React.MutableRefObject<HTMLDivElement | null>).current = el;\n refs.setReference(el);\n }, [refs]);\n\n const mergedFloatingRef = useCallback((el: HTMLDivElement | null) => {\n (tooltipRef as React.MutableRefObject<HTMLDivElement | null>).current = el;\n refs.setFloating(el);\n }, [refs]);\n\n const trigger = triggerRef.current;\n const isTriggerVisible = !!(trigger && (trigger.offsetWidth > 0 || trigger.offsetHeight > 0));\n\n return (\n <>\n <div\n ref={mergedTriggerRef}\n {...asElementProps<\"div\">(mergeProps(triggerProps))}\n className={cn(css.trigger, className, resolved.trigger)}\n >\n {children}\n </div>\n\n {shouldRender &&\n createPortal(\n <div\n ref={mergedFloatingRef}\n {...asElementProps<\"div\">(mergeProps(tooltipProps, ariaTooltipProps))}\n className={cn(css.root, resolved.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn('tooltip', 'content', css.content, resolved.content)}\n data-visible={(isVisible && isTriggerVisible) ? \"true\" : \"false\"}\n data-instant={(isInstant || !isTriggerVisible) ? \"true\" : undefined}\n style={{\n transform: (isVisible && isTriggerVisible) ? \"scale(1)\" : getInitialTransform(placement),\n }}\n >\n <Frame\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n cornerRadius={8}\n padding=\"none\"\n >\n <div className={cn('tooltip', 'frame', css.frame, resolved.frame)} data-hint={hint ? \"\" : undefined}>\n {content}\n {hint && <Badge variant=\"secondary\" size=\"sm\" className={cn(resolved.hintBadge)}>{hint}</Badge>}\n </div>\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nTooltip.displayName = \"Tooltip\";\n\nexport { Tooltip };\n",
4318
- "css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n\n .frame[data-hint] {\n @apply pr-1;\n }\n}\n",
4294
+ "tsx": "\"use client\";\n\nimport React, { useRef, useState, useEffect, useCallback, useLayoutEffect } from \"react\";\n\nimport { createPortal } from \"react-dom\";\nimport { 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 triggerWrapperRef = useRef<HTMLSpanElement>(null);\n const triggerRef = useRef<HTMLElement>(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 { 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: HTMLElement | null) => {\n (triggerRef as React.MutableRefObject<HTMLElement | 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 useLayoutEffect(() => {\n const wrapper = triggerWrapperRef.current;\n const reference = wrapper?.firstElementChild instanceof HTMLElement\n ? wrapper.firstElementChild\n : wrapper;\n\n (triggerRef as React.MutableRefObject<HTMLElement | null>).current = reference;\n refs.setReference(reference);\n }, [children, refs]);\n\n useEffect(() => {\n const trigger = triggerRef.current;\n if (!trigger || isDisabled) {\n return;\n }\n\n const handleMouseEnter = () => state.open();\n const handleMouseLeave = () => state.close();\n const handleFocusIn = () => state.open(true);\n const handleFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget instanceof Node && trigger.contains(event.relatedTarget)) {\n return;\n }\n\n state.close(true);\n };\n\n trigger.addEventListener(\"mouseenter\", handleMouseEnter);\n trigger.addEventListener(\"mouseleave\", handleMouseLeave);\n trigger.addEventListener(\"focusin\", handleFocusIn);\n trigger.addEventListener(\"focusout\", handleFocusOut);\n\n return () => {\n trigger.removeEventListener(\"mouseenter\", handleMouseEnter);\n trigger.removeEventListener(\"mouseleave\", handleMouseLeave);\n trigger.removeEventListener(\"focusin\", handleFocusIn);\n trigger.removeEventListener(\"focusout\", handleFocusOut);\n };\n }, [children, isDisabled, state]);\n\n const trigger = triggerRef.current;\n const isTriggerVisible = !!(trigger && (trigger.offsetWidth > 0 || trigger.offsetHeight > 0));\n const child = React.Children.only(children);\n\n const triggerElement = React.isValidElement(child) && child.type !== React.Fragment\n ? (\n <span\n ref={triggerWrapperRef}\n className={cn(css.trigger, className, resolved.trigger)}\n >\n {child}\n </span>\n )\n : (\n <span\n ref={mergedTriggerRef}\n className={cn(css.trigger, className, resolved.trigger)}\n style={{ display: \"inline-block\" }}\n >\n {children}\n </span>\n );\n\n return (\n <>\n {triggerElement}\n\n {shouldRender &&\n createPortal(\n <div\n ref={mergedFloatingRef}\n {...asElementProps<\"div\">(mergeProps(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",
4295
+ "css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n display: contents;\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
4296
  "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
4297
  }
4321
4298
  };
@@ -4551,6 +4528,6 @@ export const generatedCorePeerDependencies = [
4551
4528
  "react-dom"
4552
4529
  ];
4553
4530
  export const packageMetadata = {
4554
- "version": "0.3.3"
4531
+ "version": "0.3.41"
4555
4532
  };
4556
4533
  //# sourceMappingURL=generated-data.js.map