@intlayer/design-system 9.3.3 → 9.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +3 -3
  2. package/dist/esm/api/hooks/analytics.mjs +6 -5
  3. package/dist/esm/api/hooks/analytics.mjs.map +1 -1
  4. package/dist/esm/api/index.mjs +2 -2
  5. package/dist/esm/api/useAuth/useOAuth2.mjs.map +1 -1
  6. package/dist/esm/api/useIntlayerAPI.mjs +1 -3
  7. package/dist/esm/api/useIntlayerAPI.mjs.map +1 -1
  8. package/dist/esm/api/useUser/index.mjs +1 -1
  9. package/dist/esm/components/Accordion/Accordion.mjs +2 -2
  10. package/dist/esm/components/Accordion/Accordion.mjs.map +1 -1
  11. package/dist/esm/components/Carousel/index.mjs +48 -30
  12. package/dist/esm/components/Carousel/index.mjs.map +1 -1
  13. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +1 -1
  14. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
  15. package/dist/esm/components/Form/elements/OTPElement.mjs +4 -4
  16. package/dist/esm/components/Form/elements/OTPElement.mjs.map +1 -1
  17. package/dist/esm/components/Link/Link.mjs +10 -5
  18. package/dist/esm/components/Link/Link.mjs.map +1 -1
  19. package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
  20. package/dist/esm/components/Modal/Modal.mjs +2 -2
  21. package/dist/esm/components/Navbar/MobileNavbar.mjs +25 -3
  22. package/dist/esm/components/Navbar/MobileNavbar.mjs.map +1 -1
  23. package/dist/esm/components/Pagination/Pagination.mjs +4 -4
  24. package/dist/esm/components/Pagination/Pagination.mjs.map +1 -1
  25. package/dist/esm/components/Pattern/GridDistortionPattern.mjs +3 -2
  26. package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
  27. package/dist/esm/components/Popover/dynamic.mjs +68 -24
  28. package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
  29. package/dist/esm/components/RightDrawer/RightDrawer.mjs +34 -10
  30. package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
  31. package/dist/esm/components/RightDrawer/index.mjs +2 -2
  32. package/dist/esm/components/RightDrawer/useRightDrawer.mjs +37 -5
  33. package/dist/esm/components/RightDrawer/useRightDrawer.mjs.map +1 -1
  34. package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs +2 -2
  35. package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs.map +1 -1
  36. package/dist/esm/components/Tab/Tab.mjs +21 -4
  37. package/dist/esm/components/Tab/Tab.mjs.map +1 -1
  38. package/dist/esm/components/TabSelector/TabSelector.mjs +2 -2
  39. package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
  40. package/dist/esm/components/TechLogo/TechLogo.mjs +1 -0
  41. package/dist/esm/components/TechLogo/TechLogo.mjs.map +1 -1
  42. package/dist/esm/components/TechLogo/logos/Elysia.mjs +62 -0
  43. package/dist/esm/components/TechLogo/logos/Elysia.mjs.map +1 -0
  44. package/dist/esm/components/TechLogo/techLogos.mjs +3 -1
  45. package/dist/esm/components/TechLogo/techLogos.mjs.map +1 -1
  46. package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs +10 -18
  47. package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs.map +1 -1
  48. package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs +14 -13
  49. package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs.map +1 -1
  50. package/dist/esm/components/index.mjs +2 -2
  51. package/dist/esm/hooks/index.mjs +9 -9
  52. package/dist/esm/hooks/useItemSelector.mjs +12 -9
  53. package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
  54. package/dist/esm/hooks/useScrollBlockage/index.mjs +9 -10
  55. package/dist/esm/hooks/useScrollBlockage/index.mjs.map +1 -1
  56. package/dist/esm/hooks/useScrollDetection.mjs +15 -2
  57. package/dist/esm/hooks/useScrollDetection.mjs.map +1 -1
  58. package/dist/esm/hooks/useScrollY.mjs +64 -45
  59. package/dist/esm/hooks/useScrollY.mjs.map +1 -1
  60. package/dist/esm/hooks/useStickToBottom.mjs +69 -0
  61. package/dist/esm/hooks/useStickToBottom.mjs.map +1 -0
  62. package/dist/esm/providers/ThemeProvider.mjs +143 -0
  63. package/dist/esm/providers/ThemeProvider.mjs.map +1 -0
  64. package/dist/esm/providers/index.mjs +2 -1
  65. package/dist/esm/routes.mjs +4 -1
  66. package/dist/esm/routes.mjs.map +1 -1
  67. package/dist/esm/utils/index.mjs +3 -1
  68. package/dist/esm/utils/observeScrollExtent.mjs +39 -0
  69. package/dist/esm/utils/observeScrollExtent.mjs.map +1 -0
  70. package/dist/esm/utils/scheduleFrameTask.mjs +44 -0
  71. package/dist/esm/utils/scheduleFrameTask.mjs.map +1 -0
  72. package/dist/types/api/hooks/analytics.d.ts +5 -3
  73. package/dist/types/api/hooks/analytics.d.ts.map +1 -1
  74. package/dist/types/api/useIntlayerAPI.d.ts +12 -0
  75. package/dist/types/api/useIntlayerAPI.d.ts.map +1 -1
  76. package/dist/types/components/Carousel/index.d.ts.map +1 -1
  77. package/dist/types/components/Navbar/MobileNavbar.d.ts.map +1 -1
  78. package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
  79. package/dist/types/components/RightDrawer/RightDrawer.d.ts.map +1 -1
  80. package/dist/types/components/RightDrawer/index.d.ts +2 -2
  81. package/dist/types/components/RightDrawer/useRightDrawer.d.ts +34 -1
  82. package/dist/types/components/RightDrawer/useRightDrawer.d.ts.map +1 -1
  83. package/dist/types/components/Tab/Tab.d.ts.map +1 -1
  84. package/dist/types/components/TechLogo/TechLogo.d.ts.map +1 -1
  85. package/dist/types/components/TechLogo/logos/Elysia.d.ts +6 -0
  86. package/dist/types/components/TechLogo/logos/Elysia.d.ts.map +1 -0
  87. package/dist/types/components/TechLogo/techLogos.d.ts +3 -2
  88. package/dist/types/components/TechLogo/types.d.ts +1 -1
  89. package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts +1 -1
  90. package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts.map +1 -1
  91. package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts +1 -1
  92. package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts.map +1 -1
  93. package/dist/types/components/ThemeSwitcherDropDown/types.d.ts +3 -1
  94. package/dist/types/components/ThemeSwitcherDropDown/types.d.ts.map +1 -1
  95. package/dist/types/components/index.d.ts +2 -2
  96. package/dist/types/hooks/index.d.ts +2 -2
  97. package/dist/types/hooks/useItemSelector.d.ts +2 -0
  98. package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
  99. package/dist/types/hooks/useScrollBlockage/index.d.ts.map +1 -1
  100. package/dist/types/hooks/useScrollDetection.d.ts.map +1 -1
  101. package/dist/types/hooks/useScrollY.d.ts +10 -0
  102. package/dist/types/hooks/useScrollY.d.ts.map +1 -1
  103. package/dist/types/hooks/useStickToBottom.d.ts +41 -0
  104. package/dist/types/hooks/useStickToBottom.d.ts.map +1 -0
  105. package/dist/types/providers/ThemeProvider.d.ts +41 -0
  106. package/dist/types/providers/ThemeProvider.d.ts.map +1 -0
  107. package/dist/types/providers/index.d.ts +2 -1
  108. package/dist/types/routes.d.ts +4 -1
  109. package/dist/types/routes.d.ts.map +1 -1
  110. package/dist/types/utils/index.d.ts +3 -1
  111. package/dist/types/utils/observeScrollExtent.d.ts +22 -0
  112. package/dist/types/utils/observeScrollExtent.d.ts.map +1 -0
  113. package/dist/types/utils/scheduleFrameTask.d.ts +25 -0
  114. package/dist/types/utils/scheduleFrameTask.d.ts.map +1 -0
  115. package/package.json +24 -24
  116. package/dist/esm/hooks/useIsDarkMode.mjs +0 -17
  117. package/dist/esm/hooks/useIsDarkMode.mjs.map +0 -1
  118. package/dist/types/hooks/useIsDarkMode.d.ts +0 -5
  119. package/dist/types/hooks/useIsDarkMode.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic.mjs","names":["StaticDetail"],"sources":["../../../../src/components/Popover/dynamic.tsx"],"sourcesContent":["'use client';\n\nimport type { FC } from 'react';\nimport { useLayoutEffect, useRef, useState } from 'react';\nimport {\n type DetailProps,\n type PopoverProps,\n PopoverStatic,\n type PopoverType,\n Detail as StaticDetail,\n usePopoverIds,\n} from './static';\n\n/**\n * Popover Component (Client-side)\n *\n * Client-side wrapper around the static Popover component.\n * Reuses the server-side compatible implementation.\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nconst PopoverComponent: FC<PopoverProps> = (props) => {\n return <PopoverStatic {...props} />;\n};\n\n/**\n * Popover Detail Component (Client-side)\n *\n * Client-side wrapper around the static Detail component that adds automatic\n * positioning logic based on viewport constraints.\n *\n * Features:\n * - Reuses server-side compatible static Detail component\n * - Adds automatic positioning adjustment based on viewport\n * - Calculates optimal X/Y alignment to prevent overflow\n * - Dynamically adjusts max-width based on available space\n * - Listens to window resize and scroll events\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n xAlign = 'start',\n yAlign = 'below',\n ...props\n}) => {\n const popoverRef = useRef<HTMLDivElement>(null);\n const { triggerId } = usePopoverIds(props.identifier);\n const [computedXAlign, setComputedXAlign] = useState(xAlign);\n const [computedYAlign, setComputedYAlign] = useState(yAlign);\n const [maxWidth, setMaxWidth] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n const adjustPosition = () => {\n if (!popoverRef.current) return;\n\n const popoverElement = popoverRef.current;\n const triggerElement = document.getElementById(triggerId);\n\n if (!triggerElement) return;\n\n const triggerRect = triggerElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const gap = 16; // 1rem gap\n const padding = 16; // Additional padding from viewport edges\n\n // Calculate maximum width based on viewport and trigger position\n const maxWidthFromLeft = viewportWidth - triggerRect.left - padding;\n const maxWidthFromRight = triggerRect.right - padding;\n\n // Use the larger space to ensure popover can fit\n const absoluteMaxWidth = Math.max(maxWidthFromLeft, maxWidthFromRight);\n\n setMaxWidth(absoluteMaxWidth);\n\n // Force a layout calculation by temporarily making visible if needed\n const wasInvisible = popoverElement.classList.contains('invisible');\n if (wasInvisible) {\n popoverElement.style.visibility = 'hidden';\n popoverElement.classList.remove('invisible');\n }\n\n // Small delay to ensure max-width is applied and content reflows\n requestAnimationFrame(() => {\n const popoverRect = popoverElement.getBoundingClientRect();\n\n // Restore invisible state if it was invisible\n if (wasInvisible) {\n popoverElement.style.visibility = '';\n popoverElement.classList.add('invisible');\n }\n\n // Determine optimal Y alignment\n let newYAlign = yAlign;\n const spaceBelow = viewportHeight - triggerRect.bottom - gap;\n const spaceAbove = triggerRect.top - gap;\n\n if (yAlign === 'below' && spaceBelow < popoverRect.height) {\n // Not enough space below, try above\n if (spaceAbove >= popoverRect.height) {\n newYAlign = 'above';\n }\n } else if (yAlign === 'above' && spaceAbove < popoverRect.height) {\n // Not enough space above, try below\n if (spaceBelow >= popoverRect.height) {\n newYAlign = 'below';\n }\n }\n\n // Determine optimal X alignment\n let newXAlign = xAlign;\n const spaceRight = viewportWidth - triggerRect.left - padding;\n const spaceLeft = triggerRect.right - padding;\n\n if (xAlign === 'start' && spaceRight < popoverRect.width) {\n // Not enough space on the right, try left\n if (spaceLeft >= popoverRect.width) {\n newXAlign = 'end';\n }\n } else if (xAlign === 'end' && spaceLeft < popoverRect.width) {\n // Not enough space on the left, try right\n if (spaceRight >= popoverRect.width) {\n newXAlign = 'start';\n }\n }\n\n setComputedYAlign(newYAlign);\n setComputedXAlign(newXAlign);\n });\n };\n\n // Adjust position with a slight delay to ensure DOM is ready\n const timeoutId = setTimeout(adjustPosition, 0);\n\n // Listen to mouse enter on the trigger to recalculate\n const triggerElement = document.getElementById(triggerId);\n\n if (triggerElement) {\n triggerElement.addEventListener('mouseenter', adjustPosition);\n triggerElement.addEventListener('focusin', adjustPosition);\n }\n\n // Use ResizeObserver to detect popover content size changes\n const resizeObserver = new ResizeObserver(() => {\n adjustPosition();\n });\n\n if (popoverRef.current) {\n resizeObserver.observe(popoverRef.current);\n }\n\n window.addEventListener('resize', adjustPosition);\n window.addEventListener('scroll', adjustPosition, true);\n\n return () => {\n clearTimeout(timeoutId);\n if (triggerElement) {\n triggerElement.removeEventListener('mouseenter', adjustPosition);\n triggerElement.removeEventListener('focusin', adjustPosition);\n }\n resizeObserver.disconnect();\n window.removeEventListener('resize', adjustPosition);\n window.removeEventListener('scroll', adjustPosition, true);\n };\n }, [triggerId, xAlign, yAlign]);\n\n // Use the static Detail component with computed alignment values\n return (\n <StaticDetail\n {...props}\n xAlign={computedXAlign}\n yAlign={computedYAlign}\n ref={popoverRef}\n style={{\n ...props.style,\n maxWidth: maxWidth ? `${maxWidth}px` : undefined,\n }}\n />\n );\n};\n\n// Create Popover with Detail attached\nexport const Popover: PopoverType = PopoverComponent as PopoverType;\n\nPopover.Detail = Detail;\n"],"mappings":"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"}
1
+ {"version":3,"file":"dynamic.mjs","names":["StaticDetail"],"sources":["../../../../src/components/Popover/dynamic.tsx"],"sourcesContent":["'use client';\n\nimport { scheduleFrameTask } from '@utils/scheduleFrameTask';\nimport type { FC } from 'react';\nimport { useLayoutEffect, useRef, useState } from 'react';\nimport {\n type DetailProps,\n type PopoverProps,\n PopoverStatic,\n type PopoverType,\n Detail as StaticDetail,\n usePopoverIds,\n} from './static';\n\n/** Gap kept between the trigger and the panel, mirroring the `1rem` offset of the static styles. */\nconst TRIGGER_GAP = 16;\n\n/** Space kept between the panel and the edges of the viewport. */\nconst VIEWPORT_PADDING = 16;\n\n/**\n * Popover Component (Client-side)\n *\n * Client-side wrapper around the static Popover component.\n * Reuses the server-side compatible implementation.\n *\n * @param props - Popover component props\n * @returns Trigger container with popover functionality\n */\nconst PopoverComponent: FC<PopoverProps> = (props) => {\n return <PopoverStatic {...props} />;\n};\n\n/**\n * Popover Detail Component (Client-side)\n *\n * Client-side wrapper around the static Detail component that adds automatic\n * positioning logic based on viewport constraints.\n *\n * Features:\n * - Reuses server-side compatible static Detail component\n * - Adds automatic positioning adjustment based on viewport\n * - Calculates optimal X/Y alignment to prevent overflow\n * - Dynamically adjusts max-width based on available space\n * - Measures only while the panel is open, on a frame shared with the rest of\n * the page\n *\n * @param props - Popover Detail component props\n * @returns Positioned popover content with animations and accessibility\n */\nconst Detail: FC<DetailProps> = ({\n xAlign = 'start',\n yAlign = 'below',\n ...props\n}) => {\n const popoverRef = useRef<HTMLDivElement>(null);\n const { triggerId } = usePopoverIds(props.identifier);\n const [computedXAlign, setComputedXAlign] = useState(xAlign);\n const [computedYAlign, setComputedYAlign] = useState(yAlign);\n const [maxWidth, setMaxWidth] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n /** Cancels the measurement queued for the next frame, if any. */\n let cancelScheduledMeasurement: (() => void) | null = null;\n /** Cancels the read that follows the max-width write, if any. */\n let cancelScheduledRead: (() => void) | null = null;\n /** Whether the pointer or the focus is currently inside the trigger. */\n let isOpen = false;\n\n const measurePosition = () => {\n const popoverElement = popoverRef.current;\n const triggerElement = document.getElementById(triggerId);\n\n if (!popoverElement || !triggerElement) return;\n\n const triggerRect = triggerElement.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n // Calculate maximum width based on viewport and trigger position, using\n // the larger of the two sides so the panel can always fit somewhere.\n const maxWidthFromLeft =\n viewportWidth - triggerRect.left - VIEWPORT_PADDING;\n const maxWidthFromRight = triggerRect.right - VIEWPORT_PADDING;\n\n setMaxWidth(Math.max(maxWidthFromLeft, maxWidthFromRight));\n\n // A panel that is still hidden has no box to measure, so it is revealed\n // to layout only — `visibility: hidden` keeps it off the screen.\n const wasInvisible = popoverElement.classList.contains('invisible');\n if (wasInvisible) {\n popoverElement.style.visibility = 'hidden';\n popoverElement.classList.remove('invisible');\n }\n\n // The max-width above still has to be committed and laid out, so the\n // panel is measured on the next frame rather than right away.\n cancelScheduledRead = scheduleFrameTask(() => {\n cancelScheduledRead = null;\n\n const popoverRect = popoverElement.getBoundingClientRect();\n\n if (wasInvisible) {\n popoverElement.style.visibility = '';\n popoverElement.classList.add('invisible');\n }\n\n // Determine optimal Y alignment\n let newYAlign = yAlign;\n const spaceBelow = viewportHeight - triggerRect.bottom - TRIGGER_GAP;\n const spaceAbove = triggerRect.top - TRIGGER_GAP;\n\n if (yAlign === 'below' && spaceBelow < popoverRect.height) {\n // Not enough space below, try above\n if (spaceAbove >= popoverRect.height) {\n newYAlign = 'above';\n }\n } else if (yAlign === 'above' && spaceAbove < popoverRect.height) {\n // Not enough space above, try below\n if (spaceBelow >= popoverRect.height) {\n newYAlign = 'below';\n }\n }\n\n // Determine optimal X alignment\n let newXAlign = xAlign;\n const spaceRight = viewportWidth - triggerRect.left - VIEWPORT_PADDING;\n const spaceLeft = triggerRect.right - VIEWPORT_PADDING;\n\n if (xAlign === 'start' && spaceRight < popoverRect.width) {\n // Not enough space on the right, try left\n if (spaceLeft >= popoverRect.width) {\n newXAlign = 'end';\n }\n } else if (xAlign === 'end' && spaceLeft < popoverRect.width) {\n // Not enough space on the left, try right\n if (spaceRight >= popoverRect.width) {\n newXAlign = 'start';\n }\n }\n\n setComputedYAlign(newYAlign);\n setComputedXAlign(newXAlign);\n });\n };\n\n /**\n * Defers the measurement to the next frame, and to a single one however\n * many events fire.\n *\n * Reading `getBoundingClientRect()` straight from a scroll, resize or\n * observer callback forces a synchronous reflow. Sharing one frame with\n * every other measurement the page schedules keeps the reads in a single\n * batch, so React commits them all after the last one.\n */\n const scheduleMeasurement = () => {\n if (cancelScheduledMeasurement) return;\n\n cancelScheduledMeasurement = scheduleFrameTask(() => {\n cancelScheduledMeasurement = null;\n measurePosition();\n });\n };\n\n /*\n * Scrolling only matters while the panel is on screen, and a closed\n * popover is re-measured when it opens — behind an 800ms CSS delay, so the\n * measurement always lands before anything is painted. Subscribing on open\n * rather than on mount keeps a page holding several popovers from running\n * one capture-phase handler per popover on every scroll event, and leaves\n * a page whose popovers are never opened with no layout work at all.\n */\n const handleOpen = () => {\n if (!isOpen) {\n isOpen = true;\n window.addEventListener('scroll', scheduleMeasurement, {\n passive: true,\n capture: true,\n });\n }\n\n scheduleMeasurement();\n };\n\n const handleClose = () => {\n if (!isOpen) return;\n\n isOpen = false;\n window.removeEventListener('scroll', scheduleMeasurement, true);\n };\n\n const triggerElement = document.getElementById(triggerId);\n\n if (triggerElement) {\n triggerElement.addEventListener('mouseenter', handleOpen, {\n passive: true,\n });\n triggerElement.addEventListener('focusin', handleOpen, { passive: true });\n triggerElement.addEventListener('mouseleave', handleClose, {\n passive: true,\n });\n triggerElement.addEventListener('focusout', handleClose, {\n passive: true,\n });\n }\n\n // The panel keeps its own size in check: content growing past the viewport\n // has to flip the alignment even when the user did nothing.\n const resizeObserver = new ResizeObserver(() => {\n if (isOpen) scheduleMeasurement();\n });\n\n if (popoverRef.current) {\n resizeObserver.observe(popoverRef.current);\n }\n\n window.addEventListener('resize', scheduleMeasurement, { passive: true });\n\n return () => {\n cancelScheduledMeasurement?.();\n cancelScheduledRead?.();\n\n if (triggerElement) {\n triggerElement.removeEventListener('mouseenter', handleOpen);\n triggerElement.removeEventListener('focusin', handleOpen);\n triggerElement.removeEventListener('mouseleave', handleClose);\n triggerElement.removeEventListener('focusout', handleClose);\n }\n\n resizeObserver.disconnect();\n window.removeEventListener('resize', scheduleMeasurement);\n window.removeEventListener('scroll', scheduleMeasurement, true);\n };\n }, [triggerId, xAlign, yAlign]);\n\n // Use the static Detail component with computed alignment values\n return (\n <StaticDetail\n {...props}\n xAlign={computedXAlign}\n yAlign={computedYAlign}\n ref={popoverRef}\n style={{\n ...props.style,\n maxWidth: maxWidth ? `${maxWidth}px` : undefined,\n }}\n />\n );\n};\n\n// Create Popover with Detail attached\nexport const Popover: PopoverType = PopoverComponent as PopoverType;\n\nPopover.Detail = Detail;\n"],"mappings":"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"}
@@ -1,16 +1,16 @@
1
1
  'use client';
2
2
 
3
- import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
4
- import { useDevice } from "../../hooks/useDevice.mjs";
5
- import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
6
3
  import { Button } from "../Button/Button.mjs";
7
4
  import { Container } from "../Container/index.mjs";
5
+ import { useDevice } from "../../hooks/useDevice.mjs";
8
6
  import { KeyboardShortcut } from "../KeyboardShortcut/KeyboardShortcut.mjs";
9
7
  import { Popover } from "../Popover/dynamic.mjs";
8
+ import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
9
+ import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
10
10
  import { MaxWidthSmoother } from "../MaxWidthSmoother/index.mjs";
11
11
  import { isElementAtTopAndNotCovered } from "./isElementAtTopAndNotCovered.mjs";
12
- import { useRightDrawer } from "./useRightDrawer.mjs";
13
- import { useEffect, useRef } from "react";
12
+ import { useIsRightDrawerOpen, useRightDrawerActions } from "./useRightDrawer.mjs";
13
+ import { useEffect, useRef, useState } from "react";
14
14
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
15
  import { ChevronLeft, X } from "lucide-react";
16
16
  import { useIntlayer } from "react-intlayer";
@@ -23,18 +23,41 @@ const RightDrawer = ({ title, identifier, children, header, footer, closeOnOutsi
23
23
  const panelRef = useRef(null);
24
24
  const childrenContainerRef = useRef(null);
25
25
  const containerElement = useGetElementOrWindow(container);
26
- const { open: openDrawer, close: closeDrawer, isOpen: checkIsOpen } = useRightDrawer();
27
- const storeIsOpen = checkIsOpen(identifier);
26
+ const { open: openDrawer, close: closeDrawer } = useRightDrawerActions();
27
+ const storeIsOpen = useIsRightDrawerOpen(identifier);
28
28
  const isOpen = storeIsOpen;
29
+ /**
30
+ * A drawer mounted by its parent in the already-open state is inserted into
31
+ * the DOM at its final width, leaving the transition no previous value to
32
+ * start from: it pops instead of sliding. Painting the closed state for a
33
+ * frame first gives the transition its starting point.
34
+ */
35
+ const [hasEnteredOpen, setHasEnteredOpen] = useState(false);
36
+ useEffect(() => {
37
+ if (!isOpen) {
38
+ setHasEnteredOpen(false);
39
+ return;
40
+ }
41
+ let secondFrame = 0;
42
+ const firstFrame = requestAnimationFrame(() => {
43
+ secondFrame = requestAnimationFrame(() => setHasEnteredOpen(true));
44
+ });
45
+ return () => {
46
+ cancelAnimationFrame(firstFrame);
47
+ cancelAnimationFrame(secondFrame);
48
+ };
49
+ }, [isOpen]);
50
+ const isVisuallyOpen = isOpen && hasEnteredOpen;
29
51
  useScrollBlockage({
30
52
  disableScroll: isOpen,
31
53
  key: identifier ? `right_drawer_${identifier}` : "right_drawer"
32
54
  });
33
55
  useEffect(() => {
56
+ if (!isOpen) return;
34
57
  const handleClickOutside = (event) => {
35
58
  try {
36
59
  if (!panelRef.current) return;
37
- const isClickAble = isOpen && closeOnOutsideClick;
60
+ const isClickAble = closeOnOutsideClick;
38
61
  const isClickOutside = event.target && !panelRef.current.contains(event.target);
39
62
  const isAtTopAndVisible = isElementAtTopAndNotCovered(panelRef.current);
40
63
  if (isClickAble && isClickOutside && isAtTopAndVisible || !event.target) {
@@ -95,12 +118,13 @@ const RightDrawer = ({ title, identifier, children, header, footer, closeOnOutsi
95
118
  return createPortal(/* @__PURE__ */ jsx("div", {
96
119
  className: "fixed top-0 right-0 z-50 flex h-full justify-end",
97
120
  children: /* @__PURE__ */ jsx(MaxWidthSmoother, {
98
- isHidden: !isOpen,
121
+ isHidden: !isVisuallyOpen,
99
122
  align: "right",
100
123
  children: /* @__PURE__ */ jsxs(Container, {
101
- className: "relative flex h-screen w-screen flex-col text-text md:w-100",
124
+ className: "relative flex h-screen w-screen flex-col border-l text-text md:w-100",
102
125
  ref: panelRef,
103
126
  roundedSize: "none",
127
+ borderColor: "neutral",
104
128
  children: [
105
129
  /* @__PURE__ */ jsxs("div", {
106
130
  className: "flex shrink-0 flex-col gap-3 px-6 pt-6",
@@ -1 +1 @@
1
- {"version":3,"file":"RightDrawer.mjs","names":[],"sources":["../../../../src/components/RightDrawer/RightDrawer.tsx"],"sourcesContent":["'use client';\n\nimport { useGetElementOrWindow } from '@hooks/index';\nimport { useDevice } from '@hooks/useDevice';\nimport { useScrollBlockage } from '@hooks/useScrollBlockage';\nimport { ChevronLeft, X } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEventHandler,\n type MouseEventHandler,\n type ReactNode,\n useEffect,\n useRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { Container } from '../Container';\nimport { KeyboardShortcut } from '../KeyboardShortcut';\nimport { MaxWidthSmoother } from '../MaxWidthSmoother/index';\nimport { Popover } from '../Popover';\nimport { isElementAtTopAndNotCovered } from './isElementAtTopAndNotCovered';\nimport { useRightDrawer } from './useRightDrawer';\n\n/**\n * Configuration for the back button functionality in the RightDrawer\n *\n * @interface BackButtonProps\n */\ntype BackButtonProps = {\n /** Callback function triggered when the back button is clicked */\n onBack: () => void;\n /** Optional custom text for the back buttoDefaults to \"Go back\" if not provided */\n text?: string;\n};\n\n/**\n * Props configuration for the RightDrawer component\n *\n * @interface RightDrawerProps\n */\ntype RightDrawerProps = {\n /**\n * Title displayed in the drawer header\n */\n title?: ReactNode;\n\n /**\n * Unique identifier for the drawer instancRequired for store management\n */\n identifier: string;\n\n /** The content to be displayed inside the drawer */\n children?: ReactNode;\n\n /**\n * Optional header content displayed below the title\n */\n header?: ReactNode;\n\n /**\n * Optional footer content pinned to the bottom of the drawer\n */\n footer?: ReactNode;\n\n /**\n * Whether the drawer should close when clicking outside of it\n * @default true\n */\n closeOnOutsideClick?: boolean;\n\n /**\n * Configuration for an optional back button in the drawer header\n */\n backButton?: BackButtonProps;\n\n /**\n * External control for the open statWhen provided, overrides internal store state\n */\n isOpen?: boolean;\n\n /**\n * Callback function triggered when the drawer is closed\n */\n onClose?: () => void;\n\n /**\n * Optional container to render the drawer into.\n * If not provided, it will be rendered into the body.\n */\n container?: HTMLElement;\n};\n\nexport const RightDrawer: FC<RightDrawerProps> = ({\n title,\n identifier,\n children,\n header,\n footer,\n closeOnOutsideClick = true,\n backButton,\n isOpen: isOpenProp,\n onClose,\n container,\n}) => {\n const content = useIntlayer('right-drawer');\n const { isMobile } = useDevice('md');\n const panelRef = useRef<HTMLDivElement>(null);\n const childrenContainerRef = useRef<HTMLDivElement>(null);\n const containerElement = useGetElementOrWindow(container);\n\n const {\n open: openDrawer,\n close: closeDrawer,\n isOpen: checkIsOpen,\n } = useRightDrawer();\n const storeIsOpen = checkIsOpen(identifier);\n const isOpen = storeIsOpen;\n\n useScrollBlockage({\n disableScroll: isOpen,\n key: identifier ? `right_drawer_${identifier}` : 'right_drawer',\n });\n\n // Handle Click Outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n try {\n if (!panelRef.current) return;\n\n const isClickAble = isOpen && closeOnOutsideClick;\n const isClickOutside =\n event.target && !panelRef.current.contains(event.target as Node);\n const isAtTopAndVisible = isElementAtTopAndNotCovered(panelRef.current);\n\n if (\n (isClickAble && isClickOutside && isAtTopAndVisible) ||\n !event.target\n ) {\n closeDrawer(identifier);\n onClose?.();\n }\n } catch (_e) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n window.addEventListener('mousedown', handleClickOutside);\n return () => window.removeEventListener('mousedown', handleClickOutside);\n }, [isOpen, closeDrawer, onClose, closeOnOutsideClick, identifier]);\n\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n useEffect(() => {\n if (isOpenProp === undefined) return;\n if (isOpenProp === storeIsOpen) return;\n\n if (isOpenProp) {\n openDrawer(identifier);\n } else {\n closeDrawer(identifier);\n onCloseRef.current?.();\n }\n }, [isOpenProp, storeIsOpen, identifier, openDrawer, closeDrawer]);\n\n const handleSpareSpaceClick: MouseEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) {\n return;\n }\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n // Handle Keyboard on Spare Space (Linter Fix)\n const handleSpareSpaceKeyDown: KeyboardEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) return;\n\n // Allow closing via Enter or Space if focused on the spare area\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n }\n };\n\n if (!containerElement) return <></>;\n\n return createPortal(\n <div className=\"fixed top-0 right-0 z-50 flex h-full justify-end\">\n <MaxWidthSmoother isHidden={!isOpen} align=\"right\">\n <Container\n className=\"relative flex h-screen w-screen flex-col text-text md:w-100\"\n ref={panelRef}\n roundedSize=\"none\"\n >\n {/* Header */}\n <div className=\"flex shrink-0 flex-col gap-3 px-6 pt-6\">\n <div className=\"flex justify-between gap-3\">\n <div>\n {backButton && (\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label={backButton.text ?? content.goBack.value}\n onClick={backButton.onBack}\n Icon={ChevronLeft}\n >\n {backButton?.text}\n </Button>\n )}\n </div>\n <div>\n <Popover identifier=\"close-drawer\">\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label=\"Close\"\n className=\"ml-auto\"\n onClick={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n Icon={X}\n size=\"icon-md\"\n />\n\n <Popover.Detail identifier=\"close-drawer\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {content.closeDrawer}\n </span>\n <KeyboardShortcut\n shortcut=\"Escape\"\n size=\"sm\"\n onTriggered={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n </div>\n {title && (\n <h2 className=\"flex items-center justify-center font-bold text-lg\">\n {title}\n </h2>\n )}\n {header}\n </div>\n\n {/* Body */}\n <div className=\"flex min-h-0 flex-1 flex-col overflow-y-auto p-2\">\n {/** biome-ignore lint/a11y/useSemanticElements: This div is used to handle the spare space click and keydown events */}\n <div\n className=\"flex flex-1 flex-col outline-none\"\n onClick={handleSpareSpaceClick}\n onKeyDown={handleSpareSpaceKeyDown}\n ref={childrenContainerRef}\n role=\"button\" // Semantically acts as a button area\n tabIndex={0} // Makes it focusable to receive key events\n >\n {children}\n </div>\n </div>\n\n {/* Footer */}\n {footer && <div className=\"shrink-0\">{footer}</div>}\n </Container>\n </MaxWidthSmoother>\n </div>,\n containerElement\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"RightDrawer.mjs","names":[],"sources":["../../../../src/components/RightDrawer/RightDrawer.tsx"],"sourcesContent":["'use client';\n\nimport { useGetElementOrWindow } from '@hooks/index';\nimport { useDevice } from '@hooks/useDevice';\nimport { useScrollBlockage } from '@hooks/useScrollBlockage';\nimport { ChevronLeft, X } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEventHandler,\n type MouseEventHandler,\n type ReactNode,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { Container } from '../Container';\nimport { KeyboardShortcut } from '../KeyboardShortcut';\nimport { MaxWidthSmoother } from '../MaxWidthSmoother/index';\nimport { Popover } from '../Popover';\nimport { isElementAtTopAndNotCovered } from './isElementAtTopAndNotCovered';\nimport { useIsRightDrawerOpen, useRightDrawerActions } from './useRightDrawer';\n\n/**\n * Configuration for the back button functionality in the RightDrawer\n *\n * @interface BackButtonProps\n */\ntype BackButtonProps = {\n /** Callback function triggered when the back button is clicked */\n onBack: () => void;\n /** Optional custom text for the back buttoDefaults to \"Go back\" if not provided */\n text?: string;\n};\n\n/**\n * Props configuration for the RightDrawer component\n *\n * @interface RightDrawerProps\n */\ntype RightDrawerProps = {\n /**\n * Title displayed in the drawer header\n */\n title?: ReactNode;\n\n /**\n * Unique identifier for the drawer instancRequired for store management\n */\n identifier: string;\n\n /** The content to be displayed inside the drawer */\n children?: ReactNode;\n\n /**\n * Optional header content displayed below the title\n */\n header?: ReactNode;\n\n /**\n * Optional footer content pinned to the bottom of the drawer\n */\n footer?: ReactNode;\n\n /**\n * Whether the drawer should close when clicking outside of it\n * @default true\n */\n closeOnOutsideClick?: boolean;\n\n /**\n * Configuration for an optional back button in the drawer header\n */\n backButton?: BackButtonProps;\n\n /**\n * External control for the open statWhen provided, overrides internal store state\n */\n isOpen?: boolean;\n\n /**\n * Callback function triggered when the drawer is closed\n */\n onClose?: () => void;\n\n /**\n * Optional container to render the drawer into.\n * If not provided, it will be rendered into the body.\n */\n container?: HTMLElement;\n};\n\nexport const RightDrawer: FC<RightDrawerProps> = ({\n title,\n identifier,\n children,\n header,\n footer,\n closeOnOutsideClick = true,\n backButton,\n isOpen: isOpenProp,\n onClose,\n container,\n}) => {\n const content = useIntlayer('right-drawer');\n const { isMobile } = useDevice('md');\n const panelRef = useRef<HTMLDivElement>(null);\n const childrenContainerRef = useRef<HTMLDivElement>(null);\n const containerElement = useGetElementOrWindow(container);\n\n const { open: openDrawer, close: closeDrawer } = useRightDrawerActions();\n const storeIsOpen = useIsRightDrawerOpen(identifier);\n const isOpen = storeIsOpen;\n\n /**\n * A drawer mounted by its parent in the already-open state is inserted into\n * the DOM at its final width, leaving the transition no previous value to\n * start from: it pops instead of sliding. Painting the closed state for a\n * frame first gives the transition its starting point.\n */\n const [hasEnteredOpen, setHasEnteredOpen] = useState(false);\n\n useEffect(() => {\n if (!isOpen) {\n setHasEnteredOpen(false);\n return;\n }\n\n let secondFrame = 0;\n const firstFrame = requestAnimationFrame(() => {\n secondFrame = requestAnimationFrame(() => setHasEnteredOpen(true));\n });\n\n return () => {\n cancelAnimationFrame(firstFrame);\n cancelAnimationFrame(secondFrame);\n };\n }, [isOpen]);\n\n const isVisuallyOpen = isOpen && hasEnteredOpen;\n\n useScrollBlockage({\n disableScroll: isOpen,\n key: identifier ? `right_drawer_${identifier}` : 'right_drawer',\n });\n\n // Handle Click Outside\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClickOutside = (event: MouseEvent) => {\n try {\n if (!panelRef.current) return;\n\n const isClickAble = closeOnOutsideClick;\n const isClickOutside =\n event.target && !panelRef.current.contains(event.target as Node);\n const isAtTopAndVisible = isElementAtTopAndNotCovered(panelRef.current);\n\n if (\n (isClickAble && isClickOutside && isAtTopAndVisible) ||\n !event.target\n ) {\n closeDrawer(identifier);\n onClose?.();\n }\n } catch (_e) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n window.addEventListener('mousedown', handleClickOutside);\n return () => window.removeEventListener('mousedown', handleClickOutside);\n }, [isOpen, closeDrawer, onClose, closeOnOutsideClick, identifier]);\n\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n onCloseRef.current = onClose;\n }, [onClose]);\n\n useEffect(() => {\n if (isOpenProp === undefined) return;\n if (isOpenProp === storeIsOpen) return;\n\n if (isOpenProp) {\n openDrawer(identifier);\n } else {\n closeDrawer(identifier);\n onCloseRef.current?.();\n }\n }, [isOpenProp, storeIsOpen, identifier, openDrawer, closeDrawer]);\n\n const handleSpareSpaceClick: MouseEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) {\n return;\n }\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n };\n\n // Handle Keyboard on Spare Space (Linter Fix)\n const handleSpareSpaceKeyDown: KeyboardEventHandler<HTMLDivElement> = (e) => {\n if (e.target !== e.currentTarget) return;\n\n // Allow closing via Enter or Space if focused on the spare area\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n if (isMobile) {\n closeDrawer(identifier);\n onClose?.();\n }\n }\n };\n\n if (!containerElement) return <></>;\n\n return createPortal(\n <div className=\"fixed top-0 right-0 z-50 flex h-full justify-end\">\n <MaxWidthSmoother isHidden={!isVisuallyOpen} align=\"right\">\n <Container\n className=\"relative flex h-screen w-screen flex-col border-l text-text md:w-100\"\n ref={panelRef}\n roundedSize=\"none\"\n borderColor=\"neutral\"\n >\n {/* Header */}\n <div className=\"flex shrink-0 flex-col gap-3 px-6 pt-6\">\n <div className=\"flex justify-between gap-3\">\n <div>\n {backButton && (\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label={backButton.text ?? content.goBack.value}\n onClick={backButton.onBack}\n Icon={ChevronLeft}\n >\n {backButton?.text}\n </Button>\n )}\n </div>\n <div>\n <Popover identifier=\"close-drawer\">\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label=\"Close\"\n className=\"ml-auto\"\n onClick={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n Icon={X}\n size=\"icon-md\"\n />\n\n <Popover.Detail identifier=\"close-drawer\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {content.closeDrawer}\n </span>\n <KeyboardShortcut\n shortcut=\"Escape\"\n size=\"sm\"\n onTriggered={() => {\n closeDrawer(identifier);\n onClose?.();\n }}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n </div>\n {title && (\n <h2 className=\"flex items-center justify-center font-bold text-lg\">\n {title}\n </h2>\n )}\n {header}\n </div>\n\n {/* Body */}\n <div className=\"flex min-h-0 flex-1 flex-col overflow-y-auto p-2\">\n {/** biome-ignore lint/a11y/useSemanticElements: This div is used to handle the spare space click and keydown events */}\n <div\n className=\"flex flex-1 flex-col outline-none\"\n onClick={handleSpareSpaceClick}\n onKeyDown={handleSpareSpaceKeyDown}\n ref={childrenContainerRef}\n role=\"button\" // Semantically acts as a button area\n tabIndex={0} // Makes it focusable to receive key events\n >\n {children}\n </div>\n </div>\n\n {/* Footer */}\n {footer && <div className=\"shrink-0\">{footer}</div>}\n </Container>\n </MaxWidthSmoother>\n </div>,\n containerElement\n );\n};\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { drawerManager, useRightDrawer } from "./useRightDrawer.mjs";
1
+ import { drawerManager, useIsRightDrawerOpen, useRightDrawer, useRightDrawerActions } from "./useRightDrawer.mjs";
2
2
  import { RightDrawer } from "./RightDrawer.mjs";
3
3
 
4
- export { RightDrawer, drawerManager, useRightDrawer };
4
+ export { RightDrawer, drawerManager, useIsRightDrawerOpen, useRightDrawer, useRightDrawerActions };
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useSyncExternalStore } from "react";
3
+ import { useCallback, useSyncExternalStore } from "react";
4
4
 
5
5
  //#region src/components/RightDrawer/useRightDrawer.ts
6
6
  var DrawerObservable = class {
@@ -15,6 +15,7 @@ var DrawerObservable = class {
15
15
  getSnapshot = () => {
16
16
  return this.drawers;
17
17
  };
18
+ isOpen = (key) => Boolean(this.drawers[key]);
18
19
  open = (key) => {
19
20
  if (this.drawers[key]) return;
20
21
  this.drawers = {
@@ -50,16 +51,47 @@ var DrawerObservable = class {
50
51
  };
51
52
  };
52
53
  const drawerManager = new DrawerObservable();
54
+ const rightDrawerActions = {
55
+ open: drawerManager.open,
56
+ close: drawerManager.close,
57
+ set: drawerManager.set
58
+ };
59
+ const getServerSnapshot = () => false;
60
+ /**
61
+ * Returns the drawer actions without subscribing to the store.
62
+ *
63
+ * Prefer it over {@link useRightDrawer} in components that only need to open,
64
+ * close or set drawers: they then no longer re-render every time an unrelated
65
+ * drawer toggles.
66
+ */
67
+ const useRightDrawerActions = () => rightDrawerActions;
68
+ /**
69
+ * Subscribes to the open state of a single drawer.
70
+ *
71
+ * The snapshot is a boolean, so a component only re-renders when its own
72
+ * drawer toggles, instead of on every change of the shared drawer record.
73
+ *
74
+ * @param identifier - Identifier of the drawer to watch
75
+ */
76
+ const useIsRightDrawerOpen = (identifier) => {
77
+ const getSnapshot = useCallback(() => drawerManager.isOpen(identifier), [identifier]);
78
+ return useSyncExternalStore(drawerManager.subscribe, getSnapshot, getServerSnapshot);
79
+ };
80
+ /**
81
+ * Gives access to the whole drawer record.
82
+ *
83
+ * Any change to any drawer re-renders the consumer. When only one drawer
84
+ * matters, use {@link useIsRightDrawerOpen}; when only the actions matter, use
85
+ * {@link useRightDrawerActions}.
86
+ */
53
87
  const useRightDrawer = () => {
54
88
  const drawers = useSyncExternalStore(drawerManager.subscribe, drawerManager.getSnapshot, () => ({}));
55
89
  return {
56
- open: drawerManager.open,
57
- close: drawerManager.close,
58
- set: drawerManager.set,
90
+ ...rightDrawerActions,
59
91
  isOpen: (key) => !!drawers[key]
60
92
  };
61
93
  };
62
94
 
63
95
  //#endregion
64
- export { drawerManager, useRightDrawer };
96
+ export { drawerManager, useIsRightDrawerOpen, useRightDrawer, useRightDrawerActions };
65
97
  //# sourceMappingURL=useRightDrawer.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useRightDrawer.mjs","names":[],"sources":["../../../../src/components/RightDrawer/useRightDrawer.ts"],"sourcesContent":["'use client';\n\nimport { useSyncExternalStore } from 'react';\n\ntype DrawerState = Record<string, boolean>;\n\nclass DrawerObservable {\n private listeners = new Set<() => void>();\n private drawers: DrawerState = {};\n\n subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = () => {\n return this.drawers;\n };\n\n open = (key: string) => {\n if (this.drawers[key]) return;\n this.drawers = { ...this.drawers, [key]: true };\n this.emit();\n };\n\n close = (key: string) => {\n if (!this.drawers[key]) return;\n this.drawers = { ...this.drawers, [key]: false };\n this.emit();\n };\n\n set = (states: DrawerState) => {\n let changed = false;\n const newDrawers = { ...this.drawers };\n\n for (const [key, value] of Object.entries(states)) {\n if (newDrawers[key] !== value) {\n newDrawers[key] = value;\n changed = true;\n }\n }\n\n if (changed) {\n this.drawers = newDrawers;\n this.emit();\n }\n };\n\n private emit = () => {\n this.listeners.forEach((listener) => {\n listener();\n });\n };\n}\n\nexport const drawerManager = new DrawerObservable();\n\nexport const useRightDrawer = () => {\n const drawers = useSyncExternalStore(\n drawerManager.subscribe,\n drawerManager.getSnapshot,\n () => ({}) as DrawerState\n );\n\n return {\n open: drawerManager.open,\n close: drawerManager.close,\n set: drawerManager.set,\n isOpen: (key: string) => !!drawers[key],\n };\n};\n"],"mappings":";;;;;AAMA,IAAM,mBAAN,MAAuB;CACrB,AAAQ,4BAAY,IAAI,IAAgB;CACxC,AAAQ,UAAuB,CAAC;CAEhC,aAAa,aAAyB;EACpC,KAAK,UAAU,IAAI,QAAQ;EAC3B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,oBAAoB;EAClB,OAAO,KAAK;CACd;CAEA,QAAQ,QAAgB;EACtB,IAAI,KAAK,QAAQ,MAAM;EACvB,KAAK,UAAU;GAAE,GAAG,KAAK;IAAU,MAAM;EAAK;EAC9C,KAAK,KAAK;CACZ;CAEA,SAAS,QAAgB;EACvB,IAAI,CAAC,KAAK,QAAQ,MAAM;EACxB,KAAK,UAAU;GAAE,GAAG,KAAK;IAAU,MAAM;EAAM;EAC/C,KAAK,KAAK;CACZ;CAEA,OAAO,WAAwB;EAC7B,IAAI,UAAU;EACd,MAAM,aAAa,EAAE,GAAG,KAAK,QAAQ;EAErC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,WAAW,SAAS,OAAO;GAC7B,WAAW,OAAO;GAClB,UAAU;EACZ;EAGF,IAAI,SAAS;GACX,KAAK,UAAU;GACf,KAAK,KAAK;EACZ;CACF;CAEA,AAAQ,aAAa;EACnB,KAAK,UAAU,SAAS,aAAa;GACnC,SAAS;EACX,CAAC;CACH;AACF;AAEA,MAAa,gBAAgB,IAAI,iBAAiB;AAElD,MAAa,uBAAuB;CAClC,MAAM,UAAU,qBACd,cAAc,WACd,cAAc,oBACP,CAAC,EACV;CAEA,OAAO;EACL,MAAM,cAAc;EACpB,OAAO,cAAc;EACrB,KAAK,cAAc;EACnB,SAAS,QAAgB,CAAC,CAAC,QAAQ;CACrC;AACF"}
1
+ {"version":3,"file":"useRightDrawer.mjs","names":[],"sources":["../../../../src/components/RightDrawer/useRightDrawer.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useSyncExternalStore } from 'react';\n\ntype DrawerState = Record<string, boolean>;\n\nclass DrawerObservable {\n private listeners = new Set<() => void>();\n private drawers: DrawerState = {};\n\n subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = () => {\n return this.drawers;\n };\n\n isOpen = (key: string) => Boolean(this.drawers[key]);\n\n open = (key: string) => {\n if (this.drawers[key]) return;\n this.drawers = { ...this.drawers, [key]: true };\n this.emit();\n };\n\n close = (key: string) => {\n if (!this.drawers[key]) return;\n this.drawers = { ...this.drawers, [key]: false };\n this.emit();\n };\n\n set = (states: DrawerState) => {\n let changed = false;\n const newDrawers = { ...this.drawers };\n\n for (const [key, value] of Object.entries(states)) {\n if (newDrawers[key] !== value) {\n newDrawers[key] = value;\n changed = true;\n }\n }\n\n if (changed) {\n this.drawers = newDrawers;\n this.emit();\n }\n };\n\n private emit = () => {\n this.listeners.forEach((listener) => {\n listener();\n });\n };\n}\n\nexport const drawerManager = new DrawerObservable();\n\n/**\n * Actions to drive any drawer, detached from the store snapshot.\n */\ntype RightDrawerActions = {\n open: (key: string) => void;\n close: (key: string) => void;\n set: (states: DrawerState) => void;\n};\n\nconst rightDrawerActions: RightDrawerActions = {\n open: drawerManager.open,\n close: drawerManager.close,\n set: drawerManager.set,\n};\n\nconst getServerSnapshot = () => false;\n\n/**\n * Returns the drawer actions without subscribing to the store.\n *\n * Prefer it over {@link useRightDrawer} in components that only need to open,\n * close or set drawers: they then no longer re-render every time an unrelated\n * drawer toggles.\n */\nexport const useRightDrawerActions = (): RightDrawerActions =>\n rightDrawerActions;\n\n/**\n * Subscribes to the open state of a single drawer.\n *\n * The snapshot is a boolean, so a component only re-renders when its own\n * drawer toggles, instead of on every change of the shared drawer record.\n *\n * @param identifier - Identifier of the drawer to watch\n */\nexport const useIsRightDrawerOpen = (identifier: string): boolean => {\n const getSnapshot = useCallback(\n () => drawerManager.isOpen(identifier),\n [identifier]\n );\n\n return useSyncExternalStore(\n drawerManager.subscribe,\n getSnapshot,\n getServerSnapshot\n );\n};\n\n/**\n * Gives access to the whole drawer record.\n *\n * Any change to any drawer re-renders the consumer. When only one drawer\n * matters, use {@link useIsRightDrawerOpen}; when only the actions matter, use\n * {@link useRightDrawerActions}.\n */\nexport const useRightDrawer = () => {\n const drawers = useSyncExternalStore(\n drawerManager.subscribe,\n drawerManager.getSnapshot,\n () => ({}) as DrawerState\n );\n\n return {\n ...rightDrawerActions,\n isOpen: (key: string) => !!drawers[key],\n };\n};\n"],"mappings":";;;;;AAMA,IAAM,mBAAN,MAAuB;CACrB,AAAQ,4BAAY,IAAI,IAAgB;CACxC,AAAQ,UAAuB,CAAC;CAEhC,aAAa,aAAyB;EACpC,KAAK,UAAU,IAAI,QAAQ;EAC3B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,oBAAoB;EAClB,OAAO,KAAK;CACd;CAEA,UAAU,QAAgB,QAAQ,KAAK,QAAQ,IAAI;CAEnD,QAAQ,QAAgB;EACtB,IAAI,KAAK,QAAQ,MAAM;EACvB,KAAK,UAAU;GAAE,GAAG,KAAK;IAAU,MAAM;EAAK;EAC9C,KAAK,KAAK;CACZ;CAEA,SAAS,QAAgB;EACvB,IAAI,CAAC,KAAK,QAAQ,MAAM;EACxB,KAAK,UAAU;GAAE,GAAG,KAAK;IAAU,MAAM;EAAM;EAC/C,KAAK,KAAK;CACZ;CAEA,OAAO,WAAwB;EAC7B,IAAI,UAAU;EACd,MAAM,aAAa,EAAE,GAAG,KAAK,QAAQ;EAErC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,WAAW,SAAS,OAAO;GAC7B,WAAW,OAAO;GAClB,UAAU;EACZ;EAGF,IAAI,SAAS;GACX,KAAK,UAAU;GACf,KAAK,KAAK;EACZ;CACF;CAEA,AAAQ,aAAa;EACnB,KAAK,UAAU,SAAS,aAAa;GACnC,SAAS;EACX,CAAC;CACH;AACF;AAEA,MAAa,gBAAgB,IAAI,iBAAiB;AAWlD,MAAM,qBAAyC;CAC7C,MAAM,cAAc;CACpB,OAAO,cAAc;CACrB,KAAK,cAAc;AACrB;AAEA,MAAM,0BAA0B;;;;;;;;AAShC,MAAa,8BACX;;;;;;;;;AAUF,MAAa,wBAAwB,eAAgC;CACnE,MAAM,cAAc,kBACZ,cAAc,OAAO,UAAU,GACrC,CAAC,UAAU,CACb;CAEA,OAAO,qBACL,cAAc,WACd,aACA,iBACF;AACF;;;;;;;;AASA,MAAa,uBAAuB;CAClC,MAAM,UAAU,qBACd,cAAc,WACd,cAAc,oBACP,CAAC,EACV;CAEA,OAAO;EACL,GAAG;EACH,SAAS,QAAgB,CAAC,CAAC,QAAQ;CACrC;AACF"}
@@ -5,12 +5,12 @@ import { useEffect, useRef, useState } from "react";
5
5
 
6
6
  //#region src/components/SwitchSelector/useSwitchSelector.ts
7
7
  const useSwitchSelector = ({ choices = [], value, defaultValue, onChange, hoverable = true, disabled = false }, orientation) => {
8
- const [valueState, setValue] = useState(value ?? defaultValue ?? choices[0]?.value);
8
+ const [valueState, setValue] = useState(value ?? defaultValue ?? choices[0].value);
9
9
  const [hoveredIndex, setHoveredIndex] = useState(null);
10
10
  const optionsRefs = useRef([]);
11
11
  const indicatorRef = useRef(null);
12
12
  const { choiceIndicatorPosition } = useItemSelector(optionsRefs, {
13
- selector: (el) => el.getAttribute("data-indicator") === "true",
13
+ selector: (el) => el?.getAttribute("data-indicator") === "true",
14
14
  isHoverable: false,
15
15
  orientation
16
16
  });
@@ -1 +1 @@
1
- {"version":3,"file":"useSwitchSelector.mjs","names":[],"sources":["../../../../src/components/SwitchSelector/useSwitchSelector.ts"],"sourcesContent":["'use client';\n\nimport {\n type ItemSelectorOrientation,\n useItemSelector,\n} from '@hooks/useItemSelector';\nimport { useEffect, useRef, useState } from 'react';\nimport type { SwitchSelectorBaseProps } from './SwitchSelector';\n\nexport const useSwitchSelector = <T>(\n {\n choices = [],\n value,\n defaultValue,\n onChange,\n hoverable = true,\n disabled = false,\n }: SwitchSelectorBaseProps<T>,\n orientation: ItemSelectorOrientation\n) => {\n const [valueState, setValue] = useState<T>(\n value ?? defaultValue ?? choices[0]?.value\n );\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n\n const optionsRefs = useRef<HTMLButtonElement[]>([]);\n const indicatorRef = useRef<HTMLDivElement | null>(null);\n const { choiceIndicatorPosition } = useItemSelector(optionsRefs, {\n selector: (el) => el.getAttribute('data-indicator') === 'true',\n isHoverable: false,\n orientation,\n });\n\n const selectedIndex = choices.findIndex(\n (choice) => choice.value === valueState\n );\n\n // The indicator follows hover if hoverable, otherwise the selected option\n const indicatorIndex =\n hoverable && hoveredIndex !== null ? hoveredIndex : selectedIndex;\n\n const handleChange = (newValue: T) => {\n setValue(newValue);\n onChange?.(newValue);\n };\n\n useEffect(() => {\n if (value === undefined) return;\n setValue(value);\n }, [value]);\n\n return {\n valueState,\n hoveredIndex,\n setHoveredIndex,\n optionsRefs,\n indicatorRef,\n choiceIndicatorPosition,\n selectedIndex,\n indicatorIndex,\n handleChange,\n disabled,\n };\n};\n"],"mappings":";;;;;;AASA,MAAa,qBACX,EACE,UAAU,CAAC,GACX,OACA,cACA,UACA,YAAY,MACZ,WAAW,SAEb,gBACG;CACH,MAAM,CAAC,YAAY,YAAY,SAC7B,SAAS,gBAAgB,QAAQ,EAAE,EAAE,KACvC;CACA,MAAM,CAAC,cAAc,mBAAmB,SAAwB,IAAI;CAEpE,MAAM,cAAc,OAA4B,CAAC,CAAC;CAClD,MAAM,eAAe,OAA8B,IAAI;CACvD,MAAM,EAAE,4BAA4B,gBAAgB,aAAa;EAC/D,WAAW,OAAO,GAAG,aAAa,gBAAgB,MAAM;EACxD,aAAa;EACb;CACF,CAAC;CAED,MAAM,gBAAgB,QAAQ,WAC3B,WAAW,OAAO,UAAU,UAC/B;CAGA,MAAM,iBACJ,aAAa,iBAAiB,OAAO,eAAe;CAEtD,MAAM,gBAAgB,aAAgB;EACpC,SAAS,QAAQ;EACjB,WAAW,QAAQ;CACrB;CAEA,gBAAgB;EACd,IAAI,UAAU,QAAW;EACzB,SAAS,KAAK;CAChB,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"useSwitchSelector.mjs","names":[],"sources":["../../../../src/components/SwitchSelector/useSwitchSelector.ts"],"sourcesContent":["'use client';\n\nimport {\n type ItemSelectorOrientation,\n useItemSelector,\n} from '@hooks/useItemSelector';\nimport { useEffect, useRef, useState } from 'react';\nimport type { SwitchSelectorBaseProps } from './SwitchSelector';\n\nexport const useSwitchSelector = <T>(\n {\n choices = [],\n value,\n defaultValue,\n onChange,\n hoverable = true,\n disabled = false,\n }: SwitchSelectorBaseProps<T>,\n orientation: ItemSelectorOrientation\n) => {\n const [valueState, setValue] = useState<T>(\n value ?? defaultValue ?? choices[0]!.value\n );\n const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);\n\n const optionsRefs = useRef<HTMLButtonElement[]>([]);\n const indicatorRef = useRef<HTMLDivElement | null>(null);\n const { choiceIndicatorPosition } = useItemSelector(optionsRefs, {\n selector: (el) => el?.getAttribute('data-indicator') === 'true',\n isHoverable: false,\n orientation,\n });\n\n const selectedIndex = choices.findIndex(\n (choice) => choice.value === valueState\n );\n\n // The indicator follows hover if hoverable, otherwise the selected option\n const indicatorIndex =\n hoverable && hoveredIndex !== null ? hoveredIndex : selectedIndex;\n\n const handleChange = (newValue: T) => {\n setValue(newValue);\n onChange?.(newValue);\n };\n\n useEffect(() => {\n if (value === undefined) return;\n setValue(value);\n }, [value]);\n\n return {\n valueState,\n hoveredIndex,\n setHoveredIndex,\n optionsRefs,\n indicatorRef,\n choiceIndicatorPosition,\n selectedIndex,\n indicatorIndex,\n handleChange,\n disabled,\n };\n};\n"],"mappings":";;;;;;AASA,MAAa,qBACX,EACE,UAAU,CAAC,GACX,OACA,cACA,UACA,YAAY,MACZ,WAAW,SAEb,gBACG;CACH,MAAM,CAAC,YAAY,YAAY,SAC7B,SAAS,gBAAgB,QAAQ,EAAE,CAAE,KACvC;CACA,MAAM,CAAC,cAAc,mBAAmB,SAAwB,IAAI;CAEpE,MAAM,cAAc,OAA4B,CAAC,CAAC;CAClD,MAAM,eAAe,OAA8B,IAAI;CACvD,MAAM,EAAE,4BAA4B,gBAAgB,aAAa;EAC/D,WAAW,OAAO,IAAI,aAAa,gBAAgB,MAAM;EACzD,aAAa;EACb;CACF,CAAC;CAED,MAAM,gBAAgB,QAAQ,WAC3B,WAAW,OAAO,UAAU,UAC/B;CAGA,MAAM,iBACJ,aAAa,iBAAiB,OAAO,eAAe;CAEtD,MAAM,gBAAgB,aAAgB;EACpC,SAAS,QAAQ;EACjB,WAAW,QAAQ;CACrB;CAEA,gBAAgB;EACd,IAAI,UAAU,QAAW;EACzB,SAAS,KAAK;CAChB,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
- import { useHorizontalSwipe } from "../../hooks/useHorizontalSwipe.mjs";
5
4
  import { TabSelector } from "../TabSelector/TabSelector.mjs";
5
+ import { useHorizontalSwipe } from "../../hooks/useHorizontalSwipe.mjs";
6
6
  import { useTabContext } from "./TabContext.mjs";
7
7
  import { Children, createContext, isValidElement, useState } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -17,6 +17,22 @@ import { cva } from "class-variance-authority";
17
17
  * the one panel, and silently breaks the association for screen readers.
18
18
  */
19
19
  const toIdFragment = (value) => value.trim().replace(/\s+/g, "-").replace(/[^\w-]/g, "");
20
+ /**
21
+ * Builds one id fragment per tab, free of collisions.
22
+ *
23
+ * Sanitizing alone is not enough: two values can reduce to the same fragment
24
+ * (`>=9.4` and `<9.4` both reduce to `94`), which would give two tabs the same
25
+ * DOM id and cross-wire `aria-controls` between their panels.
26
+ */
27
+ const toIdFragments = (values) => {
28
+ const fragmentUsageCount = /* @__PURE__ */ new Map();
29
+ return values.map((value, index) => {
30
+ const fragment = toIdFragment(value) || String(index);
31
+ const previousUsageCount = fragmentUsageCount.get(fragment) ?? 0;
32
+ fragmentUsageCount.set(fragment, previousUsageCount + 1);
33
+ return previousUsageCount === 0 ? fragment : `${fragment}-${previousUsageCount + 1}`;
34
+ });
35
+ };
20
36
  const TabContext = createContext(void 0);
21
37
  const tabContainerVariant = cva("relative w-full rounded-lg", {
22
38
  variants: {
@@ -81,6 +97,7 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
81
97
  const currentTabValue = (group && typeof tabsValues === "object" ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;
82
98
  const activeTabIndex = tabItems.findIndex((tab) => tab.value === currentTabValue);
83
99
  const tabsCount = tabItems.length;
100
+ const idFragments = toIdFragments(tabItems.map(({ value }) => value));
84
101
  const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({
85
102
  itemIndex: activeTabIndex,
86
103
  itemCount: tabsCount,
@@ -115,9 +132,9 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
115
132
  className: cn("flex shrink-0 gap-3 p-3", headerClassName),
116
133
  children: /* @__PURE__ */ jsx(TabSelector, {
117
134
  selectedChoice: currentTabValue,
118
- tabs: tabItems.map(({ label, value, disabled }) => {
135
+ tabs: tabItems.map(({ label, value, disabled }, index) => {
119
136
  const isActive = currentTabValue === value;
120
- const idFragment = toIdFragment(value);
137
+ const idFragment = idFragments[index];
121
138
  return /* @__PURE__ */ jsx("button", {
122
139
  className: cn("cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none", !isActive && "text-neutral/70"),
123
140
  "data-active": isActive,
@@ -145,7 +162,7 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
145
162
  },
146
163
  children: tabItems.map(({ value, children, className: itemClassName }, index) => {
147
164
  const isActive = index === activeTabIndex;
148
- const idFragment = toIdFragment(value);
165
+ const idFragment = idFragments[index];
149
166
  return /* @__PURE__ */ jsx("div", {
150
167
  role: "tabpanel",
151
168
  "aria-labelledby": `tab-${idFragment}`,
@@ -1 +1 @@
1
- {"version":3,"file":"Tab.mjs","names":[],"sources":["../../../../src/components/Tab/Tab.tsx"],"sourcesContent":["'use client';\n\nimport { useHorizontalSwipe } from '@hooks/useHorizontalSwipe';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Children,\n createContext,\n type HTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useState,\n} from 'react';\nimport { TabSelector } from '../TabSelector';\nimport { useTabContext } from './TabContext';\n\n/**\n * Builds an id fragment usable in `id` / `aria-controls`.\n *\n * Tab values come from markdown (`<Tab value=\"Extract command\">`), so they can\n * hold spaces — which makes `aria-controls` reference a list of ids rather than\n * the one panel, and silently breaks the association for screen readers.\n */\nconst toIdFragment = (value: string) =>\n value\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/[^\\w-]/g, '');\n\n// Context for managing tab state\ntype TabContextType = {\n activeTab: string;\n setActiveTab: (tab: string) => void;\n};\n\nconst TabContext = createContext<TabContextType | undefined>(undefined);\n\n// Tab container variants\nconst tabContainerVariant = cva('relative w-full rounded-lg', {\n variants: {\n background: {\n with: 'border border-neutral/20 bg-background/2 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur',\n without: '',\n },\n variant: {\n default: '',\n bordered: 'border-2',\n ghost: 'border-0 bg-transparent shadow-none',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\nexport type TabProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof tabContainerVariant> & {\n defaultTab?: string;\n group?: string;\n children: ReactNode;\n headerClassName?: string;\n fullHeight?: boolean;\n };\n\nexport type TabItemProps = HTMLAttributes<HTMLDivElement> & {\n label?: string;\n value?: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\n/** A tab item whose identifier has been resolved to a usable string. */\ntype ResolvedTabItem = TabItemProps & { value: string };\n\n/**\n * Resolves the identifier of a tab.\n *\n * Both `label` and `value` are optional for markdown-authored tabs\n * (`<Tab label=\"next-intl\">`), so the identifier falls back to the label, then\n * to the position, keeping every tab addressable instead of rendering ids from\n * `undefined`.\n */\nconst resolveTabValue = (\n { value, label }: TabItemProps,\n index: number\n): string => {\n const identifier = value ?? label;\n\n return typeof identifier === 'string' ? identifier : String(index);\n};\n\n/**\n * TabItem component that represents a single tab\n * Must be used as a child of the Tab component\n */\nconst TabItem = ({ children, ...props }: TabItemProps) => (\n // This component is primarily used for its props by the parent Tab component\n // The actual rendering is handled by the Tab component\n <div {...props}>{children}</div>\n);\n\n// Add display name for better debugging\nTabItem.displayName = 'TabItem';\n\n/**\n * Tab container component that manages tab state and renders tab headers and content\n *\n * Example:\n * ```jsx\n * <Tab defaultTab=\"tab1\">\n * <Tab.Item label=\"First Tab\" value=\"tab1\">\n * Content for first tab\n * </Tab.Item>\n * <Tab.Item label=\"Second Tab\" value=\"tab2\">\n * Content for second tab\n * </Tab.Item>\n * </Tab>\n * ```\n */\nconst TabComponent = ({\n defaultTab,\n group,\n variant,\n children,\n className,\n headerClassName,\n fullHeight,\n ...props\n}: TabProps) => {\n // Extract TabItem children to get their props\n const tabItems: ResolvedTabItem[] = (\n Children.toArray(children).filter((child) => {\n return isValidElement(child) && child.type === TabItem;\n }) as ReactElement<TabItemProps>[]\n ).map((child, index) => ({\n ...child.props,\n value: resolveTabValue(child.props, index),\n }));\n\n const firstTabValue = tabItems[0]?.value;\n const { tabsValues, setTabsValues } = useTabContext();\n const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? '');\n const hasGroup = group && typeof tabsValues === 'object';\n const currentTabValue =\n (hasGroup ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;\n const activeTabIndex = tabItems.findIndex(\n (tab) => tab.value === currentTabValue\n );\n\n const tabsCount = tabItems.length;\n\n const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({\n itemIndex: activeTabIndex,\n itemCount: tabsCount,\n onSwipeLeft: () => {\n const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);\n const nextValue = tabItems[targetIndex]?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n onSwipeRight: () => {\n const targetIndex = Math.max(0, activeTabIndex - 1);\n const nextValue = tabItems[targetIndex]?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n });\n\n const handleSetActiveTab = (tab: string) => {\n setActiveTab(tab);\n\n if (typeof setTabsValues === 'function') {\n setTabsValues((prev) => ({ ...prev, [group!]: tab }));\n }\n };\n\n const contextValue: TabContextType = {\n activeTab: activeTab ?? firstTabValue ?? '',\n setActiveTab: handleSetActiveTab,\n };\n\n return (\n <TabContext.Provider value={contextValue}>\n <div\n className={cn(\n tabContainerVariant({ variant }),\n fullHeight && 'flex h-full flex-col overflow-hidden',\n className\n )}\n {...props}\n >\n {/* Tab Headers — `TabSelector` is the tablist; wrapping it in another\n one would nest two, leaving the outer without any `tab` child. */}\n <div className={cn('flex shrink-0 gap-3 p-3', headerClassName)}>\n <TabSelector\n selectedChoice={currentTabValue}\n tabs={tabItems.map(({ label, value, disabled }) => {\n const isActive = currentTabValue === value;\n const idFragment = toIdFragment(value);\n\n return (\n <button\n key={value}\n className={cn(\n 'cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none',\n !isActive && 'text-neutral/70'\n )}\n data-active={isActive}\n disabled={disabled}\n onClick={() => !disabled && handleSetActiveTab(value)}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`tabpanel-${idFragment}`}\n id={`tab-${idFragment}`}\n type=\"button\"\n >\n {label ?? value}\n </button>\n );\n })}\n hoverable\n color=\"text\"\n />\n </div>\n {/* Tab Content */}\n {/* Clipper: no overflow; uses clip-path */}\n <div\n className={cn(\n 'relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]',\n fullHeight && 'min-h-0 flex-1'\n )}\n {...containerProps}\n >\n {/* Track — presentational: it carries the sliding transform, while\n the panels it holds are the tablist's controlled regions. */}\n <div\n className={cn(\n 'grid w-full min-w-0',\n fullHeight && 'h-full',\n isDragging\n ? 'transition-none'\n : 'transition-transform duration-300 ease-in-out'\n )}\n style={{\n gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,\n transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`,\n }}\n >\n {tabItems.map(\n ({ value, children, className: itemClassName }, index) => {\n const isActive = index === activeTabIndex;\n const idFragment = toIdFragment(value);\n\n return (\n <div\n key={value}\n role=\"tabpanel\"\n aria-labelledby={`tab-${idFragment}`}\n id={`tabpanel-${idFragment}`}\n // `inert` takes the whole subtree out of the tab order and\n // the accessibility tree at once. `aria-hidden` alone leaves\n // the buttons and links of an off-screen panel focusable,\n // which strands keyboard users on invisible controls.\n inert={!isActive}\n tabIndex={isActive ? 0 : -1}\n data-active={isActive}\n className={cn(\n 'w-full min-w-0 p-3 opacity-100 transition-opacity duration-300 ease-in-out',\n fullHeight && 'h-full overflow-y-auto',\n !isActive && 'pointer-events-none opacity-0', // prevent offscreen interaction\n itemClassName\n )}\n >\n <div\n className={cn(\n 'flex w-full min-w-0 flex-col items-stretch gap-6',\n fullHeight && 'min-h-full'\n )}\n >\n {children}\n </div>\n </div>\n );\n }\n )}\n </div>\n </div>\n </div>\n </TabContext.Provider>\n );\n};\n\n// Create the compound component\nexport const Tab = Object.assign(TabComponent, {\n Item: TabItem,\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"Tab.mjs","names":[],"sources":["../../../../src/components/Tab/Tab.tsx"],"sourcesContent":["'use client';\n\nimport { useHorizontalSwipe } from '@hooks/useHorizontalSwipe';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Children,\n createContext,\n type HTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useState,\n} from 'react';\nimport { TabSelector } from '../TabSelector';\nimport { useTabContext } from './TabContext';\n\n/**\n * Builds an id fragment usable in `id` / `aria-controls`.\n *\n * Tab values come from markdown (`<Tab value=\"Extract command\">`), so they can\n * hold spaces — which makes `aria-controls` reference a list of ids rather than\n * the one panel, and silently breaks the association for screen readers.\n */\nconst toIdFragment = (value: string) =>\n value\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/[^\\w-]/g, '');\n\n/**\n * Builds one id fragment per tab, free of collisions.\n *\n * Sanitizing alone is not enough: two values can reduce to the same fragment\n * (`>=9.4` and `<9.4` both reduce to `94`), which would give two tabs the same\n * DOM id and cross-wire `aria-controls` between their panels.\n */\nconst toIdFragments = (values: string[]): string[] => {\n const fragmentUsageCount = new Map<string, number>();\n\n return values.map((value, index) => {\n const fragment = toIdFragment(value) || String(index);\n const previousUsageCount = fragmentUsageCount.get(fragment) ?? 0;\n\n fragmentUsageCount.set(fragment, previousUsageCount + 1);\n\n return previousUsageCount === 0\n ? fragment\n : `${fragment}-${previousUsageCount + 1}`;\n });\n};\n\n// Context for managing tab state\ntype TabContextType = {\n activeTab: string;\n setActiveTab: (tab: string) => void;\n};\n\nconst TabContext = createContext<TabContextType | undefined>(undefined);\n\n// Tab container variants\nconst tabContainerVariant = cva('relative w-full rounded-lg', {\n variants: {\n background: {\n with: 'border border-neutral/20 bg-background/2 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur',\n without: '',\n },\n variant: {\n default: '',\n bordered: 'border-2',\n ghost: 'border-0 bg-transparent shadow-none',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\nexport type TabProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof tabContainerVariant> & {\n defaultTab?: string;\n group?: string;\n children: ReactNode;\n headerClassName?: string;\n fullHeight?: boolean;\n };\n\nexport type TabItemProps = HTMLAttributes<HTMLDivElement> & {\n label?: string;\n value?: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\n/** A tab item whose identifier has been resolved to a usable string. */\ntype ResolvedTabItem = TabItemProps & { value: string };\n\n/**\n * Resolves the identifier of a tab.\n *\n * Both `label` and `value` are optional for markdown-authored tabs\n * (`<Tab label=\"next-intl\">`), so the identifier falls back to the label, then\n * to the position, keeping every tab addressable instead of rendering ids from\n * `undefined`.\n */\nconst resolveTabValue = (\n { value, label }: TabItemProps,\n index: number\n): string => {\n const identifier = value ?? label;\n\n return typeof identifier === 'string' ? identifier : String(index);\n};\n\n/**\n * TabItem component that represents a single tab\n * Must be used as a child of the Tab component\n */\nconst TabItem = ({ children, ...props }: TabItemProps) => (\n // This component is primarily used for its props by the parent Tab component\n // The actual rendering is handled by the Tab component\n <div {...props}>{children}</div>\n);\n\n// Add display name for better debugging\nTabItem.displayName = 'TabItem';\n\n/**\n * Tab container component that manages tab state and renders tab headers and content\n *\n * Example:\n * ```jsx\n * <Tab defaultTab=\"tab1\">\n * <Tab.Item label=\"First Tab\" value=\"tab1\">\n * Content for first tab\n * </Tab.Item>\n * <Tab.Item label=\"Second Tab\" value=\"tab2\">\n * Content for second tab\n * </Tab.Item>\n * </Tab>\n * ```\n */\nconst TabComponent = ({\n defaultTab,\n group,\n variant,\n children,\n className,\n headerClassName,\n fullHeight,\n ...props\n}: TabProps) => {\n // Extract TabItem children to get their props\n const tabItems: ResolvedTabItem[] = (\n Children.toArray(children).filter((child) => {\n return isValidElement(child) && child.type === TabItem;\n }) as ReactElement<TabItemProps>[]\n ).map((child, index) => ({\n ...child.props,\n value: resolveTabValue(child.props, index),\n }));\n\n const firstTabValue = tabItems[0]?.value;\n const { tabsValues, setTabsValues } = useTabContext();\n const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? '');\n const hasGroup = group && typeof tabsValues === 'object';\n const currentTabValue =\n (hasGroup ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;\n const activeTabIndex = tabItems.findIndex(\n (tab) => tab.value === currentTabValue\n );\n\n const tabsCount = tabItems.length;\n const idFragments = toIdFragments(tabItems.map(({ value }) => value));\n\n const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({\n itemIndex: activeTabIndex,\n itemCount: tabsCount,\n onSwipeLeft: () => {\n const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);\n const nextValue = tabItems[targetIndex]?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n onSwipeRight: () => {\n const targetIndex = Math.max(0, activeTabIndex - 1);\n const nextValue = tabItems[targetIndex]?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n });\n\n const handleSetActiveTab = (tab: string) => {\n setActiveTab(tab);\n\n if (typeof setTabsValues === 'function') {\n setTabsValues((prev) => ({ ...prev, [group!]: tab }));\n }\n };\n\n const contextValue: TabContextType = {\n activeTab: activeTab ?? firstTabValue ?? '',\n setActiveTab: handleSetActiveTab,\n };\n\n return (\n <TabContext.Provider value={contextValue}>\n <div\n className={cn(\n tabContainerVariant({ variant }),\n fullHeight && 'flex h-full flex-col overflow-hidden',\n className\n )}\n {...props}\n >\n {/* Tab Headers — `TabSelector` is the tablist; wrapping it in another\n one would nest two, leaving the outer without any `tab` child. */}\n <div className={cn('flex shrink-0 gap-3 p-3', headerClassName)}>\n <TabSelector\n selectedChoice={currentTabValue}\n tabs={tabItems.map(({ label, value, disabled }, index) => {\n const isActive = currentTabValue === value;\n const idFragment = idFragments[index];\n\n return (\n <button\n key={value}\n className={cn(\n 'cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none',\n !isActive && 'text-neutral/70'\n )}\n data-active={isActive}\n disabled={disabled}\n onClick={() => !disabled && handleSetActiveTab(value)}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`tabpanel-${idFragment}`}\n id={`tab-${idFragment}`}\n type=\"button\"\n >\n {label ?? value}\n </button>\n );\n })}\n hoverable\n color=\"text\"\n />\n </div>\n {/* Tab Content */}\n {/* Clipper: no overflow; uses clip-path */}\n <div\n className={cn(\n 'relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]',\n fullHeight && 'min-h-0 flex-1'\n )}\n {...containerProps}\n >\n {/* Track — presentational: it carries the sliding transform, while\n the panels it holds are the tablist's controlled regions. */}\n <div\n className={cn(\n 'grid w-full min-w-0',\n fullHeight && 'h-full',\n isDragging\n ? 'transition-none'\n : 'transition-transform duration-300 ease-in-out'\n )}\n style={{\n gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,\n transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`,\n }}\n >\n {tabItems.map(\n ({ value, children, className: itemClassName }, index) => {\n const isActive = index === activeTabIndex;\n const idFragment = idFragments[index];\n\n return (\n <div\n key={value}\n role=\"tabpanel\"\n aria-labelledby={`tab-${idFragment}`}\n id={`tabpanel-${idFragment}`}\n // `inert` takes the whole subtree out of the tab order and\n // the accessibility tree at once. `aria-hidden` alone leaves\n // the buttons and links of an off-screen panel focusable,\n // which strands keyboard users on invisible controls.\n inert={!isActive}\n tabIndex={isActive ? 0 : -1}\n data-active={isActive}\n className={cn(\n 'w-full min-w-0 p-3 opacity-100 transition-opacity duration-300 ease-in-out',\n fullHeight && 'h-full overflow-y-auto',\n !isActive && 'pointer-events-none opacity-0', // prevent offscreen interaction\n itemClassName\n )}\n >\n <div\n className={cn(\n 'flex w-full min-w-0 flex-col items-stretch gap-6',\n fullHeight && 'min-h-full'\n )}\n >\n {children}\n </div>\n </div>\n );\n }\n )}\n </div>\n </div>\n </div>\n </TabContext.Provider>\n );\n};\n\n// Create the compound component\nexport const Tab = Object.assign(TabComponent, {\n Item: TabItem,\n});\n"],"mappings":"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"}
@@ -57,12 +57,12 @@ const indicatorVariant = cva("absolute -z-1 rounded-lg duration-300 ease-in-out
57
57
  const TabSelector = ({ tabs, selectedChoice, onTabClick, color = "primary", hoverable = false, orientation = "horizontal", className }) => {
58
58
  const optionsRefs = useRef([]);
59
59
  const indicatorRef = useRef(null);
60
- const { choiceIndicatorPosition, calculatePosition } = useItemSelector(optionsRefs, {
60
+ const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(optionsRefs, {
61
61
  isHoverable: hoverable,
62
62
  orientation
63
63
  });
64
64
  useEffect(() => {
65
- calculatePosition();
65
+ scheduleMeasurement();
66
66
  }, [selectedChoice]);
67
67
  return /* @__PURE__ */ jsxs("div", {
68
68
  className: cn(tabSelectorVariant({ color }), className),
@@ -1 +1 @@
1
- {"version":3,"file":"TabSelector.mjs","names":[],"sources":["../../../../src/components/TabSelector/TabSelector.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ItemSelectorOrientation,\n useItemSelector,\n} from '@hooks/useItemSelector';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n cloneElement,\n type HTMLAttributes,\n type MouseEvent,\n type ReactElement,\n useEffect,\n useRef,\n} from 'react';\n\nexport type TabSelectorColor =\n | 'primary'\n | 'secondary'\n | 'error'\n | 'neutral'\n | 'light'\n | 'dark'\n | 'text';\n\nconst tabSelectorVariant = cva(\n 'relative z-0 flex size-full flex-row items-center gap-2',\n {\n variants: {\n color: {\n primary: 'border-primary text-primary',\n secondary: 'border-secondary text-secondary',\n error: 'border-error bg-error text-error',\n neutral: 'border-neutral text-neutral',\n light: 'border-white text-white',\n dark: 'border-neutral-800 text-neutral-800',\n text: 'border-text text-text',\n },\n },\n defaultVariants: {\n color: 'primary',\n },\n }\n);\n\nconst indicatorVariant = cva(\n 'absolute -z-1 rounded-lg duration-300 ease-in-out motion-reduce:transition-none',\n {\n variants: {\n color: {\n primary: 'bg-primary/10 aria-selected:text-text',\n secondary: 'bg-secondary/10 aria-selected:text-text',\n error: 'bg-error/10 aria-selected:text-text',\n neutral: 'bg-neutral/10 aria-selected:text-white/10',\n light: 'bg-white/10 aria-selected:text-black',\n dark: 'bg-neutral-800/10 aria-selected:text-white',\n text: 'bg-text/10 aria-selected:text-text-opposite',\n },\n orientation: {\n horizontal: 'top-0 h-full w-auto transition-[left,width]',\n vertical: 'left-0 h-auto w-full transition-[top,height]',\n },\n },\n defaultVariants: {\n orientation: 'horizontal',\n },\n }\n);\n\nexport type TabSelectorItemProps = HTMLAttributes<HTMLElement> & {\n key: string | number;\n};\n\nexport type TabSelectorProps<T extends TabSelectorItemProps> = {\n tabs: ReactElement<T>[];\n selectedChoice: T['key'];\n onTabClick?: (choice: T['key']) => void;\n hoverable?: boolean;\n orientation?: ItemSelectorOrientation;\n} & HTMLAttributes<HTMLElement> &\n Omit<VariantProps<typeof tabSelectorVariant>, 'color'> & {\n color?: TabSelectorColor | `${TabSelectorColor}`;\n };\n\n/**\n *\n * Component that allows the user to select one of the provided choices.\n *\n * Example:\n * ```jsx\n * <TabSelector\n * selectedChoice=\"option1\"\n * onTabClick={(choice) => console.log(choice)}\n * hoverable={true}\n * >\n * <Button key=\"option1\"/>\n * <Button key=\"option2\"/>\n * <Button key=\"option3\"/>\n * </TabSelector>\n * ```\n */\nexport const TabSelector = <T extends TabSelectorItemProps>({\n tabs,\n selectedChoice,\n onTabClick,\n color = 'primary',\n hoverable = false,\n orientation = 'horizontal',\n className,\n}: TabSelectorProps<T>) => {\n const optionsRefs = useRef<HTMLElement[]>([]);\n const indicatorRef = useRef<HTMLDivElement | null>(null);\n const { choiceIndicatorPosition, calculatePosition } = useItemSelector(\n optionsRefs,\n { isHoverable: hoverable, orientation }\n );\n\n useEffect(() => {\n calculatePosition();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedChoice]);\n\n return (\n <div\n className={cn(\n tabSelectorVariant({\n color,\n }),\n className\n )}\n aria-orientation={orientation}\n aria-multiselectable=\"false\"\n role=\"tablist\"\n >\n {tabs.map((Tab, index) => {\n const key = Tab.key!;\n\n const isSelected = selectedChoice === key;\n\n return cloneElement(Tab, {\n key: key ?? index,\n role: 'tab',\n onClick: (e: MouseEvent<HTMLElement>) => {\n Tab.props?.onClick?.(e);\n onTabClick?.(key);\n },\n 'aria-selected': isSelected,\n 'data-active': isSelected as unknown as string,\n tabIndex: isSelected ? 0 : -1,\n ref: (el: HTMLElement) => {\n optionsRefs.current[index] = el!;\n },\n } as unknown as T);\n })}\n {choiceIndicatorPosition && (\n <div\n className={cn(\n indicatorVariant({\n color,\n orientation,\n })\n )}\n style={choiceIndicatorPosition}\n ref={indicatorRef}\n key={`${selectedChoice}${JSON.stringify(tabs.map((tab) => tab.key))}`}\n />\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;AA0BA,MAAM,qBAAqB,IACzB,2DACA;CACE,UAAU,EACR,OAAO;EACL,SAAS;EACT,WAAW;EACX,OAAO;EACP,SAAS;EACT,OAAO;EACP,MAAM;EACN,MAAM;CACR,EACF;CACA,iBAAiB,EACf,OAAO,UACT;AACF,CACF;AAEA,MAAM,mBAAmB,IACvB,mFACA;CACE,UAAU;EACR,OAAO;GACL,SAAS;GACT,WAAW;GACX,OAAO;GACP,SAAS;GACT,OAAO;GACP,MAAM;GACN,MAAM;EACR;EACA,aAAa;GACX,YAAY;GACZ,UAAU;EACZ;CACF;CACA,iBAAiB,EACf,aAAa,aACf;AACF,CACF;;;;;;;;;;;;;;;;;;AAkCA,MAAa,eAA+C,EAC1D,MACA,gBACA,YACA,QAAQ,WACR,YAAY,OACZ,cAAc,cACd,gBACyB;CACzB,MAAM,cAAc,OAAsB,CAAC,CAAC;CAC5C,MAAM,eAAe,OAA8B,IAAI;CACvD,MAAM,EAAE,yBAAyB,sBAAsB,gBACrD,aACA;EAAE,aAAa;EAAW;CAAY,CACxC;CAEA,gBAAgB;EACd,kBAAkB;CAEpB,GAAG,CAAC,cAAc,CAAC;CAEnB,OACE,qBAAC,OAAD;EACE,WAAW,GACT,mBAAmB,EACjB,MACF,CAAC,GACD,SACF;EACA,oBAAkB;EAClB,wBAAqB;EACrB,MAAK;YATP,CAWG,KAAK,KAAK,KAAK,UAAU;GACxB,MAAM,MAAM,IAAI;GAEhB,MAAM,aAAa,mBAAmB;GAEtC,OAAO,aAAa,KAAK;IACvB,KAAK,OAAO;IACZ,MAAM;IACN,UAAU,MAA+B;KACvC,IAAI,OAAO,UAAU,CAAC;KACtB,aAAa,GAAG;IAClB;IACA,iBAAiB;IACjB,eAAe;IACf,UAAU,aAAa,IAAI;IAC3B,MAAM,OAAoB;KACxB,YAAY,QAAQ,SAAS;IAC/B;GACF,CAAiB;EACnB,CAAC,GACA,2BACC,oBAAC,OAAD;GACE,WAAW,GACT,iBAAiB;IACf;IACA;GACF,CAAC,CACH;GACA,OAAO;GACP,KAAK;EAEN,GADM,GAAG,iBAAiB,KAAK,UAAU,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,GACnE,CAEA;;AAET"}
1
+ {"version":3,"file":"TabSelector.mjs","names":[],"sources":["../../../../src/components/TabSelector/TabSelector.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ItemSelectorOrientation,\n useItemSelector,\n} from '@hooks/useItemSelector';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n cloneElement,\n type HTMLAttributes,\n type MouseEvent,\n type ReactElement,\n useEffect,\n useRef,\n} from 'react';\n\nexport type TabSelectorColor =\n | 'primary'\n | 'secondary'\n | 'error'\n | 'neutral'\n | 'light'\n | 'dark'\n | 'text';\n\nconst tabSelectorVariant = cva(\n 'relative z-0 flex size-full flex-row items-center gap-2',\n {\n variants: {\n color: {\n primary: 'border-primary text-primary',\n secondary: 'border-secondary text-secondary',\n error: 'border-error bg-error text-error',\n neutral: 'border-neutral text-neutral',\n light: 'border-white text-white',\n dark: 'border-neutral-800 text-neutral-800',\n text: 'border-text text-text',\n },\n },\n defaultVariants: {\n color: 'primary',\n },\n }\n);\n\nconst indicatorVariant = cva(\n 'absolute -z-1 rounded-lg duration-300 ease-in-out motion-reduce:transition-none',\n {\n variants: {\n color: {\n primary: 'bg-primary/10 aria-selected:text-text',\n secondary: 'bg-secondary/10 aria-selected:text-text',\n error: 'bg-error/10 aria-selected:text-text',\n neutral: 'bg-neutral/10 aria-selected:text-white/10',\n light: 'bg-white/10 aria-selected:text-black',\n dark: 'bg-neutral-800/10 aria-selected:text-white',\n text: 'bg-text/10 aria-selected:text-text-opposite',\n },\n orientation: {\n horizontal: 'top-0 h-full w-auto transition-[left,width]',\n vertical: 'left-0 h-auto w-full transition-[top,height]',\n },\n },\n defaultVariants: {\n orientation: 'horizontal',\n },\n }\n);\n\nexport type TabSelectorItemProps = HTMLAttributes<HTMLElement> & {\n key: string | number;\n};\n\nexport type TabSelectorProps<T extends TabSelectorItemProps> = {\n tabs: ReactElement<T>[];\n selectedChoice: T['key'];\n onTabClick?: (choice: T['key']) => void;\n hoverable?: boolean;\n orientation?: ItemSelectorOrientation;\n} & HTMLAttributes<HTMLElement> &\n Omit<VariantProps<typeof tabSelectorVariant>, 'color'> & {\n color?: TabSelectorColor | `${TabSelectorColor}`;\n };\n\n/**\n *\n * Component that allows the user to select one of the provided choices.\n *\n * Example:\n * ```jsx\n * <TabSelector\n * selectedChoice=\"option1\"\n * onTabClick={(choice) => console.log(choice)}\n * hoverable={true}\n * >\n * <Button key=\"option1\"/>\n * <Button key=\"option2\"/>\n * <Button key=\"option3\"/>\n * </TabSelector>\n * ```\n */\nexport const TabSelector = <T extends TabSelectorItemProps>({\n tabs,\n selectedChoice,\n onTabClick,\n color = 'primary',\n hoverable = false,\n orientation = 'horizontal',\n className,\n}: TabSelectorProps<T>) => {\n const optionsRefs = useRef<HTMLElement[]>([]);\n const indicatorRef = useRef<HTMLDivElement | null>(null);\n const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(\n optionsRefs,\n { isHoverable: hoverable, orientation }\n );\n\n useEffect(() => {\n scheduleMeasurement();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedChoice]);\n\n return (\n <div\n className={cn(\n tabSelectorVariant({\n color,\n }),\n className\n )}\n aria-orientation={orientation}\n aria-multiselectable=\"false\"\n role=\"tablist\"\n >\n {tabs.map((Tab, index) => {\n const key = Tab.key!;\n\n const isSelected = selectedChoice === key;\n\n return cloneElement(Tab, {\n key: key ?? index,\n role: 'tab',\n onClick: (e: MouseEvent<HTMLElement>) => {\n Tab.props?.onClick?.(e);\n onTabClick?.(key);\n },\n 'aria-selected': isSelected,\n 'data-active': isSelected as unknown as string,\n tabIndex: isSelected ? 0 : -1,\n ref: (el: HTMLElement) => {\n optionsRefs.current[index] = el!;\n },\n } as unknown as T);\n })}\n {choiceIndicatorPosition && (\n <div\n className={cn(\n indicatorVariant({\n color,\n orientation,\n })\n )}\n style={choiceIndicatorPosition}\n ref={indicatorRef}\n key={`${selectedChoice}${JSON.stringify(tabs.map((tab) => tab.key))}`}\n />\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;AA0BA,MAAM,qBAAqB,IACzB,2DACA;CACE,UAAU,EACR,OAAO;EACL,SAAS;EACT,WAAW;EACX,OAAO;EACP,SAAS;EACT,OAAO;EACP,MAAM;EACN,MAAM;CACR,EACF;CACA,iBAAiB,EACf,OAAO,UACT;AACF,CACF;AAEA,MAAM,mBAAmB,IACvB,mFACA;CACE,UAAU;EACR,OAAO;GACL,SAAS;GACT,WAAW;GACX,OAAO;GACP,SAAS;GACT,OAAO;GACP,MAAM;GACN,MAAM;EACR;EACA,aAAa;GACX,YAAY;GACZ,UAAU;EACZ;CACF;CACA,iBAAiB,EACf,aAAa,aACf;AACF,CACF;;;;;;;;;;;;;;;;;;AAkCA,MAAa,eAA+C,EAC1D,MACA,gBACA,YACA,QAAQ,WACR,YAAY,OACZ,cAAc,cACd,gBACyB;CACzB,MAAM,cAAc,OAAsB,CAAC,CAAC;CAC5C,MAAM,eAAe,OAA8B,IAAI;CACvD,MAAM,EAAE,yBAAyB,wBAAwB,gBACvD,aACA;EAAE,aAAa;EAAW;CAAY,CACxC;CAEA,gBAAgB;EACd,oBAAoB;CAEtB,GAAG,CAAC,cAAc,CAAC;CAEnB,OACE,qBAAC,OAAD;EACE,WAAW,GACT,mBAAmB,EACjB,MACF,CAAC,GACD,SACF;EACA,oBAAkB;EAClB,wBAAqB;EACrB,MAAK;YATP,CAWG,KAAK,KAAK,KAAK,UAAU;GACxB,MAAM,MAAM,IAAI;GAEhB,MAAM,aAAa,mBAAmB;GAEtC,OAAO,aAAa,KAAK;IACvB,KAAK,OAAO;IACZ,MAAM;IACN,UAAU,MAA+B;KACvC,IAAI,OAAO,UAAU,CAAC;KACtB,aAAa,GAAG;IAClB;IACA,iBAAiB;IACjB,eAAe;IACf,UAAU,aAAa,IAAI;IAC3B,MAAM,OAAoB;KACxB,YAAY,QAAQ,SAAS;IAC/B;GACF,CAAiB;EACnB,CAAC,GACA,2BACC,oBAAC,OAAD;GACE,WAAW,GACT,iBAAiB;IACf;IACA;GACF,CAAC,CACH;GACA,OAAO;GACP,KAAK;EAEN,GADM,GAAG,iBAAiB,KAAK,UAAU,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,GACnE,CAEA;;AAET"}
@@ -13,6 +13,7 @@ const logoRecord = {
13
13
  adonis: dynamicLogo(() => import("./logos/Adonis.mjs"), "AdonisLogo"),
14
14
  angular: dynamicLogo(() => import("./logos/Angular.mjs"), "AngularLogo"),
15
15
  astro: dynamicLogo(() => import("./logos/Astro.mjs"), "AstroLogo"),
16
+ elysia: dynamicLogo(() => import("./logos/Elysia.mjs"), "ElysiaLogo"),
16
17
  express: dynamicLogo(() => import("./logos/Express.mjs"), "ExpressLogo"),
17
18
  fastify: dynamicLogo(() => import("./logos/Fastify.mjs"), "FastifyLogo"),
18
19
  hono: dynamicLogo(() => import("./logos/Hono.mjs"), "HonoLogo"),
@@ -1 +1 @@
1
- {"version":3,"file":"TechLogo.mjs","names":[],"sources":["../../../../src/components/TechLogo/TechLogo.tsx"],"sourcesContent":["import { type FC, type JSX, lazy, Suspense, type SVGProps } from 'react';\nimport { cn } from '../../utils/cn';\nimport type { TechLogoName } from './types';\n\nexport type TechLogoProps = SVGProps<SVGSVGElement> & {\n name: TechLogoName;\n};\n\n/**\n * This ensures React.lazy always receives a valid component.\n */\nconst dynamicLogo = (importFn: () => Promise<any>, exportName: string) =>\n lazy(async () => {\n const module = await importFn();\n const asset = module[exportName];\n\n return { default: asset };\n });\n\nconst logoRecord: Record<TechLogoName, ReturnType<typeof dynamicLogo>> = {\n adonis: dynamicLogo(() => import('./logos/Adonis'), 'AdonisLogo'),\n angular: dynamicLogo(() => import('./logos/Angular'), 'AngularLogo'),\n astro: dynamicLogo(() => import('./logos/Astro'), 'AstroLogo'),\n express: dynamicLogo(() => import('./logos/Express'), 'ExpressLogo'),\n fastify: dynamicLogo(() => import('./logos/Fastify'), 'FastifyLogo'),\n hono: dynamicLogo(() => import('./logos/Hono'), 'HonoLogo'),\n lynx: dynamicLogo(() => import('./logos/Lynx'), 'LynxLogo'),\n nestjs: dynamicLogo(() => import('./logos/NestJS'), 'NestJSLogo'),\n nextjs: dynamicLogo(() => import('./logos/Nextjs'), 'NextJSLogo'),\n node: dynamicLogo(() => import('./logos/Node'), 'NodejsLogo'),\n nuxt: dynamicLogo(() => import('./logos/Nuxt'), 'NuxtLogo'),\n preact: dynamicLogo(() => import('./logos/Preact'), 'PreactLogo'),\n react: dynamicLogo(() => import('./logos/Reactjs'), 'ReactLogo'),\n solid: dynamicLogo(() => import('./logos/Solid'), 'SolidLogo'),\n svelte: dynamicLogo(() => import('./logos/Svelte'), 'SvelteLogo'),\n tanstack: dynamicLogo(() => import('./logos/Tanstack'), 'TanstackLogo'),\n vite: dynamicLogo(() => import('./logos/Vitejs'), 'ViteLogo'),\n vue: dynamicLogo(() => import('./logos/Vuejs'), 'VuejsLogo'),\n lit: dynamicLogo(() => import('./logos/Lit'), 'LitLogo'),\n vanilla: dynamicLogo(() => import('./logos/Vanilla'), 'JavaScriptLogo'),\n anthropic: dynamicLogo(() => import('./logos/Anthropic'), 'AnthropicLogo'),\n claude: dynamicLogo(() => import('./logos/Claude'), 'ClaudeLogo'),\n chatgpt: dynamicLogo(() => import('./logos/ChatGPT'), 'ChatGPTLogo'),\n deepseek: dynamicLogo(() => import('./logos/DeepSeek'), 'DeepSeekLogo'),\n gemini: dynamicLogo(() => import('./logos/Gemini'), 'GeminiLogo'),\n googleai: dynamicLogo(() => import('./logos/GoogleAI'), 'GoogleAILogo'),\n grok: dynamicLogo(() => import('./logos/Grok'), 'GrokLogo'),\n mistral: dynamicLogo(() => import('./logos/Mistral'), 'MistralLogo'),\n ollama: dynamicLogo(() => import('./logos/Ollama'), 'OllamaLogo'),\n openai: dynamicLogo(() => import('./logos/OpenAI'), 'OpenAILogo'),\n perplexity: dynamicLogo(() => import('./logos/Perplexity'), 'PerplexityLogo'),\n github: dynamicLogo(() => import('./logos/GitHub'), 'GitHubLogo'),\n gitlab: dynamicLogo(() => import('./logos/GitLab'), 'GitLabLogo'),\n bitbucket: dynamicLogo(() => import('./logos/Bitbucket'), 'BitbucketLogo'),\n google: dynamicLogo(() => import('./logos/Google'), 'GoogleLogo'),\n linkedin: dynamicLogo(() => import('./logos/LinkedIn'), 'LinkedInLogo'),\n};\n\nexport const TechLogo: FC<TechLogoProps> = ({\n name,\n ...props\n}): JSX.Element => {\n const LazyLogo = logoRecord[name as TechLogoName];\n\n if (!LazyLogo) {\n return <></>;\n }\n\n return (\n <Suspense\n fallback={\n <div className={cn('animate-pulse bg-neutral-200', props.className)} />\n }\n >\n <LazyLogo {...(props as any)} />\n </Suspense>\n );\n};\n"],"mappings":";;;;;;;;AAWA,MAAM,eAAe,UAA8B,eACjD,KAAK,YAAY;CAIf,OAAO,EAAE,UAFK,MADO,SAAS,EACV,CAAC,YAEG;AAC1B,CAAC;AAEH,MAAM,aAAmE;CACvE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,OAAO,kBAAkB,OAAO,sBAAkB,WAAW;CAC7D,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,MAAM,kBAAkB,OAAO,qBAAiB,YAAY;CAC5D,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,OAAO,kBAAkB,OAAO,wBAAoB,WAAW;CAC/D,OAAO,kBAAkB,OAAO,sBAAkB,WAAW;CAC7D,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;CACtE,MAAM,kBAAkB,OAAO,uBAAmB,UAAU;CAC5D,KAAK,kBAAkB,OAAO,sBAAkB,WAAW;CAC3D,KAAK,kBAAkB,OAAO,oBAAgB,SAAS;CACvD,SAAS,kBAAkB,OAAO,wBAAoB,gBAAgB;CACtE,WAAW,kBAAkB,OAAO,0BAAsB,eAAe;CACzE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;CACtE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;CACtE,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,YAAY,kBAAkB,OAAO,2BAAuB,gBAAgB;CAC5E,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,WAAW,kBAAkB,OAAO,0BAAsB,eAAe;CACzE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;AACxE;AAEA,MAAa,YAA+B,EAC1C,MACA,GAAG,YACc;CACjB,MAAM,WAAW,WAAW;CAE5B,IAAI,CAAC,UACH,OAAO,iCAAI;CAGb,OACE,oBAAC,UAAD;EACE,UACE,oBAAC,OAAD,EAAK,WAAW,GAAG,gCAAgC,MAAM,SAAS,EAAI;YAGxE,oBAAC,UAAD,EAAU,GAAK,MAAgB;CACvB;AAEd"}
1
+ {"version":3,"file":"TechLogo.mjs","names":[],"sources":["../../../../src/components/TechLogo/TechLogo.tsx"],"sourcesContent":["import { type FC, type JSX, lazy, Suspense, type SVGProps } from 'react';\nimport { cn } from '../../utils/cn';\nimport type { TechLogoName } from './types';\n\nexport type TechLogoProps = SVGProps<SVGSVGElement> & {\n name: TechLogoName;\n};\n\n/**\n * This ensures React.lazy always receives a valid component.\n */\nconst dynamicLogo = (importFn: () => Promise<any>, exportName: string) =>\n lazy(async () => {\n const module = await importFn();\n const asset = module[exportName];\n\n return { default: asset };\n });\n\nconst logoRecord: Record<TechLogoName, ReturnType<typeof dynamicLogo>> = {\n adonis: dynamicLogo(() => import('./logos/Adonis'), 'AdonisLogo'),\n angular: dynamicLogo(() => import('./logos/Angular'), 'AngularLogo'),\n astro: dynamicLogo(() => import('./logos/Astro'), 'AstroLogo'),\n elysia: dynamicLogo(() => import('./logos/Elysia'), 'ElysiaLogo'),\n express: dynamicLogo(() => import('./logos/Express'), 'ExpressLogo'),\n fastify: dynamicLogo(() => import('./logos/Fastify'), 'FastifyLogo'),\n hono: dynamicLogo(() => import('./logos/Hono'), 'HonoLogo'),\n lynx: dynamicLogo(() => import('./logos/Lynx'), 'LynxLogo'),\n nestjs: dynamicLogo(() => import('./logos/NestJS'), 'NestJSLogo'),\n nextjs: dynamicLogo(() => import('./logos/Nextjs'), 'NextJSLogo'),\n node: dynamicLogo(() => import('./logos/Node'), 'NodejsLogo'),\n nuxt: dynamicLogo(() => import('./logos/Nuxt'), 'NuxtLogo'),\n preact: dynamicLogo(() => import('./logos/Preact'), 'PreactLogo'),\n react: dynamicLogo(() => import('./logos/Reactjs'), 'ReactLogo'),\n solid: dynamicLogo(() => import('./logos/Solid'), 'SolidLogo'),\n svelte: dynamicLogo(() => import('./logos/Svelte'), 'SvelteLogo'),\n tanstack: dynamicLogo(() => import('./logos/Tanstack'), 'TanstackLogo'),\n vite: dynamicLogo(() => import('./logos/Vitejs'), 'ViteLogo'),\n vue: dynamicLogo(() => import('./logos/Vuejs'), 'VuejsLogo'),\n lit: dynamicLogo(() => import('./logos/Lit'), 'LitLogo'),\n vanilla: dynamicLogo(() => import('./logos/Vanilla'), 'JavaScriptLogo'),\n anthropic: dynamicLogo(() => import('./logos/Anthropic'), 'AnthropicLogo'),\n claude: dynamicLogo(() => import('./logos/Claude'), 'ClaudeLogo'),\n chatgpt: dynamicLogo(() => import('./logos/ChatGPT'), 'ChatGPTLogo'),\n deepseek: dynamicLogo(() => import('./logos/DeepSeek'), 'DeepSeekLogo'),\n gemini: dynamicLogo(() => import('./logos/Gemini'), 'GeminiLogo'),\n googleai: dynamicLogo(() => import('./logos/GoogleAI'), 'GoogleAILogo'),\n grok: dynamicLogo(() => import('./logos/Grok'), 'GrokLogo'),\n mistral: dynamicLogo(() => import('./logos/Mistral'), 'MistralLogo'),\n ollama: dynamicLogo(() => import('./logos/Ollama'), 'OllamaLogo'),\n openai: dynamicLogo(() => import('./logos/OpenAI'), 'OpenAILogo'),\n perplexity: dynamicLogo(() => import('./logos/Perplexity'), 'PerplexityLogo'),\n github: dynamicLogo(() => import('./logos/GitHub'), 'GitHubLogo'),\n gitlab: dynamicLogo(() => import('./logos/GitLab'), 'GitLabLogo'),\n bitbucket: dynamicLogo(() => import('./logos/Bitbucket'), 'BitbucketLogo'),\n google: dynamicLogo(() => import('./logos/Google'), 'GoogleLogo'),\n linkedin: dynamicLogo(() => import('./logos/LinkedIn'), 'LinkedInLogo'),\n};\n\nexport const TechLogo: FC<TechLogoProps> = ({\n name,\n ...props\n}): JSX.Element => {\n const LazyLogo = logoRecord[name as TechLogoName];\n\n if (!LazyLogo) {\n return <></>;\n }\n\n return (\n <Suspense\n fallback={\n <div className={cn('animate-pulse bg-neutral-200', props.className)} />\n }\n >\n <LazyLogo {...(props as any)} />\n </Suspense>\n );\n};\n"],"mappings":";;;;;;;;AAWA,MAAM,eAAe,UAA8B,eACjD,KAAK,YAAY;CAIf,OAAO,EAAE,UAFK,MADO,SAAS,EACV,CAAC,YAEG;AAC1B,CAAC;AAEH,MAAM,aAAmE;CACvE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,OAAO,kBAAkB,OAAO,sBAAkB,WAAW;CAC7D,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,MAAM,kBAAkB,OAAO,qBAAiB,YAAY;CAC5D,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,OAAO,kBAAkB,OAAO,wBAAoB,WAAW;CAC/D,OAAO,kBAAkB,OAAO,sBAAkB,WAAW;CAC7D,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;CACtE,MAAM,kBAAkB,OAAO,uBAAmB,UAAU;CAC5D,KAAK,kBAAkB,OAAO,sBAAkB,WAAW;CAC3D,KAAK,kBAAkB,OAAO,oBAAgB,SAAS;CACvD,SAAS,kBAAkB,OAAO,wBAAoB,gBAAgB;CACtE,WAAW,kBAAkB,OAAO,0BAAsB,eAAe;CACzE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;CACtE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;CACtE,MAAM,kBAAkB,OAAO,qBAAiB,UAAU;CAC1D,SAAS,kBAAkB,OAAO,wBAAoB,aAAa;CACnE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,YAAY,kBAAkB,OAAO,2BAAuB,gBAAgB;CAC5E,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,WAAW,kBAAkB,OAAO,0BAAsB,eAAe;CACzE,QAAQ,kBAAkB,OAAO,uBAAmB,YAAY;CAChE,UAAU,kBAAkB,OAAO,yBAAqB,cAAc;AACxE;AAEA,MAAa,YAA+B,EAC1C,MACA,GAAG,YACc;CACjB,MAAM,WAAW,WAAW;CAE5B,IAAI,CAAC,UACH,OAAO,iCAAI;CAGb,OACE,oBAAC,UAAD;EACE,UACE,oBAAC,OAAD,EAAK,WAAW,GAAG,gCAAgC,MAAM,SAAS,EAAI;YAGxE,oBAAC,UAAD,EAAU,GAAK,MAAgB;CACvB;AAEd"}