@nofinite/nui 1.0.2 → 1.1.1

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 (182) hide show
  1. package/dist/components/accordion/Accordion.cjs.map +1 -1
  2. package/dist/components/accordion/Accordion.js.map +1 -1
  3. package/dist/components/alert/Alert.cjs.map +1 -1
  4. package/dist/components/alert/Alert.js.map +1 -1
  5. package/dist/components/avatar/Avatar.cjs.map +1 -1
  6. package/dist/components/avatar/Avatar.js.map +1 -1
  7. package/dist/components/avatar/AvatarGroup.cjs.map +1 -1
  8. package/dist/components/avatar/AvatarGroup.js.map +1 -1
  9. package/dist/components/badge/Badge.cjs.map +1 -1
  10. package/dist/components/badge/Badge.js.map +1 -1
  11. package/dist/components/badge/BadgeGroup.cjs.map +1 -1
  12. package/dist/components/badge/BadgeGroup.js.map +1 -1
  13. package/dist/components/breadcrumbs/Breadcrumbs.cjs.map +1 -1
  14. package/dist/components/breadcrumbs/Breadcrumbs.js.map +1 -1
  15. package/dist/components/button/Button.cjs.map +1 -1
  16. package/dist/components/button/Button.js.map +1 -1
  17. package/dist/components/card/Card.cjs.map +1 -1
  18. package/dist/components/card/Card.js.map +1 -1
  19. package/dist/components/checkbox/Checkbox.cjs.map +1 -1
  20. package/dist/components/checkbox/Checkbox.js.map +1 -1
  21. package/dist/components/chip/Chip.cjs.map +1 -1
  22. package/dist/components/chip/Chip.js.map +1 -1
  23. package/dist/components/combobox/Combobox.cjs.map +1 -1
  24. package/dist/components/combobox/Combobox.js.map +1 -1
  25. package/dist/components/commandpalette/CommandPalette.cjs.map +1 -1
  26. package/dist/components/commandpalette/CommandPalette.js.map +1 -1
  27. package/dist/components/contextmenu/ContextMenu.cjs.map +1 -1
  28. package/dist/components/contextmenu/ContextMenu.js.map +1 -1
  29. package/dist/components/datagrid/DataGrid.cjs.map +1 -1
  30. package/dist/components/datagrid/DataGrid.js.map +1 -1
  31. package/dist/components/datepicker/DatePicker.cjs.map +1 -1
  32. package/dist/components/datepicker/DatePicker.js.map +1 -1
  33. package/dist/components/daterangepicker/DateRangePicker.cjs.map +1 -1
  34. package/dist/components/daterangepicker/DateRangePicker.js.map +1 -1
  35. package/dist/components/drawer/Drawer.cjs.map +1 -1
  36. package/dist/components/drawer/Drawer.js.map +1 -1
  37. package/dist/components/dropdown/Dropdown.cjs.map +1 -1
  38. package/dist/components/dropdown/Dropdown.js.map +1 -1
  39. package/dist/components/fileuploader/FileUploader.cjs.map +1 -1
  40. package/dist/components/fileuploader/FileUploader.js.map +1 -1
  41. package/dist/components/hovercard/HoverCard.cjs.map +1 -1
  42. package/dist/components/hovercard/HoverCard.js.map +1 -1
  43. package/dist/components/input/Input.cjs.map +1 -1
  44. package/dist/components/input/Input.js.map +1 -1
  45. package/dist/components/layout/Container.cjs.map +1 -1
  46. package/dist/components/layout/Container.js.map +1 -1
  47. package/dist/components/layout/Flex.cjs.map +1 -1
  48. package/dist/components/layout/Flex.js.map +1 -1
  49. package/dist/components/layout/Grid.cjs.map +1 -1
  50. package/dist/components/layout/Grid.js.map +1 -1
  51. package/dist/components/layout/HStack.cjs.map +1 -1
  52. package/dist/components/layout/HStack.js.map +1 -1
  53. package/dist/components/layout/Stack.cjs.map +1 -1
  54. package/dist/components/layout/Stack.js.map +1 -1
  55. package/dist/components/megamenu/MegaMenu.cjs.map +1 -1
  56. package/dist/components/megamenu/MegaMenu.js.map +1 -1
  57. package/dist/components/modal/Modal.cjs.map +1 -1
  58. package/dist/components/modal/Modal.js.map +1 -1
  59. package/dist/components/pagination/Pagination.cjs.map +1 -1
  60. package/dist/components/pagination/Pagination.js.map +1 -1
  61. package/dist/components/popover/Popover.cjs.map +1 -1
  62. package/dist/components/popover/Popover.js.map +1 -1
  63. package/dist/components/progress/Progress.cjs.map +1 -1
  64. package/dist/components/progress/Progress.js.map +1 -1
  65. package/dist/components/radiogroup/RadioGroup.cjs.map +1 -1
  66. package/dist/components/radiogroup/RadioGroup.js.map +1 -1
  67. package/dist/components/rating/Rating.cjs.map +1 -1
  68. package/dist/components/rating/Rating.js.map +1 -1
  69. package/dist/components/select/Select.cjs +2 -0
  70. package/dist/components/select/Select.cjs.map +1 -0
  71. package/dist/components/select/Select.js +165 -0
  72. package/dist/components/select/Select.js.map +1 -0
  73. package/dist/components/skeleton/Skeleton.cjs.map +1 -1
  74. package/dist/components/skeleton/Skeleton.js.map +1 -1
  75. package/dist/components/slider/Slider.cjs.map +1 -1
  76. package/dist/components/slider/Slider.js.map +1 -1
  77. package/dist/components/spinner/Spinner.cjs.map +1 -1
  78. package/dist/components/spinner/Spinner.js.map +1 -1
  79. package/dist/components/stepper/Stepper.cjs.map +1 -1
  80. package/dist/components/stepper/Stepper.js.map +1 -1
  81. package/dist/components/switch/Switch.cjs.map +1 -1
  82. package/dist/components/switch/Switch.js.map +1 -1
  83. package/dist/components/table/Table.cjs.map +1 -1
  84. package/dist/components/table/Table.js.map +1 -1
  85. package/dist/components/tabs/Tabs.cjs.map +1 -1
  86. package/dist/components/tabs/Tabs.js.map +1 -1
  87. package/dist/components/textarea/Textarea.cjs.map +1 -1
  88. package/dist/components/textarea/Textarea.js.map +1 -1
  89. package/dist/components/toast/Toast.cjs.map +1 -1
  90. package/dist/components/toast/Toast.js.map +1 -1
  91. package/dist/components/tooltip/Tooltip.cjs.map +1 -1
  92. package/dist/components/tooltip/Tooltip.js.map +1 -1
  93. package/dist/components/treeview/TreeView.cjs.map +1 -1
  94. package/dist/components/treeview/TreeView.js.map +1 -1
  95. package/dist/components/virtuallist/VirtualList.cjs.map +1 -1
  96. package/dist/components/virtuallist/VirtualList.js.map +1 -1
  97. package/dist/index.cjs +1 -1
  98. package/dist/index.js +58 -56
  99. package/dist/index.js.map +1 -1
  100. package/dist/package.json +33 -0
  101. package/dist/styles/{index.css → nui.css} +1 -1
  102. package/dist/theme/NUIProvider.cjs.map +1 -1
  103. package/dist/theme/NUIProvider.js.map +1 -1
  104. package/dist/theme/useTheme.cjs.map +1 -1
  105. package/dist/theme/useTheme.js.map +1 -1
  106. package/dist/types/components/accordion/Accordion.d.ts.map +1 -1
  107. package/dist/types/components/alert/Alert.d.ts.map +1 -1
  108. package/dist/types/components/avatar/Avatar.d.ts.map +1 -1
  109. package/dist/types/components/avatar/AvatarGroup.d.ts.map +1 -1
  110. package/dist/types/components/badge/Badge.d.ts.map +1 -1
  111. package/dist/types/components/badge/BadgeGroup.d.ts.map +1 -1
  112. package/dist/types/components/breadcrumbs/Breadcrumbs.d.ts.map +1 -1
  113. package/dist/types/components/button/Button.d.ts.map +1 -1
  114. package/dist/types/components/card/Card.d.ts.map +1 -1
  115. package/dist/types/components/checkbox/Checkbox.d.ts.map +1 -1
  116. package/dist/types/components/chip/Chip.d.ts.map +1 -1
  117. package/dist/types/components/combobox/Combobox.d.ts.map +1 -1
  118. package/dist/types/components/commandpalette/CommandPalette.d.ts.map +1 -1
  119. package/dist/types/components/contextmenu/ContextMenu.d.ts.map +1 -1
  120. package/dist/types/components/datagrid/DataGrid.d.ts.map +1 -1
  121. package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
  122. package/dist/types/components/daterangepicker/DateRangePicker.d.ts.map +1 -1
  123. package/dist/types/components/drawer/Drawer.d.ts.map +1 -1
  124. package/dist/types/components/dropdown/Dropdown.d.ts.map +1 -1
  125. package/dist/types/components/fileuploader/FileUploader.d.ts.map +1 -1
  126. package/dist/types/components/hovercard/HoverCard.d.ts.map +1 -1
  127. package/dist/types/components/input/Input.d.ts.map +1 -1
  128. package/dist/types/components/layout/Container.d.ts.map +1 -1
  129. package/dist/types/components/layout/Flex.d.ts.map +1 -1
  130. package/dist/types/components/layout/Grid.d.ts.map +1 -1
  131. package/dist/types/components/layout/HStack.d.ts.map +1 -1
  132. package/dist/types/components/layout/Stack.d.ts.map +1 -1
  133. package/dist/types/components/megamenu/MegaMenu.d.ts.map +1 -1
  134. package/dist/types/components/modal/Modal.d.ts.map +1 -1
  135. package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
  136. package/dist/types/components/popover/Popover.d.ts.map +1 -1
  137. package/dist/types/components/progress/Progress.d.ts.map +1 -1
  138. package/dist/types/components/radiogroup/RadioGroup.d.ts.map +1 -1
  139. package/dist/types/components/rating/Rating.d.ts.map +1 -1
  140. package/dist/types/components/select/Select.d.ts.map +1 -1
  141. package/dist/types/components/skeleton/Skeleton.d.ts.map +1 -1
  142. package/dist/types/components/slider/Slider.d.ts.map +1 -1
  143. package/dist/types/components/spinner/Spinner.d.ts.map +1 -1
  144. package/dist/types/components/stepper/Stepper.d.ts.map +1 -1
  145. package/dist/types/components/switch/Switch.d.ts.map +1 -1
  146. package/dist/types/components/table/Table.d.ts.map +1 -1
  147. package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
  148. package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
  149. package/dist/types/components/toast/Toast.d.ts.map +1 -1
  150. package/dist/types/components/tooltip/Tooltip.d.ts.map +1 -1
  151. package/dist/types/components/treeview/TreeView.d.ts.map +1 -1
  152. package/dist/types/components/virtuallist/VirtualList.d.ts.map +1 -1
  153. package/dist/types/index.d.ts +1 -0
  154. package/dist/types/index.d.ts.map +1 -1
  155. package/dist/types/theme/NUIProvider.d.ts.map +1 -1
  156. package/dist/types/theme/useTheme.d.ts.map +1 -1
  157. package/dist/types/utils/generateid/generateId.d.ts.map +1 -1
  158. package/dist/types/utils/index.d.ts.map +1 -1
  159. package/dist/types/utils/inertmanager/inertManager.d.ts.map +1 -1
  160. package/dist/types/utils/keyboardnav/keyboardNav.d.ts.map +1 -1
  161. package/dist/types/utils/onclickoutside/onClickOutside.d.ts.map +1 -1
  162. package/dist/types/utils/portal/portal.d.ts.map +1 -1
  163. package/dist/types/utils/restorefocus/restoreFocus.d.ts.map +1 -1
  164. package/dist/types/utils/scrolllock/scrollLock.d.ts.map +1 -1
  165. package/dist/types/utils/trapfocus/trapFocus.d.ts.map +1 -1
  166. package/dist/utils/generateid/generateId.cjs.map +1 -1
  167. package/dist/utils/generateid/generateId.js.map +1 -1
  168. package/dist/utils/inertmanager/inertManager.cjs.map +1 -1
  169. package/dist/utils/inertmanager/inertManager.js.map +1 -1
  170. package/dist/utils/keyboardnav/keyboardNav.cjs.map +1 -1
  171. package/dist/utils/keyboardnav/keyboardNav.js.map +1 -1
  172. package/dist/utils/onclickoutside/onClickOutside.cjs.map +1 -1
  173. package/dist/utils/onclickoutside/onClickOutside.js.map +1 -1
  174. package/dist/utils/portal/portal.cjs.map +1 -1
  175. package/dist/utils/portal/portal.js.map +1 -1
  176. package/dist/utils/restorefocus/restoreFocus.cjs.map +1 -1
  177. package/dist/utils/restorefocus/restoreFocus.js.map +1 -1
  178. package/dist/utils/scrolllock/scrollLock.cjs.map +1 -1
  179. package/dist/utils/scrolllock/scrollLock.js.map +1 -1
  180. package/dist/utils/trapfocus/trapFocus.cjs.map +1 -1
  181. package/dist/utils/trapfocus/trapFocus.js.map +1 -1
  182. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.cjs","sources":["../../../../src/components/popover/Popover.tsx"],"sourcesContent":["import React, {\r\n createContext,\r\n useContext,\r\n useRef,\r\n useEffect,\r\n useState,\r\n useLayoutEffect,\r\n useId,\r\n} from 'react';\r\n\r\nimport './Popover.css';\r\n\r\n// Import your existing utilities\r\n// Ensure these paths match your folder structure exactly!\r\nimport { Portal, onClickOutside, restoreFocus, trapFocus } from '../../utils/index'; \r\n\r\ntype Placement = 'top' | 'bottom' | 'left' | 'right';\r\n\r\nconst PopoverContext = createContext<{\r\n open: boolean;\r\n setOpen: React.Dispatch<React.SetStateAction<boolean>>;\r\n triggerRef: React.RefObject<HTMLElement | null>;\r\n contentRef: React.RefObject<HTMLDivElement | null>;\r\n contentId: string;\r\n} | null>(null);\r\n\r\nfunction usePopover() {\r\n const ctx = useContext(PopoverContext);\r\n if (!ctx) throw new Error('Popover components must be inside <Popover>');\r\n return ctx;\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Root\r\n * ------------------------------------------------------*/\r\nexport function Popover({ children }: { children: React.ReactNode }) {\r\n const [open, setOpen] = useState(false);\r\n const triggerRef = useRef<HTMLElement | null>(null);\r\n const contentRef = useRef<HTMLDivElement | null>(null);\r\n const contentId = `popover-${useId()}`;\r\n\r\n // Restore focus to trigger on close\r\n useEffect(() => {\r\n if (!open) {\r\n restoreFocus(triggerRef.current);\r\n }\r\n }, [open]);\r\n\r\n return (\r\n <PopoverContext.Provider\r\n value={{ open, setOpen, triggerRef, contentRef, contentId }}\r\n >\r\n {/* Wrapper ensures trigger stays in document flow */}\r\n <div className=\"ui-popover-root\">{children}</div>\r\n </PopoverContext.Provider>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Trigger\r\n * ------------------------------------------------------*/\r\nexport function PopoverTrigger({\r\n children,\r\n className = '',\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { open, setOpen, triggerRef, contentId } = usePopover();\r\n\r\n return (\r\n <button\r\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\r\n type=\"button\"\r\n className={`ui-popover-trigger ${className}`}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n aria-controls={open ? contentId : undefined}\r\n onClick={(e) => {\r\n // Stop propagation to prevent immediate close by external listeners\r\n e.stopPropagation();\r\n setOpen((s) => !s);\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && open) {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n }}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Content (Portaled & Positioned)\r\n * ------------------------------------------------------*/\r\nexport function PopoverContent({\r\n children,\r\n className = '',\r\n placement = 'bottom',\r\n offset = 8,\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n placement?: Placement;\r\n offset?: number;\r\n}) {\r\n const { open, setOpen, triggerRef, contentRef, contentId } = usePopover();\r\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\r\n\r\n // 1. Trap Focus when open (FIXED RETURN TYPE)\r\n useEffect(() => {\r\n if (open && contentRef.current) {\r\n // Return the cleanup function from trapFocus\r\n return trapFocus(contentRef.current);\r\n }\r\n // Explicitly return undefined so TS knows we handled the \"else\" case\r\n return undefined;\r\n }, [open, contentRef]);\r\n\r\n // 2. Click Outside Logic\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const cleanup = onClickOutside(contentRef, () => setOpen(false));\r\n return cleanup;\r\n }, [open, setOpen, contentRef, triggerRef]);\r\n\r\n // 3. Global Escape Key\r\n useEffect(() => {\r\n if (!open) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [open, setOpen]);\r\n\r\n // 4. Calculate Position\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) return;\r\n\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n\r\n let top = 0;\r\n let left = 0;\r\n\r\n switch (placement) {\r\n case 'bottom':\r\n top = rect.bottom + offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'top':\r\n top = rect.top - offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'left':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.left - offset + scrollX;\r\n break;\r\n case 'right':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.right + offset + scrollX;\r\n break;\r\n }\r\n\r\n setCoords({ top, left });\r\n }, [open, placement, offset, triggerRef]);\r\n\r\n if (!open) return null;\r\n\r\n return (\r\n <Portal>\r\n <div\r\n id={contentId}\r\n ref={contentRef}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n className={`ui-popover-content ui-popover-${placement} ${className}`}\r\n style={{\r\n position: 'absolute',\r\n top: coords?.top ?? 0,\r\n left: coords?.left ?? 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </Portal>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Close Button\r\n * ------------------------------------------------------*/\r\nexport function PopoverClose({\r\n children = 'Close',\r\n className = '',\r\n}: {\r\n children?: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { setOpen } = usePopover();\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n className={`ui-popover-close ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"],"names":["PopoverContext","createContext","usePopover","ctx","useContext","Popover","children","open","setOpen","useState","triggerRef","useRef","contentRef","contentId","useId","useEffect","restoreFocus","jsx","PopoverTrigger","className","e","s","PopoverContent","placement","offset","coords","setCoords","trapFocus","onClickOutside","handler","useLayoutEffect","rect","scrollX","scrollY","top","left","Portal","PopoverClose"],"mappings":"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"}
1
+ {"version":3,"file":"Popover.cjs","sources":["../../../src/components/popover/Popover.tsx"],"sourcesContent":["import React, {\r\n createContext,\r\n useContext,\r\n useRef,\r\n useEffect,\r\n useState,\r\n useLayoutEffect,\r\n useId,\r\n} from 'react';\r\n\r\nimport './Popover.css';\r\n\r\n// Import your existing utilities\r\n// Ensure these paths match your folder structure exactly!\r\nimport { Portal, onClickOutside, restoreFocus, trapFocus } from '../../utils/index'; \r\n\r\ntype Placement = 'top' | 'bottom' | 'left' | 'right';\r\n\r\nconst PopoverContext = createContext<{\r\n open: boolean;\r\n setOpen: React.Dispatch<React.SetStateAction<boolean>>;\r\n triggerRef: React.RefObject<HTMLElement | null>;\r\n contentRef: React.RefObject<HTMLDivElement | null>;\r\n contentId: string;\r\n} | null>(null);\r\n\r\nfunction usePopover() {\r\n const ctx = useContext(PopoverContext);\r\n if (!ctx) throw new Error('Popover components must be inside <Popover>');\r\n return ctx;\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Root\r\n * ------------------------------------------------------*/\r\nexport function Popover({ children }: { children: React.ReactNode }) {\r\n const [open, setOpen] = useState(false);\r\n const triggerRef = useRef<HTMLElement | null>(null);\r\n const contentRef = useRef<HTMLDivElement | null>(null);\r\n const contentId = `popover-${useId()}`;\r\n\r\n // Restore focus to trigger on close\r\n useEffect(() => {\r\n if (!open) {\r\n restoreFocus(triggerRef.current);\r\n }\r\n }, [open]);\r\n\r\n return (\r\n <PopoverContext.Provider\r\n value={{ open, setOpen, triggerRef, contentRef, contentId }}\r\n >\r\n {/* Wrapper ensures trigger stays in document flow */}\r\n <div className=\"ui-popover-root\">{children}</div>\r\n </PopoverContext.Provider>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Trigger\r\n * ------------------------------------------------------*/\r\nexport function PopoverTrigger({\r\n children,\r\n className = '',\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { open, setOpen, triggerRef, contentId } = usePopover();\r\n\r\n return (\r\n <button\r\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\r\n type=\"button\"\r\n className={`ui-popover-trigger ${className}`}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n aria-controls={open ? contentId : undefined}\r\n onClick={(e) => {\r\n // Stop propagation to prevent immediate close by external listeners\r\n e.stopPropagation();\r\n setOpen((s) => !s);\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && open) {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n }}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Content (Portaled & Positioned)\r\n * ------------------------------------------------------*/\r\nexport function PopoverContent({\r\n children,\r\n className = '',\r\n placement = 'bottom',\r\n offset = 8,\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n placement?: Placement;\r\n offset?: number;\r\n}) {\r\n const { open, setOpen, triggerRef, contentRef, contentId } = usePopover();\r\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\r\n\r\n // 1. Trap Focus when open (FIXED RETURN TYPE)\r\n useEffect(() => {\r\n if (open && contentRef.current) {\r\n // Return the cleanup function from trapFocus\r\n return trapFocus(contentRef.current);\r\n }\r\n // Explicitly return undefined so TS knows we handled the \"else\" case\r\n return undefined;\r\n }, [open, contentRef]);\r\n\r\n // 2. Click Outside Logic\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const cleanup = onClickOutside(contentRef, () => setOpen(false));\r\n return cleanup;\r\n }, [open, setOpen, contentRef, triggerRef]);\r\n\r\n // 3. Global Escape Key\r\n useEffect(() => {\r\n if (!open) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [open, setOpen]);\r\n\r\n // 4. Calculate Position\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) return;\r\n\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n\r\n let top = 0;\r\n let left = 0;\r\n\r\n switch (placement) {\r\n case 'bottom':\r\n top = rect.bottom + offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'top':\r\n top = rect.top - offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'left':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.left - offset + scrollX;\r\n break;\r\n case 'right':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.right + offset + scrollX;\r\n break;\r\n }\r\n\r\n setCoords({ top, left });\r\n }, [open, placement, offset, triggerRef]);\r\n\r\n if (!open) return null;\r\n\r\n return (\r\n <Portal>\r\n <div\r\n id={contentId}\r\n ref={contentRef}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n className={`ui-popover-content ui-popover-${placement} ${className}`}\r\n style={{\r\n position: 'absolute',\r\n top: coords?.top ?? 0,\r\n left: coords?.left ?? 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </Portal>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Close Button\r\n * ------------------------------------------------------*/\r\nexport function PopoverClose({\r\n children = 'Close',\r\n className = '',\r\n}: {\r\n children?: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { setOpen } = usePopover();\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n className={`ui-popover-close ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"],"names":["PopoverContext","createContext","usePopover","ctx","useContext","Popover","children","open","setOpen","useState","triggerRef","useRef","contentRef","contentId","useId","useEffect","restoreFocus","jsx","PopoverTrigger","className","e","s","PopoverContent","placement","offset","coords","setCoords","trapFocus","onClickOutside","handler","useLayoutEffect","rect","scrollX","scrollY","top","left","Portal","PopoverClose"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.js","sources":["../../../../src/components/popover/Popover.tsx"],"sourcesContent":["import React, {\r\n createContext,\r\n useContext,\r\n useRef,\r\n useEffect,\r\n useState,\r\n useLayoutEffect,\r\n useId,\r\n} from 'react';\r\n\r\nimport './Popover.css';\r\n\r\n// Import your existing utilities\r\n// Ensure these paths match your folder structure exactly!\r\nimport { Portal, onClickOutside, restoreFocus, trapFocus } from '../../utils/index'; \r\n\r\ntype Placement = 'top' | 'bottom' | 'left' | 'right';\r\n\r\nconst PopoverContext = createContext<{\r\n open: boolean;\r\n setOpen: React.Dispatch<React.SetStateAction<boolean>>;\r\n triggerRef: React.RefObject<HTMLElement | null>;\r\n contentRef: React.RefObject<HTMLDivElement | null>;\r\n contentId: string;\r\n} | null>(null);\r\n\r\nfunction usePopover() {\r\n const ctx = useContext(PopoverContext);\r\n if (!ctx) throw new Error('Popover components must be inside <Popover>');\r\n return ctx;\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Root\r\n * ------------------------------------------------------*/\r\nexport function Popover({ children }: { children: React.ReactNode }) {\r\n const [open, setOpen] = useState(false);\r\n const triggerRef = useRef<HTMLElement | null>(null);\r\n const contentRef = useRef<HTMLDivElement | null>(null);\r\n const contentId = `popover-${useId()}`;\r\n\r\n // Restore focus to trigger on close\r\n useEffect(() => {\r\n if (!open) {\r\n restoreFocus(triggerRef.current);\r\n }\r\n }, [open]);\r\n\r\n return (\r\n <PopoverContext.Provider\r\n value={{ open, setOpen, triggerRef, contentRef, contentId }}\r\n >\r\n {/* Wrapper ensures trigger stays in document flow */}\r\n <div className=\"ui-popover-root\">{children}</div>\r\n </PopoverContext.Provider>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Trigger\r\n * ------------------------------------------------------*/\r\nexport function PopoverTrigger({\r\n children,\r\n className = '',\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { open, setOpen, triggerRef, contentId } = usePopover();\r\n\r\n return (\r\n <button\r\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\r\n type=\"button\"\r\n className={`ui-popover-trigger ${className}`}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n aria-controls={open ? contentId : undefined}\r\n onClick={(e) => {\r\n // Stop propagation to prevent immediate close by external listeners\r\n e.stopPropagation();\r\n setOpen((s) => !s);\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && open) {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n }}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Content (Portaled & Positioned)\r\n * ------------------------------------------------------*/\r\nexport function PopoverContent({\r\n children,\r\n className = '',\r\n placement = 'bottom',\r\n offset = 8,\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n placement?: Placement;\r\n offset?: number;\r\n}) {\r\n const { open, setOpen, triggerRef, contentRef, contentId } = usePopover();\r\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\r\n\r\n // 1. Trap Focus when open (FIXED RETURN TYPE)\r\n useEffect(() => {\r\n if (open && contentRef.current) {\r\n // Return the cleanup function from trapFocus\r\n return trapFocus(contentRef.current);\r\n }\r\n // Explicitly return undefined so TS knows we handled the \"else\" case\r\n return undefined;\r\n }, [open, contentRef]);\r\n\r\n // 2. Click Outside Logic\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const cleanup = onClickOutside(contentRef, () => setOpen(false));\r\n return cleanup;\r\n }, [open, setOpen, contentRef, triggerRef]);\r\n\r\n // 3. Global Escape Key\r\n useEffect(() => {\r\n if (!open) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [open, setOpen]);\r\n\r\n // 4. Calculate Position\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) return;\r\n\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n\r\n let top = 0;\r\n let left = 0;\r\n\r\n switch (placement) {\r\n case 'bottom':\r\n top = rect.bottom + offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'top':\r\n top = rect.top - offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'left':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.left - offset + scrollX;\r\n break;\r\n case 'right':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.right + offset + scrollX;\r\n break;\r\n }\r\n\r\n setCoords({ top, left });\r\n }, [open, placement, offset, triggerRef]);\r\n\r\n if (!open) return null;\r\n\r\n return (\r\n <Portal>\r\n <div\r\n id={contentId}\r\n ref={contentRef}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n className={`ui-popover-content ui-popover-${placement} ${className}`}\r\n style={{\r\n position: 'absolute',\r\n top: coords?.top ?? 0,\r\n left: coords?.left ?? 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </Portal>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Close Button\r\n * ------------------------------------------------------*/\r\nexport function PopoverClose({\r\n children = 'Close',\r\n className = '',\r\n}: {\r\n children?: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { setOpen } = usePopover();\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n className={`ui-popover-close ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"],"names":["PopoverContext","createContext","usePopover","ctx","useContext","Popover","children","open","setOpen","useState","triggerRef","useRef","contentRef","contentId","useId","useEffect","restoreFocus","jsx","PopoverTrigger","className","e","s","PopoverContent","placement","offset","coords","setCoords","trapFocus","onClickOutside","handler","useLayoutEffect","rect","scrollX","scrollY","top","left","Portal","PopoverClose"],"mappings":"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"}
1
+ {"version":3,"file":"Popover.js","sources":["../../../src/components/popover/Popover.tsx"],"sourcesContent":["import React, {\r\n createContext,\r\n useContext,\r\n useRef,\r\n useEffect,\r\n useState,\r\n useLayoutEffect,\r\n useId,\r\n} from 'react';\r\n\r\nimport './Popover.css';\r\n\r\n// Import your existing utilities\r\n// Ensure these paths match your folder structure exactly!\r\nimport { Portal, onClickOutside, restoreFocus, trapFocus } from '../../utils/index'; \r\n\r\ntype Placement = 'top' | 'bottom' | 'left' | 'right';\r\n\r\nconst PopoverContext = createContext<{\r\n open: boolean;\r\n setOpen: React.Dispatch<React.SetStateAction<boolean>>;\r\n triggerRef: React.RefObject<HTMLElement | null>;\r\n contentRef: React.RefObject<HTMLDivElement | null>;\r\n contentId: string;\r\n} | null>(null);\r\n\r\nfunction usePopover() {\r\n const ctx = useContext(PopoverContext);\r\n if (!ctx) throw new Error('Popover components must be inside <Popover>');\r\n return ctx;\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Root\r\n * ------------------------------------------------------*/\r\nexport function Popover({ children }: { children: React.ReactNode }) {\r\n const [open, setOpen] = useState(false);\r\n const triggerRef = useRef<HTMLElement | null>(null);\r\n const contentRef = useRef<HTMLDivElement | null>(null);\r\n const contentId = `popover-${useId()}`;\r\n\r\n // Restore focus to trigger on close\r\n useEffect(() => {\r\n if (!open) {\r\n restoreFocus(triggerRef.current);\r\n }\r\n }, [open]);\r\n\r\n return (\r\n <PopoverContext.Provider\r\n value={{ open, setOpen, triggerRef, contentRef, contentId }}\r\n >\r\n {/* Wrapper ensures trigger stays in document flow */}\r\n <div className=\"ui-popover-root\">{children}</div>\r\n </PopoverContext.Provider>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Trigger\r\n * ------------------------------------------------------*/\r\nexport function PopoverTrigger({\r\n children,\r\n className = '',\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { open, setOpen, triggerRef, contentId } = usePopover();\r\n\r\n return (\r\n <button\r\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\r\n type=\"button\"\r\n className={`ui-popover-trigger ${className}`}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n aria-controls={open ? contentId : undefined}\r\n onClick={(e) => {\r\n // Stop propagation to prevent immediate close by external listeners\r\n e.stopPropagation();\r\n setOpen((s) => !s);\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && open) {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n }}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Content (Portaled & Positioned)\r\n * ------------------------------------------------------*/\r\nexport function PopoverContent({\r\n children,\r\n className = '',\r\n placement = 'bottom',\r\n offset = 8,\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n placement?: Placement;\r\n offset?: number;\r\n}) {\r\n const { open, setOpen, triggerRef, contentRef, contentId } = usePopover();\r\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\r\n\r\n // 1. Trap Focus when open (FIXED RETURN TYPE)\r\n useEffect(() => {\r\n if (open && contentRef.current) {\r\n // Return the cleanup function from trapFocus\r\n return trapFocus(contentRef.current);\r\n }\r\n // Explicitly return undefined so TS knows we handled the \"else\" case\r\n return undefined;\r\n }, [open, contentRef]);\r\n\r\n // 2. Click Outside Logic\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const cleanup = onClickOutside(contentRef, () => setOpen(false));\r\n return cleanup;\r\n }, [open, setOpen, contentRef, triggerRef]);\r\n\r\n // 3. Global Escape Key\r\n useEffect(() => {\r\n if (!open) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [open, setOpen]);\r\n\r\n // 4. Calculate Position\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) return;\r\n\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n\r\n let top = 0;\r\n let left = 0;\r\n\r\n switch (placement) {\r\n case 'bottom':\r\n top = rect.bottom + offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'top':\r\n top = rect.top - offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'left':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.left - offset + scrollX;\r\n break;\r\n case 'right':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.right + offset + scrollX;\r\n break;\r\n }\r\n\r\n setCoords({ top, left });\r\n }, [open, placement, offset, triggerRef]);\r\n\r\n if (!open) return null;\r\n\r\n return (\r\n <Portal>\r\n <div\r\n id={contentId}\r\n ref={contentRef}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n className={`ui-popover-content ui-popover-${placement} ${className}`}\r\n style={{\r\n position: 'absolute',\r\n top: coords?.top ?? 0,\r\n left: coords?.left ?? 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </Portal>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Close Button\r\n * ------------------------------------------------------*/\r\nexport function PopoverClose({\r\n children = 'Close',\r\n className = '',\r\n}: {\r\n children?: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { setOpen } = usePopover();\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n className={`ui-popover-close ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"],"names":["PopoverContext","createContext","usePopover","ctx","useContext","Popover","children","open","setOpen","useState","triggerRef","useRef","contentRef","contentId","useId","useEffect","restoreFocus","jsx","PopoverTrigger","className","e","s","PopoverContent","placement","offset","coords","setCoords","trapFocus","onClickOutside","handler","useLayoutEffect","rect","scrollX","scrollY","top","left","Portal","PopoverClose"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Progress.cjs","sources":["../../../../src/components/progress/Progress.tsx"],"sourcesContent":["/**\r\n * Progress.tsx — FINAL VERSION\r\n * --------------------------------\r\n * Features:\r\n * - Determinate mode (value/max)\r\n * - Indeterminate mode\r\n * - Smooth animation\r\n * - ARIA compliant\r\n * - Pure TSX + CSS (no inline styles except width)\r\n */\r\n\r\nimport './Progress.css';\r\n\r\nexport interface ProgressProps {\r\n /** Current value (determinate mode). */\r\n value?: number;\r\n\r\n /** Maximum value (default: 100). */\r\n max?: number;\r\n\r\n /** Indeterminate mode when value is undefined. */\r\n indeterminate?: boolean;\r\n\r\n /** Optional label for screen readers. */\r\n label?: string;\r\n\r\n className?: string;\r\n}\r\n\r\nexport function Progress({\r\n value,\r\n max = 100,\r\n indeterminate = false,\r\n label = 'Progress',\r\n className = '',\r\n}: ProgressProps) {\r\n const isIndeterminate = indeterminate || value === undefined;\r\n\r\n const percentage = Math.min(Math.max((value ?? 0) / max, 0), 1) * 100;\r\n\r\n return (\r\n <div\r\n className={`ui-progress ${className}`}\r\n role=\"progressbar\"\r\n aria-valuemin={isIndeterminate ? undefined : 0}\r\n aria-valuemax={isIndeterminate ? undefined : max}\r\n aria-valuenow={isIndeterminate ? undefined : value}\r\n aria-label={label}\r\n >\r\n <div\r\n className={`ui-progress-bar ${isIndeterminate ? 'indeterminate' : ''}`}\r\n style={!isIndeterminate ? { width: `${percentage}%` } : undefined}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"names":["Progress","value","max","indeterminate","label","className","isIndeterminate","percentage","jsx"],"mappings":"mJA6BO,SAASA,EAAS,CACvB,MAAAC,EACA,IAAAC,EAAM,IACN,cAAAC,EAAgB,GAChB,MAAAC,EAAQ,WACR,UAAAC,EAAY,EACd,EAAkB,CAChB,MAAMC,EAAkBH,GAAiBF,IAAU,OAE7CM,EAAa,KAAK,IAAI,KAAK,KAAKN,GAAS,GAAKC,EAAK,CAAC,EAAG,CAAC,EAAI,IAElE,OACEM,EAAAA,IAAC,MAAA,CACC,UAAW,eAAeH,CAAS,GACnC,KAAK,cACL,gBAAeC,EAAkB,OAAY,EAC7C,gBAAeA,EAAkB,OAAYJ,EAC7C,gBAAeI,EAAkB,OAAYL,EAC7C,aAAYG,EAEZ,SAAAI,EAAAA,IAAC,MAAA,CACC,UAAW,mBAAmBF,EAAkB,gBAAkB,EAAE,GACpE,MAAQA,EAAgD,OAA9B,CAAE,MAAO,GAAGC,CAAU,IAAQ,CAAA,CAC1D,CAAA,CAGN"}
1
+ {"version":3,"file":"Progress.cjs","sources":["../../../src/components/progress/Progress.tsx"],"sourcesContent":["/**\r\n * Progress.tsx — FINAL VERSION\r\n * --------------------------------\r\n * Features:\r\n * - Determinate mode (value/max)\r\n * - Indeterminate mode\r\n * - Smooth animation\r\n * - ARIA compliant\r\n * - Pure TSX + CSS (no inline styles except width)\r\n */\r\n\r\nimport './Progress.css';\r\n\r\nexport interface ProgressProps {\r\n /** Current value (determinate mode). */\r\n value?: number;\r\n\r\n /** Maximum value (default: 100). */\r\n max?: number;\r\n\r\n /** Indeterminate mode when value is undefined. */\r\n indeterminate?: boolean;\r\n\r\n /** Optional label for screen readers. */\r\n label?: string;\r\n\r\n className?: string;\r\n}\r\n\r\nexport function Progress({\r\n value,\r\n max = 100,\r\n indeterminate = false,\r\n label = 'Progress',\r\n className = '',\r\n}: ProgressProps) {\r\n const isIndeterminate = indeterminate || value === undefined;\r\n\r\n const percentage = Math.min(Math.max((value ?? 0) / max, 0), 1) * 100;\r\n\r\n return (\r\n <div\r\n className={`ui-progress ${className}`}\r\n role=\"progressbar\"\r\n aria-valuemin={isIndeterminate ? undefined : 0}\r\n aria-valuemax={isIndeterminate ? undefined : max}\r\n aria-valuenow={isIndeterminate ? undefined : value}\r\n aria-label={label}\r\n >\r\n <div\r\n className={`ui-progress-bar ${isIndeterminate ? 'indeterminate' : ''}`}\r\n style={!isIndeterminate ? { width: `${percentage}%` } : undefined}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"names":["Progress","value","max","indeterminate","label","className","isIndeterminate","percentage","jsx"],"mappings":"mJA6BO,SAASA,EAAS,CACvB,MAAAC,EACA,IAAAC,EAAM,IACN,cAAAC,EAAgB,GAChB,MAAAC,EAAQ,WACR,UAAAC,EAAY,EACd,EAAkB,CAChB,MAAMC,EAAkBH,GAAiBF,IAAU,OAE7CM,EAAa,KAAK,IAAI,KAAK,KAAKN,GAAS,GAAKC,EAAK,CAAC,EAAG,CAAC,EAAI,IAElE,OACEM,EAAAA,IAAC,MAAA,CACC,UAAW,eAAeH,CAAS,GACnC,KAAK,cACL,gBAAeC,EAAkB,OAAY,EAC7C,gBAAeA,EAAkB,OAAYJ,EAC7C,gBAAeI,EAAkB,OAAYL,EAC7C,aAAYG,EAEZ,SAAAI,EAAAA,IAAC,MAAA,CACC,UAAW,mBAAmBF,EAAkB,gBAAkB,EAAE,GACpE,MAAQA,EAAgD,OAA9B,CAAE,MAAO,GAAGC,CAAU,IAAQ,CAAA,CAC1D,CAAA,CAGN"}
@@ -1 +1 @@
1
- {"version":3,"file":"Progress.js","sources":["../../../../src/components/progress/Progress.tsx"],"sourcesContent":["/**\r\n * Progress.tsx — FINAL VERSION\r\n * --------------------------------\r\n * Features:\r\n * - Determinate mode (value/max)\r\n * - Indeterminate mode\r\n * - Smooth animation\r\n * - ARIA compliant\r\n * - Pure TSX + CSS (no inline styles except width)\r\n */\r\n\r\nimport './Progress.css';\r\n\r\nexport interface ProgressProps {\r\n /** Current value (determinate mode). */\r\n value?: number;\r\n\r\n /** Maximum value (default: 100). */\r\n max?: number;\r\n\r\n /** Indeterminate mode when value is undefined. */\r\n indeterminate?: boolean;\r\n\r\n /** Optional label for screen readers. */\r\n label?: string;\r\n\r\n className?: string;\r\n}\r\n\r\nexport function Progress({\r\n value,\r\n max = 100,\r\n indeterminate = false,\r\n label = 'Progress',\r\n className = '',\r\n}: ProgressProps) {\r\n const isIndeterminate = indeterminate || value === undefined;\r\n\r\n const percentage = Math.min(Math.max((value ?? 0) / max, 0), 1) * 100;\r\n\r\n return (\r\n <div\r\n className={`ui-progress ${className}`}\r\n role=\"progressbar\"\r\n aria-valuemin={isIndeterminate ? undefined : 0}\r\n aria-valuemax={isIndeterminate ? undefined : max}\r\n aria-valuenow={isIndeterminate ? undefined : value}\r\n aria-label={label}\r\n >\r\n <div\r\n className={`ui-progress-bar ${isIndeterminate ? 'indeterminate' : ''}`}\r\n style={!isIndeterminate ? { width: `${percentage}%` } : undefined}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"names":["Progress","value","max","indeterminate","label","className","isIndeterminate","percentage","jsx"],"mappings":";;AA6BO,SAASA,EAAS;AAAA,EACvB,OAAAC;AAAA,EACA,KAAAC,IAAM;AAAA,EACN,eAAAC,IAAgB;AAAA,EAChB,OAAAC,IAAQ;AAAA,EACR,WAAAC,IAAY;AACd,GAAkB;AAChB,QAAMC,IAAkBH,KAAiBF,MAAU,QAE7CM,IAAa,KAAK,IAAI,KAAK,KAAKN,KAAS,KAAKC,GAAK,CAAC,GAAG,CAAC,IAAI;AAElE,SACE,gBAAAM;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,eAAeH,CAAS;AAAA,MACnC,MAAK;AAAA,MACL,iBAAeC,IAAkB,SAAY;AAAA,MAC7C,iBAAeA,IAAkB,SAAYJ;AAAA,MAC7C,iBAAeI,IAAkB,SAAYL;AAAA,MAC7C,cAAYG;AAAA,MAEZ,UAAA,gBAAAI;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW,mBAAmBF,IAAkB,kBAAkB,EAAE;AAAA,UACpE,OAAQA,IAAgD,SAA9B,EAAE,OAAO,GAAGC,CAAU;QAAQ;AAAA,MAAA;AAAA,IAC1D;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"Progress.js","sources":["../../../src/components/progress/Progress.tsx"],"sourcesContent":["/**\r\n * Progress.tsx — FINAL VERSION\r\n * --------------------------------\r\n * Features:\r\n * - Determinate mode (value/max)\r\n * - Indeterminate mode\r\n * - Smooth animation\r\n * - ARIA compliant\r\n * - Pure TSX + CSS (no inline styles except width)\r\n */\r\n\r\nimport './Progress.css';\r\n\r\nexport interface ProgressProps {\r\n /** Current value (determinate mode). */\r\n value?: number;\r\n\r\n /** Maximum value (default: 100). */\r\n max?: number;\r\n\r\n /** Indeterminate mode when value is undefined. */\r\n indeterminate?: boolean;\r\n\r\n /** Optional label for screen readers. */\r\n label?: string;\r\n\r\n className?: string;\r\n}\r\n\r\nexport function Progress({\r\n value,\r\n max = 100,\r\n indeterminate = false,\r\n label = 'Progress',\r\n className = '',\r\n}: ProgressProps) {\r\n const isIndeterminate = indeterminate || value === undefined;\r\n\r\n const percentage = Math.min(Math.max((value ?? 0) / max, 0), 1) * 100;\r\n\r\n return (\r\n <div\r\n className={`ui-progress ${className}`}\r\n role=\"progressbar\"\r\n aria-valuemin={isIndeterminate ? undefined : 0}\r\n aria-valuemax={isIndeterminate ? undefined : max}\r\n aria-valuenow={isIndeterminate ? undefined : value}\r\n aria-label={label}\r\n >\r\n <div\r\n className={`ui-progress-bar ${isIndeterminate ? 'indeterminate' : ''}`}\r\n style={!isIndeterminate ? { width: `${percentage}%` } : undefined}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"names":["Progress","value","max","indeterminate","label","className","isIndeterminate","percentage","jsx"],"mappings":";;AA6BO,SAASA,EAAS;AAAA,EACvB,OAAAC;AAAA,EACA,KAAAC,IAAM;AAAA,EACN,eAAAC,IAAgB;AAAA,EAChB,OAAAC,IAAQ;AAAA,EACR,WAAAC,IAAY;AACd,GAAkB;AAChB,QAAMC,IAAkBH,KAAiBF,MAAU,QAE7CM,IAAa,KAAK,IAAI,KAAK,KAAKN,KAAS,KAAKC,GAAK,CAAC,GAAG,CAAC,IAAI;AAElE,SACE,gBAAAM;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,eAAeH,CAAS;AAAA,MACnC,MAAK;AAAA,MACL,iBAAeC,IAAkB,SAAY;AAAA,MAC7C,iBAAeA,IAAkB,SAAYJ;AAAA,MAC7C,iBAAeI,IAAkB,SAAYL;AAAA,MAC7C,cAAYG;AAAA,MAEZ,UAAA,gBAAAI;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW,mBAAmBF,IAAkB,kBAAkB,EAAE;AAAA,UACpE,OAAQA,IAAgD,SAA9B,EAAE,OAAO,GAAGC,CAAU;QAAQ;AAAA,MAAA;AAAA,IAC1D;AAAA,EAAA;AAGN;"}
@@ -1 +1 @@
1
- {"version":3,"file":"RadioGroup.cjs","sources":["../../../../src/components/radiogroup/RadioGroup.tsx"],"sourcesContent":["/**\r\n * RadioGroup.tsx\r\n * ----------------\r\n * Fully accessible WAI-ARIA radio group.\r\n * - Arrow key navigation\r\n * - Home/End keys\r\n * - Controlled & uncontrolled\r\n * - Disabled support\r\n */\r\n\r\nimport React, { useState, useCallback, useRef } from 'react';\r\nimport './RadioGroup.css';\r\n\r\nexport interface RadioOption {\r\n label: React.ReactNode;\r\n value: string;\r\n disabled?: boolean;\r\n}\r\n\r\ninterface RadioGroupProps {\r\n options: RadioOption[];\r\n value?: string; // controlled\r\n defaultValue?: string; // uncontrolled\r\n onChange?: (value: string) => void;\r\n name?: string; // form support\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function RadioGroup({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n name,\r\n disabled = false,\r\n className = '',\r\n}: RadioGroupProps) {\r\n const isControlled = value !== undefined;\r\n\r\n const [internalValue, setInternalValue] = useState(defaultValue);\r\n const selected = isControlled ? value : internalValue;\r\n\r\n const itemRefs = useRef<Array<HTMLInputElement | null>>([]);\r\n\r\n const select = useCallback(\r\n (val: string) => {\r\n if (!isControlled) setInternalValue(val);\r\n onChange?.(val);\r\n },\r\n [isControlled, onChange]\r\n );\r\n\r\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\r\n const enabledIndices = options\r\n .map((o, i) => (!o.disabled ? i : null))\r\n .filter((i) => i !== null) as number[];\r\n\r\n const currentIndex = enabledIndices.indexOf(index);\r\n if (currentIndex === -1) return;\r\n\r\n let nextIndex = currentIndex;\r\n\r\n switch (e.key) {\r\n case 'ArrowDown':\r\n case 'ArrowRight':\r\n nextIndex = (currentIndex + 1) % enabledIndices.length;\r\n break;\r\n\r\n case 'ArrowUp':\r\n case 'ArrowLeft':\r\n nextIndex =\r\n (currentIndex - 1 + enabledIndices.length) % enabledIndices.length;\r\n break;\r\n\r\n case 'Home':\r\n nextIndex = 0;\r\n break;\r\n\r\n case 'End':\r\n nextIndex = enabledIndices.length - 1;\r\n break;\r\n\r\n default:\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n const targetIndex = enabledIndices[nextIndex];\r\n const target = itemRefs.current[targetIndex];\r\n target?.focus();\r\n select(options[targetIndex].value);\r\n };\r\n\r\n return (\r\n <div className={`ui-radio-group ${className}`} role=\"radiogroup\">\r\n {options.map((opt, i) => {\r\n const isChecked = selected === opt.value;\r\n\r\n return (\r\n <label\r\n key={opt.value}\r\n className=\"ui-radio-item\"\r\n aria-disabled={disabled || opt.disabled}\r\n >\r\n <input\r\n ref={(el) => {\r\n itemRefs.current[i] = el;\r\n }}\r\n type=\"radio\"\r\n className=\"ui-radio-input\"\r\n name={name}\r\n value={opt.value}\r\n disabled={disabled || opt.disabled}\r\n checked={isChecked}\r\n onChange={() => select(opt.value)}\r\n onKeyDown={(e) => onKeyDown(e, i)}\r\n data-state={isChecked ? 'checked' : 'unchecked'}\r\n />\r\n\r\n <span className=\"ui-radio-label\">{opt.label}</span>\r\n </label>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["RadioGroup","options","value","defaultValue","onChange","name","disabled","className","isControlled","internalValue","setInternalValue","useState","selected","itemRefs","useRef","select","useCallback","val","onKeyDown","index","enabledIndices","o","i","currentIndex","nextIndex","targetIndex","jsx","opt","isChecked","jsxs","el","e"],"mappings":"wKA6BO,SAASA,EAAW,CACzB,QAAAC,EACA,MAAAC,EACA,aAAAC,EACA,SAAAC,EACA,KAAAC,EACA,SAAAC,EAAW,GACX,UAAAC,EAAY,EACd,EAAoB,CAClB,MAAMC,EAAeN,IAAU,OAEzB,CAACO,EAAeC,CAAgB,EAAIC,EAAAA,SAASR,CAAY,EACzDS,EAAWJ,EAAeN,EAAQO,EAElCI,EAAWC,EAAAA,OAAuC,EAAE,EAEpDC,EAASC,EAAAA,YACZC,GAAgB,CACVT,GAAcE,EAAiBO,CAAG,EACvCb,IAAWa,CAAG,CAChB,EACA,CAACT,EAAcJ,CAAQ,CAAA,EAGnBc,EAAY,CAAC,EAAwBC,IAAkB,CAC3D,MAAMC,EAAiBnB,EACpB,IAAI,CAACoB,EAAGC,IAAQD,EAAE,SAAe,KAAJC,CAAS,EACtC,OAAQ,GAAM,IAAM,IAAI,EAErBC,EAAeH,EAAe,QAAQD,CAAK,EACjD,GAAII,IAAiB,GAAI,OAEzB,IAAIC,EAAYD,EAEhB,OAAQ,EAAE,IAAA,CACR,IAAK,YACL,IAAK,aACHC,GAAaD,EAAe,GAAKH,EAAe,OAChD,MAEF,IAAK,UACL,IAAK,YACHI,GACGD,EAAe,EAAIH,EAAe,QAAUA,EAAe,OAC9D,MAEF,IAAK,OACHI,EAAY,EACZ,MAEF,IAAK,MACHA,EAAYJ,EAAe,OAAS,EACpC,MAEF,QACE,MAAA,CAGJ,EAAE,eAAA,EACF,MAAMK,EAAcL,EAAeI,CAAS,EAC7BX,EAAS,QAAQY,CAAW,GACnC,MAAA,EACRV,EAAOd,EAAQwB,CAAW,EAAE,KAAK,CACnC,EAEA,OACEC,EAAAA,IAAC,MAAA,CAAI,UAAW,kBAAkBnB,CAAS,GAAI,KAAK,aACjD,SAAAN,EAAQ,IAAI,CAAC0B,EAAKL,IAAM,CACvB,MAAMM,EAAYhB,IAAae,EAAI,MAEnC,OACEE,EAAAA,KAAC,QAAA,CAEC,UAAU,gBACV,gBAAevB,GAAYqB,EAAI,SAE/B,SAAA,CAAAD,EAAAA,IAAC,QAAA,CACC,IAAMI,GAAO,CACXjB,EAAS,QAAQS,CAAC,EAAIQ,CACxB,EACA,KAAK,QACL,UAAU,iBACV,KAAAzB,EACA,MAAOsB,EAAI,MACX,SAAUrB,GAAYqB,EAAI,SAC1B,QAASC,EACT,SAAU,IAAMb,EAAOY,EAAI,KAAK,EAChC,UAAYI,GAAMb,EAAUa,EAAGT,CAAC,EAChC,aAAYM,EAAY,UAAY,WAAA,CAAA,EAGtCF,EAAAA,IAAC,OAAA,CAAK,UAAU,iBAAkB,WAAI,KAAA,CAAM,CAAA,CAAA,EAnBvCC,EAAI,KAAA,CAsBf,CAAC,CAAA,CACH,CAEJ"}
1
+ {"version":3,"file":"RadioGroup.cjs","sources":["../../../src/components/radiogroup/RadioGroup.tsx"],"sourcesContent":["/**\r\n * RadioGroup.tsx\r\n * ----------------\r\n * Fully accessible WAI-ARIA radio group.\r\n * - Arrow key navigation\r\n * - Home/End keys\r\n * - Controlled & uncontrolled\r\n * - Disabled support\r\n */\r\n\r\nimport React, { useState, useCallback, useRef } from 'react';\r\nimport './RadioGroup.css';\r\n\r\nexport interface RadioOption {\r\n label: React.ReactNode;\r\n value: string;\r\n disabled?: boolean;\r\n}\r\n\r\ninterface RadioGroupProps {\r\n options: RadioOption[];\r\n value?: string; // controlled\r\n defaultValue?: string; // uncontrolled\r\n onChange?: (value: string) => void;\r\n name?: string; // form support\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function RadioGroup({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n name,\r\n disabled = false,\r\n className = '',\r\n}: RadioGroupProps) {\r\n const isControlled = value !== undefined;\r\n\r\n const [internalValue, setInternalValue] = useState(defaultValue);\r\n const selected = isControlled ? value : internalValue;\r\n\r\n const itemRefs = useRef<Array<HTMLInputElement | null>>([]);\r\n\r\n const select = useCallback(\r\n (val: string) => {\r\n if (!isControlled) setInternalValue(val);\r\n onChange?.(val);\r\n },\r\n [isControlled, onChange]\r\n );\r\n\r\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\r\n const enabledIndices = options\r\n .map((o, i) => (!o.disabled ? i : null))\r\n .filter((i) => i !== null) as number[];\r\n\r\n const currentIndex = enabledIndices.indexOf(index);\r\n if (currentIndex === -1) return;\r\n\r\n let nextIndex = currentIndex;\r\n\r\n switch (e.key) {\r\n case 'ArrowDown':\r\n case 'ArrowRight':\r\n nextIndex = (currentIndex + 1) % enabledIndices.length;\r\n break;\r\n\r\n case 'ArrowUp':\r\n case 'ArrowLeft':\r\n nextIndex =\r\n (currentIndex - 1 + enabledIndices.length) % enabledIndices.length;\r\n break;\r\n\r\n case 'Home':\r\n nextIndex = 0;\r\n break;\r\n\r\n case 'End':\r\n nextIndex = enabledIndices.length - 1;\r\n break;\r\n\r\n default:\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n const targetIndex = enabledIndices[nextIndex];\r\n const target = itemRefs.current[targetIndex];\r\n target?.focus();\r\n select(options[targetIndex].value);\r\n };\r\n\r\n return (\r\n <div className={`ui-radio-group ${className}`} role=\"radiogroup\">\r\n {options.map((opt, i) => {\r\n const isChecked = selected === opt.value;\r\n\r\n return (\r\n <label\r\n key={opt.value}\r\n className=\"ui-radio-item\"\r\n aria-disabled={disabled || opt.disabled}\r\n >\r\n <input\r\n ref={(el) => {\r\n itemRefs.current[i] = el;\r\n }}\r\n type=\"radio\"\r\n className=\"ui-radio-input\"\r\n name={name}\r\n value={opt.value}\r\n disabled={disabled || opt.disabled}\r\n checked={isChecked}\r\n onChange={() => select(opt.value)}\r\n onKeyDown={(e) => onKeyDown(e, i)}\r\n data-state={isChecked ? 'checked' : 'unchecked'}\r\n />\r\n\r\n <span className=\"ui-radio-label\">{opt.label}</span>\r\n </label>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["RadioGroup","options","value","defaultValue","onChange","name","disabled","className","isControlled","internalValue","setInternalValue","useState","selected","itemRefs","useRef","select","useCallback","val","onKeyDown","index","enabledIndices","o","i","currentIndex","nextIndex","targetIndex","jsx","opt","isChecked","jsxs","el","e"],"mappings":"wKA6BO,SAASA,EAAW,CACzB,QAAAC,EACA,MAAAC,EACA,aAAAC,EACA,SAAAC,EACA,KAAAC,EACA,SAAAC,EAAW,GACX,UAAAC,EAAY,EACd,EAAoB,CAClB,MAAMC,EAAeN,IAAU,OAEzB,CAACO,EAAeC,CAAgB,EAAIC,EAAAA,SAASR,CAAY,EACzDS,EAAWJ,EAAeN,EAAQO,EAElCI,EAAWC,EAAAA,OAAuC,EAAE,EAEpDC,EAASC,EAAAA,YACZC,GAAgB,CACVT,GAAcE,EAAiBO,CAAG,EACvCb,IAAWa,CAAG,CAChB,EACA,CAACT,EAAcJ,CAAQ,CAAA,EAGnBc,EAAY,CAAC,EAAwBC,IAAkB,CAC3D,MAAMC,EAAiBnB,EACpB,IAAI,CAACoB,EAAGC,IAAQD,EAAE,SAAe,KAAJC,CAAS,EACtC,OAAQ,GAAM,IAAM,IAAI,EAErBC,EAAeH,EAAe,QAAQD,CAAK,EACjD,GAAII,IAAiB,GAAI,OAEzB,IAAIC,EAAYD,EAEhB,OAAQ,EAAE,IAAA,CACR,IAAK,YACL,IAAK,aACHC,GAAaD,EAAe,GAAKH,EAAe,OAChD,MAEF,IAAK,UACL,IAAK,YACHI,GACGD,EAAe,EAAIH,EAAe,QAAUA,EAAe,OAC9D,MAEF,IAAK,OACHI,EAAY,EACZ,MAEF,IAAK,MACHA,EAAYJ,EAAe,OAAS,EACpC,MAEF,QACE,MAAA,CAGJ,EAAE,eAAA,EACF,MAAMK,EAAcL,EAAeI,CAAS,EAC7BX,EAAS,QAAQY,CAAW,GACnC,MAAA,EACRV,EAAOd,EAAQwB,CAAW,EAAE,KAAK,CACnC,EAEA,OACEC,EAAAA,IAAC,MAAA,CAAI,UAAW,kBAAkBnB,CAAS,GAAI,KAAK,aACjD,SAAAN,EAAQ,IAAI,CAAC0B,EAAKL,IAAM,CACvB,MAAMM,EAAYhB,IAAae,EAAI,MAEnC,OACEE,EAAAA,KAAC,QAAA,CAEC,UAAU,gBACV,gBAAevB,GAAYqB,EAAI,SAE/B,SAAA,CAAAD,EAAAA,IAAC,QAAA,CACC,IAAMI,GAAO,CACXjB,EAAS,QAAQS,CAAC,EAAIQ,CACxB,EACA,KAAK,QACL,UAAU,iBACV,KAAAzB,EACA,MAAOsB,EAAI,MACX,SAAUrB,GAAYqB,EAAI,SAC1B,QAASC,EACT,SAAU,IAAMb,EAAOY,EAAI,KAAK,EAChC,UAAYI,GAAMb,EAAUa,EAAGT,CAAC,EAChC,aAAYM,EAAY,UAAY,WAAA,CAAA,EAGtCF,EAAAA,IAAC,OAAA,CAAK,UAAU,iBAAkB,WAAI,KAAA,CAAM,CAAA,CAAA,EAnBvCC,EAAI,KAAA,CAsBf,CAAC,CAAA,CACH,CAEJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"RadioGroup.js","sources":["../../../../src/components/radiogroup/RadioGroup.tsx"],"sourcesContent":["/**\r\n * RadioGroup.tsx\r\n * ----------------\r\n * Fully accessible WAI-ARIA radio group.\r\n * - Arrow key navigation\r\n * - Home/End keys\r\n * - Controlled & uncontrolled\r\n * - Disabled support\r\n */\r\n\r\nimport React, { useState, useCallback, useRef } from 'react';\r\nimport './RadioGroup.css';\r\n\r\nexport interface RadioOption {\r\n label: React.ReactNode;\r\n value: string;\r\n disabled?: boolean;\r\n}\r\n\r\ninterface RadioGroupProps {\r\n options: RadioOption[];\r\n value?: string; // controlled\r\n defaultValue?: string; // uncontrolled\r\n onChange?: (value: string) => void;\r\n name?: string; // form support\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function RadioGroup({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n name,\r\n disabled = false,\r\n className = '',\r\n}: RadioGroupProps) {\r\n const isControlled = value !== undefined;\r\n\r\n const [internalValue, setInternalValue] = useState(defaultValue);\r\n const selected = isControlled ? value : internalValue;\r\n\r\n const itemRefs = useRef<Array<HTMLInputElement | null>>([]);\r\n\r\n const select = useCallback(\r\n (val: string) => {\r\n if (!isControlled) setInternalValue(val);\r\n onChange?.(val);\r\n },\r\n [isControlled, onChange]\r\n );\r\n\r\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\r\n const enabledIndices = options\r\n .map((o, i) => (!o.disabled ? i : null))\r\n .filter((i) => i !== null) as number[];\r\n\r\n const currentIndex = enabledIndices.indexOf(index);\r\n if (currentIndex === -1) return;\r\n\r\n let nextIndex = currentIndex;\r\n\r\n switch (e.key) {\r\n case 'ArrowDown':\r\n case 'ArrowRight':\r\n nextIndex = (currentIndex + 1) % enabledIndices.length;\r\n break;\r\n\r\n case 'ArrowUp':\r\n case 'ArrowLeft':\r\n nextIndex =\r\n (currentIndex - 1 + enabledIndices.length) % enabledIndices.length;\r\n break;\r\n\r\n case 'Home':\r\n nextIndex = 0;\r\n break;\r\n\r\n case 'End':\r\n nextIndex = enabledIndices.length - 1;\r\n break;\r\n\r\n default:\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n const targetIndex = enabledIndices[nextIndex];\r\n const target = itemRefs.current[targetIndex];\r\n target?.focus();\r\n select(options[targetIndex].value);\r\n };\r\n\r\n return (\r\n <div className={`ui-radio-group ${className}`} role=\"radiogroup\">\r\n {options.map((opt, i) => {\r\n const isChecked = selected === opt.value;\r\n\r\n return (\r\n <label\r\n key={opt.value}\r\n className=\"ui-radio-item\"\r\n aria-disabled={disabled || opt.disabled}\r\n >\r\n <input\r\n ref={(el) => {\r\n itemRefs.current[i] = el;\r\n }}\r\n type=\"radio\"\r\n className=\"ui-radio-input\"\r\n name={name}\r\n value={opt.value}\r\n disabled={disabled || opt.disabled}\r\n checked={isChecked}\r\n onChange={() => select(opt.value)}\r\n onKeyDown={(e) => onKeyDown(e, i)}\r\n data-state={isChecked ? 'checked' : 'unchecked'}\r\n />\r\n\r\n <span className=\"ui-radio-label\">{opt.label}</span>\r\n </label>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["RadioGroup","options","value","defaultValue","onChange","name","disabled","className","isControlled","internalValue","setInternalValue","useState","selected","itemRefs","useRef","select","useCallback","val","onKeyDown","index","enabledIndices","o","i","currentIndex","nextIndex","targetIndex","jsx","opt","isChecked","jsxs","el","e"],"mappings":";;;AA6BO,SAASA,EAAW;AAAA,EACzB,SAAAC;AAAA,EACA,OAAAC;AAAA,EACA,cAAAC;AAAA,EACA,UAAAC;AAAA,EACA,MAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,WAAAC,IAAY;AACd,GAAoB;AAClB,QAAMC,IAAeN,MAAU,QAEzB,CAACO,GAAeC,CAAgB,IAAIC,EAASR,CAAY,GACzDS,IAAWJ,IAAeN,IAAQO,GAElCI,IAAWC,EAAuC,EAAE,GAEpDC,IAASC;AAAA,IACb,CAACC,MAAgB;AACf,MAAKT,KAAcE,EAAiBO,CAAG,GACvCb,IAAWa,CAAG;AAAA,IAChB;AAAA,IACA,CAACT,GAAcJ,CAAQ;AAAA,EAAA,GAGnBc,IAAY,CAAC,GAAwBC,MAAkB;AAC3D,UAAMC,IAAiBnB,EACpB,IAAI,CAACoB,GAAGC,MAAQD,EAAE,WAAe,OAAJC,CAAS,EACtC,OAAO,CAACA,MAAMA,MAAM,IAAI,GAErBC,IAAeH,EAAe,QAAQD,CAAK;AACjD,QAAII,MAAiB,GAAI;AAEzB,QAAIC,IAAYD;AAEhB,YAAQ,EAAE,KAAA;AAAA,MACR,KAAK;AAAA,MACL,KAAK;AACH,QAAAC,KAAaD,IAAe,KAAKH,EAAe;AAChD;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,QAAAI,KACGD,IAAe,IAAIH,EAAe,UAAUA,EAAe;AAC9D;AAAA,MAEF,KAAK;AACH,QAAAI,IAAY;AACZ;AAAA,MAEF,KAAK;AACH,QAAAA,IAAYJ,EAAe,SAAS;AACpC;AAAA,MAEF;AACE;AAAA,IAAA;AAGJ,MAAE,eAAA;AACF,UAAMK,IAAcL,EAAeI,CAAS;AAE5C,IADeX,EAAS,QAAQY,CAAW,GACnC,MAAA,GACRV,EAAOd,EAAQwB,CAAW,EAAE,KAAK;AAAA,EACnC;AAEA,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAW,kBAAkBnB,CAAS,IAAI,MAAK,cACjD,UAAAN,EAAQ,IAAI,CAAC0B,GAAKL,MAAM;AACvB,UAAMM,IAAYhB,MAAae,EAAI;AAEnC,WACE,gBAAAE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QACV,iBAAevB,KAAYqB,EAAI;AAAA,QAE/B,UAAA;AAAA,UAAA,gBAAAD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAK,CAACI,MAAO;AACX,gBAAAjB,EAAS,QAAQS,CAAC,IAAIQ;AAAA,cACxB;AAAA,cACA,MAAK;AAAA,cACL,WAAU;AAAA,cACV,MAAAzB;AAAA,cACA,OAAOsB,EAAI;AAAA,cACX,UAAUrB,KAAYqB,EAAI;AAAA,cAC1B,SAASC;AAAA,cACT,UAAU,MAAMb,EAAOY,EAAI,KAAK;AAAA,cAChC,WAAW,CAACI,MAAMb,EAAUa,GAAGT,CAAC;AAAA,cAChC,cAAYM,IAAY,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGtC,gBAAAF,EAAC,QAAA,EAAK,WAAU,kBAAkB,YAAI,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,MAnBvCC,EAAI;AAAA,IAAA;AAAA,EAsBf,CAAC,EAAA,CACH;AAEJ;"}
1
+ {"version":3,"file":"RadioGroup.js","sources":["../../../src/components/radiogroup/RadioGroup.tsx"],"sourcesContent":["/**\r\n * RadioGroup.tsx\r\n * ----------------\r\n * Fully accessible WAI-ARIA radio group.\r\n * - Arrow key navigation\r\n * - Home/End keys\r\n * - Controlled & uncontrolled\r\n * - Disabled support\r\n */\r\n\r\nimport React, { useState, useCallback, useRef } from 'react';\r\nimport './RadioGroup.css';\r\n\r\nexport interface RadioOption {\r\n label: React.ReactNode;\r\n value: string;\r\n disabled?: boolean;\r\n}\r\n\r\ninterface RadioGroupProps {\r\n options: RadioOption[];\r\n value?: string; // controlled\r\n defaultValue?: string; // uncontrolled\r\n onChange?: (value: string) => void;\r\n name?: string; // form support\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function RadioGroup({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n name,\r\n disabled = false,\r\n className = '',\r\n}: RadioGroupProps) {\r\n const isControlled = value !== undefined;\r\n\r\n const [internalValue, setInternalValue] = useState(defaultValue);\r\n const selected = isControlled ? value : internalValue;\r\n\r\n const itemRefs = useRef<Array<HTMLInputElement | null>>([]);\r\n\r\n const select = useCallback(\r\n (val: string) => {\r\n if (!isControlled) setInternalValue(val);\r\n onChange?.(val);\r\n },\r\n [isControlled, onChange]\r\n );\r\n\r\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\r\n const enabledIndices = options\r\n .map((o, i) => (!o.disabled ? i : null))\r\n .filter((i) => i !== null) as number[];\r\n\r\n const currentIndex = enabledIndices.indexOf(index);\r\n if (currentIndex === -1) return;\r\n\r\n let nextIndex = currentIndex;\r\n\r\n switch (e.key) {\r\n case 'ArrowDown':\r\n case 'ArrowRight':\r\n nextIndex = (currentIndex + 1) % enabledIndices.length;\r\n break;\r\n\r\n case 'ArrowUp':\r\n case 'ArrowLeft':\r\n nextIndex =\r\n (currentIndex - 1 + enabledIndices.length) % enabledIndices.length;\r\n break;\r\n\r\n case 'Home':\r\n nextIndex = 0;\r\n break;\r\n\r\n case 'End':\r\n nextIndex = enabledIndices.length - 1;\r\n break;\r\n\r\n default:\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n const targetIndex = enabledIndices[nextIndex];\r\n const target = itemRefs.current[targetIndex];\r\n target?.focus();\r\n select(options[targetIndex].value);\r\n };\r\n\r\n return (\r\n <div className={`ui-radio-group ${className}`} role=\"radiogroup\">\r\n {options.map((opt, i) => {\r\n const isChecked = selected === opt.value;\r\n\r\n return (\r\n <label\r\n key={opt.value}\r\n className=\"ui-radio-item\"\r\n aria-disabled={disabled || opt.disabled}\r\n >\r\n <input\r\n ref={(el) => {\r\n itemRefs.current[i] = el;\r\n }}\r\n type=\"radio\"\r\n className=\"ui-radio-input\"\r\n name={name}\r\n value={opt.value}\r\n disabled={disabled || opt.disabled}\r\n checked={isChecked}\r\n onChange={() => select(opt.value)}\r\n onKeyDown={(e) => onKeyDown(e, i)}\r\n data-state={isChecked ? 'checked' : 'unchecked'}\r\n />\r\n\r\n <span className=\"ui-radio-label\">{opt.label}</span>\r\n </label>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["RadioGroup","options","value","defaultValue","onChange","name","disabled","className","isControlled","internalValue","setInternalValue","useState","selected","itemRefs","useRef","select","useCallback","val","onKeyDown","index","enabledIndices","o","i","currentIndex","nextIndex","targetIndex","jsx","opt","isChecked","jsxs","el","e"],"mappings":";;;AA6BO,SAASA,EAAW;AAAA,EACzB,SAAAC;AAAA,EACA,OAAAC;AAAA,EACA,cAAAC;AAAA,EACA,UAAAC;AAAA,EACA,MAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,WAAAC,IAAY;AACd,GAAoB;AAClB,QAAMC,IAAeN,MAAU,QAEzB,CAACO,GAAeC,CAAgB,IAAIC,EAASR,CAAY,GACzDS,IAAWJ,IAAeN,IAAQO,GAElCI,IAAWC,EAAuC,EAAE,GAEpDC,IAASC;AAAA,IACb,CAACC,MAAgB;AACf,MAAKT,KAAcE,EAAiBO,CAAG,GACvCb,IAAWa,CAAG;AAAA,IAChB;AAAA,IACA,CAACT,GAAcJ,CAAQ;AAAA,EAAA,GAGnBc,IAAY,CAAC,GAAwBC,MAAkB;AAC3D,UAAMC,IAAiBnB,EACpB,IAAI,CAACoB,GAAGC,MAAQD,EAAE,WAAe,OAAJC,CAAS,EACtC,OAAO,CAACA,MAAMA,MAAM,IAAI,GAErBC,IAAeH,EAAe,QAAQD,CAAK;AACjD,QAAII,MAAiB,GAAI;AAEzB,QAAIC,IAAYD;AAEhB,YAAQ,EAAE,KAAA;AAAA,MACR,KAAK;AAAA,MACL,KAAK;AACH,QAAAC,KAAaD,IAAe,KAAKH,EAAe;AAChD;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,QAAAI,KACGD,IAAe,IAAIH,EAAe,UAAUA,EAAe;AAC9D;AAAA,MAEF,KAAK;AACH,QAAAI,IAAY;AACZ;AAAA,MAEF,KAAK;AACH,QAAAA,IAAYJ,EAAe,SAAS;AACpC;AAAA,MAEF;AACE;AAAA,IAAA;AAGJ,MAAE,eAAA;AACF,UAAMK,IAAcL,EAAeI,CAAS;AAE5C,IADeX,EAAS,QAAQY,CAAW,GACnC,MAAA,GACRV,EAAOd,EAAQwB,CAAW,EAAE,KAAK;AAAA,EACnC;AAEA,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAW,kBAAkBnB,CAAS,IAAI,MAAK,cACjD,UAAAN,EAAQ,IAAI,CAAC0B,GAAKL,MAAM;AACvB,UAAMM,IAAYhB,MAAae,EAAI;AAEnC,WACE,gBAAAE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QACV,iBAAevB,KAAYqB,EAAI;AAAA,QAE/B,UAAA;AAAA,UAAA,gBAAAD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAK,CAACI,MAAO;AACX,gBAAAjB,EAAS,QAAQS,CAAC,IAAIQ;AAAA,cACxB;AAAA,cACA,MAAK;AAAA,cACL,WAAU;AAAA,cACV,MAAAzB;AAAA,cACA,OAAOsB,EAAI;AAAA,cACX,UAAUrB,KAAYqB,EAAI;AAAA,cAC1B,SAASC;AAAA,cACT,UAAU,MAAMb,EAAOY,EAAI,KAAK;AAAA,cAChC,WAAW,CAACI,MAAMb,EAAUa,GAAGT,CAAC;AAAA,cAChC,cAAYM,IAAY,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGtC,gBAAAF,EAAC,QAAA,EAAK,WAAU,kBAAkB,YAAI,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,MAnBvCC,EAAI;AAAA,IAAA;AAAA,EAsBf,CAAC,EAAA,CACH;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"Rating.cjs","sources":["../../../../src/components/rating/Rating.tsx"],"sourcesContent":["/**\r\n * Rating.tsx — FINAL VERSION\r\n * ---------------------------\r\n * Features:\r\n * - Click to select\r\n * - Hover preview\r\n * - Keyboard navigation\r\n * - Theme-aware\r\n * - Accessible\r\n */\r\n\r\nimport React, { useState } from 'react';\r\nimport './Rating.css';\r\n\r\nexport interface RatingProps {\r\n /** Current value (1–max). If undefined → uncontrolled mode */\r\n value?: number;\r\n\r\n /** Max number of stars */\r\n max?: number;\r\n\r\n /** Change handler */\r\n onChange?: (value: number) => void;\r\n\r\n /** Custom icon */\r\n icon?: React.ReactNode;\r\n\r\n /** Custom filled icon */\r\n iconFilled?: React.ReactNode;\r\n\r\n /** Size */\r\n size?: 'sm' | 'md' | 'lg';\r\n\r\n /** Optional class */\r\n className?: string;\r\n}\r\n\r\nexport function Rating({\r\n value,\r\n max = 5,\r\n onChange,\r\n icon = '☆',\r\n iconFilled = '★',\r\n size = 'md',\r\n className = '',\r\n}: RatingProps) {\r\n // uncontrolled internal value\r\n const [internal, setInternal] = useState(0);\r\n const current = value ?? internal;\r\n\r\n const [hover, setHover] = useState<number | null>(null);\r\n\r\n const finalValue = hover ?? current;\r\n\r\n const handleSelect = (v: number) => {\r\n if (value === undefined) setInternal(v);\r\n onChange?.(v);\r\n };\r\n\r\n const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (e.key === 'ArrowRight') handleSelect(Math.min((current ?? 0) + 1, max));\r\n if (e.key === 'ArrowLeft') handleSelect(Math.max((current ?? 0) - 1, 1));\r\n };\r\n\r\n return (\r\n <div\r\n className={`ui-rating ui-rating--${size} ${className}`}\r\n role=\"radiogroup\"\r\n tabIndex={0}\r\n onKeyDown={handleKey}\r\n aria-label=\"Rating\"\r\n >\r\n {Array.from({ length: max }, (_, i) => {\r\n const index = i + 1;\r\n const filled = index <= finalValue;\r\n\r\n return (\r\n <span\r\n key={index}\r\n role=\"radio\"\r\n aria-checked={current === index}\r\n tabIndex={-1}\r\n className={`ui-rating-item ${filled ? 'filled' : ''}`}\r\n onMouseEnter={() => setHover(index)}\r\n onMouseLeave={() => setHover(null)}\r\n onClick={() => handleSelect(index)}\r\n >\r\n {filled ? iconFilled : icon}\r\n </span>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["Rating","value","max","onChange","icon","iconFilled","size","className","internal","setInternal","useState","current","hover","setHover","finalValue","handleSelect","v","handleKey","jsx","_","i","index","filled"],"mappings":"oKAqCO,SAASA,EAAO,CACrB,MAAAC,EACA,IAAAC,EAAM,EACN,SAAAC,EACA,KAAAC,EAAO,IACP,WAAAC,EAAa,IACb,KAAAC,EAAO,KACP,UAAAC,EAAY,EACd,EAAgB,CAEd,KAAM,CAACC,EAAUC,CAAW,EAAIC,EAAAA,SAAS,CAAC,EACpCC,EAAUV,GAASO,EAEnB,CAACI,EAAOC,CAAQ,EAAIH,EAAAA,SAAwB,IAAI,EAEhDI,EAAaF,GAASD,EAEtBI,EAAgBC,GAAc,CAC9Bf,IAAU,QAAWQ,EAAYO,CAAC,EACtCb,IAAWa,CAAC,CACd,EAEMC,EAAa,GAA2C,CACxD,EAAE,MAAQ,cAAcF,EAAa,KAAK,KAAKJ,GAAW,GAAK,EAAGT,CAAG,CAAC,EACtE,EAAE,MAAQ,aAAaa,EAAa,KAAK,KAAKJ,GAAW,GAAK,EAAG,CAAC,CAAC,CACzE,EAEA,OACEO,EAAAA,IAAC,MAAA,CACC,UAAW,wBAAwBZ,CAAI,IAAIC,CAAS,GACpD,KAAK,aACL,SAAU,EACV,UAAWU,EACX,aAAW,SAEV,SAAA,MAAM,KAAK,CAAE,OAAQf,GAAO,CAACiB,EAAGC,IAAM,CACrC,MAAMC,EAAQD,EAAI,EACZE,EAASD,GAASP,EAExB,OACEI,EAAAA,IAAC,OAAA,CAEC,KAAK,QACL,eAAcP,IAAYU,EAC1B,SAAU,GACV,UAAW,kBAAkBC,EAAS,SAAW,EAAE,GACnD,aAAc,IAAMT,EAASQ,CAAK,EAClC,aAAc,IAAMR,EAAS,IAAI,EACjC,QAAS,IAAME,EAAaM,CAAK,EAEhC,WAAShB,EAAaD,CAAA,EATlBiB,CAAA,CAYX,CAAC,CAAA,CAAA,CAGP"}
1
+ {"version":3,"file":"Rating.cjs","sources":["../../../src/components/rating/Rating.tsx"],"sourcesContent":["/**\r\n * Rating.tsx — FINAL VERSION\r\n * ---------------------------\r\n * Features:\r\n * - Click to select\r\n * - Hover preview\r\n * - Keyboard navigation\r\n * - Theme-aware\r\n * - Accessible\r\n */\r\n\r\nimport React, { useState } from 'react';\r\nimport './Rating.css';\r\n\r\nexport interface RatingProps {\r\n /** Current value (1–max). If undefined → uncontrolled mode */\r\n value?: number;\r\n\r\n /** Max number of stars */\r\n max?: number;\r\n\r\n /** Change handler */\r\n onChange?: (value: number) => void;\r\n\r\n /** Custom icon */\r\n icon?: React.ReactNode;\r\n\r\n /** Custom filled icon */\r\n iconFilled?: React.ReactNode;\r\n\r\n /** Size */\r\n size?: 'sm' | 'md' | 'lg';\r\n\r\n /** Optional class */\r\n className?: string;\r\n}\r\n\r\nexport function Rating({\r\n value,\r\n max = 5,\r\n onChange,\r\n icon = '☆',\r\n iconFilled = '★',\r\n size = 'md',\r\n className = '',\r\n}: RatingProps) {\r\n // uncontrolled internal value\r\n const [internal, setInternal] = useState(0);\r\n const current = value ?? internal;\r\n\r\n const [hover, setHover] = useState<number | null>(null);\r\n\r\n const finalValue = hover ?? current;\r\n\r\n const handleSelect = (v: number) => {\r\n if (value === undefined) setInternal(v);\r\n onChange?.(v);\r\n };\r\n\r\n const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (e.key === 'ArrowRight') handleSelect(Math.min((current ?? 0) + 1, max));\r\n if (e.key === 'ArrowLeft') handleSelect(Math.max((current ?? 0) - 1, 1));\r\n };\r\n\r\n return (\r\n <div\r\n className={`ui-rating ui-rating--${size} ${className}`}\r\n role=\"radiogroup\"\r\n tabIndex={0}\r\n onKeyDown={handleKey}\r\n aria-label=\"Rating\"\r\n >\r\n {Array.from({ length: max }, (_, i) => {\r\n const index = i + 1;\r\n const filled = index <= finalValue;\r\n\r\n return (\r\n <span\r\n key={index}\r\n role=\"radio\"\r\n aria-checked={current === index}\r\n tabIndex={-1}\r\n className={`ui-rating-item ${filled ? 'filled' : ''}`}\r\n onMouseEnter={() => setHover(index)}\r\n onMouseLeave={() => setHover(null)}\r\n onClick={() => handleSelect(index)}\r\n >\r\n {filled ? iconFilled : icon}\r\n </span>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["Rating","value","max","onChange","icon","iconFilled","size","className","internal","setInternal","useState","current","hover","setHover","finalValue","handleSelect","v","handleKey","jsx","_","i","index","filled"],"mappings":"oKAqCO,SAASA,EAAO,CACrB,MAAAC,EACA,IAAAC,EAAM,EACN,SAAAC,EACA,KAAAC,EAAO,IACP,WAAAC,EAAa,IACb,KAAAC,EAAO,KACP,UAAAC,EAAY,EACd,EAAgB,CAEd,KAAM,CAACC,EAAUC,CAAW,EAAIC,EAAAA,SAAS,CAAC,EACpCC,EAAUV,GAASO,EAEnB,CAACI,EAAOC,CAAQ,EAAIH,EAAAA,SAAwB,IAAI,EAEhDI,EAAaF,GAASD,EAEtBI,EAAgBC,GAAc,CAC9Bf,IAAU,QAAWQ,EAAYO,CAAC,EACtCb,IAAWa,CAAC,CACd,EAEMC,EAAa,GAA2C,CACxD,EAAE,MAAQ,cAAcF,EAAa,KAAK,KAAKJ,GAAW,GAAK,EAAGT,CAAG,CAAC,EACtE,EAAE,MAAQ,aAAaa,EAAa,KAAK,KAAKJ,GAAW,GAAK,EAAG,CAAC,CAAC,CACzE,EAEA,OACEO,EAAAA,IAAC,MAAA,CACC,UAAW,wBAAwBZ,CAAI,IAAIC,CAAS,GACpD,KAAK,aACL,SAAU,EACV,UAAWU,EACX,aAAW,SAEV,SAAA,MAAM,KAAK,CAAE,OAAQf,GAAO,CAACiB,EAAGC,IAAM,CACrC,MAAMC,EAAQD,EAAI,EACZE,EAASD,GAASP,EAExB,OACEI,EAAAA,IAAC,OAAA,CAEC,KAAK,QACL,eAAcP,IAAYU,EAC1B,SAAU,GACV,UAAW,kBAAkBC,EAAS,SAAW,EAAE,GACnD,aAAc,IAAMT,EAASQ,CAAK,EAClC,aAAc,IAAMR,EAAS,IAAI,EACjC,QAAS,IAAME,EAAaM,CAAK,EAEhC,WAAShB,EAAaD,CAAA,EATlBiB,CAAA,CAYX,CAAC,CAAA,CAAA,CAGP"}
@@ -1 +1 @@
1
- {"version":3,"file":"Rating.js","sources":["../../../../src/components/rating/Rating.tsx"],"sourcesContent":["/**\r\n * Rating.tsx — FINAL VERSION\r\n * ---------------------------\r\n * Features:\r\n * - Click to select\r\n * - Hover preview\r\n * - Keyboard navigation\r\n * - Theme-aware\r\n * - Accessible\r\n */\r\n\r\nimport React, { useState } from 'react';\r\nimport './Rating.css';\r\n\r\nexport interface RatingProps {\r\n /** Current value (1–max). If undefined → uncontrolled mode */\r\n value?: number;\r\n\r\n /** Max number of stars */\r\n max?: number;\r\n\r\n /** Change handler */\r\n onChange?: (value: number) => void;\r\n\r\n /** Custom icon */\r\n icon?: React.ReactNode;\r\n\r\n /** Custom filled icon */\r\n iconFilled?: React.ReactNode;\r\n\r\n /** Size */\r\n size?: 'sm' | 'md' | 'lg';\r\n\r\n /** Optional class */\r\n className?: string;\r\n}\r\n\r\nexport function Rating({\r\n value,\r\n max = 5,\r\n onChange,\r\n icon = '☆',\r\n iconFilled = '★',\r\n size = 'md',\r\n className = '',\r\n}: RatingProps) {\r\n // uncontrolled internal value\r\n const [internal, setInternal] = useState(0);\r\n const current = value ?? internal;\r\n\r\n const [hover, setHover] = useState<number | null>(null);\r\n\r\n const finalValue = hover ?? current;\r\n\r\n const handleSelect = (v: number) => {\r\n if (value === undefined) setInternal(v);\r\n onChange?.(v);\r\n };\r\n\r\n const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (e.key === 'ArrowRight') handleSelect(Math.min((current ?? 0) + 1, max));\r\n if (e.key === 'ArrowLeft') handleSelect(Math.max((current ?? 0) - 1, 1));\r\n };\r\n\r\n return (\r\n <div\r\n className={`ui-rating ui-rating--${size} ${className}`}\r\n role=\"radiogroup\"\r\n tabIndex={0}\r\n onKeyDown={handleKey}\r\n aria-label=\"Rating\"\r\n >\r\n {Array.from({ length: max }, (_, i) => {\r\n const index = i + 1;\r\n const filled = index <= finalValue;\r\n\r\n return (\r\n <span\r\n key={index}\r\n role=\"radio\"\r\n aria-checked={current === index}\r\n tabIndex={-1}\r\n className={`ui-rating-item ${filled ? 'filled' : ''}`}\r\n onMouseEnter={() => setHover(index)}\r\n onMouseLeave={() => setHover(null)}\r\n onClick={() => handleSelect(index)}\r\n >\r\n {filled ? iconFilled : icon}\r\n </span>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["Rating","value","max","onChange","icon","iconFilled","size","className","internal","setInternal","useState","current","hover","setHover","finalValue","handleSelect","v","handleKey","jsx","_","i","index","filled"],"mappings":";;;AAqCO,SAASA,EAAO;AAAA,EACrB,OAAAC;AAAA,EACA,KAAAC,IAAM;AAAA,EACN,UAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,YAAAC,IAAa;AAAA,EACb,MAAAC,IAAO;AAAA,EACP,WAAAC,IAAY;AACd,GAAgB;AAEd,QAAM,CAACC,GAAUC,CAAW,IAAIC,EAAS,CAAC,GACpCC,IAAUV,KAASO,GAEnB,CAACI,GAAOC,CAAQ,IAAIH,EAAwB,IAAI,GAEhDI,IAAaF,KAASD,GAEtBI,IAAe,CAACC,MAAc;AAClC,IAAIf,MAAU,UAAWQ,EAAYO,CAAC,GACtCb,IAAWa,CAAC;AAAA,EACd,GAEMC,IAAY,CAAC,MAA2C;AAC5D,IAAI,EAAE,QAAQ,gBAAcF,EAAa,KAAK,KAAKJ,KAAW,KAAK,GAAGT,CAAG,CAAC,GACtE,EAAE,QAAQ,eAAaa,EAAa,KAAK,KAAKJ,KAAW,KAAK,GAAG,CAAC,CAAC;AAAA,EACzE;AAEA,SACE,gBAAAO;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,wBAAwBZ,CAAI,IAAIC,CAAS;AAAA,MACpD,MAAK;AAAA,MACL,UAAU;AAAA,MACV,WAAWU;AAAA,MACX,cAAW;AAAA,MAEV,UAAA,MAAM,KAAK,EAAE,QAAQf,KAAO,CAACiB,GAAGC,MAAM;AACrC,cAAMC,IAAQD,IAAI,GACZE,IAASD,KAASP;AAExB,eACE,gBAAAI;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,MAAK;AAAA,YACL,gBAAcP,MAAYU;AAAA,YAC1B,UAAU;AAAA,YACV,WAAW,kBAAkBC,IAAS,WAAW,EAAE;AAAA,YACnD,cAAc,MAAMT,EAASQ,CAAK;AAAA,YAClC,cAAc,MAAMR,EAAS,IAAI;AAAA,YACjC,SAAS,MAAME,EAAaM,CAAK;AAAA,YAEhC,cAAShB,IAAaD;AAAA,UAAA;AAAA,UATlBiB;AAAA,QAAA;AAAA,MAYX,CAAC;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"Rating.js","sources":["../../../src/components/rating/Rating.tsx"],"sourcesContent":["/**\r\n * Rating.tsx — FINAL VERSION\r\n * ---------------------------\r\n * Features:\r\n * - Click to select\r\n * - Hover preview\r\n * - Keyboard navigation\r\n * - Theme-aware\r\n * - Accessible\r\n */\r\n\r\nimport React, { useState } from 'react';\r\nimport './Rating.css';\r\n\r\nexport interface RatingProps {\r\n /** Current value (1–max). If undefined → uncontrolled mode */\r\n value?: number;\r\n\r\n /** Max number of stars */\r\n max?: number;\r\n\r\n /** Change handler */\r\n onChange?: (value: number) => void;\r\n\r\n /** Custom icon */\r\n icon?: React.ReactNode;\r\n\r\n /** Custom filled icon */\r\n iconFilled?: React.ReactNode;\r\n\r\n /** Size */\r\n size?: 'sm' | 'md' | 'lg';\r\n\r\n /** Optional class */\r\n className?: string;\r\n}\r\n\r\nexport function Rating({\r\n value,\r\n max = 5,\r\n onChange,\r\n icon = '☆',\r\n iconFilled = '★',\r\n size = 'md',\r\n className = '',\r\n}: RatingProps) {\r\n // uncontrolled internal value\r\n const [internal, setInternal] = useState(0);\r\n const current = value ?? internal;\r\n\r\n const [hover, setHover] = useState<number | null>(null);\r\n\r\n const finalValue = hover ?? current;\r\n\r\n const handleSelect = (v: number) => {\r\n if (value === undefined) setInternal(v);\r\n onChange?.(v);\r\n };\r\n\r\n const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (e.key === 'ArrowRight') handleSelect(Math.min((current ?? 0) + 1, max));\r\n if (e.key === 'ArrowLeft') handleSelect(Math.max((current ?? 0) - 1, 1));\r\n };\r\n\r\n return (\r\n <div\r\n className={`ui-rating ui-rating--${size} ${className}`}\r\n role=\"radiogroup\"\r\n tabIndex={0}\r\n onKeyDown={handleKey}\r\n aria-label=\"Rating\"\r\n >\r\n {Array.from({ length: max }, (_, i) => {\r\n const index = i + 1;\r\n const filled = index <= finalValue;\r\n\r\n return (\r\n <span\r\n key={index}\r\n role=\"radio\"\r\n aria-checked={current === index}\r\n tabIndex={-1}\r\n className={`ui-rating-item ${filled ? 'filled' : ''}`}\r\n onMouseEnter={() => setHover(index)}\r\n onMouseLeave={() => setHover(null)}\r\n onClick={() => handleSelect(index)}\r\n >\r\n {filled ? iconFilled : icon}\r\n </span>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["Rating","value","max","onChange","icon","iconFilled","size","className","internal","setInternal","useState","current","hover","setHover","finalValue","handleSelect","v","handleKey","jsx","_","i","index","filled"],"mappings":";;;AAqCO,SAASA,EAAO;AAAA,EACrB,OAAAC;AAAA,EACA,KAAAC,IAAM;AAAA,EACN,UAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,YAAAC,IAAa;AAAA,EACb,MAAAC,IAAO;AAAA,EACP,WAAAC,IAAY;AACd,GAAgB;AAEd,QAAM,CAACC,GAAUC,CAAW,IAAIC,EAAS,CAAC,GACpCC,IAAUV,KAASO,GAEnB,CAACI,GAAOC,CAAQ,IAAIH,EAAwB,IAAI,GAEhDI,IAAaF,KAASD,GAEtBI,IAAe,CAACC,MAAc;AAClC,IAAIf,MAAU,UAAWQ,EAAYO,CAAC,GACtCb,IAAWa,CAAC;AAAA,EACd,GAEMC,IAAY,CAAC,MAA2C;AAC5D,IAAI,EAAE,QAAQ,gBAAcF,EAAa,KAAK,KAAKJ,KAAW,KAAK,GAAGT,CAAG,CAAC,GACtE,EAAE,QAAQ,eAAaa,EAAa,KAAK,KAAKJ,KAAW,KAAK,GAAG,CAAC,CAAC;AAAA,EACzE;AAEA,SACE,gBAAAO;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,wBAAwBZ,CAAI,IAAIC,CAAS;AAAA,MACpD,MAAK;AAAA,MACL,UAAU;AAAA,MACV,WAAWU;AAAA,MACX,cAAW;AAAA,MAEV,UAAA,MAAM,KAAK,EAAE,QAAQf,KAAO,CAACiB,GAAGC,MAAM;AACrC,cAAMC,IAAQD,IAAI,GACZE,IAASD,KAASP;AAExB,eACE,gBAAAI;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,MAAK;AAAA,YACL,gBAAcP,MAAYU;AAAA,YAC1B,UAAU;AAAA,YACV,WAAW,kBAAkBC,IAAS,WAAW,EAAE;AAAA,YACnD,cAAc,MAAMT,EAASQ,CAAK;AAAA,YAClC,cAAc,MAAMR,EAAS,IAAI;AAAA,YACjC,SAAS,MAAME,EAAaM,CAAK;AAAA,YAEhC,cAAShB,IAAaD;AAAA,UAAA;AAAA,UATlBiB;AAAA,QAAA;AAAA,MAYX,CAAC;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react/jsx-runtime"),l=require("react");;/* empty css */const z=require("../../utils/restorefocus/restoreFocus.cjs"),G=require("../../utils/onclickoutside/onClickOutside.cjs"),J=require("../../utils/portal/portal.cjs");function L(s,h,g){const o=s.length;let d=h;for(let r=0;r<o;r++)if(d=(d+g+o)%o,!s[d].disabled)return d;return-1}function Q({options:s,value:h,defaultValue:g,onChange:o,placeholder:d="Select...",disabled:r=!1,name:C,id:O,className:T=""}){const I=h!==void 0,[$,K]=l.useState(g),b=I?h:$,S=s.find(e=>e.value===b)??null,[i,x]=l.useState(!1),[a,f]=l.useState(()=>s.findIndex(e=>!e.disabled)),v=l.useRef({buffer:"",lastTime:0}),p=l.useRef(null),m=l.useRef(null),P=l.useId(),R=O??P,y=`${R}-listbox`,E=`${R}-label`,V=l.useCallback(()=>{r||x(!0)},[r]),F=l.useCallback(()=>{x(!1),z.restoreFocus(p.current)},[]),M=l.useCallback(()=>{r||x(e=>!e)},[r]);l.useEffect(()=>{if(i)return G.onClickOutside(m,()=>x(!1))},[i]),l.useEffect(()=>{if(!i)return;const e=s.findIndex(n=>n.value===b&&!n.disabled),t=e>=0?e:s.findIndex(n=>!n.disabled);f(t>=0?t:-1),setTimeout(()=>{m.current?.focus()},0)},[i,s,b]);const N=l.useCallback(e=>{if(e<0||e>=s.length)return;const t=s[e];t.disabled||(I||K(t.value),o?.(t.value),x(!1),p.current?.focus())},[I,s,o]),U=e=>{r||["ArrowDown","ArrowUp"," ","Enter"].includes(e.key)&&(e.preventDefault(),V())},B=e=>{const t=e.key;if(t==="ArrowDown"){e.preventDefault();const n=L(s,a,1);n>=0&&f(n)}else if(t==="ArrowUp"){e.preventDefault();const n=L(s,a,-1);n>=0&&f(n)}else if(t==="Home"){e.preventDefault();const n=s.findIndex(u=>!u.disabled);n>=0&&f(n)}else if(t==="End"){e.preventDefault();const n=s.length-1-[...s].reverse().findIndex(u=>!u.disabled);n>=0&&f(n)}else if(t==="Enter"||t===" ")e.preventDefault(),a>=0&&N(a);else if(t==="Escape")e.preventDefault(),F();else if(t.length===1&&t.match(/\S/)){const n=Date.now();n-v.current.lastTime>700&&(v.current.buffer=""),v.current.buffer+=t.toLowerCase(),v.current.lastTime=n;const u=v.current.buffer,w=a>=0?a+1:0,A=s.length;for(let k=0;k<A;k++){const j=(w+k)%A,W=String(s[j].label).toLowerCase();if(!s[j].disabled&&W.startsWith(u)){f(j);break}}}},H=e=>{N(e)},[D,q]=l.useState(null);return l.useEffect(()=>{if(!i||!p.current){q(null);return}const e=p.current.getBoundingClientRect(),t=e.bottom+6,n=e.left;q({top:t,left:n})},[i]),c.jsxs("div",{className:`ui-select ${T}`,children:[C&&c.jsx("input",{type:"hidden",name:C,value:b??""}),c.jsxs("button",{ref:p,id:E,type:"button",className:"ui-select-trigger","aria-haspopup":"listbox","aria-expanded":i,"aria-controls":i?y:void 0,"aria-disabled":r||void 0,onClick:()=>M(),onKeyDown:U,disabled:r,children:[c.jsx("span",{children:S?c.jsx("span",{children:S.label}):c.jsx("span",{className:"ui-select-placeholder",children:d})}),c.jsx("span",{className:"ui-select-chevron","aria-hidden":!0,children:"▾"})]}),i&&D&&c.jsx(J.Portal,{children:c.jsx("div",{id:y,ref:m,className:"ui-select-listbox",role:"listbox","aria-labelledby":E,tabIndex:0,style:{top:D.top,left:D.left},onKeyDown:B,children:s.map((e,t)=>{const n=b===e.value,u=a===t;return c.jsx("div",{id:`${y}-option-${t}`,role:"option","aria-selected":n,"aria-disabled":e.disabled||void 0,className:"ui-select-option",tabIndex:-1,onMouseDown:w=>w.preventDefault(),onClick:()=>!e.disabled&&H(t),style:{outline:u?"2px solid var(--color-primary)":void 0},children:e.label},e.value)})})})]})}exports.Select=Q;
2
+ //# sourceMappingURL=Select.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Select.cjs","sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["/**\r\n * Select.tsx\r\n * ----------\r\n * Accessible single-select (listbox) component.\r\n *\r\n * Features:\r\n * - WAI-ARIA listbox pattern\r\n * - Keyboard navigation (ArrowUp/ArrowDown, Home, End, Enter, Space, Esc)\r\n * - Typeahead support\r\n * - Controlled + uncontrolled modes\r\n * - Portal rendering for the listbox\r\n * - Click outside to close\r\n * - Proper ARIA attributes and relationships\r\n * - Hidden input for form compatibility (optional name prop)\r\n *\r\n * Usage:\r\n * <Select\r\n * options={[{ value: 'a', label: 'Apple' }, { value: 'b', label: 'Banana' }]}\r\n * value={value} // optional controlled value\r\n * onChange={(v) => setValue(v)} // required if controlled\r\n * placeholder=\"Select fruit\"\r\n * name=\"fruit\" // optional, adds hidden input\r\n * />\r\n */\r\n\r\nimport React, {\r\n useState,\r\n useRef,\r\n useEffect,\r\n useCallback,\r\n KeyboardEvent,\r\n} from 'react';\r\nimport './Select.css';\r\nimport { Portal, onClickOutside, restoreFocus } from '../../utils/index';\r\n\r\ntype Option = {\r\n value: string;\r\n label: React.ReactNode;\r\n disabled?: boolean;\r\n};\r\n\r\ninterface SelectProps {\r\n options: Option[];\r\n value?: string; // controlled value\r\n defaultValue?: string; // uncontrolled initial\r\n onChange?: (value: string) => void;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n name?: string; // if provided, render hidden input for forms\r\n id?: string;\r\n className?: string;\r\n}\r\n\r\n/* Helper: find next enabled index given direction */\r\nfunction findNextEnabled(options: Option[], start: number, direction: 1 | -1) {\r\n const len = options.length;\r\n let i = start;\r\n for (let step = 0; step < len; step++) {\r\n i = (i + direction + len) % len;\r\n if (!options[i].disabled) return i;\r\n }\r\n return -1;\r\n}\r\n\r\nexport function Select({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n placeholder = 'Select...',\r\n disabled = false,\r\n name,\r\n id,\r\n className = '',\r\n}: SelectProps) {\r\n // Controlled vs uncontrolled\r\n const isControlled = value !== undefined;\r\n const [internalValue, setInternalValue] = useState<string | undefined>(\r\n defaultValue\r\n );\r\n const selectedValue = isControlled ? value : internalValue;\r\n\r\n const selectedOption = options.find((o) => o.value === selectedValue) ?? null;\r\n\r\n const [open, setOpen] = useState(false);\r\n const [activeIndex, setActiveIndex] = useState<number>(() =>\r\n options.findIndex((o) => !o.disabled)\r\n );\r\n\r\n // Typeahead buffer\r\n const typeaheadRef = useRef({ buffer: '', lastTime: 0 });\r\n\r\n // Refs\r\n const triggerRef = useRef<HTMLButtonElement | null>(null);\r\n const listRef = useRef<HTMLDivElement | null>(null);\r\n\r\n // Unique ids\r\n const reactId = React.useId();\r\n const baseId = id ?? reactId;\r\n const listboxId = `${baseId}-listbox`;\r\n const labelId = `${baseId}-label`;\r\n\r\n // Open/close helpers\r\n const openList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen(true);\r\n }, [disabled]);\r\n\r\n const closeList = useCallback(() => {\r\n setOpen(false);\r\n // restore focus to trigger\r\n restoreFocus(triggerRef.current);\r\n }, []);\r\n\r\n // Toggle\r\n const toggleList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen((s) => !s);\r\n }, [disabled]);\r\n\r\n // Click outside to close\r\n useEffect(() => {\r\n if (!open) return;\r\n return onClickOutside(listRef, () => setOpen(false));\r\n }, [open]);\r\n\r\n // Ensure that when opening, activeIndex is valid and focus moves to listbox\r\n useEffect(() => {\r\n if (!open) return;\r\n // set active to selected or first enabled\r\n const selIndex = options.findIndex(\r\n (o) => o.value === selectedValue && !o.disabled\r\n );\r\n const idx =\r\n selIndex >= 0 ? selIndex : options.findIndex((o) => !o.disabled);\r\n setActiveIndex(idx >= 0 ? idx : -1);\r\n\r\n // focus the list container so keyboard events are captured\r\n setTimeout(() => {\r\n listRef.current?.focus();\r\n }, 0);\r\n }, [open, options, selectedValue]);\r\n\r\n // Selection handler\r\n const selectIndex = useCallback(\r\n (i: number) => {\r\n if (i < 0 || i >= options.length) return;\r\n const opt = options[i];\r\n if (opt.disabled) return;\r\n if (!isControlled) setInternalValue(opt.value);\r\n onChange?.(opt.value);\r\n setOpen(false);\r\n // restore focus\r\n triggerRef.current?.focus();\r\n },\r\n [isControlled, options, onChange]\r\n );\r\n\r\n // Keyboard handling when trigger has focus\r\n const onTriggerKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\r\n if (disabled) return;\r\n if (['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(e.key)) {\r\n e.preventDefault();\r\n openList();\r\n }\r\n };\r\n\r\n // Keyboard handling when listbox has focus\r\n const onListKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n const key = e.key;\r\n if (key === 'ArrowDown') {\r\n e.preventDefault();\r\n const next = findNextEnabled(options, activeIndex, 1);\r\n if (next >= 0) setActiveIndex(next);\r\n } else if (key === 'ArrowUp') {\r\n e.preventDefault();\r\n const prev = findNextEnabled(options, activeIndex, -1);\r\n if (prev >= 0) setActiveIndex(prev);\r\n } else if (key === 'Home') {\r\n e.preventDefault();\r\n const first = options.findIndex((o) => !o.disabled);\r\n if (first >= 0) setActiveIndex(first);\r\n } else if (key === 'End') {\r\n e.preventDefault();\r\n const last =\r\n options.length -\r\n 1 -\r\n [...options].reverse().findIndex((o) => !o.disabled);\r\n if (last >= 0) setActiveIndex(last);\r\n } else if (key === 'Enter' || key === ' ') {\r\n e.preventDefault();\r\n if (activeIndex >= 0) selectIndex(activeIndex);\r\n } else if (key === 'Escape') {\r\n e.preventDefault();\r\n closeList();\r\n } else if (key.length === 1 && key.match(/\\S/)) {\r\n // typeahead: accumulate buffer of characters\r\n const now = Date.now();\r\n if (now - typeaheadRef.current.lastTime > 700) {\r\n typeaheadRef.current.buffer = '';\r\n }\r\n typeaheadRef.current.buffer += key.toLowerCase();\r\n typeaheadRef.current.lastTime = now;\r\n\r\n const buf = typeaheadRef.current.buffer;\r\n const start = activeIndex >= 0 ? activeIndex + 1 : 0;\r\n const len = options.length;\r\n // search forward from next item, wrapping\r\n for (let i = 0; i < len; i++) {\r\n const idx = (start + i) % len;\r\n const lab = String(options[idx].label).toLowerCase();\r\n if (!options[idx].disabled && lab.startsWith(buf)) {\r\n setActiveIndex(idx);\r\n break;\r\n }\r\n }\r\n }\r\n };\r\n\r\n // Click on option\r\n const onOptionClick = (i: number) => {\r\n selectIndex(i);\r\n };\r\n\r\n // compute listbox position based on trigger\r\n const [pos, setPos] = useState<{ top: number; left: number } | null>(null);\r\n useEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null);\r\n return;\r\n }\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const top = rect.bottom + 6;\r\n const left = rect.left;\r\n setPos({ top, left });\r\n }, [open]);\r\n\r\n // render\r\n return (\r\n <div className={`ui-select ${className}`}>\r\n {/* Hidden input for form compatibility */}\r\n {name && <input type=\"hidden\" name={name} value={selectedValue ?? ''} />}\r\n\r\n <button\r\n ref={triggerRef}\r\n id={labelId}\r\n type=\"button\"\r\n className=\"ui-select-trigger\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={open}\r\n aria-controls={open ? listboxId : undefined}\r\n aria-disabled={disabled || undefined}\r\n onClick={() => toggleList()}\r\n onKeyDown={onTriggerKeyDown}\r\n disabled={disabled}\r\n >\r\n <span>\r\n {selectedOption ? (\r\n <span>{selectedOption.label}</span>\r\n ) : (\r\n <span className=\"ui-select-placeholder\">{placeholder}</span>\r\n )}\r\n </span>\r\n <span className=\"ui-select-chevron\" aria-hidden>\r\n ▾\r\n </span>\r\n </button>\r\n\r\n {open && pos && (\r\n <Portal>\r\n <div\r\n id={listboxId}\r\n ref={listRef}\r\n className=\"ui-select-listbox\"\r\n role=\"listbox\"\r\n aria-labelledby={labelId}\r\n tabIndex={0}\r\n style={{\r\n top: pos.top,\r\n left: pos.left,\r\n }}\r\n onKeyDown={onListKeyDown}\r\n >\r\n {options.map((opt, i) => {\r\n const isSelected = selectedValue === opt.value;\r\n const isActive = activeIndex === i;\r\n return (\r\n <div\r\n key={opt.value}\r\n id={`${listboxId}-option-${i}`}\r\n role=\"option\"\r\n aria-selected={isSelected}\r\n aria-disabled={opt.disabled || undefined}\r\n className=\"ui-select-option\"\r\n tabIndex={-1}\r\n onMouseDown={(e) => e.preventDefault()} /* prevent blur */\r\n onClick={() => !opt.disabled && onOptionClick(i)}\r\n // Visual focus when active (not moving DOM focus)\r\n style={{\r\n outline: isActive\r\n ? '2px solid var(--color-primary)'\r\n : undefined,\r\n }}\r\n >\r\n {opt.label}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </Portal>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"names":["findNextEnabled","options","start","direction","len","i","step","Select","value","defaultValue","onChange","placeholder","disabled","name","id","className","isControlled","internalValue","setInternalValue","useState","selectedValue","selectedOption","o","open","setOpen","activeIndex","setActiveIndex","typeaheadRef","useRef","triggerRef","listRef","reactId","React","baseId","listboxId","labelId","openList","useCallback","closeList","restoreFocus","toggleList","s","useEffect","onClickOutside","selIndex","idx","selectIndex","opt","onTriggerKeyDown","onListKeyDown","key","next","prev","first","last","now","buf","lab","onOptionClick","pos","setPos","rect","top","left","jsxs","jsx","Portal","isSelected","isActive","e"],"mappings":"uUAsDA,SAASA,EAAgBC,EAAmBC,EAAeC,EAAmB,CAC5E,MAAMC,EAAMH,EAAQ,OACpB,IAAII,EAAIH,EACR,QAASI,EAAO,EAAGA,EAAOF,EAAKE,IAE7B,GADAD,GAAKA,EAAIF,EAAYC,GAAOA,EACxB,CAACH,EAAQI,CAAC,EAAE,SAAU,OAAOA,EAEnC,MAAO,EACT,CAEO,SAASE,EAAO,CACrB,QAAAN,EACA,MAAAO,EACA,aAAAC,EACA,SAAAC,EACA,YAAAC,EAAc,YACd,SAAAC,EAAW,GACX,KAAAC,EACA,GAAAC,EACA,UAAAC,EAAY,EACd,EAAgB,CAEd,MAAMC,EAAeR,IAAU,OACzB,CAACS,EAAeC,CAAgB,EAAIC,EAAAA,SACxCV,CAAA,EAEIW,EAAgBJ,EAAeR,EAAQS,EAEvCI,EAAiBpB,EAAQ,KAAMqB,GAAMA,EAAE,QAAUF,CAAa,GAAK,KAEnE,CAACG,EAAMC,CAAO,EAAIL,EAAAA,SAAS,EAAK,EAChC,CAACM,EAAaC,CAAc,EAAIP,EAAAA,SAAiB,IACrDlB,EAAQ,UAAWqB,GAAM,CAACA,EAAE,QAAQ,CAAA,EAIhCK,EAAeC,EAAAA,OAAO,CAAE,OAAQ,GAAI,SAAU,EAAG,EAGjDC,EAAaD,EAAAA,OAAiC,IAAI,EAClDE,EAAUF,EAAAA,OAA8B,IAAI,EAG5CG,EAAUC,EAAM,MAAA,EAChBC,EAASnB,GAAMiB,EACfG,EAAY,GAAGD,CAAM,WACrBE,EAAU,GAAGF,CAAM,SAGnBG,EAAWC,EAAAA,YAAY,IAAM,CAC7BzB,GACJY,EAAQ,EAAI,CACd,EAAG,CAACZ,CAAQ,CAAC,EAEP0B,EAAYD,EAAAA,YAAY,IAAM,CAClCb,EAAQ,EAAK,EAEbe,EAAAA,aAAaV,EAAW,OAAO,CACjC,EAAG,CAAA,CAAE,EAGCW,EAAaH,EAAAA,YAAY,IAAM,CAC/BzB,GACJY,EAASiB,GAAM,CAACA,CAAC,CACnB,EAAG,CAAC7B,CAAQ,CAAC,EAGb8B,EAAAA,UAAU,IAAM,CACd,GAAKnB,EACL,OAAOoB,EAAAA,eAAeb,EAAS,IAAMN,EAAQ,EAAK,CAAC,CACrD,EAAG,CAACD,CAAI,CAAC,EAGTmB,EAAAA,UAAU,IAAM,CACd,GAAI,CAACnB,EAAM,OAEX,MAAMqB,EAAW3C,EAAQ,UACtBqB,GAAMA,EAAE,QAAUF,GAAiB,CAACE,EAAE,QAAA,EAEnCuB,EACJD,GAAY,EAAIA,EAAW3C,EAAQ,UAAWqB,GAAM,CAACA,EAAE,QAAQ,EACjEI,EAAemB,GAAO,EAAIA,EAAM,EAAE,EAGlC,WAAW,IAAM,CACff,EAAQ,SAAS,MAAA,CACnB,EAAG,CAAC,CACN,EAAG,CAACP,EAAMtB,EAASmB,CAAa,CAAC,EAGjC,MAAM0B,EAAcT,EAAAA,YACjBhC,GAAc,CACb,GAAIA,EAAI,GAAKA,GAAKJ,EAAQ,OAAQ,OAClC,MAAM8C,EAAM9C,EAAQI,CAAC,EACjB0C,EAAI,WACH/B,GAAcE,EAAiB6B,EAAI,KAAK,EAC7CrC,IAAWqC,EAAI,KAAK,EACpBvB,EAAQ,EAAK,EAEbK,EAAW,SAAS,MAAA,EACtB,EACA,CAACb,EAAcf,EAASS,CAAQ,CAAA,EAI5BsC,EAAoB,GAAwC,CAC5DpC,GACA,CAAC,YAAa,UAAW,IAAK,OAAO,EAAE,SAAS,EAAE,GAAG,IACvD,EAAE,eAAA,EACFwB,EAAA,EAEJ,EAGMa,EAAiB,GAAqC,CAC1D,MAAMC,EAAM,EAAE,IACd,GAAIA,IAAQ,YAAa,CACvB,EAAE,eAAA,EACF,MAAMC,EAAOnD,EAAgBC,EAASwB,EAAa,CAAC,EAChD0B,GAAQ,GAAGzB,EAAeyB,CAAI,CACpC,SAAWD,IAAQ,UAAW,CAC5B,EAAE,eAAA,EACF,MAAME,EAAOpD,EAAgBC,EAASwB,EAAa,EAAE,EACjD2B,GAAQ,GAAG1B,EAAe0B,CAAI,CACpC,SAAWF,IAAQ,OAAQ,CACzB,EAAE,eAAA,EACF,MAAMG,EAAQpD,EAAQ,UAAWqB,GAAM,CAACA,EAAE,QAAQ,EAC9C+B,GAAS,GAAG3B,EAAe2B,CAAK,CACtC,SAAWH,IAAQ,MAAO,CACxB,EAAE,eAAA,EACF,MAAMI,EACJrD,EAAQ,OACR,EACA,CAAC,GAAGA,CAAO,EAAE,QAAA,EAAU,UAAWqB,GAAM,CAACA,EAAE,QAAQ,EACjDgC,GAAQ,GAAG5B,EAAe4B,CAAI,CACpC,SAAWJ,IAAQ,SAAWA,IAAQ,IACpC,EAAE,eAAA,EACEzB,GAAe,GAAGqB,EAAYrB,CAAW,UACpCyB,IAAQ,SACjB,EAAE,eAAA,EACFZ,EAAA,UACSY,EAAI,SAAW,GAAKA,EAAI,MAAM,IAAI,EAAG,CAE9C,MAAMK,EAAM,KAAK,IAAA,EACbA,EAAM5B,EAAa,QAAQ,SAAW,MACxCA,EAAa,QAAQ,OAAS,IAEhCA,EAAa,QAAQ,QAAUuB,EAAI,YAAA,EACnCvB,EAAa,QAAQ,SAAW4B,EAEhC,MAAMC,EAAM7B,EAAa,QAAQ,OAC3BzB,EAAQuB,GAAe,EAAIA,EAAc,EAAI,EAC7CrB,EAAMH,EAAQ,OAEpB,QAASI,EAAI,EAAGA,EAAID,EAAKC,IAAK,CAC5B,MAAMwC,GAAO3C,EAAQG,GAAKD,EACpBqD,EAAM,OAAOxD,EAAQ4C,CAAG,EAAE,KAAK,EAAE,YAAA,EACvC,GAAI,CAAC5C,EAAQ4C,CAAG,EAAE,UAAYY,EAAI,WAAWD,CAAG,EAAG,CACjD9B,EAAemB,CAAG,EAClB,KACF,CACF,CACF,CACF,EAGMa,EAAiBrD,GAAc,CACnCyC,EAAYzC,CAAC,CACf,EAGM,CAACsD,EAAKC,CAAM,EAAIzC,EAAAA,SAA+C,IAAI,EACzEuB,OAAAA,EAAAA,UAAU,IAAM,CACd,GAAI,CAACnB,GAAQ,CAACM,EAAW,QAAS,CAChC+B,EAAO,IAAI,EACX,MACF,CACA,MAAMC,EAAOhC,EAAW,QAAQ,sBAAA,EAC1BiC,EAAMD,EAAK,OAAS,EACpBE,EAAOF,EAAK,KAClBD,EAAO,CAAE,IAAAE,EAAK,KAAAC,EAAM,CACtB,EAAG,CAACxC,CAAI,CAAC,EAIPyC,EAAAA,KAAC,MAAA,CAAI,UAAW,aAAajD,CAAS,GAEnC,SAAA,CAAAF,SAAS,QAAA,CAAM,KAAK,SAAS,KAAAA,EAAY,MAAOO,GAAiB,GAAI,EAEtE4C,EAAAA,KAAC,SAAA,CACC,IAAKnC,EACL,GAAIM,EACJ,KAAK,SACL,UAAU,oBACV,gBAAc,UACd,gBAAeZ,EACf,gBAAeA,EAAOW,EAAY,OAClC,gBAAetB,GAAY,OAC3B,QAAS,IAAM4B,EAAA,EACf,UAAWQ,EACX,SAAApC,EAEA,SAAA,CAAAqD,MAAC,OAAA,CACE,SAAA5C,EACC4C,EAAAA,IAAC,OAAA,CAAM,SAAA5C,EAAe,KAAA,CAAM,EAE5B4C,EAAAA,IAAC,OAAA,CAAK,UAAU,wBAAyB,SAAAtD,CAAA,CAAY,EAEzD,QACC,OAAA,CAAK,UAAU,oBAAoB,cAAW,GAAC,SAAA,GAAA,CAEhD,CAAA,CAAA,CAAA,EAGDY,GAAQoC,GACPM,EAAAA,IAACC,EAAAA,OAAA,CACC,SAAAD,EAAAA,IAAC,MAAA,CACC,GAAI/B,EACJ,IAAKJ,EACL,UAAU,oBACV,KAAK,UACL,kBAAiBK,EACjB,SAAU,EACV,MAAO,CACL,IAAKwB,EAAI,IACT,KAAMA,EAAI,IAAA,EAEZ,UAAWV,EAEV,SAAAhD,EAAQ,IAAI,CAAC8C,EAAK1C,IAAM,CACvB,MAAM8D,EAAa/C,IAAkB2B,EAAI,MACnCqB,EAAW3C,IAAgBpB,EACjC,OACE4D,EAAAA,IAAC,MAAA,CAEC,GAAI,GAAG/B,CAAS,WAAW7B,CAAC,GAC5B,KAAK,SACL,gBAAe8D,EACf,gBAAepB,EAAI,UAAY,OAC/B,UAAU,mBACV,SAAU,GACV,YAAcsB,GAAMA,EAAE,eAAA,EACtB,QAAS,IAAM,CAACtB,EAAI,UAAYW,EAAcrD,CAAC,EAE/C,MAAO,CACL,QAAS+D,EACL,iCACA,MAAA,EAGL,SAAArB,EAAI,KAAA,EAhBAA,EAAI,KAAA,CAmBf,CAAC,CAAA,CAAA,CACH,CACF,CAAA,EAEJ,CAEJ"}
@@ -0,0 +1,165 @@
1
+ import { jsxs as V, jsx as i } from "react/jsx-runtime";
2
+ import X, { useState as x, useRef as A, useCallback as h, useEffect as E } from "react";
3
+ /* empty css */
4
+ import { restoreFocus as Y } from "../../utils/restorefocus/restoreFocus.js";
5
+ import { onClickOutside as Z } from "../../utils/onclickoutside/onClickOutside.js";
6
+ import { Portal as _ } from "../../utils/portal/portal.js";
7
+ function j(l, m, I) {
8
+ const o = l.length;
9
+ let f = m;
10
+ for (let r = 0; r < o; r++)
11
+ if (f = (f + I + o) % o, !l[f].disabled) return f;
12
+ return -1;
13
+ }
14
+ function ie({
15
+ options: l,
16
+ value: m,
17
+ defaultValue: I,
18
+ onChange: o,
19
+ placeholder: f = "Select...",
20
+ disabled: r = !1,
21
+ name: L,
22
+ id: P,
23
+ className: U = ""
24
+ }) {
25
+ const D = m !== void 0, [B, F] = x(
26
+ I
27
+ ), d = D ? m : B, R = l.find((e) => e.value === d) ?? null, [s, b] = x(!1), [c, u] = x(
28
+ () => l.findIndex((e) => !e.disabled)
29
+ ), v = A({ buffer: "", lastTime: 0 }), p = A(null), g = A(null), H = X.useId(), S = P ?? H, w = `${S}-listbox`, T = `${S}-label`, M = h(() => {
30
+ r || b(!0);
31
+ }, [r]), W = h(() => {
32
+ b(!1), Y(p.current);
33
+ }, []), q = h(() => {
34
+ r || b((e) => !e);
35
+ }, [r]);
36
+ E(() => {
37
+ if (s)
38
+ return Z(g, () => b(!1));
39
+ }, [s]), E(() => {
40
+ if (!s) return;
41
+ const e = l.findIndex(
42
+ (n) => n.value === d && !n.disabled
43
+ ), t = e >= 0 ? e : l.findIndex((n) => !n.disabled);
44
+ u(t >= 0 ? t : -1), setTimeout(() => {
45
+ g.current?.focus();
46
+ }, 0);
47
+ }, [s, l, d]);
48
+ const $ = h(
49
+ (e) => {
50
+ if (e < 0 || e >= l.length) return;
51
+ const t = l[e];
52
+ t.disabled || (D || F(t.value), o?.(t.value), b(!1), p.current?.focus());
53
+ },
54
+ [D, l, o]
55
+ ), z = (e) => {
56
+ r || ["ArrowDown", "ArrowUp", " ", "Enter"].includes(e.key) && (e.preventDefault(), M());
57
+ }, G = (e) => {
58
+ const t = e.key;
59
+ if (t === "ArrowDown") {
60
+ e.preventDefault();
61
+ const n = j(l, c, 1);
62
+ n >= 0 && u(n);
63
+ } else if (t === "ArrowUp") {
64
+ e.preventDefault();
65
+ const n = j(l, c, -1);
66
+ n >= 0 && u(n);
67
+ } else if (t === "Home") {
68
+ e.preventDefault();
69
+ const n = l.findIndex((a) => !a.disabled);
70
+ n >= 0 && u(n);
71
+ } else if (t === "End") {
72
+ e.preventDefault();
73
+ const n = l.length - 1 - [...l].reverse().findIndex((a) => !a.disabled);
74
+ n >= 0 && u(n);
75
+ } else if (t === "Enter" || t === " ")
76
+ e.preventDefault(), c >= 0 && $(c);
77
+ else if (t === "Escape")
78
+ e.preventDefault(), W();
79
+ else if (t.length === 1 && t.match(/\S/)) {
80
+ const n = Date.now();
81
+ n - v.current.lastTime > 700 && (v.current.buffer = ""), v.current.buffer += t.toLowerCase(), v.current.lastTime = n;
82
+ const a = v.current.buffer, k = c >= 0 ? c + 1 : 0, O = l.length;
83
+ for (let C = 0; C < O; C++) {
84
+ const N = (k + C) % O, Q = String(l[N].label).toLowerCase();
85
+ if (!l[N].disabled && Q.startsWith(a)) {
86
+ u(N);
87
+ break;
88
+ }
89
+ }
90
+ }
91
+ }, J = (e) => {
92
+ $(e);
93
+ }, [y, K] = x(null);
94
+ return E(() => {
95
+ if (!s || !p.current) {
96
+ K(null);
97
+ return;
98
+ }
99
+ const e = p.current.getBoundingClientRect(), t = e.bottom + 6, n = e.left;
100
+ K({ top: t, left: n });
101
+ }, [s]), /* @__PURE__ */ V("div", { className: `ui-select ${U}`, children: [
102
+ L && /* @__PURE__ */ i("input", { type: "hidden", name: L, value: d ?? "" }),
103
+ /* @__PURE__ */ V(
104
+ "button",
105
+ {
106
+ ref: p,
107
+ id: T,
108
+ type: "button",
109
+ className: "ui-select-trigger",
110
+ "aria-haspopup": "listbox",
111
+ "aria-expanded": s,
112
+ "aria-controls": s ? w : void 0,
113
+ "aria-disabled": r || void 0,
114
+ onClick: () => q(),
115
+ onKeyDown: z,
116
+ disabled: r,
117
+ children: [
118
+ /* @__PURE__ */ i("span", { children: R ? /* @__PURE__ */ i("span", { children: R.label }) : /* @__PURE__ */ i("span", { className: "ui-select-placeholder", children: f }) }),
119
+ /* @__PURE__ */ i("span", { className: "ui-select-chevron", "aria-hidden": !0, children: "▾" })
120
+ ]
121
+ }
122
+ ),
123
+ s && y && /* @__PURE__ */ i(_, { children: /* @__PURE__ */ i(
124
+ "div",
125
+ {
126
+ id: w,
127
+ ref: g,
128
+ className: "ui-select-listbox",
129
+ role: "listbox",
130
+ "aria-labelledby": T,
131
+ tabIndex: 0,
132
+ style: {
133
+ top: y.top,
134
+ left: y.left
135
+ },
136
+ onKeyDown: G,
137
+ children: l.map((e, t) => {
138
+ const n = d === e.value, a = c === t;
139
+ return /* @__PURE__ */ i(
140
+ "div",
141
+ {
142
+ id: `${w}-option-${t}`,
143
+ role: "option",
144
+ "aria-selected": n,
145
+ "aria-disabled": e.disabled || void 0,
146
+ className: "ui-select-option",
147
+ tabIndex: -1,
148
+ onMouseDown: (k) => k.preventDefault(),
149
+ onClick: () => !e.disabled && J(t),
150
+ style: {
151
+ outline: a ? "2px solid var(--color-primary)" : void 0
152
+ },
153
+ children: e.label
154
+ },
155
+ e.value
156
+ );
157
+ })
158
+ }
159
+ ) })
160
+ ] });
161
+ }
162
+ export {
163
+ ie as Select
164
+ };
165
+ //# sourceMappingURL=Select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Select.js","sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["/**\r\n * Select.tsx\r\n * ----------\r\n * Accessible single-select (listbox) component.\r\n *\r\n * Features:\r\n * - WAI-ARIA listbox pattern\r\n * - Keyboard navigation (ArrowUp/ArrowDown, Home, End, Enter, Space, Esc)\r\n * - Typeahead support\r\n * - Controlled + uncontrolled modes\r\n * - Portal rendering for the listbox\r\n * - Click outside to close\r\n * - Proper ARIA attributes and relationships\r\n * - Hidden input for form compatibility (optional name prop)\r\n *\r\n * Usage:\r\n * <Select\r\n * options={[{ value: 'a', label: 'Apple' }, { value: 'b', label: 'Banana' }]}\r\n * value={value} // optional controlled value\r\n * onChange={(v) => setValue(v)} // required if controlled\r\n * placeholder=\"Select fruit\"\r\n * name=\"fruit\" // optional, adds hidden input\r\n * />\r\n */\r\n\r\nimport React, {\r\n useState,\r\n useRef,\r\n useEffect,\r\n useCallback,\r\n KeyboardEvent,\r\n} from 'react';\r\nimport './Select.css';\r\nimport { Portal, onClickOutside, restoreFocus } from '../../utils/index';\r\n\r\ntype Option = {\r\n value: string;\r\n label: React.ReactNode;\r\n disabled?: boolean;\r\n};\r\n\r\ninterface SelectProps {\r\n options: Option[];\r\n value?: string; // controlled value\r\n defaultValue?: string; // uncontrolled initial\r\n onChange?: (value: string) => void;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n name?: string; // if provided, render hidden input for forms\r\n id?: string;\r\n className?: string;\r\n}\r\n\r\n/* Helper: find next enabled index given direction */\r\nfunction findNextEnabled(options: Option[], start: number, direction: 1 | -1) {\r\n const len = options.length;\r\n let i = start;\r\n for (let step = 0; step < len; step++) {\r\n i = (i + direction + len) % len;\r\n if (!options[i].disabled) return i;\r\n }\r\n return -1;\r\n}\r\n\r\nexport function Select({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n placeholder = 'Select...',\r\n disabled = false,\r\n name,\r\n id,\r\n className = '',\r\n}: SelectProps) {\r\n // Controlled vs uncontrolled\r\n const isControlled = value !== undefined;\r\n const [internalValue, setInternalValue] = useState<string | undefined>(\r\n defaultValue\r\n );\r\n const selectedValue = isControlled ? value : internalValue;\r\n\r\n const selectedOption = options.find((o) => o.value === selectedValue) ?? null;\r\n\r\n const [open, setOpen] = useState(false);\r\n const [activeIndex, setActiveIndex] = useState<number>(() =>\r\n options.findIndex((o) => !o.disabled)\r\n );\r\n\r\n // Typeahead buffer\r\n const typeaheadRef = useRef({ buffer: '', lastTime: 0 });\r\n\r\n // Refs\r\n const triggerRef = useRef<HTMLButtonElement | null>(null);\r\n const listRef = useRef<HTMLDivElement | null>(null);\r\n\r\n // Unique ids\r\n const reactId = React.useId();\r\n const baseId = id ?? reactId;\r\n const listboxId = `${baseId}-listbox`;\r\n const labelId = `${baseId}-label`;\r\n\r\n // Open/close helpers\r\n const openList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen(true);\r\n }, [disabled]);\r\n\r\n const closeList = useCallback(() => {\r\n setOpen(false);\r\n // restore focus to trigger\r\n restoreFocus(triggerRef.current);\r\n }, []);\r\n\r\n // Toggle\r\n const toggleList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen((s) => !s);\r\n }, [disabled]);\r\n\r\n // Click outside to close\r\n useEffect(() => {\r\n if (!open) return;\r\n return onClickOutside(listRef, () => setOpen(false));\r\n }, [open]);\r\n\r\n // Ensure that when opening, activeIndex is valid and focus moves to listbox\r\n useEffect(() => {\r\n if (!open) return;\r\n // set active to selected or first enabled\r\n const selIndex = options.findIndex(\r\n (o) => o.value === selectedValue && !o.disabled\r\n );\r\n const idx =\r\n selIndex >= 0 ? selIndex : options.findIndex((o) => !o.disabled);\r\n setActiveIndex(idx >= 0 ? idx : -1);\r\n\r\n // focus the list container so keyboard events are captured\r\n setTimeout(() => {\r\n listRef.current?.focus();\r\n }, 0);\r\n }, [open, options, selectedValue]);\r\n\r\n // Selection handler\r\n const selectIndex = useCallback(\r\n (i: number) => {\r\n if (i < 0 || i >= options.length) return;\r\n const opt = options[i];\r\n if (opt.disabled) return;\r\n if (!isControlled) setInternalValue(opt.value);\r\n onChange?.(opt.value);\r\n setOpen(false);\r\n // restore focus\r\n triggerRef.current?.focus();\r\n },\r\n [isControlled, options, onChange]\r\n );\r\n\r\n // Keyboard handling when trigger has focus\r\n const onTriggerKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\r\n if (disabled) return;\r\n if (['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(e.key)) {\r\n e.preventDefault();\r\n openList();\r\n }\r\n };\r\n\r\n // Keyboard handling when listbox has focus\r\n const onListKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n const key = e.key;\r\n if (key === 'ArrowDown') {\r\n e.preventDefault();\r\n const next = findNextEnabled(options, activeIndex, 1);\r\n if (next >= 0) setActiveIndex(next);\r\n } else if (key === 'ArrowUp') {\r\n e.preventDefault();\r\n const prev = findNextEnabled(options, activeIndex, -1);\r\n if (prev >= 0) setActiveIndex(prev);\r\n } else if (key === 'Home') {\r\n e.preventDefault();\r\n const first = options.findIndex((o) => !o.disabled);\r\n if (first >= 0) setActiveIndex(first);\r\n } else if (key === 'End') {\r\n e.preventDefault();\r\n const last =\r\n options.length -\r\n 1 -\r\n [...options].reverse().findIndex((o) => !o.disabled);\r\n if (last >= 0) setActiveIndex(last);\r\n } else if (key === 'Enter' || key === ' ') {\r\n e.preventDefault();\r\n if (activeIndex >= 0) selectIndex(activeIndex);\r\n } else if (key === 'Escape') {\r\n e.preventDefault();\r\n closeList();\r\n } else if (key.length === 1 && key.match(/\\S/)) {\r\n // typeahead: accumulate buffer of characters\r\n const now = Date.now();\r\n if (now - typeaheadRef.current.lastTime > 700) {\r\n typeaheadRef.current.buffer = '';\r\n }\r\n typeaheadRef.current.buffer += key.toLowerCase();\r\n typeaheadRef.current.lastTime = now;\r\n\r\n const buf = typeaheadRef.current.buffer;\r\n const start = activeIndex >= 0 ? activeIndex + 1 : 0;\r\n const len = options.length;\r\n // search forward from next item, wrapping\r\n for (let i = 0; i < len; i++) {\r\n const idx = (start + i) % len;\r\n const lab = String(options[idx].label).toLowerCase();\r\n if (!options[idx].disabled && lab.startsWith(buf)) {\r\n setActiveIndex(idx);\r\n break;\r\n }\r\n }\r\n }\r\n };\r\n\r\n // Click on option\r\n const onOptionClick = (i: number) => {\r\n selectIndex(i);\r\n };\r\n\r\n // compute listbox position based on trigger\r\n const [pos, setPos] = useState<{ top: number; left: number } | null>(null);\r\n useEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null);\r\n return;\r\n }\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const top = rect.bottom + 6;\r\n const left = rect.left;\r\n setPos({ top, left });\r\n }, [open]);\r\n\r\n // render\r\n return (\r\n <div className={`ui-select ${className}`}>\r\n {/* Hidden input for form compatibility */}\r\n {name && <input type=\"hidden\" name={name} value={selectedValue ?? ''} />}\r\n\r\n <button\r\n ref={triggerRef}\r\n id={labelId}\r\n type=\"button\"\r\n className=\"ui-select-trigger\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={open}\r\n aria-controls={open ? listboxId : undefined}\r\n aria-disabled={disabled || undefined}\r\n onClick={() => toggleList()}\r\n onKeyDown={onTriggerKeyDown}\r\n disabled={disabled}\r\n >\r\n <span>\r\n {selectedOption ? (\r\n <span>{selectedOption.label}</span>\r\n ) : (\r\n <span className=\"ui-select-placeholder\">{placeholder}</span>\r\n )}\r\n </span>\r\n <span className=\"ui-select-chevron\" aria-hidden>\r\n ▾\r\n </span>\r\n </button>\r\n\r\n {open && pos && (\r\n <Portal>\r\n <div\r\n id={listboxId}\r\n ref={listRef}\r\n className=\"ui-select-listbox\"\r\n role=\"listbox\"\r\n aria-labelledby={labelId}\r\n tabIndex={0}\r\n style={{\r\n top: pos.top,\r\n left: pos.left,\r\n }}\r\n onKeyDown={onListKeyDown}\r\n >\r\n {options.map((opt, i) => {\r\n const isSelected = selectedValue === opt.value;\r\n const isActive = activeIndex === i;\r\n return (\r\n <div\r\n key={opt.value}\r\n id={`${listboxId}-option-${i}`}\r\n role=\"option\"\r\n aria-selected={isSelected}\r\n aria-disabled={opt.disabled || undefined}\r\n className=\"ui-select-option\"\r\n tabIndex={-1}\r\n onMouseDown={(e) => e.preventDefault()} /* prevent blur */\r\n onClick={() => !opt.disabled && onOptionClick(i)}\r\n // Visual focus when active (not moving DOM focus)\r\n style={{\r\n outline: isActive\r\n ? '2px solid var(--color-primary)'\r\n : undefined,\r\n }}\r\n >\r\n {opt.label}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </Portal>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"names":["findNextEnabled","options","start","direction","len","i","step","Select","value","defaultValue","onChange","placeholder","disabled","name","id","className","isControlled","internalValue","setInternalValue","useState","selectedValue","selectedOption","o","open","setOpen","activeIndex","setActiveIndex","typeaheadRef","useRef","triggerRef","listRef","reactId","React","baseId","listboxId","labelId","openList","useCallback","closeList","restoreFocus","toggleList","s","useEffect","onClickOutside","selIndex","idx","selectIndex","opt","onTriggerKeyDown","onListKeyDown","key","next","prev","first","last","now","buf","lab","onOptionClick","pos","setPos","rect","top","left","jsxs","jsx","Portal","isSelected","isActive","e"],"mappings":";;;;;;AAsDA,SAASA,EAAgBC,GAAmBC,GAAeC,GAAmB;AAC5E,QAAMC,IAAMH,EAAQ;AACpB,MAAII,IAAIH;AACR,WAASI,IAAO,GAAGA,IAAOF,GAAKE;AAE7B,QADAD,KAAKA,IAAIF,IAAYC,KAAOA,GACxB,CAACH,EAAQI,CAAC,EAAE,SAAU,QAAOA;AAEnC,SAAO;AACT;AAEO,SAASE,GAAO;AAAA,EACrB,SAAAN;AAAA,EACA,OAAAO;AAAA,EACA,cAAAC;AAAA,EACA,UAAAC;AAAA,EACA,aAAAC,IAAc;AAAA,EACd,UAAAC,IAAW;AAAA,EACX,MAAAC;AAAA,EACA,IAAAC;AAAA,EACA,WAAAC,IAAY;AACd,GAAgB;AAEd,QAAMC,IAAeR,MAAU,QACzB,CAACS,GAAeC,CAAgB,IAAIC;AAAA,IACxCV;AAAA,EAAA,GAEIW,IAAgBJ,IAAeR,IAAQS,GAEvCI,IAAiBpB,EAAQ,KAAK,CAACqB,MAAMA,EAAE,UAAUF,CAAa,KAAK,MAEnE,CAACG,GAAMC,CAAO,IAAIL,EAAS,EAAK,GAChC,CAACM,GAAaC,CAAc,IAAIP;AAAA,IAAiB,MACrDlB,EAAQ,UAAU,CAACqB,MAAM,CAACA,EAAE,QAAQ;AAAA,EAAA,GAIhCK,IAAeC,EAAO,EAAE,QAAQ,IAAI,UAAU,GAAG,GAGjDC,IAAaD,EAAiC,IAAI,GAClDE,IAAUF,EAA8B,IAAI,GAG5CG,IAAUC,EAAM,MAAA,GAChBC,IAASnB,KAAMiB,GACfG,IAAY,GAAGD,CAAM,YACrBE,IAAU,GAAGF,CAAM,UAGnBG,IAAWC,EAAY,MAAM;AACjC,IAAIzB,KACJY,EAAQ,EAAI;AAAA,EACd,GAAG,CAACZ,CAAQ,CAAC,GAEP0B,IAAYD,EAAY,MAAM;AAClC,IAAAb,EAAQ,EAAK,GAEbe,EAAaV,EAAW,OAAO;AAAA,EACjC,GAAG,CAAA,CAAE,GAGCW,IAAaH,EAAY,MAAM;AACnC,IAAIzB,KACJY,EAAQ,CAACiB,MAAM,CAACA,CAAC;AAAA,EACnB,GAAG,CAAC7B,CAAQ,CAAC;AAGb,EAAA8B,EAAU,MAAM;AACd,QAAKnB;AACL,aAAOoB,EAAeb,GAAS,MAAMN,EAAQ,EAAK,CAAC;AAAA,EACrD,GAAG,CAACD,CAAI,CAAC,GAGTmB,EAAU,MAAM;AACd,QAAI,CAACnB,EAAM;AAEX,UAAMqB,IAAW3C,EAAQ;AAAA,MACvB,CAACqB,MAAMA,EAAE,UAAUF,KAAiB,CAACE,EAAE;AAAA,IAAA,GAEnCuB,IACJD,KAAY,IAAIA,IAAW3C,EAAQ,UAAU,CAACqB,MAAM,CAACA,EAAE,QAAQ;AACjE,IAAAI,EAAemB,KAAO,IAAIA,IAAM,EAAE,GAGlC,WAAW,MAAM;AACf,MAAAf,EAAQ,SAAS,MAAA;AAAA,IACnB,GAAG,CAAC;AAAA,EACN,GAAG,CAACP,GAAMtB,GAASmB,CAAa,CAAC;AAGjC,QAAM0B,IAAcT;AAAA,IAClB,CAAChC,MAAc;AACb,UAAIA,IAAI,KAAKA,KAAKJ,EAAQ,OAAQ;AAClC,YAAM8C,IAAM9C,EAAQI,CAAC;AACrB,MAAI0C,EAAI,aACH/B,KAAcE,EAAiB6B,EAAI,KAAK,GAC7CrC,IAAWqC,EAAI,KAAK,GACpBvB,EAAQ,EAAK,GAEbK,EAAW,SAAS,MAAA;AAAA,IACtB;AAAA,IACA,CAACb,GAAcf,GAASS,CAAQ;AAAA,EAAA,GAI5BsC,IAAmB,CAAC,MAAwC;AAChE,IAAIpC,KACA,CAAC,aAAa,WAAW,KAAK,OAAO,EAAE,SAAS,EAAE,GAAG,MACvD,EAAE,eAAA,GACFwB,EAAA;AAAA,EAEJ,GAGMa,IAAgB,CAAC,MAAqC;AAC1D,UAAMC,IAAM,EAAE;AACd,QAAIA,MAAQ,aAAa;AACvB,QAAE,eAAA;AACF,YAAMC,IAAOnD,EAAgBC,GAASwB,GAAa,CAAC;AACpD,MAAI0B,KAAQ,KAAGzB,EAAeyB,CAAI;AAAA,IACpC,WAAWD,MAAQ,WAAW;AAC5B,QAAE,eAAA;AACF,YAAME,IAAOpD,EAAgBC,GAASwB,GAAa,EAAE;AACrD,MAAI2B,KAAQ,KAAG1B,EAAe0B,CAAI;AAAA,IACpC,WAAWF,MAAQ,QAAQ;AACzB,QAAE,eAAA;AACF,YAAMG,IAAQpD,EAAQ,UAAU,CAACqB,MAAM,CAACA,EAAE,QAAQ;AAClD,MAAI+B,KAAS,KAAG3B,EAAe2B,CAAK;AAAA,IACtC,WAAWH,MAAQ,OAAO;AACxB,QAAE,eAAA;AACF,YAAMI,IACJrD,EAAQ,SACR,IACA,CAAC,GAAGA,CAAO,EAAE,QAAA,EAAU,UAAU,CAACqB,MAAM,CAACA,EAAE,QAAQ;AACrD,MAAIgC,KAAQ,KAAG5B,EAAe4B,CAAI;AAAA,IACpC,WAAWJ,MAAQ,WAAWA,MAAQ;AACpC,QAAE,eAAA,GACEzB,KAAe,KAAGqB,EAAYrB,CAAW;AAAA,aACpCyB,MAAQ;AACjB,QAAE,eAAA,GACFZ,EAAA;AAAA,aACSY,EAAI,WAAW,KAAKA,EAAI,MAAM,IAAI,GAAG;AAE9C,YAAMK,IAAM,KAAK,IAAA;AACjB,MAAIA,IAAM5B,EAAa,QAAQ,WAAW,QACxCA,EAAa,QAAQ,SAAS,KAEhCA,EAAa,QAAQ,UAAUuB,EAAI,YAAA,GACnCvB,EAAa,QAAQ,WAAW4B;AAEhC,YAAMC,IAAM7B,EAAa,QAAQ,QAC3BzB,IAAQuB,KAAe,IAAIA,IAAc,IAAI,GAC7CrB,IAAMH,EAAQ;AAEpB,eAASI,IAAI,GAAGA,IAAID,GAAKC,KAAK;AAC5B,cAAMwC,KAAO3C,IAAQG,KAAKD,GACpBqD,IAAM,OAAOxD,EAAQ4C,CAAG,EAAE,KAAK,EAAE,YAAA;AACvC,YAAI,CAAC5C,EAAQ4C,CAAG,EAAE,YAAYY,EAAI,WAAWD,CAAG,GAAG;AACjD,UAAA9B,EAAemB,CAAG;AAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAGMa,IAAgB,CAACrD,MAAc;AACnC,IAAAyC,EAAYzC,CAAC;AAAA,EACf,GAGM,CAACsD,GAAKC,CAAM,IAAIzC,EAA+C,IAAI;AACzE,SAAAuB,EAAU,MAAM;AACd,QAAI,CAACnB,KAAQ,CAACM,EAAW,SAAS;AAChC,MAAA+B,EAAO,IAAI;AACX;AAAA,IACF;AACA,UAAMC,IAAOhC,EAAW,QAAQ,sBAAA,GAC1BiC,IAAMD,EAAK,SAAS,GACpBE,IAAOF,EAAK;AAClB,IAAAD,EAAO,EAAE,KAAAE,GAAK,MAAAC,GAAM;AAAA,EACtB,GAAG,CAACxC,CAAI,CAAC,GAIP,gBAAAyC,EAAC,OAAA,EAAI,WAAW,aAAajD,CAAS,IAEnC,UAAA;AAAA,IAAAF,uBAAS,SAAA,EAAM,MAAK,UAAS,MAAAA,GAAY,OAAOO,KAAiB,IAAI;AAAA,IAEtE,gBAAA4C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAKnC;AAAA,QACL,IAAIM;AAAA,QACJ,MAAK;AAAA,QACL,WAAU;AAAA,QACV,iBAAc;AAAA,QACd,iBAAeZ;AAAA,QACf,iBAAeA,IAAOW,IAAY;AAAA,QAClC,iBAAetB,KAAY;AAAA,QAC3B,SAAS,MAAM4B,EAAA;AAAA,QACf,WAAWQ;AAAA,QACX,UAAApC;AAAA,QAEA,UAAA;AAAA,UAAA,gBAAAqD,EAAC,QAAA,EACE,UAAA5C,IACC,gBAAA4C,EAAC,QAAA,EAAM,UAAA5C,EAAe,MAAA,CAAM,IAE5B,gBAAA4C,EAAC,QAAA,EAAK,WAAU,yBAAyB,UAAAtD,EAAA,CAAY,GAEzD;AAAA,4BACC,QAAA,EAAK,WAAU,qBAAoB,eAAW,IAAC,UAAA,IAAA,CAEhD;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGDY,KAAQoC,KACP,gBAAAM,EAACC,GAAA,EACC,UAAA,gBAAAD;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAI/B;AAAA,QACJ,KAAKJ;AAAA,QACL,WAAU;AAAA,QACV,MAAK;AAAA,QACL,mBAAiBK;AAAA,QACjB,UAAU;AAAA,QACV,OAAO;AAAA,UACL,KAAKwB,EAAI;AAAA,UACT,MAAMA,EAAI;AAAA,QAAA;AAAA,QAEZ,WAAWV;AAAA,QAEV,UAAAhD,EAAQ,IAAI,CAAC8C,GAAK1C,MAAM;AACvB,gBAAM8D,IAAa/C,MAAkB2B,EAAI,OACnCqB,IAAW3C,MAAgBpB;AACjC,iBACE,gBAAA4D;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,IAAI,GAAG/B,CAAS,WAAW7B,CAAC;AAAA,cAC5B,MAAK;AAAA,cACL,iBAAe8D;AAAA,cACf,iBAAepB,EAAI,YAAY;AAAA,cAC/B,WAAU;AAAA,cACV,UAAU;AAAA,cACV,aAAa,CAACsB,MAAMA,EAAE,eAAA;AAAA,cACtB,SAAS,MAAM,CAACtB,EAAI,YAAYW,EAAcrD,CAAC;AAAA,cAE/C,OAAO;AAAA,gBACL,SAAS+D,IACL,mCACA;AAAA,cAAA;AAAA,cAGL,UAAArB,EAAI;AAAA,YAAA;AAAA,YAhBAA,EAAI;AAAA,UAAA;AAAA,QAmBf,CAAC;AAAA,MAAA;AAAA,IAAA,EACH,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"Skeleton.cjs","sources":["../../../../src/components/skeleton/Skeleton.tsx"],"sourcesContent":["/**\r\n * Skeleton.tsx\r\n * Lightweight, accessible skeleton (placeholder) component.\r\n *\r\n * Props:\r\n * - size: visual thickness (xs, sm, md, lg, xl)\r\n * - width: number | string (e.g. 200, \"50%\", \"100px\")\r\n * - animated: whether to show shimmer (respects prefers-reduced-motion)\r\n * - circle: renders as a circle (useful for avatars)\r\n * - className: pass-through for custom styling\r\n *\r\n * Accessibility:\r\n * - role=\"status\" with aria-busy on parent usage is recommended by developer.\r\n * - For individual skeletons that should be ignored by assistive tech set aria-hidden=true.\r\n */\r\n/**\r\n * Skeleton.tsx + Helper Components\r\n * --------------------------------\r\n * Adds:\r\n * - Skeleton.Text\r\n * - Skeleton.Paragraph\r\n * - Skeleton.Avatar\r\n * - Skeleton.Button\r\n * - Skeleton.Card\r\n */\r\n\r\nimport React from 'react';\r\nimport './Skeleton.css';\r\n\r\ntype Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n\r\ninterface SkeletonProps {\r\n size?: Size;\r\n width?: number | string;\r\n animated?: boolean;\r\n circle?: boolean;\r\n ariaHidden?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nfunction formatWidth(w?: number | string): string | undefined {\r\n if (w === undefined) return undefined;\r\n if (typeof w === 'number') return `${w}px`;\r\n return w;\r\n}\r\n\r\nexport function Skeleton({\r\n size = 'md',\r\n width,\r\n animated = true,\r\n circle = false,\r\n ariaHidden = true,\r\n className = '',\r\n style,\r\n}: SkeletonProps) {\r\n const classes = [\r\n 'ui-skeleton',\r\n `ui-skeleton--${size}`,\r\n animated && 'ui-skeleton--animated',\r\n circle && 'ui-skeleton--circle',\r\n className,\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <span\r\n className={classes}\r\n style={{\r\n width: formatWidth(width),\r\n display: width ? 'block' : undefined,\r\n ...style,\r\n }}\r\n aria-hidden={ariaHidden}\r\n role=\"presentation\"\r\n />\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Helper Components\r\n * ------------------------------------------------------*/\r\n\r\n/**\r\n * Simple text line.\r\n * Default width: 100%\r\n */\r\nfunction Text({\r\n width = '100%',\r\n size = 'md',\r\n}: {\r\n width?: number | string;\r\n size?: Size;\r\n}) {\r\n return <Skeleton width={width} size={size} />;\r\n}\r\n\r\n/**\r\n * Paragraph: multiple lines of text skeleton\r\n * lines default = 3\r\n */\r\nfunction Paragraph({\r\n lines = 3,\r\n size = 'md',\r\n}: {\r\n lines?: number;\r\n size?: Size;\r\n}) {\r\n const arr = Array.from({ length: lines });\r\n\r\n return (\r\n <div aria-hidden=\"true\">\r\n {arr.map((_, i) => (\r\n <div key={i} style={{ marginBlockEnd: '8px' }}>\r\n <Skeleton width={i === lines - 1 ? '75%' : '100%'} size={size} />\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Avatar placeholder, circle with dynamic size\r\n */\r\nfunction AvatarSkeleton({ size = 40 }: { size?: number }) {\r\n return <Skeleton circle width={size} animated />;\r\n}\r\n\r\n/**\r\n * Button skeleton (pill-like shape)\r\n */\r\nfunction ButtonSkeleton({\r\n width = 100,\r\n height = 36,\r\n}: {\r\n width?: number;\r\n height?: number;\r\n}) {\r\n return (\r\n <Skeleton\r\n width={width}\r\n size=\"md\"\r\n animated\r\n style={{\r\n height,\r\n borderRadius: 'var(--radius-md)',\r\n }}\r\n />\r\n );\r\n}\r\n\r\n/**\r\n * Card Skeleton: header + lines + footer bar\r\n */\r\nfunction CardSkeleton() {\r\n return (\r\n <div aria-hidden=\"true\">\r\n <Skeleton width=\"60%\" size=\"lg\" style={{ marginBottom: 12 }} />\r\n <Paragraph lines={3} />\r\n <Skeleton width=\"40%\" size=\"md\" style={{ marginTop: 16 }} />\r\n </div>\r\n );\r\n}\r\n\r\n/* Attach helpers */\r\nSkeleton.Text = Text;\r\nSkeleton.Paragraph = Paragraph;\r\nSkeleton.Avatar = AvatarSkeleton;\r\nSkeleton.Button = ButtonSkeleton;\r\nSkeleton.Card = CardSkeleton;\r\n\r\nexport default Skeleton;\r\n"],"names":["formatWidth","w","Skeleton","size","width","animated","circle","ariaHidden","className","style","classes","jsx","Text","Paragraph","lines","arr","_","AvatarSkeleton","ButtonSkeleton","height","CardSkeleton","jsxs"],"mappings":"+KAyCA,SAASA,EAAYC,EAAyC,CAC5D,GAAIA,IAAM,OACV,OAAI,OAAOA,GAAM,SAAiB,GAAGA,CAAC,KAC/BA,CACT,CAEO,SAASC,EAAS,CACvB,KAAAC,EAAO,KACP,MAAAC,EACA,SAAAC,EAAW,GACX,OAAAC,EAAS,GACT,WAAAC,EAAa,GACb,UAAAC,EAAY,GACZ,MAAAC,CACF,EAAkB,CAChB,MAAMC,EAAU,CACd,cACA,gBAAgBP,CAAI,GACpBE,GAAY,wBACZC,GAAU,sBACVE,CAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG,EAEX,OACEG,EAAAA,IAAC,OAAA,CACC,UAAWD,EACX,MAAO,CACL,MAAOV,EAAYI,CAAK,EACxB,QAASA,EAAQ,QAAU,OAC3B,GAAGK,CAAA,EAEL,cAAaF,EACb,KAAK,cAAA,CAAA,CAGX,CAUA,SAASK,EAAK,CACZ,MAAAR,EAAQ,OACR,KAAAD,EAAO,IACT,EAGG,CACD,OAAOQ,EAAAA,IAACT,EAAA,CAAS,MAAAE,EAAc,KAAAD,CAAA,CAAY,CAC7C,CAMA,SAASU,EAAU,CACjB,MAAAC,EAAQ,EACR,KAAAX,EAAO,IACT,EAGG,CACD,MAAMY,EAAM,MAAM,KAAK,CAAE,OAAQD,EAAO,EAExC,OACEH,EAAAA,IAAC,MAAA,CAAI,cAAY,OACd,SAAAI,EAAI,IAAI,CAACC,EAAG,IACXL,EAAAA,IAAC,MAAA,CAAY,MAAO,CAAE,eAAgB,KAAA,EACpC,SAAAA,EAAAA,IAACT,EAAA,CAAS,MAAO,IAAMY,EAAQ,EAAI,MAAQ,OAAQ,KAAAX,CAAA,CAAY,CAAA,EADvD,CAEV,CACD,EACH,CAEJ,CAKA,SAASc,EAAe,CAAE,KAAAd,EAAO,IAAyB,CACxD,aAAQD,EAAA,CAAS,OAAM,GAAC,MAAOC,EAAM,SAAQ,GAAC,CAChD,CAKA,SAASe,EAAe,CACtB,MAAAd,EAAQ,IACR,OAAAe,EAAS,EACX,EAGG,CACD,OACER,EAAAA,IAACT,EAAA,CACC,MAAAE,EACA,KAAK,KACL,SAAQ,GACR,MAAO,CACL,OAAAe,EACA,aAAc,kBAAA,CAChB,CAAA,CAGN,CAKA,SAASC,GAAe,CACtB,OACEC,EAAAA,KAAC,MAAA,CAAI,cAAY,OACf,SAAA,CAAAV,EAAAA,IAACT,EAAA,CAAS,MAAM,MAAM,KAAK,KAAK,MAAO,CAAE,aAAc,EAAA,CAAG,CAAG,EAC7DS,EAAAA,IAACE,EAAA,CAAU,MAAO,CAAA,CAAG,EACrBF,EAAAA,IAACT,EAAA,CAAS,MAAM,MAAM,KAAK,KAAK,MAAO,CAAE,UAAW,GAAG,CAAG,CAAA,EAC5D,CAEJ,CAGAA,EAAS,KAAOU,EAChBV,EAAS,UAAYW,EACrBX,EAAS,OAASe,EAClBf,EAAS,OAASgB,EAClBhB,EAAS,KAAOkB"}
1
+ {"version":3,"file":"Skeleton.cjs","sources":["../../../src/components/skeleton/Skeleton.tsx"],"sourcesContent":["/**\r\n * Skeleton.tsx\r\n * Lightweight, accessible skeleton (placeholder) component.\r\n *\r\n * Props:\r\n * - size: visual thickness (xs, sm, md, lg, xl)\r\n * - width: number | string (e.g. 200, \"50%\", \"100px\")\r\n * - animated: whether to show shimmer (respects prefers-reduced-motion)\r\n * - circle: renders as a circle (useful for avatars)\r\n * - className: pass-through for custom styling\r\n *\r\n * Accessibility:\r\n * - role=\"status\" with aria-busy on parent usage is recommended by developer.\r\n * - For individual skeletons that should be ignored by assistive tech set aria-hidden=true.\r\n */\r\n/**\r\n * Skeleton.tsx + Helper Components\r\n * --------------------------------\r\n * Adds:\r\n * - Skeleton.Text\r\n * - Skeleton.Paragraph\r\n * - Skeleton.Avatar\r\n * - Skeleton.Button\r\n * - Skeleton.Card\r\n */\r\n\r\nimport React from 'react';\r\nimport './Skeleton.css';\r\n\r\ntype Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n\r\ninterface SkeletonProps {\r\n size?: Size;\r\n width?: number | string;\r\n animated?: boolean;\r\n circle?: boolean;\r\n ariaHidden?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nfunction formatWidth(w?: number | string): string | undefined {\r\n if (w === undefined) return undefined;\r\n if (typeof w === 'number') return `${w}px`;\r\n return w;\r\n}\r\n\r\nexport function Skeleton({\r\n size = 'md',\r\n width,\r\n animated = true,\r\n circle = false,\r\n ariaHidden = true,\r\n className = '',\r\n style,\r\n}: SkeletonProps) {\r\n const classes = [\r\n 'ui-skeleton',\r\n `ui-skeleton--${size}`,\r\n animated && 'ui-skeleton--animated',\r\n circle && 'ui-skeleton--circle',\r\n className,\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <span\r\n className={classes}\r\n style={{\r\n width: formatWidth(width),\r\n display: width ? 'block' : undefined,\r\n ...style,\r\n }}\r\n aria-hidden={ariaHidden}\r\n role=\"presentation\"\r\n />\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Helper Components\r\n * ------------------------------------------------------*/\r\n\r\n/**\r\n * Simple text line.\r\n * Default width: 100%\r\n */\r\nfunction Text({\r\n width = '100%',\r\n size = 'md',\r\n}: {\r\n width?: number | string;\r\n size?: Size;\r\n}) {\r\n return <Skeleton width={width} size={size} />;\r\n}\r\n\r\n/**\r\n * Paragraph: multiple lines of text skeleton\r\n * lines default = 3\r\n */\r\nfunction Paragraph({\r\n lines = 3,\r\n size = 'md',\r\n}: {\r\n lines?: number;\r\n size?: Size;\r\n}) {\r\n const arr = Array.from({ length: lines });\r\n\r\n return (\r\n <div aria-hidden=\"true\">\r\n {arr.map((_, i) => (\r\n <div key={i} style={{ marginBlockEnd: '8px' }}>\r\n <Skeleton width={i === lines - 1 ? '75%' : '100%'} size={size} />\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Avatar placeholder, circle with dynamic size\r\n */\r\nfunction AvatarSkeleton({ size = 40 }: { size?: number }) {\r\n return <Skeleton circle width={size} animated />;\r\n}\r\n\r\n/**\r\n * Button skeleton (pill-like shape)\r\n */\r\nfunction ButtonSkeleton({\r\n width = 100,\r\n height = 36,\r\n}: {\r\n width?: number;\r\n height?: number;\r\n}) {\r\n return (\r\n <Skeleton\r\n width={width}\r\n size=\"md\"\r\n animated\r\n style={{\r\n height,\r\n borderRadius: 'var(--radius-md)',\r\n }}\r\n />\r\n );\r\n}\r\n\r\n/**\r\n * Card Skeleton: header + lines + footer bar\r\n */\r\nfunction CardSkeleton() {\r\n return (\r\n <div aria-hidden=\"true\">\r\n <Skeleton width=\"60%\" size=\"lg\" style={{ marginBottom: 12 }} />\r\n <Paragraph lines={3} />\r\n <Skeleton width=\"40%\" size=\"md\" style={{ marginTop: 16 }} />\r\n </div>\r\n );\r\n}\r\n\r\n/* Attach helpers */\r\nSkeleton.Text = Text;\r\nSkeleton.Paragraph = Paragraph;\r\nSkeleton.Avatar = AvatarSkeleton;\r\nSkeleton.Button = ButtonSkeleton;\r\nSkeleton.Card = CardSkeleton;\r\n\r\nexport default Skeleton;\r\n"],"names":["formatWidth","w","Skeleton","size","width","animated","circle","ariaHidden","className","style","classes","jsx","Text","Paragraph","lines","arr","_","AvatarSkeleton","ButtonSkeleton","height","CardSkeleton","jsxs"],"mappings":"+KAyCA,SAASA,EAAYC,EAAyC,CAC5D,GAAIA,IAAM,OACV,OAAI,OAAOA,GAAM,SAAiB,GAAGA,CAAC,KAC/BA,CACT,CAEO,SAASC,EAAS,CACvB,KAAAC,EAAO,KACP,MAAAC,EACA,SAAAC,EAAW,GACX,OAAAC,EAAS,GACT,WAAAC,EAAa,GACb,UAAAC,EAAY,GACZ,MAAAC,CACF,EAAkB,CAChB,MAAMC,EAAU,CACd,cACA,gBAAgBP,CAAI,GACpBE,GAAY,wBACZC,GAAU,sBACVE,CAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG,EAEX,OACEG,EAAAA,IAAC,OAAA,CACC,UAAWD,EACX,MAAO,CACL,MAAOV,EAAYI,CAAK,EACxB,QAASA,EAAQ,QAAU,OAC3B,GAAGK,CAAA,EAEL,cAAaF,EACb,KAAK,cAAA,CAAA,CAGX,CAUA,SAASK,EAAK,CACZ,MAAAR,EAAQ,OACR,KAAAD,EAAO,IACT,EAGG,CACD,OAAOQ,EAAAA,IAACT,EAAA,CAAS,MAAAE,EAAc,KAAAD,CAAA,CAAY,CAC7C,CAMA,SAASU,EAAU,CACjB,MAAAC,EAAQ,EACR,KAAAX,EAAO,IACT,EAGG,CACD,MAAMY,EAAM,MAAM,KAAK,CAAE,OAAQD,EAAO,EAExC,OACEH,EAAAA,IAAC,MAAA,CAAI,cAAY,OACd,SAAAI,EAAI,IAAI,CAACC,EAAG,IACXL,EAAAA,IAAC,MAAA,CAAY,MAAO,CAAE,eAAgB,KAAA,EACpC,SAAAA,EAAAA,IAACT,EAAA,CAAS,MAAO,IAAMY,EAAQ,EAAI,MAAQ,OAAQ,KAAAX,CAAA,CAAY,CAAA,EADvD,CAEV,CACD,EACH,CAEJ,CAKA,SAASc,EAAe,CAAE,KAAAd,EAAO,IAAyB,CACxD,aAAQD,EAAA,CAAS,OAAM,GAAC,MAAOC,EAAM,SAAQ,GAAC,CAChD,CAKA,SAASe,EAAe,CACtB,MAAAd,EAAQ,IACR,OAAAe,EAAS,EACX,EAGG,CACD,OACER,EAAAA,IAACT,EAAA,CACC,MAAAE,EACA,KAAK,KACL,SAAQ,GACR,MAAO,CACL,OAAAe,EACA,aAAc,kBAAA,CAChB,CAAA,CAGN,CAKA,SAASC,GAAe,CACtB,OACEC,EAAAA,KAAC,MAAA,CAAI,cAAY,OACf,SAAA,CAAAV,EAAAA,IAACT,EAAA,CAAS,MAAM,MAAM,KAAK,KAAK,MAAO,CAAE,aAAc,EAAA,CAAG,CAAG,EAC7DS,EAAAA,IAACE,EAAA,CAAU,MAAO,CAAA,CAAG,EACrBF,EAAAA,IAACT,EAAA,CAAS,MAAM,MAAM,KAAK,KAAK,MAAO,CAAE,UAAW,GAAG,CAAG,CAAA,EAC5D,CAEJ,CAGAA,EAAS,KAAOU,EAChBV,EAAS,UAAYW,EACrBX,EAAS,OAASe,EAClBf,EAAS,OAASgB,EAClBhB,EAAS,KAAOkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"Skeleton.js","sources":["../../../../src/components/skeleton/Skeleton.tsx"],"sourcesContent":["/**\r\n * Skeleton.tsx\r\n * Lightweight, accessible skeleton (placeholder) component.\r\n *\r\n * Props:\r\n * - size: visual thickness (xs, sm, md, lg, xl)\r\n * - width: number | string (e.g. 200, \"50%\", \"100px\")\r\n * - animated: whether to show shimmer (respects prefers-reduced-motion)\r\n * - circle: renders as a circle (useful for avatars)\r\n * - className: pass-through for custom styling\r\n *\r\n * Accessibility:\r\n * - role=\"status\" with aria-busy on parent usage is recommended by developer.\r\n * - For individual skeletons that should be ignored by assistive tech set aria-hidden=true.\r\n */\r\n/**\r\n * Skeleton.tsx + Helper Components\r\n * --------------------------------\r\n * Adds:\r\n * - Skeleton.Text\r\n * - Skeleton.Paragraph\r\n * - Skeleton.Avatar\r\n * - Skeleton.Button\r\n * - Skeleton.Card\r\n */\r\n\r\nimport React from 'react';\r\nimport './Skeleton.css';\r\n\r\ntype Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n\r\ninterface SkeletonProps {\r\n size?: Size;\r\n width?: number | string;\r\n animated?: boolean;\r\n circle?: boolean;\r\n ariaHidden?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nfunction formatWidth(w?: number | string): string | undefined {\r\n if (w === undefined) return undefined;\r\n if (typeof w === 'number') return `${w}px`;\r\n return w;\r\n}\r\n\r\nexport function Skeleton({\r\n size = 'md',\r\n width,\r\n animated = true,\r\n circle = false,\r\n ariaHidden = true,\r\n className = '',\r\n style,\r\n}: SkeletonProps) {\r\n const classes = [\r\n 'ui-skeleton',\r\n `ui-skeleton--${size}`,\r\n animated && 'ui-skeleton--animated',\r\n circle && 'ui-skeleton--circle',\r\n className,\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <span\r\n className={classes}\r\n style={{\r\n width: formatWidth(width),\r\n display: width ? 'block' : undefined,\r\n ...style,\r\n }}\r\n aria-hidden={ariaHidden}\r\n role=\"presentation\"\r\n />\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Helper Components\r\n * ------------------------------------------------------*/\r\n\r\n/**\r\n * Simple text line.\r\n * Default width: 100%\r\n */\r\nfunction Text({\r\n width = '100%',\r\n size = 'md',\r\n}: {\r\n width?: number | string;\r\n size?: Size;\r\n}) {\r\n return <Skeleton width={width} size={size} />;\r\n}\r\n\r\n/**\r\n * Paragraph: multiple lines of text skeleton\r\n * lines default = 3\r\n */\r\nfunction Paragraph({\r\n lines = 3,\r\n size = 'md',\r\n}: {\r\n lines?: number;\r\n size?: Size;\r\n}) {\r\n const arr = Array.from({ length: lines });\r\n\r\n return (\r\n <div aria-hidden=\"true\">\r\n {arr.map((_, i) => (\r\n <div key={i} style={{ marginBlockEnd: '8px' }}>\r\n <Skeleton width={i === lines - 1 ? '75%' : '100%'} size={size} />\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Avatar placeholder, circle with dynamic size\r\n */\r\nfunction AvatarSkeleton({ size = 40 }: { size?: number }) {\r\n return <Skeleton circle width={size} animated />;\r\n}\r\n\r\n/**\r\n * Button skeleton (pill-like shape)\r\n */\r\nfunction ButtonSkeleton({\r\n width = 100,\r\n height = 36,\r\n}: {\r\n width?: number;\r\n height?: number;\r\n}) {\r\n return (\r\n <Skeleton\r\n width={width}\r\n size=\"md\"\r\n animated\r\n style={{\r\n height,\r\n borderRadius: 'var(--radius-md)',\r\n }}\r\n />\r\n );\r\n}\r\n\r\n/**\r\n * Card Skeleton: header + lines + footer bar\r\n */\r\nfunction CardSkeleton() {\r\n return (\r\n <div aria-hidden=\"true\">\r\n <Skeleton width=\"60%\" size=\"lg\" style={{ marginBottom: 12 }} />\r\n <Paragraph lines={3} />\r\n <Skeleton width=\"40%\" size=\"md\" style={{ marginTop: 16 }} />\r\n </div>\r\n );\r\n}\r\n\r\n/* Attach helpers */\r\nSkeleton.Text = Text;\r\nSkeleton.Paragraph = Paragraph;\r\nSkeleton.Avatar = AvatarSkeleton;\r\nSkeleton.Button = ButtonSkeleton;\r\nSkeleton.Card = CardSkeleton;\r\n\r\nexport default Skeleton;\r\n"],"names":["formatWidth","w","Skeleton","size","width","animated","circle","ariaHidden","className","style","classes","jsx","Text","Paragraph","lines","arr","_","AvatarSkeleton","ButtonSkeleton","height","CardSkeleton","jsxs"],"mappings":";;AAyCA,SAASA,EAAYC,GAAyC;AAC5D,MAAIA,MAAM;AACV,WAAI,OAAOA,KAAM,WAAiB,GAAGA,CAAC,OAC/BA;AACT;AAEO,SAASC,EAAS;AAAA,EACvB,MAAAC,IAAO;AAAA,EACP,OAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,QAAAC,IAAS;AAAA,EACT,YAAAC,IAAa;AAAA,EACb,WAAAC,IAAY;AAAA,EACZ,OAAAC;AACF,GAAkB;AAChB,QAAMC,IAAU;AAAA,IACd;AAAA,IACA,gBAAgBP,CAAI;AAAA,IACpBE,KAAY;AAAA,IACZC,KAAU;AAAA,IACVE;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,gBAAAG;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWD;AAAA,MACX,OAAO;AAAA,QACL,OAAOV,EAAYI,CAAK;AAAA,QACxB,SAASA,IAAQ,UAAU;AAAA,QAC3B,GAAGK;AAAA,MAAA;AAAA,MAEL,eAAaF;AAAA,MACb,MAAK;AAAA,IAAA;AAAA,EAAA;AAGX;AAUA,SAASK,EAAK;AAAA,EACZ,OAAAR,IAAQ;AAAA,EACR,MAAAD,IAAO;AACT,GAGG;AACD,SAAO,gBAAAQ,EAACT,GAAA,EAAS,OAAAE,GAAc,MAAAD,EAAA,CAAY;AAC7C;AAMA,SAASU,EAAU;AAAA,EACjB,OAAAC,IAAQ;AAAA,EACR,MAAAX,IAAO;AACT,GAGG;AACD,QAAMY,IAAM,MAAM,KAAK,EAAE,QAAQD,GAAO;AAExC,SACE,gBAAAH,EAAC,OAAA,EAAI,eAAY,QACd,UAAAI,EAAI,IAAI,CAACC,GAAG,MACX,gBAAAL,EAAC,OAAA,EAAY,OAAO,EAAE,gBAAgB,MAAA,GACpC,UAAA,gBAAAA,EAACT,GAAA,EAAS,OAAO,MAAMY,IAAQ,IAAI,QAAQ,QAAQ,MAAAX,EAAA,CAAY,EAAA,GADvD,CAEV,CACD,GACH;AAEJ;AAKA,SAASc,EAAe,EAAE,MAAAd,IAAO,MAAyB;AACxD,2BAAQD,GAAA,EAAS,QAAM,IAAC,OAAOC,GAAM,UAAQ,IAAC;AAChD;AAKA,SAASe,EAAe;AAAA,EACtB,OAAAd,IAAQ;AAAA,EACR,QAAAe,IAAS;AACX,GAGG;AACD,SACE,gBAAAR;AAAA,IAACT;AAAA,IAAA;AAAA,MACC,OAAAE;AAAA,MACA,MAAK;AAAA,MACL,UAAQ;AAAA,MACR,OAAO;AAAA,QACL,QAAAe;AAAA,QACA,cAAc;AAAA,MAAA;AAAA,IAChB;AAAA,EAAA;AAGN;AAKA,SAASC,IAAe;AACtB,SACE,gBAAAC,EAAC,OAAA,EAAI,eAAY,QACf,UAAA;AAAA,IAAA,gBAAAV,EAACT,GAAA,EAAS,OAAM,OAAM,MAAK,MAAK,OAAO,EAAE,cAAc,GAAA,EAAG,CAAG;AAAA,IAC7D,gBAAAS,EAACE,GAAA,EAAU,OAAO,EAAA,CAAG;AAAA,IACrB,gBAAAF,EAACT,GAAA,EAAS,OAAM,OAAM,MAAK,MAAK,OAAO,EAAE,WAAW,KAAG,CAAG;AAAA,EAAA,GAC5D;AAEJ;AAGAA,EAAS,OAAOU;AAChBV,EAAS,YAAYW;AACrBX,EAAS,SAASe;AAClBf,EAAS,SAASgB;AAClBhB,EAAS,OAAOkB;"}
1
+ {"version":3,"file":"Skeleton.js","sources":["../../../src/components/skeleton/Skeleton.tsx"],"sourcesContent":["/**\r\n * Skeleton.tsx\r\n * Lightweight, accessible skeleton (placeholder) component.\r\n *\r\n * Props:\r\n * - size: visual thickness (xs, sm, md, lg, xl)\r\n * - width: number | string (e.g. 200, \"50%\", \"100px\")\r\n * - animated: whether to show shimmer (respects prefers-reduced-motion)\r\n * - circle: renders as a circle (useful for avatars)\r\n * - className: pass-through for custom styling\r\n *\r\n * Accessibility:\r\n * - role=\"status\" with aria-busy on parent usage is recommended by developer.\r\n * - For individual skeletons that should be ignored by assistive tech set aria-hidden=true.\r\n */\r\n/**\r\n * Skeleton.tsx + Helper Components\r\n * --------------------------------\r\n * Adds:\r\n * - Skeleton.Text\r\n * - Skeleton.Paragraph\r\n * - Skeleton.Avatar\r\n * - Skeleton.Button\r\n * - Skeleton.Card\r\n */\r\n\r\nimport React from 'react';\r\nimport './Skeleton.css';\r\n\r\ntype Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n\r\ninterface SkeletonProps {\r\n size?: Size;\r\n width?: number | string;\r\n animated?: boolean;\r\n circle?: boolean;\r\n ariaHidden?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nfunction formatWidth(w?: number | string): string | undefined {\r\n if (w === undefined) return undefined;\r\n if (typeof w === 'number') return `${w}px`;\r\n return w;\r\n}\r\n\r\nexport function Skeleton({\r\n size = 'md',\r\n width,\r\n animated = true,\r\n circle = false,\r\n ariaHidden = true,\r\n className = '',\r\n style,\r\n}: SkeletonProps) {\r\n const classes = [\r\n 'ui-skeleton',\r\n `ui-skeleton--${size}`,\r\n animated && 'ui-skeleton--animated',\r\n circle && 'ui-skeleton--circle',\r\n className,\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <span\r\n className={classes}\r\n style={{\r\n width: formatWidth(width),\r\n display: width ? 'block' : undefined,\r\n ...style,\r\n }}\r\n aria-hidden={ariaHidden}\r\n role=\"presentation\"\r\n />\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Helper Components\r\n * ------------------------------------------------------*/\r\n\r\n/**\r\n * Simple text line.\r\n * Default width: 100%\r\n */\r\nfunction Text({\r\n width = '100%',\r\n size = 'md',\r\n}: {\r\n width?: number | string;\r\n size?: Size;\r\n}) {\r\n return <Skeleton width={width} size={size} />;\r\n}\r\n\r\n/**\r\n * Paragraph: multiple lines of text skeleton\r\n * lines default = 3\r\n */\r\nfunction Paragraph({\r\n lines = 3,\r\n size = 'md',\r\n}: {\r\n lines?: number;\r\n size?: Size;\r\n}) {\r\n const arr = Array.from({ length: lines });\r\n\r\n return (\r\n <div aria-hidden=\"true\">\r\n {arr.map((_, i) => (\r\n <div key={i} style={{ marginBlockEnd: '8px' }}>\r\n <Skeleton width={i === lines - 1 ? '75%' : '100%'} size={size} />\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Avatar placeholder, circle with dynamic size\r\n */\r\nfunction AvatarSkeleton({ size = 40 }: { size?: number }) {\r\n return <Skeleton circle width={size} animated />;\r\n}\r\n\r\n/**\r\n * Button skeleton (pill-like shape)\r\n */\r\nfunction ButtonSkeleton({\r\n width = 100,\r\n height = 36,\r\n}: {\r\n width?: number;\r\n height?: number;\r\n}) {\r\n return (\r\n <Skeleton\r\n width={width}\r\n size=\"md\"\r\n animated\r\n style={{\r\n height,\r\n borderRadius: 'var(--radius-md)',\r\n }}\r\n />\r\n );\r\n}\r\n\r\n/**\r\n * Card Skeleton: header + lines + footer bar\r\n */\r\nfunction CardSkeleton() {\r\n return (\r\n <div aria-hidden=\"true\">\r\n <Skeleton width=\"60%\" size=\"lg\" style={{ marginBottom: 12 }} />\r\n <Paragraph lines={3} />\r\n <Skeleton width=\"40%\" size=\"md\" style={{ marginTop: 16 }} />\r\n </div>\r\n );\r\n}\r\n\r\n/* Attach helpers */\r\nSkeleton.Text = Text;\r\nSkeleton.Paragraph = Paragraph;\r\nSkeleton.Avatar = AvatarSkeleton;\r\nSkeleton.Button = ButtonSkeleton;\r\nSkeleton.Card = CardSkeleton;\r\n\r\nexport default Skeleton;\r\n"],"names":["formatWidth","w","Skeleton","size","width","animated","circle","ariaHidden","className","style","classes","jsx","Text","Paragraph","lines","arr","_","AvatarSkeleton","ButtonSkeleton","height","CardSkeleton","jsxs"],"mappings":";;AAyCA,SAASA,EAAYC,GAAyC;AAC5D,MAAIA,MAAM;AACV,WAAI,OAAOA,KAAM,WAAiB,GAAGA,CAAC,OAC/BA;AACT;AAEO,SAASC,EAAS;AAAA,EACvB,MAAAC,IAAO;AAAA,EACP,OAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,QAAAC,IAAS;AAAA,EACT,YAAAC,IAAa;AAAA,EACb,WAAAC,IAAY;AAAA,EACZ,OAAAC;AACF,GAAkB;AAChB,QAAMC,IAAU;AAAA,IACd;AAAA,IACA,gBAAgBP,CAAI;AAAA,IACpBE,KAAY;AAAA,IACZC,KAAU;AAAA,IACVE;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,gBAAAG;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWD;AAAA,MACX,OAAO;AAAA,QACL,OAAOV,EAAYI,CAAK;AAAA,QACxB,SAASA,IAAQ,UAAU;AAAA,QAC3B,GAAGK;AAAA,MAAA;AAAA,MAEL,eAAaF;AAAA,MACb,MAAK;AAAA,IAAA;AAAA,EAAA;AAGX;AAUA,SAASK,EAAK;AAAA,EACZ,OAAAR,IAAQ;AAAA,EACR,MAAAD,IAAO;AACT,GAGG;AACD,SAAO,gBAAAQ,EAACT,GAAA,EAAS,OAAAE,GAAc,MAAAD,EAAA,CAAY;AAC7C;AAMA,SAASU,EAAU;AAAA,EACjB,OAAAC,IAAQ;AAAA,EACR,MAAAX,IAAO;AACT,GAGG;AACD,QAAMY,IAAM,MAAM,KAAK,EAAE,QAAQD,GAAO;AAExC,SACE,gBAAAH,EAAC,OAAA,EAAI,eAAY,QACd,UAAAI,EAAI,IAAI,CAACC,GAAG,MACX,gBAAAL,EAAC,OAAA,EAAY,OAAO,EAAE,gBAAgB,MAAA,GACpC,UAAA,gBAAAA,EAACT,GAAA,EAAS,OAAO,MAAMY,IAAQ,IAAI,QAAQ,QAAQ,MAAAX,EAAA,CAAY,EAAA,GADvD,CAEV,CACD,GACH;AAEJ;AAKA,SAASc,EAAe,EAAE,MAAAd,IAAO,MAAyB;AACxD,2BAAQD,GAAA,EAAS,QAAM,IAAC,OAAOC,GAAM,UAAQ,IAAC;AAChD;AAKA,SAASe,EAAe;AAAA,EACtB,OAAAd,IAAQ;AAAA,EACR,QAAAe,IAAS;AACX,GAGG;AACD,SACE,gBAAAR;AAAA,IAACT;AAAA,IAAA;AAAA,MACC,OAAAE;AAAA,MACA,MAAK;AAAA,MACL,UAAQ;AAAA,MACR,OAAO;AAAA,QACL,QAAAe;AAAA,QACA,cAAc;AAAA,MAAA;AAAA,IAChB;AAAA,EAAA;AAGN;AAKA,SAASC,IAAe;AACtB,SACE,gBAAAC,EAAC,OAAA,EAAI,eAAY,QACf,UAAA;AAAA,IAAA,gBAAAV,EAACT,GAAA,EAAS,OAAM,OAAM,MAAK,MAAK,OAAO,EAAE,cAAc,GAAA,EAAG,CAAG;AAAA,IAC7D,gBAAAS,EAACE,GAAA,EAAU,OAAO,EAAA,CAAG;AAAA,IACrB,gBAAAF,EAACT,GAAA,EAAS,OAAM,OAAM,MAAK,MAAK,OAAO,EAAE,WAAW,KAAG,CAAG;AAAA,EAAA,GAC5D;AAEJ;AAGAA,EAAS,OAAOU;AAChBV,EAAS,YAAYW;AACrBX,EAAS,SAASe;AAClBf,EAAS,SAASgB;AAClBhB,EAAS,OAAOkB;"}