@intlayer/design-system 9.3.3 → 9.4.0-canary.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 (79) hide show
  1. package/README.md +1 -1
  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/components/Accordion/Accordion.mjs +2 -2
  5. package/dist/esm/components/Accordion/Accordion.mjs.map +1 -1
  6. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +1 -1
  7. package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
  8. package/dist/esm/components/Form/elements/OTPElement.mjs +4 -4
  9. package/dist/esm/components/Form/elements/OTPElement.mjs.map +1 -1
  10. package/dist/esm/components/Link/Link.mjs +10 -5
  11. package/dist/esm/components/Link/Link.mjs.map +1 -1
  12. package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
  13. package/dist/esm/components/Modal/Modal.mjs +2 -2
  14. package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
  15. package/dist/esm/components/Pagination/Pagination.mjs +4 -4
  16. package/dist/esm/components/Pagination/Pagination.mjs.map +1 -1
  17. package/dist/esm/components/Pattern/GridDistortionPattern.mjs +3 -2
  18. package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
  19. package/dist/esm/components/Popover/dynamic.mjs +68 -24
  20. package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
  21. package/dist/esm/components/RightDrawer/RightDrawer.mjs +5 -4
  22. package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
  23. package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs +2 -2
  24. package/dist/esm/components/SwitchSelector/useSwitchSelector.mjs.map +1 -1
  25. package/dist/esm/components/Tab/Tab.mjs +1 -1
  26. package/dist/esm/components/TabSelector/TabSelector.mjs +2 -2
  27. package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
  28. package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs +10 -18
  29. package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs.map +1 -1
  30. package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs +14 -13
  31. package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs.map +1 -1
  32. package/dist/esm/hooks/index.mjs +9 -9
  33. package/dist/esm/hooks/useItemSelector.mjs +12 -9
  34. package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
  35. package/dist/esm/hooks/useScrollY.mjs +64 -45
  36. package/dist/esm/hooks/useScrollY.mjs.map +1 -1
  37. package/dist/esm/hooks/useStickToBottom.mjs +69 -0
  38. package/dist/esm/hooks/useStickToBottom.mjs.map +1 -0
  39. package/dist/esm/providers/ThemeProvider.mjs +143 -0
  40. package/dist/esm/providers/ThemeProvider.mjs.map +1 -0
  41. package/dist/esm/providers/index.mjs +2 -1
  42. package/dist/esm/routes.mjs +2 -1
  43. package/dist/esm/routes.mjs.map +1 -1
  44. package/dist/esm/utils/index.mjs +3 -1
  45. package/dist/esm/utils/observeScrollExtent.mjs +39 -0
  46. package/dist/esm/utils/observeScrollExtent.mjs.map +1 -0
  47. package/dist/esm/utils/scheduleFrameTask.mjs +44 -0
  48. package/dist/esm/utils/scheduleFrameTask.mjs.map +1 -0
  49. package/dist/types/api/hooks/analytics.d.ts +5 -3
  50. package/dist/types/api/hooks/analytics.d.ts.map +1 -1
  51. package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
  52. package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts +1 -1
  53. package/dist/types/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.d.ts.map +1 -1
  54. package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts +1 -1
  55. package/dist/types/components/ThemeSwitcherDropDown/MobileThemeSwitcher.d.ts.map +1 -1
  56. package/dist/types/components/ThemeSwitcherDropDown/types.d.ts +3 -1
  57. package/dist/types/components/ThemeSwitcherDropDown/types.d.ts.map +1 -1
  58. package/dist/types/hooks/index.d.ts +2 -2
  59. package/dist/types/hooks/useItemSelector.d.ts +2 -0
  60. package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
  61. package/dist/types/hooks/useScrollY.d.ts +10 -0
  62. package/dist/types/hooks/useScrollY.d.ts.map +1 -1
  63. package/dist/types/hooks/useStickToBottom.d.ts +41 -0
  64. package/dist/types/hooks/useStickToBottom.d.ts.map +1 -0
  65. package/dist/types/providers/ThemeProvider.d.ts +41 -0
  66. package/dist/types/providers/ThemeProvider.d.ts.map +1 -0
  67. package/dist/types/providers/index.d.ts +2 -1
  68. package/dist/types/routes.d.ts +2 -1
  69. package/dist/types/routes.d.ts.map +1 -1
  70. package/dist/types/utils/index.d.ts +3 -1
  71. package/dist/types/utils/observeScrollExtent.d.ts +22 -0
  72. package/dist/types/utils/observeScrollExtent.d.ts.map +1 -0
  73. package/dist/types/utils/scheduleFrameTask.d.ts +25 -0
  74. package/dist/types/utils/scheduleFrameTask.d.ts.map +1 -0
  75. package/package.json +18 -18
  76. package/dist/esm/hooks/useIsDarkMode.mjs +0 -17
  77. package/dist/esm/hooks/useIsDarkMode.mjs.map +0 -1
  78. package/dist/types/hooks/useIsDarkMode.d.ts +0 -5
  79. package/dist/types/hooks/useIsDarkMode.d.ts.map +0 -1
package/README.md CHANGED
@@ -278,4 +278,4 @@ Contribute on [GitHub](https://github.com/aymericzip/intlayer), [GitLab](https:/
278
278
 
279
279
  If you like Intlayer, give us a ⭐ on GitHub. It helps others discover the project! [See why GitHub Stars matter](https://github.com/aymericzip/intlayer/blob/main/CONTRIBUTING.md#why-github-stars-matter-).
280
280
 
281
- [![Star History Chart](https://api.star-history.com/svg?repos=aymericzip/intlayer&type=Date)](https://star-history.com/#aymericzip/intlayer&Date)
281
+ [![Star History Chart](https://star-history.dera.page/svg?repos=aymericzip/intlayer&type=Date)](https://star-history.dera.page/#aymericzip/intlayer&Date)
@@ -6,21 +6,22 @@ import { useAppQuery } from "./utils.mjs";
6
6
  //#region src/api/hooks/analytics.ts
7
7
  /**
8
8
  * Fetches the audience report for the active project: distinct visitors
9
- * (today / 7d / window), page views, the daily evolution series, and locale +
9
+ * (today / 7d / window), page views, the evolution series, and locale +
10
10
  * country breakdowns.
11
11
  *
12
- * @param days - Rolling window size in days (default 30).
12
+ * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,
13
+ * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).
13
14
  * @param options - Extra react-query options.
14
15
  */
15
- const useGetAnalyticsAudience = (days = 30, options) => {
16
+ const useGetAnalyticsAudience = (range = 30, options) => {
16
17
  const analyticsAPI = useAnalyticsAPI();
17
18
  return useAppQuery({
18
19
  queryKey: [
19
20
  "analytics",
20
21
  "audience",
21
- days
22
+ range
22
23
  ],
23
- queryFn: ({ signal }) => analyticsAPI.getAudience(days, { signal }),
24
+ queryFn: ({ signal }) => analyticsAPI.getAudience(range, { signal }),
24
25
  requireUser: true,
25
26
  requireOrganization: true,
26
27
  requireProject: true,
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.mjs","names":[],"sources":["../../../../src/api/hooks/analytics.ts"],"sourcesContent":["'use client';\n\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport { useAnalyticsAPI } from '../useIntlayerAPI';\nimport { useAppQuery } from './utils';\n\n/**\n * Fetches the audience report for the active project: distinct visitors\n * (today / 7d / window), page views, the daily evolution series, and locale +\n * country breakdowns.\n *\n * @param days - Rolling window size in days (default 30).\n * @param options - Extra react-query options.\n */\nexport const useGetAnalyticsAudience = (\n days = 30,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'audience', days],\n queryFn: ({ signal }) => analyticsAPI.getAudience(days, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches per-content exposure totals — \"which content is actually shown\".\n *\n * @param options - Extra react-query options.\n */\nexport const useGetContentStats = (options?: Partial<UseQueryOptions>) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'content-stats'],\n queryFn: ({ signal }) => analyticsAPI.getContentStats({ signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches A/B experiment results for the active project.\n *\n * @param experimentKey - The experiment to evaluate.\n * @param options - Extra react-query options.\n */\nexport const useGetExperimentResults = (\n experimentKey: string,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'experiment', experimentKey],\n queryFn: ({ signal }) =>\n analyticsAPI.getExperimentResults(experimentKey, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n enabled: Boolean(experimentKey),\n ...options,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAa,2BACX,OAAO,IACP,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAY;EAAI;EACxC,UAAU,EAAE,aAAa,aAAa,YAAY,MAAM,EAAE,OAAO,CAAC;EAClE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;AAOA,MAAa,sBAAsB,YAAuC;CACxE,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU,CAAC,aAAa,eAAe;EACvC,UAAU,EAAE,aAAa,aAAa,gBAAgB,EAAE,OAAO,CAAC;EAChE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;;AAQA,MAAa,2BACX,eACA,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAc;EAAa;EACnD,UAAU,EAAE,aACV,aAAa,qBAAqB,eAAe,EAAE,OAAO,CAAC;EAC7D,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,SAAS,QAAQ,aAAa;EAC9B,GAAG;CACL,CAAC;AACH"}
1
+ {"version":3,"file":"analytics.mjs","names":[],"sources":["../../../../src/api/hooks/analytics.ts"],"sourcesContent":["'use client';\n\nimport type { AudienceRange } from '@intlayer/backend';\nimport type { UseQueryOptions } from '@tanstack/react-query';\nimport { useAnalyticsAPI } from '../useIntlayerAPI';\nimport { useAppQuery } from './utils';\n\n/**\n * Fetches the audience report for the active project: distinct visitors\n * (today / 7d / window), page views, the evolution series, and locale +\n * country breakdowns.\n *\n * @param range - Rolling window, either a named range (`1h`, `24h`, `7d`,\n * `30d`, `90d`, `6mo`, `1y`, `3y`) or a number of days (default 30).\n * @param options - Extra react-query options.\n */\nexport const useGetAnalyticsAudience = (\n range: AudienceRange | number = 30,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'audience', range],\n queryFn: ({ signal }) => analyticsAPI.getAudience(range, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches per-content exposure totals — \"which content is actually shown\".\n *\n * @param options - Extra react-query options.\n */\nexport const useGetContentStats = (options?: Partial<UseQueryOptions>) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'content-stats'],\n queryFn: ({ signal }) => analyticsAPI.getContentStats({ signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n ...options,\n });\n};\n\n/**\n * Fetches A/B experiment results for the active project.\n *\n * @param experimentKey - The experiment to evaluate.\n * @param options - Extra react-query options.\n */\nexport const useGetExperimentResults = (\n experimentKey: string,\n options?: Partial<UseQueryOptions>\n) => {\n const analyticsAPI = useAnalyticsAPI();\n\n return useAppQuery({\n queryKey: ['analytics', 'experiment', experimentKey],\n queryFn: ({ signal }) =>\n analyticsAPI.getExperimentResults(experimentKey, { signal }),\n requireUser: true,\n requireOrganization: true,\n requireProject: true,\n enabled: Boolean(experimentKey),\n ...options,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,MAAa,2BACX,QAAgC,IAChC,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAY;EAAK;EACzC,UAAU,EAAE,aAAa,aAAa,YAAY,OAAO,EAAE,OAAO,CAAC;EACnE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;AAOA,MAAa,sBAAsB,YAAuC;CACxE,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU,CAAC,aAAa,eAAe;EACvC,UAAU,EAAE,aAAa,aAAa,gBAAgB,EAAE,OAAO,CAAC;EAChE,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,GAAG;CACL,CAAC;AACH;;;;;;;AAQA,MAAa,2BACX,eACA,YACG;CACH,MAAM,eAAe,gBAAgB;CAErC,OAAO,YAAY;EACjB,UAAU;GAAC;GAAa;GAAc;EAAa;EACnD,UAAU,EAAE,aACV,aAAa,qBAAqB,eAAe,EAAE,OAAO,CAAC;EAC7D,aAAa;EACb,qBAAqB;EACrB,gBAAgB;EAChB,SAAS,QAAQ,aAAa;EAC9B,GAAG;CACL,CAAC;AACH"}
@@ -45,7 +45,7 @@ import { ChevronDown } from "lucide-react";
45
45
  * <p>Controlled content</p>
46
46
  * </Accordion>
47
47
  */
48
- const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle, onClick, disabled = false, contentClassName, headerClassName, contentId, "aria-label": ariaLabel, ...props }) => {
48
+ const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle, onClick, disabled = false, contentClassName, headerClassName, contentId, "aria-label": ariaLabel, className, ...props }) => {
49
49
  const isControlled = isOpen !== void 0;
50
50
  const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);
51
51
  const id = useId();
@@ -66,7 +66,7 @@ const Accordion = ({ children, header, isOpen, defaultIsOpen = false, onToggle,
66
66
  }
67
67
  };
68
68
  return /* @__PURE__ */ jsxs("div", {
69
- className: "w-full",
69
+ className: cn("w-full", className),
70
70
  children: [/* @__PURE__ */ jsx(Button, {
71
71
  variant: "hoverable",
72
72
  color: "text",
@@ -1 +1 @@
1
- {"version":3,"file":"Accordion.mjs","names":[],"sources":["../../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronDown } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n useId,\n useState,\n} from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\nexport interface AccordionProps\n extends Omit<ButtonProps, 'children' | 'onToggle'> {\n /** The content displayed in the accordion header */\n header: ReactNode;\n /** The collapsible content inside the accordion */\n children: ReactNode;\n /** Controls whether the accordion is open (controlled mode) */\n isOpen?: boolean;\n /** Default open state (uncontrolled mode) */\n defaultIsOpen?: boolean;\n /** Called when the accordion state changes */\n onToggle?: (isOpen: boolean) => void;\n /** Disable the accordion interaction */\n disabled?: boolean;\n /** Custom class for the content container */\n contentClassName?: string;\n /** Custom class for the header container */\n headerClassName?: string;\n /** Accessible label for screen readers */\n 'aria-label'?: string;\n /** ID for the accordion content (for aria-controls) */\n contentId?: string;\n}\n\n/**\n * Accordion component that allows the user to expand and collapse content.\n * It provides a header with a chevron icon that controls the visibility of the content.\n *\n * Features:\n * - Supports both controlled and uncontrolled modes\n * - Accessible with proper ARIA attributes\n * - Keyboard navigation support\n * - Smooth animations for expand/collapse\n * - Customizable styling\n *\n * @param header - The content of the header.\n * @param children - The content to be expanded and collapsed.\n * @param isOpen - Controlled state for whether the content is expanded.\n * @param defaultIsOpen - Default open state for uncontrolled mode.\n * @param onToggle - Callback when the accordion state changes.\n * @param disabled - Whether the accordion is disabled.\n * @param contentClassName - Custom class for the content container.\n * @param headerClassName - Custom class for the header.\n * @param contentId - ID for the content (used for aria-controls).\n *\n * @example\n * // Uncontrolled mode\n * <Accordion header=\"Accordion Header\" defaultIsOpen={true}>\n * <p>Accordion content</p>\n * </Accordion>\n *\n * @example\n * // Controlled mode\n * <Accordion\n * header=\"Controlled Accordion\"\n * isOpen={isOpen}\n * onToggle={setIsOpen}\n * >\n * <p>Controlled content</p>\n * </Accordion>\n */\nexport const Accordion: FC<AccordionProps> = ({\n children,\n header,\n isOpen,\n defaultIsOpen = false,\n onToggle,\n onClick,\n disabled = false,\n contentClassName,\n headerClassName,\n contentId,\n 'aria-label': ariaLabel,\n ...props\n}) => {\n // Determine if we're in controlled or uncontrolled mode\n const isControlled = isOpen !== undefined;\n const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);\n const id = useId();\n\n // Use controlled value if provided, otherwise use internal state\n const isExpandedState = isControlled ? isOpen : internalIsOpen;\n const isHidden = !isExpandedState;\n\n // Generate unique ID for content if not provided\n const generatedContentId = contentId ?? `${id}-accordion-content`;\n\n const handleToggle = (e: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const newIsOpen = !isExpandedState;\n\n // Update internal state if uncontrolled\n if (!isControlled) {\n setInternalIsOpen(newIsOpen);\n }\n\n // Call external handlers\n onToggle?.(newIsOpen);\n onClick?.(e);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Enter and Space should toggle the accordion\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle(e as any);\n }\n };\n\n return (\n <div className=\"w-full\">\n <Button\n variant=\"hoverable\"\n color=\"text\"\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n isFullWidth\n className={cn(\n 'flex items-center justify-between gap-2',\n headerClassName\n )}\n IconRight={ChevronDown}\n iconClassName={cn(\n 'transform transition-transform duration-500 ease-in-out',\n isExpandedState ? 'rotate-0' : '-rotate-180'\n )}\n aria-expanded={isExpandedState}\n aria-controls={generatedContentId}\n aria-label={ariaLabel}\n role=\"button\"\n {...props}\n >\n {header}\n </Button>\n\n <MaxHeightSmoother\n id={generatedContentId}\n tabIndex={isHidden ? -1 : undefined}\n isHidden={isHidden}\n className={contentClassName}\n role=\"region\"\n aria-labelledby={generatedContentId}\n >\n {children}\n </MaxHeightSmoother>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,MAAa,aAAiC,EAC5C,UACA,QACA,QACA,gBAAgB,OAChB,UACA,SACA,WAAW,OACX,kBACA,iBACA,WACA,cAAc,WACd,GAAG,YACC;CAEJ,MAAM,eAAe,WAAW;CAChC,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,aAAa;CAClE,MAAM,KAAK,MAAM;CAGjB,MAAM,kBAAkB,eAAe,SAAS;CAChD,MAAM,WAAW,CAAC;CAGlB,MAAM,qBAAqB,aAAa,GAAG,GAAG;CAE9C,MAAM,gBAAgB,MAAqC;EACzD,IAAI,UAAU;EAEd,MAAM,YAAY,CAAC;EAGnB,IAAI,CAAC,cACH,kBAAkB,SAAS;EAI7B,WAAW,SAAS;EACpB,UAAU,CAAC;CACb;CAEA,MAAM,iBAAiB,MAAwC;EAE7D,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;GACtC,EAAE,eAAe;GACjB,aAAa,CAAQ;EACvB;CACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,QAAD;GACE,SAAQ;GACR,OAAM;GACN,SAAS;GACT,WAAW;GACD;GACV;GACA,WAAW,GACT,2CACA,eACF;GACA,WAAW;GACX,eAAe,GACb,2DACA,kBAAkB,aAAa,aACjC;GACA,iBAAe;GACf,iBAAe;GACf,cAAY;GACZ,MAAK;GACL,GAAI;aAEH;EACK,IAER,oBAAC,mBAAD;GACE,IAAI;GACJ,UAAU,WAAW,KAAK;GAChB;GACV,WAAW;GACX,MAAK;GACL,mBAAiB;GAEhB;EACgB,EAChB;;AAET"}
1
+ {"version":3,"file":"Accordion.mjs","names":[],"sources":["../../../../src/components/Accordion/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { ChevronDown } from 'lucide-react';\nimport {\n type FC,\n type KeyboardEvent,\n type MouseEvent,\n type ReactNode,\n useId,\n useState,\n} from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\nexport interface AccordionProps\n extends Omit<ButtonProps, 'children' | 'onToggle'> {\n /** The content displayed in the accordion header */\n header: ReactNode;\n /** The collapsible content inside the accordion */\n children: ReactNode;\n /** Controls whether the accordion is open (controlled mode) */\n isOpen?: boolean;\n /** Default open state (uncontrolled mode) */\n defaultIsOpen?: boolean;\n /** Called when the accordion state changes */\n onToggle?: (isOpen: boolean) => void;\n /** Disable the accordion interaction */\n disabled?: boolean;\n /** Custom class for the content container */\n contentClassName?: string;\n /** Custom class for the header container */\n headerClassName?: string;\n /** Accessible label for screen readers */\n 'aria-label'?: string;\n /** ID for the accordion content (for aria-controls) */\n contentId?: string;\n}\n\n/**\n * Accordion component that allows the user to expand and collapse content.\n * It provides a header with a chevron icon that controls the visibility of the content.\n *\n * Features:\n * - Supports both controlled and uncontrolled modes\n * - Accessible with proper ARIA attributes\n * - Keyboard navigation support\n * - Smooth animations for expand/collapse\n * - Customizable styling\n *\n * @param header - The content of the header.\n * @param children - The content to be expanded and collapsed.\n * @param isOpen - Controlled state for whether the content is expanded.\n * @param defaultIsOpen - Default open state for uncontrolled mode.\n * @param onToggle - Callback when the accordion state changes.\n * @param disabled - Whether the accordion is disabled.\n * @param contentClassName - Custom class for the content container.\n * @param headerClassName - Custom class for the header.\n * @param contentId - ID for the content (used for aria-controls).\n *\n * @example\n * // Uncontrolled mode\n * <Accordion header=\"Accordion Header\" defaultIsOpen={true}>\n * <p>Accordion content</p>\n * </Accordion>\n *\n * @example\n * // Controlled mode\n * <Accordion\n * header=\"Controlled Accordion\"\n * isOpen={isOpen}\n * onToggle={setIsOpen}\n * >\n * <p>Controlled content</p>\n * </Accordion>\n */\nexport const Accordion: FC<AccordionProps> = ({\n children,\n header,\n isOpen,\n defaultIsOpen = false,\n onToggle,\n onClick,\n disabled = false,\n contentClassName,\n headerClassName,\n contentId,\n 'aria-label': ariaLabel,\n className,\n ...props\n}) => {\n // Determine if we're in controlled or uncontrolled mode\n const isControlled = isOpen !== undefined;\n const [internalIsOpen, setInternalIsOpen] = useState(defaultIsOpen);\n const id = useId();\n\n // Use controlled value if provided, otherwise use internal state\n const isExpandedState = isControlled ? isOpen : internalIsOpen;\n const isHidden = !isExpandedState;\n\n // Generate unique ID for content if not provided\n const generatedContentId = contentId ?? `${id}-accordion-content`;\n\n const handleToggle = (e: MouseEvent<HTMLButtonElement>) => {\n if (disabled) return;\n\n const newIsOpen = !isExpandedState;\n\n // Update internal state if uncontrolled\n if (!isControlled) {\n setInternalIsOpen(newIsOpen);\n }\n\n // Call external handlers\n onToggle?.(newIsOpen);\n onClick?.(e);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n // Enter and Space should toggle the accordion\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle(e as any);\n }\n };\n\n return (\n <div className={cn('w-full', className)}>\n <Button\n variant=\"hoverable\"\n color=\"text\"\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n disabled={disabled}\n isFullWidth\n className={cn(\n 'flex items-center justify-between gap-2',\n headerClassName\n )}\n IconRight={ChevronDown}\n iconClassName={cn(\n 'transform transition-transform duration-500 ease-in-out',\n isExpandedState ? 'rotate-0' : '-rotate-180'\n )}\n aria-expanded={isExpandedState}\n aria-controls={generatedContentId}\n aria-label={ariaLabel}\n role=\"button\"\n {...props}\n >\n {header}\n </Button>\n\n <MaxHeightSmoother\n id={generatedContentId}\n tabIndex={isHidden ? -1 : undefined}\n isHidden={isHidden}\n className={contentClassName}\n role=\"region\"\n aria-labelledby={generatedContentId}\n >\n {children}\n </MaxHeightSmoother>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,MAAa,aAAiC,EAC5C,UACA,QACA,QACA,gBAAgB,OAChB,UACA,SACA,WAAW,OACX,kBACA,iBACA,WACA,cAAc,WACd,WACA,GAAG,YACC;CAEJ,MAAM,eAAe,WAAW;CAChC,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,aAAa;CAClE,MAAM,KAAK,MAAM;CAGjB,MAAM,kBAAkB,eAAe,SAAS;CAChD,MAAM,WAAW,CAAC;CAGlB,MAAM,qBAAqB,aAAa,GAAG,GAAG;CAE9C,MAAM,gBAAgB,MAAqC;EACzD,IAAI,UAAU;EAEd,MAAM,YAAY,CAAC;EAGnB,IAAI,CAAC,cACH,kBAAkB,SAAS;EAI7B,WAAW,SAAS;EACpB,UAAU,CAAC;CACb;CAEA,MAAM,iBAAiB,MAAwC;EAE7D,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;GACtC,EAAE,eAAe;GACjB,aAAa,CAAQ;EACvB;CACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,UAAU,SAAS;YAAtC,CACE,oBAAC,QAAD;GACE,SAAQ;GACR,OAAM;GACN,SAAS;GACT,WAAW;GACD;GACV;GACA,WAAW,GACT,2CACA,eACF;GACA,WAAW;GACX,eAAe,GACb,2DACA,kBAAkB,aAAa,aACjC;GACA,iBAAe;GACf,iBAAe;GACf,cAAY;GACZ,MAAK;GACL,GAAI;aAEH;EACK,IAER,oBAAC,mBAAD;GACE,IAAI;GACJ,UAAU,WAAW,KAAK;GAChB;GACV,WAAW;GACX,MAAK;GACL,mBAAiB;GAEhB;EACgB,EAChB;;AAET"}
@@ -75,7 +75,7 @@ const ExpandCollapse = ({ isRollable = true, minHeight = DEFAULT_MIN_HEIGHT, chi
75
75
  return;
76
76
  }
77
77
  const observer = new ResizeObserver(([entry]) => {
78
- if (entry) setCodeContainerHeight(entry.target.clientHeight);
78
+ if (entry) setCodeContainerHeight(entry.contentRect.height);
79
79
  });
80
80
  observer.observe(container);
81
81
  resizeObserverRef.current = observer;
@@ -1 +1 @@
1
- {"version":3,"file":"ExpandCollapse.mjs","names":[],"sources":["../../../../src/components/ExpandCollapse/ExpandCollapse.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the ExpandCollapse component\n */\nexport type ExpandCollapseProps = {\n /** Whether the component should provide expand/collapse functionality. If false, renders children directly */\n isRollable?: boolean;\n /** Minimum height in pixels before showing the expand/collapse toggle */\n minHeight?: number;\n /** Content that may overflow and trigger the expand/collapse behavior */\n children: ReactNode;\n /** Additional CSS classes for styling customization */\n className?: string;\n};\n\n/** Default minimum height threshold for triggering expand/collapse behavior */\nconst DEFAULT_MIN_HEIGHT = 700;\n\n/**\n * ExpandCollapse Component\n *\n * A smart content container that automatically provides expand/collapse functionality\n * when content exceeds a specified height threshold. Features smooth animations,\n * internationalized toggle text, and intelligent height detection.\n *\n * @example\n * ```tsx\n * <ExpandCollapse minHeight={300}>\n * <div>Very long content that will be collapsed...</div>\n * </ExpandCollapse>\n * ```\n *\n * ## Key Features\n * - **Smart Detection**: Automatically detects when content exceeds height threshold\n * - **Smooth Animations**: Uses CSS transitions for smooth expand/collapse effects\n * - **Internationalization**: Toggle text supports multiple languages via Intlayer\n * - **Customizable Height**: Configurable minimum height threshold\n * - **Performance Optimized**: Only applies collapse behavior when necessary\n * - **Accessibility**: Proper ARIA attributes and keyboard support\n *\n * ## Behavior Logic\n * 1. **Measurement Phase**: Measures actual content height on mount\n * 2. **Comparison**: Compares content height against minHeight threshold\n * 3. **Conditional Rendering**:\n * - If content ≤ minHeight: Renders normally without collapse functionality\n * - If content > minHeight: Enables expand/collapse with toggle button\n * 4. **State Management**: Manages collapsed/expanded state with smooth transitions\n *\n * ## When to Use\n * - Long-form content (articles, documentation, code blocks)\n * - Lists or tables that may grow beyond comfortable viewing height\n * - User-generated content with unpredictable length\n * - FAQ sections or expandable content cards\n * - Code examples or JSON data display\n *\n * ## Accessibility Features\n * - **Keyboard Navigation**: Toggle button is focusable and keyboard accessible\n * - **Screen Reader Support**: Proper ARIA labels and state announcements\n * - **Visual Indicators**: Clear visual cues for collapsed/expanded states\n * - **Smooth Animations**: Respects user preferences for reduced motion\n *\n * ## Internationalization\n * - Supports multiple languages through Intlayer integration\n * - Toggle text automatically adapts to current locale\n * - Includes translations for \"Show all\" and \"Show less\" states\n *\n * @param props - ExpandCollapseProps\n * @returns React functional component\n */\nexport const ExpandCollapse: FC<ExpandCollapseProps> = ({\n isRollable = true,\n minHeight = DEFAULT_MIN_HEIGHT,\n children,\n className,\n}) => {\n const [codeContainerHeight, setCodeContainerHeight] = useState(0);\n const [isCollapsed, setIsCollapsed] = useState(true);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const { expandCollapseContent } = useIntlayer('expand-collapse');\n\n const isTooBig = codeContainerHeight > minHeight;\n\n /*\n * A `ResizeObserver` is handed the size the browser has already computed,\n * where reading `clientHeight` from an effect forces it to lay the page out\n * again on the spot. That distinction matters here because a documentation\n * page mounts one of these per code block: dozens of synchronous reflows over\n * a very large document. The observer also reports later size changes, which\n * is what the window `resize` listener used to cover.\n *\n * It attaches through a callback ref rather than an effect because crossing\n * the `minHeight` threshold re-parents the container into `MaxHeightSmoother`\n * — a different element, which an effect bound to mount would never observe.\n */\n const observeContainer = useCallback((container: HTMLDivElement | null) => {\n resizeObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n\n if (!container) return;\n\n if (typeof ResizeObserver === 'undefined') {\n setCodeContainerHeight(container.clientHeight);\n return;\n }\n\n const observer = new ResizeObserver(([entry]) => {\n // Read inside the callback, where the layout is already up to date.\n if (entry) setCodeContainerHeight(entry.target.clientHeight);\n });\n\n observer.observe(container);\n resizeObserverRef.current = observer;\n }, []);\n\n useEffect(() => () => resizeObserverRef.current?.disconnect(), []);\n\n if (!isRollable) {\n return children;\n }\n\n if (!isTooBig) {\n return (\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n );\n }\n\n return (\n <MaxHeightSmoother\n isHidden={isCollapsed}\n minHeight={minHeight}\n className=\"w-full overflow-x-auto overflow-y-hidden\"\n >\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n <button\n className={cn(\n 'absolute right-0 bottom-0 flex w-full cursor-pointer items-center justify-center rounded-t-2xl bg-linear-to-t from-card/80 to-transparent px-3 py-0.5 text-md text-neutral-700 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all duration-300 hover:py-1 dark:text-neutral-400',\n isCollapsed ? 'w-full' : 'w-32'\n )}\n type=\"button\"\n onClick={() => setIsCollapsed((prev) => !prev)}\n >\n {expandCollapseContent(isCollapsed)}\n </button>\n </MaxHeightSmoother>\n );\n};\n"],"mappings":";;;;;;;;;;AA6BA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqD3B,MAAa,kBAA2C,EACtD,aAAa,MACb,YAAY,oBACZ,UACA,gBACI;CACJ,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,CAAC;CAChE,MAAM,CAAC,aAAa,kBAAkB,SAAS,IAAI;CACnD,MAAM,oBAAoB,OAA8B,IAAI;CAC5D,MAAM,EAAE,0BAA0B,YAAY,iBAAiB;CAE/D,MAAM,WAAW,sBAAsB;CAcvC,MAAM,mBAAmB,aAAa,cAAqC;EACzE,kBAAkB,SAAS,WAAW;EACtC,kBAAkB,UAAU;EAE5B,IAAI,CAAC,WAAW;EAEhB,IAAI,OAAO,mBAAmB,aAAa;GACzC,uBAAuB,UAAU,YAAY;GAC7C;EACF;EAEA,MAAM,WAAW,IAAI,gBAAgB,CAAC,WAAW;GAE/C,IAAI,OAAO,uBAAuB,MAAM,OAAO,YAAY;EAC7D,CAAC;EAED,SAAS,QAAQ,SAAS;EAC1B,kBAAkB,UAAU;CAC9B,GAAG,CAAC,CAAC;CAEL,sBAAsB,kBAAkB,SAAS,WAAW,GAAG,CAAC,CAAC;CAEjE,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,CAAC,UACH,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,eAAe,SAAS;EAAG,KAAK;EAChD;CACE;CAIT,OACE,qBAAC,mBAAD;EACE,UAAU;EACC;EACX,WAAU;YAHZ,CAKE,oBAAC,OAAD;GAAK,WAAW,GAAG,eAAe,SAAS;GAAG,KAAK;GAChD;EACE,IACL,oBAAC,UAAD;GACE,WAAW,GACT,qSACA,cAAc,WAAW,MAC3B;GACA,MAAK;GACL,eAAe,gBAAgB,SAAS,CAAC,IAAI;aAE5C,sBAAsB,WAAW;EAC5B,EACS;;AAEvB"}
1
+ {"version":3,"file":"ExpandCollapse.mjs","names":[],"sources":["../../../../src/components/ExpandCollapse/ExpandCollapse.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type FC,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the ExpandCollapse component\n */\nexport type ExpandCollapseProps = {\n /** Whether the component should provide expand/collapse functionality. If false, renders children directly */\n isRollable?: boolean;\n /** Minimum height in pixels before showing the expand/collapse toggle */\n minHeight?: number;\n /** Content that may overflow and trigger the expand/collapse behavior */\n children: ReactNode;\n /** Additional CSS classes for styling customization */\n className?: string;\n};\n\n/** Default minimum height threshold for triggering expand/collapse behavior */\nconst DEFAULT_MIN_HEIGHT = 700;\n\n/**\n * ExpandCollapse Component\n *\n * A smart content container that automatically provides expand/collapse functionality\n * when content exceeds a specified height threshold. Features smooth animations,\n * internationalized toggle text, and intelligent height detection.\n *\n * @example\n * ```tsx\n * <ExpandCollapse minHeight={300}>\n * <div>Very long content that will be collapsed...</div>\n * </ExpandCollapse>\n * ```\n *\n * ## Key Features\n * - **Smart Detection**: Automatically detects when content exceeds height threshold\n * - **Smooth Animations**: Uses CSS transitions for smooth expand/collapse effects\n * - **Internationalization**: Toggle text supports multiple languages via Intlayer\n * - **Customizable Height**: Configurable minimum height threshold\n * - **Performance Optimized**: Only applies collapse behavior when necessary\n * - **Accessibility**: Proper ARIA attributes and keyboard support\n *\n * ## Behavior Logic\n * 1. **Measurement Phase**: Measures actual content height on mount\n * 2. **Comparison**: Compares content height against minHeight threshold\n * 3. **Conditional Rendering**:\n * - If content ≤ minHeight: Renders normally without collapse functionality\n * - If content > minHeight: Enables expand/collapse with toggle button\n * 4. **State Management**: Manages collapsed/expanded state with smooth transitions\n *\n * ## When to Use\n * - Long-form content (articles, documentation, code blocks)\n * - Lists or tables that may grow beyond comfortable viewing height\n * - User-generated content with unpredictable length\n * - FAQ sections or expandable content cards\n * - Code examples or JSON data display\n *\n * ## Accessibility Features\n * - **Keyboard Navigation**: Toggle button is focusable and keyboard accessible\n * - **Screen Reader Support**: Proper ARIA labels and state announcements\n * - **Visual Indicators**: Clear visual cues for collapsed/expanded states\n * - **Smooth Animations**: Respects user preferences for reduced motion\n *\n * ## Internationalization\n * - Supports multiple languages through Intlayer integration\n * - Toggle text automatically adapts to current locale\n * - Includes translations for \"Show all\" and \"Show less\" states\n *\n * @param props - ExpandCollapseProps\n * @returns React functional component\n */\nexport const ExpandCollapse: FC<ExpandCollapseProps> = ({\n isRollable = true,\n minHeight = DEFAULT_MIN_HEIGHT,\n children,\n className,\n}) => {\n const [codeContainerHeight, setCodeContainerHeight] = useState(0);\n const [isCollapsed, setIsCollapsed] = useState(true);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const { expandCollapseContent } = useIntlayer('expand-collapse');\n\n const isTooBig = codeContainerHeight > minHeight;\n\n /*\n * A `ResizeObserver` is handed the size the browser has already computed,\n * where reading `clientHeight` from an effect forces it to lay the page out\n * again on the spot. That distinction matters here because a documentation\n * page mounts one of these per code block: dozens of synchronous reflows over\n * a very large document. The observer also reports later size changes, which\n * is what the window `resize` listener used to cover.\n *\n * It attaches through a callback ref rather than an effect because crossing\n * the `minHeight` threshold re-parents the container into `MaxHeightSmoother`\n * — a different element, which an effect bound to mount would never observe.\n */\n const observeContainer = useCallback((container: HTMLDivElement | null) => {\n resizeObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n\n if (!container) return;\n\n if (typeof ResizeObserver === 'undefined') {\n setCodeContainerHeight(container.clientHeight);\n return;\n }\n\n const observer = new ResizeObserver(([entry]) => {\n // The entry already carries the measured box, so nothing is read back\n // from the element. `clientHeight` here would still force a reflow\n // whenever an earlier observer in the same delivery had dirtied the\n // styles — and a documentation page delivers one per code block.\n if (entry) setCodeContainerHeight(entry.contentRect.height);\n });\n\n observer.observe(container);\n resizeObserverRef.current = observer;\n }, []);\n\n useEffect(() => () => resizeObserverRef.current?.disconnect(), []);\n\n if (!isRollable) {\n return children;\n }\n\n if (!isTooBig) {\n return (\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n );\n }\n\n return (\n <MaxHeightSmoother\n isHidden={isCollapsed}\n minHeight={minHeight}\n className=\"w-full overflow-x-auto overflow-y-hidden\"\n >\n <div className={cn('grid w-full', className)} ref={observeContainer}>\n {children}\n </div>\n <button\n className={cn(\n 'absolute right-0 bottom-0 flex w-full cursor-pointer items-center justify-center rounded-t-2xl bg-linear-to-t from-card/80 to-transparent px-3 py-0.5 text-md text-neutral-700 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur transition-all duration-300 hover:py-1 dark:text-neutral-400',\n isCollapsed ? 'w-full' : 'w-32'\n )}\n type=\"button\"\n onClick={() => setIsCollapsed((prev) => !prev)}\n >\n {expandCollapseContent(isCollapsed)}\n </button>\n </MaxHeightSmoother>\n );\n};\n"],"mappings":";;;;;;;;;;AA6BA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqD3B,MAAa,kBAA2C,EACtD,aAAa,MACb,YAAY,oBACZ,UACA,gBACI;CACJ,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,CAAC;CAChE,MAAM,CAAC,aAAa,kBAAkB,SAAS,IAAI;CACnD,MAAM,oBAAoB,OAA8B,IAAI;CAC5D,MAAM,EAAE,0BAA0B,YAAY,iBAAiB;CAE/D,MAAM,WAAW,sBAAsB;CAcvC,MAAM,mBAAmB,aAAa,cAAqC;EACzE,kBAAkB,SAAS,WAAW;EACtC,kBAAkB,UAAU;EAE5B,IAAI,CAAC,WAAW;EAEhB,IAAI,OAAO,mBAAmB,aAAa;GACzC,uBAAuB,UAAU,YAAY;GAC7C;EACF;EAEA,MAAM,WAAW,IAAI,gBAAgB,CAAC,WAAW;GAK/C,IAAI,OAAO,uBAAuB,MAAM,YAAY,MAAM;EAC5D,CAAC;EAED,SAAS,QAAQ,SAAS;EAC1B,kBAAkB,UAAU;CAC9B,GAAG,CAAC,CAAC;CAEL,sBAAsB,kBAAkB,SAAS,WAAW,GAAG,CAAC,CAAC;CAEjE,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,CAAC,UACH,OACE,oBAAC,OAAD;EAAK,WAAW,GAAG,eAAe,SAAS;EAAG,KAAK;EAChD;CACE;CAIT,OACE,qBAAC,mBAAD;EACE,UAAU;EACC;EACX,WAAU;YAHZ,CAKE,oBAAC,OAAD;GAAK,WAAW,GAAG,eAAe,SAAS;GAAG,KAAK;GAChD;EACE,IACL,oBAAC,UAAD;GACE,WAAW,GACT,qSACA,cAAc,WAAW,MAC3B;GACA,MAAK;GACL,eAAe,gBAAgB,SAAS,CAAC,IAAI;aAE5C,sBAAsB,WAAW;EAC5B,EACS;;AAEvB"}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
4
3
  import { InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "../../Input/OTPInput.mjs";
4
+ import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
5
5
  import { FormField, useFormField } from "../FormField.mjs";
6
6
  import { FormItemLayout } from "../layout/FormItemLayout.mjs";
7
7
  import { useEffect, useRef } from "react";
@@ -13,12 +13,12 @@ const selector = (option) => option?.getAttribute("aria-selected") === "true";
13
13
  const OTPFieldContent = ({ field, name, label, description, isRequired, info, showErrorMessage, children, slots = 6, maxLength = 6, ...props }) => {
14
14
  const { error } = useFormField();
15
15
  const optionsRefs = useRef([]);
16
- const { choiceIndicatorPosition, calculatePosition } = useItemSelector(optionsRefs, {
16
+ const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(optionsRefs, {
17
17
  selector,
18
18
  isHoverable: true
19
19
  });
20
20
  useEffect(() => {
21
- calculatePosition();
21
+ scheduleMeasurement();
22
22
  }, []);
23
23
  return /* @__PURE__ */ jsx(FormItemLayout, {
24
24
  htmlFor: name,
@@ -33,7 +33,7 @@ const OTPFieldContent = ({ field, name, label, description, isRequired, info, sh
33
33
  defaultValue: field.value,
34
34
  maxLength,
35
35
  pattern: "^[0-9]+$",
36
- onActiveSlotChange: calculatePosition,
36
+ onActiveSlotChange: scheduleMeasurement,
37
37
  ...props,
38
38
  children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
39
39
  /* @__PURE__ */ jsxs(InputOTPGroup, { children: [
@@ -1 +1 @@
1
- {"version":3,"file":"OTPElement.mjs","names":[],"sources":["../../../../../src/components/Form/elements/OTPElement.tsx"],"sourcesContent":["'use client';\n\nimport {\n InputIndicator,\n InputOTP,\n InputOTPGroup,\n InputOTPSeparator,\n InputOTPSlot,\n} from '@components/Input/OTPInput';\nimport { useItemSelector } from '@hooks/useItemSelector';\nimport { type ComponentProps, type ReactNode, useEffect, useRef } from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { FormField, useFormField } from '../FormField';\nimport { FormItemLayout } from '../layout/FormItemLayout';\nimport type { FormElementProps } from './FormElement';\n\ntype OTPElementsProps = Omit<FormElementProps<typeof InputOTP>, 'Element'> &\n Omit<ComponentProps<typeof InputOTP>, 'children'> & {\n name: string;\n description?: string;\n placeholder?: string;\n className?: string;\n children?: ReactNode;\n /** Number of OTP slots (default: 6) */\n slots?: number;\n };\n\nconst selector = (option: HTMLElement) =>\n option?.getAttribute('aria-selected') === 'true';\n\nconst OTPFieldContent = ({\n field,\n name,\n label,\n description,\n isRequired,\n info,\n showErrorMessage,\n children,\n slots = 6,\n maxLength = 6,\n ...props\n}: Omit<OTPElementsProps, 'control'> & { field: any }) => {\n const { error } = useFormField();\n\n const optionsRefs = useRef<HTMLElement[]>([]);\n const { choiceIndicatorPosition, calculatePosition } = useItemSelector(\n optionsRefs,\n {\n selector,\n isHoverable: true,\n }\n );\n\n useEffect(() => {\n calculatePosition();\n }, []);\n\n return (\n <FormItemLayout\n htmlFor={name}\n label={label}\n description={description}\n isRequired={isRequired}\n info={info}\n showErrorMessage={showErrorMessage}\n aria-invalid={!!error}\n >\n <InputOTP\n onChange={field.onChange}\n defaultValue={field.value}\n maxLength={maxLength}\n pattern=\"^[0-9]+$\"\n onActiveSlotChange={calculatePosition}\n {...(props as any)}\n >\n {children ?? (\n <>\n <InputOTPGroup>\n <InputOTPSlot\n index={0}\n ref={(el) => {\n optionsRefs.current[0] = el!;\n }}\n />\n <InputOTPSlot\n index={1}\n ref={(el) => {\n optionsRefs.current[1] = el!;\n }}\n />\n <InputOTPSlot\n index={2}\n ref={(el) => {\n optionsRefs.current[2] = el!;\n }}\n />\n </InputOTPGroup>\n <InputOTPSeparator />\n <InputOTPGroup>\n <InputOTPSlot\n index={3}\n ref={(el) => {\n optionsRefs.current[3] = el!;\n }}\n />\n <InputOTPSlot\n index={4}\n ref={(el) => {\n optionsRefs.current[4] = el!;\n }}\n />\n <InputOTPSlot\n index={5}\n ref={(el) => {\n optionsRefs.current[5] = el!;\n }}\n />\n </InputOTPGroup>\n {choiceIndicatorPosition && (\n <InputIndicator style={choiceIndicatorPosition} />\n )}\n </>\n )}\n </InputOTP>\n </FormItemLayout>\n );\n};\n\nexport const OTPElement = ({\n name,\n description,\n label,\n isRequired,\n info,\n showErrorMessage,\n children,\n slots = 6,\n maxLength = 6,\n ...props\n}: OTPElementsProps) => {\n const { control } = useFormContext();\n\n return (\n <FormField\n control={control}\n name={name}\n render={({ field }) => (\n <OTPFieldContent\n field={field}\n name={name}\n label={label}\n description={description}\n isRequired={isRequired}\n info={info}\n showErrorMessage={showErrorMessage}\n slots={slots}\n maxLength={maxLength}\n {...props}\n >\n {children}\n </OTPFieldContent>\n )}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;AA2BA,MAAM,YAAY,WAChB,QAAQ,aAAa,eAAe,MAAM;AAE5C,MAAM,mBAAmB,EACvB,OACA,MACA,OACA,aACA,YACA,MACA,kBACA,UACA,QAAQ,GACR,YAAY,GACZ,GAAG,YACqD;CACxD,MAAM,EAAE,UAAU,aAAa;CAE/B,MAAM,cAAc,OAAsB,CAAC,CAAC;CAC5C,MAAM,EAAE,yBAAyB,sBAAsB,gBACrD,aACA;EACE;EACA,aAAa;CACf,CACF;CAEA,gBAAgB;EACd,kBAAkB;CACpB,GAAG,CAAC,CAAC;CAEL,OACE,oBAAC,gBAAD;EACE,SAAS;EACF;EACM;EACD;EACN;EACY;EAClB,gBAAc,CAAC,CAAC;YAEhB,oBAAC,UAAD;GACE,UAAU,MAAM;GAChB,cAAc,MAAM;GACT;GACX,SAAQ;GACR,oBAAoB;GACpB,GAAK;aAEJ,YACC;IACE,qBAAC,eAAD;KACE,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;IACY;IACf,oBAAC,mBAAD,CAAoB;IACpB,qBAAC,eAAD;KACE,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;IACY;IACd,2BACC,oBAAC,gBAAD,EAAgB,OAAO,wBAA0B;GAEnD;EAEI;CACI;AAEpB;AAEA,MAAa,cAAc,EACzB,MACA,aACA,OACA,YACA,MACA,kBACA,UACA,QAAQ,GACR,YAAY,GACZ,GAAG,YACmB;CACtB,MAAM,EAAE,YAAY,eAAe;CAEnC,OACE,oBAAC,WAAD;EACW;EACH;EACN,SAAS,EAAE,YACT,oBAAC,iBAAD;GACS;GACD;GACC;GACM;GACD;GACN;GACY;GACX;GACI;GACX,GAAI;GAEH;EACc;CAEpB;AAEL"}
1
+ {"version":3,"file":"OTPElement.mjs","names":[],"sources":["../../../../../src/components/Form/elements/OTPElement.tsx"],"sourcesContent":["'use client';\n\nimport {\n InputIndicator,\n InputOTP,\n InputOTPGroup,\n InputOTPSeparator,\n InputOTPSlot,\n} from '@components/Input/OTPInput';\nimport { useItemSelector } from '@hooks/useItemSelector';\nimport { type ComponentProps, type ReactNode, useEffect, useRef } from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { FormField, useFormField } from '../FormField';\nimport { FormItemLayout } from '../layout/FormItemLayout';\nimport type { FormElementProps } from './FormElement';\n\ntype OTPElementsProps = Omit<FormElementProps<typeof InputOTP>, 'Element'> &\n Omit<ComponentProps<typeof InputOTP>, 'children'> & {\n name: string;\n description?: string;\n placeholder?: string;\n className?: string;\n children?: ReactNode;\n /** Number of OTP slots (default: 6) */\n slots?: number;\n };\n\nconst selector = (option: HTMLElement) =>\n option?.getAttribute('aria-selected') === 'true';\n\nconst OTPFieldContent = ({\n field,\n name,\n label,\n description,\n isRequired,\n info,\n showErrorMessage,\n children,\n slots = 6,\n maxLength = 6,\n ...props\n}: Omit<OTPElementsProps, 'control'> & { field: any }) => {\n const { error } = useFormField();\n\n const optionsRefs = useRef<HTMLElement[]>([]);\n const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(\n optionsRefs,\n {\n selector,\n isHoverable: true,\n }\n );\n\n useEffect(() => {\n scheduleMeasurement();\n }, []);\n\n return (\n <FormItemLayout\n htmlFor={name}\n label={label}\n description={description}\n isRequired={isRequired}\n info={info}\n showErrorMessage={showErrorMessage}\n aria-invalid={!!error}\n >\n <InputOTP\n onChange={field.onChange}\n defaultValue={field.value}\n maxLength={maxLength}\n pattern=\"^[0-9]+$\"\n onActiveSlotChange={scheduleMeasurement}\n {...(props as any)}\n >\n {children ?? (\n <>\n <InputOTPGroup>\n <InputOTPSlot\n index={0}\n ref={(el) => {\n optionsRefs.current[0] = el!;\n }}\n />\n <InputOTPSlot\n index={1}\n ref={(el) => {\n optionsRefs.current[1] = el!;\n }}\n />\n <InputOTPSlot\n index={2}\n ref={(el) => {\n optionsRefs.current[2] = el!;\n }}\n />\n </InputOTPGroup>\n <InputOTPSeparator />\n <InputOTPGroup>\n <InputOTPSlot\n index={3}\n ref={(el) => {\n optionsRefs.current[3] = el!;\n }}\n />\n <InputOTPSlot\n index={4}\n ref={(el) => {\n optionsRefs.current[4] = el!;\n }}\n />\n <InputOTPSlot\n index={5}\n ref={(el) => {\n optionsRefs.current[5] = el!;\n }}\n />\n </InputOTPGroup>\n {choiceIndicatorPosition && (\n <InputIndicator style={choiceIndicatorPosition} />\n )}\n </>\n )}\n </InputOTP>\n </FormItemLayout>\n );\n};\n\nexport const OTPElement = ({\n name,\n description,\n label,\n isRequired,\n info,\n showErrorMessage,\n children,\n slots = 6,\n maxLength = 6,\n ...props\n}: OTPElementsProps) => {\n const { control } = useFormContext();\n\n return (\n <FormField\n control={control}\n name={name}\n render={({ field }) => (\n <OTPFieldContent\n field={field}\n name={name}\n label={label}\n description={description}\n isRequired={isRequired}\n info={info}\n showErrorMessage={showErrorMessage}\n slots={slots}\n maxLength={maxLength}\n {...props}\n >\n {children}\n </OTPFieldContent>\n )}\n />\n );\n};\n"],"mappings":";;;;;;;;;;;AA2BA,MAAM,YAAY,WAChB,QAAQ,aAAa,eAAe,MAAM;AAE5C,MAAM,mBAAmB,EACvB,OACA,MACA,OACA,aACA,YACA,MACA,kBACA,UACA,QAAQ,GACR,YAAY,GACZ,GAAG,YACqD;CACxD,MAAM,EAAE,UAAU,aAAa;CAE/B,MAAM,cAAc,OAAsB,CAAC,CAAC;CAC5C,MAAM,EAAE,yBAAyB,wBAAwB,gBACvD,aACA;EACE;EACA,aAAa;CACf,CACF;CAEA,gBAAgB;EACd,oBAAoB;CACtB,GAAG,CAAC,CAAC;CAEL,OACE,oBAAC,gBAAD;EACE,SAAS;EACF;EACM;EACD;EACN;EACY;EAClB,gBAAc,CAAC,CAAC;YAEhB,oBAAC,UAAD;GACE,UAAU,MAAM;GAChB,cAAc,MAAM;GACT;GACX,SAAQ;GACR,oBAAoB;GACpB,GAAK;aAEJ,YACC;IACE,qBAAC,eAAD;KACE,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;IACY;IACf,oBAAC,mBAAD,CAAoB;IACpB,qBAAC,eAAD;KACE,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;KACD,oBAAC,cAAD;MACE,OAAO;MACP,MAAM,OAAO;OACX,YAAY,QAAQ,KAAK;MAC3B;KACD;IACY;IACd,2BACC,oBAAC,gBAAD,EAAgB,OAAO,wBAA0B;GAEnD;EAEI;CACI;AAEpB;AAEA,MAAa,cAAc,EACzB,MACA,aACA,OACA,YACA,MACA,kBACA,UACA,QAAQ,GACR,YAAY,GACZ,GAAG,YACmB;CACtB,MAAM,EAAE,YAAY,eAAe;CAEnC,OACE,oBAAC,WAAD;EACW;EACH;EACN,SAAS,EAAE,YACT,oBAAC,iBAAD;GACS;GACD;GACC;GACM;GACD;GACN;GACY;GACX;GACI;GACX,GAAI;GAEH;EACc;CAEpB;AAEL"}
@@ -155,17 +155,23 @@ const isTextChildren = (children) => {
155
155
  return false;
156
156
  };
157
157
  const Link = (props) => {
158
- const { variant = "default", color = "custom", roundedSize, children, label, className, isActive, underlined, locale, size: sizeProp, isExternalLink: isExternalLinkProp, isPageSection: isPageSectionProp, href: hrefProp, ...otherProps } = props;
158
+ const { variant = "default", color = "custom", roundedSize, children, label, className, isActive, underlined, locale, size: sizeProp, isExternalLink: isExternalLinkProp, isPageSection: isPageSectionProp, href: hrefProp, rel: relProp, target: targetProp, ...otherProps } = props;
159
159
  const isButton = variant === "button" || variant === "button-outlined";
160
160
  const size = sizeProp ?? (isButton ? "md" : "custom");
161
161
  const isExternalLink = isExternalLinkProp ?? checkIsExternalLink(props);
162
162
  const isPageSection = isPageSectionProp ?? hrefProp?.startsWith("#") ?? false;
163
163
  const isChildrenString = isTextChildren(children);
164
- const rel = isExternalLink ? "noopener noreferrer nofollow" : void 0;
165
- const target = isExternalLink ? "_blank" : "_self";
164
+ /**
165
+ * External links always carry the full safety/SEO `rel`, so a caller cannot
166
+ * accidentally strip `nofollow`. Internal links keep whatever the caller asked for.
167
+ */
168
+ const rel = isExternalLink ? "noopener noreferrer nofollow" : relProp;
169
+ const target = isExternalLink ? "_blank" : targetProp ?? "_self";
166
170
  const resolvedHref = locale && hrefProp && !isExternalLink && !isPageSection ? getLocalizedUrl(hrefProp, locale) : hrefProp;
171
+ const href = resolvedHref === "" ? void 0 : resolvedHref;
167
172
  return /* @__PURE__ */ jsxs("a", {
168
- href: resolvedHref === "" ? void 0 : resolvedHref,
173
+ ...otherProps,
174
+ href,
169
175
  "aria-label": label,
170
176
  rel,
171
177
  target,
@@ -179,7 +185,6 @@ const Link = (props) => {
179
185
  size,
180
186
  className
181
187
  })),
182
- ...otherProps,
183
188
  children: [
184
189
  isButton && isChildrenString ? /* @__PURE__ */ jsx("span", { children }) : children,
185
190
  isExternalLink && isChildrenString && /* @__PURE__ */ jsx(ExternalLink, { className: "ml-2 inline-block size-4" }),
@@ -1 +1 @@
1
- {"version":3,"file":"Link.mjs","names":[],"sources":["../../../../src/components/Link/Link.tsx"],"sourcesContent":["import { getLocalizedUrl } from '@intlayer/core/localization';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ExternalLink, MoveRight } from 'lucide-react';\nimport {\n type AnchorHTMLAttributes,\n type DetailedHTMLProps,\n type FC,\n isValidElement,\n type ReactNode,\n} from 'react';\n\nexport type LinkVariant =\n | 'default'\n | 'invisible-link'\n | 'button'\n | 'button-outlined'\n | 'hoverable';\n\n/**\n * Color theme variants for Link component\n */\nexport type LinkColor =\n | 'primary'\n | 'secondary'\n | 'neutral'\n | 'light'\n | 'dark'\n | 'text'\n | 'text-inverse'\n | 'error'\n | 'success'\n | 'custom';\n\nexport type LinkRoundedSize =\n | 'none'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | 'full';\n\nexport type LinkSize = 'sm' | 'md' | 'lg' | 'xl' | 'custom';\n\nexport type LinkUnderlined = 'default' | 'true' | 'false';\n\nexport const linkVariants = cva(\n 'gap-3 transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'h-auto justify-start border-inherit bg-current/0 px-1 font-medium decoration-[1.5] underline-offset-5 hover:bg-current/0 hover:text-current/80 hover:underline hover:underline-offset-6',\n 'invisible-link':\n 'h-auto justify-start border-inherit bg-current/0 px-1',\n\n button:\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full bg-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text-opposite hover:bg-current/90 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n 'button-outlined':\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full border-[1.3px] border-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text hover:bg-current/20 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n hoverable:\n 'rounded-lg border-none bg-current/0 transition *:text-current! hover:bg-current/10 aria-[current]:bg-current/5',\n },\n roundedSize: {\n none: 'rounded-none',\n sm: 'rounded-lg [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl',\n md: 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n lg: 'rounded-2xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-3xl',\n xl: 'rounded-3xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-4xl',\n '2xl':\n 'rounded-4xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[2.5rem]',\n '3xl':\n 'rounded-[2.5rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[3rem]',\n full: 'rounded-full',\n },\n color: {\n primary: 'text-primary',\n secondary: 'text-secondary',\n neutral: 'text-neutral',\n light: 'text-white',\n dark: 'text-neutral-800',\n text: 'text-text',\n 'text-inverse': 'text-text-opposite',\n error: 'text-error',\n success: 'text-success',\n custom: '',\n },\n size: {\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n xl: 'text-xl',\n custom: '',\n },\n underlined: {\n default: '',\n true: 'underline',\n false: 'no-underline',\n },\n },\n // Compound variants handle height and padding\n compoundVariants: [\n // ---------------------------------------------------------\n // FIX START: Correctly Handle Contrast for TEXT_INVERSE\n // ---------------------------------------------------------\n {\n // Filled Button + Inverse Color (e.g., White Button):\n // We DO NOT override parent text color (it must remain 'text-opposite' so bg-current is white).\n // We ONLY override children to be 'text-text' (Dark) so they show up on white.\n variant: 'button',\n color: 'text-inverse',\n class: '*:text-text',\n },\n {\n // Outlined Button + Inverse Color (e.g., White Border):\n // Parent is 'text-opposite' (Border is white).\n // Children must also be 'text-opposite' (White text) to show on dark background.\n variant: 'button-outlined',\n color: 'text-inverse',\n class: 'text-text-opposite *:text-text-opposite',\n },\n\n // Min height and padding for button variants\n {\n variant: ['button', 'button-outlined'],\n size: 'sm',\n class: 'min-h-7 px-3 text-xs max-md:py-1',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'md',\n class: 'min-h-8 px-6 text-sm max-md:py-2',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'lg',\n class: 'min-h-10 px-8 text-lg max-md:py-3',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'xl',\n class: 'min-h-11 px-10 text-xl max-md:py-4',\n },\n // Ring color variants\n {\n variant: ['button', 'button-outlined'],\n color: 'primary',\n class: 'ring-primary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'secondary',\n class: 'ring-secondary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'neutral',\n class: 'ring-neutral/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'light',\n class: 'ring-white/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'dark',\n class: 'ring-neutral-800/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text',\n class: 'ring-text/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text-inverse',\n class: 'ring-text-opposite/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'error',\n class: 'ring-error/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'success',\n class: 'ring-success/20',\n },\n ],\n\n defaultVariants: {\n variant: 'default',\n roundedSize: 'md',\n underlined: 'default',\n size: 'custom',\n },\n }\n);\n\nexport type LinkProps = DetailedHTMLProps<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n HTMLAnchorElement\n> &\n VariantProps<typeof linkVariants> & {\n label: string;\n isExternalLink?: boolean;\n isPageSection?: boolean;\n isActive?: boolean;\n locale?: LocalesValues;\n };\n\nexport const checkIsExternalLink = (\n {\n href,\n isExternalLink: isExternalLinkProp,\n }: Pick<LinkProps, 'href' | 'isExternalLink'>,\n url?: string\n): boolean => {\n // Explicit prop override takes precedence\n if (typeof isExternalLinkProp === 'boolean') {\n return isExternalLinkProp;\n }\n\n const isValidHref = typeof href === 'string' && href.trim() !== '';\n\n if (!isValidHref) return false;\n\n // Relative URLs (e.g., '/about') are always internal\n if (!/^https?:\\/\\//.test(href)) {\n return false;\n }\n\n // Compare base domains\n if (url) {\n try {\n const hrefHost = new URL(href).hostname;\n // Ensure the reference url has a protocol so URL() can parse it correctly\n const currentHost = new URL(\n url.startsWith('http') ? url : `https://${url}`\n ).hostname;\n\n // Extract the root domain (e.g., 'app.intlayer.org' -> 'intlayer.org')\n const getBaseDomain = (host: string) =>\n host.split('.').slice(-2).join('.');\n\n return getBaseDomain(hrefHost) !== getBaseDomain(currentHost);\n } catch {\n // If URL parsing fails for any reason, default to treating it as external\n return true;\n }\n }\n\n // Absolute URL with no comparison URL provided\n return true;\n};\n\nexport const isTextChildren = (children: ReactNode): boolean => {\n if (typeof children === 'string' || typeof children === 'number') {\n return true;\n }\n if (Array.isArray(children)) {\n return children.every(isTextChildren);\n }\n if (isValidElement(children)) {\n return isTextChildren(\n (children.props as { children?: ReactNode }).children\n );\n }\n return false;\n};\n\nexport const Link: FC<LinkProps> = (props) => {\n const {\n variant = 'default',\n color = 'custom',\n roundedSize,\n children,\n label,\n className,\n isActive,\n underlined,\n locale,\n size: sizeProp,\n isExternalLink: isExternalLinkProp,\n isPageSection: isPageSectionProp,\n href: hrefProp,\n ...otherProps\n } = props;\n\n const isButton = variant === 'button' || variant === 'button-outlined';\n const size = sizeProp ?? (isButton ? 'md' : 'custom');\n\n const isExternalLink = isExternalLinkProp ?? checkIsExternalLink(props);\n const isPageSection = isPageSectionProp ?? hrefProp?.startsWith('#') ?? false;\n\n const isChildrenString = isTextChildren(children);\n\n const rel = isExternalLink ? 'noopener noreferrer nofollow' : undefined;\n\n const target = isExternalLink ? '_blank' : '_self';\n\n const resolvedHref =\n locale && hrefProp && !isExternalLink && !isPageSection\n ? getLocalizedUrl(hrefProp, locale)\n : hrefProp;\n\n const href = resolvedHref === '' ? undefined : resolvedHref;\n\n return (\n <a\n href={href}\n aria-label={label}\n rel={rel}\n target={target}\n aria-current={isActive ? 'page' : undefined}\n suppressHydrationWarning\n className={cn(\n linkVariants({\n variant,\n color,\n roundedSize,\n underlined,\n size,\n className,\n })\n )}\n {...otherProps}\n >\n {isButton && isChildrenString ? <span>{children}</span> : children}\n\n {isExternalLink && isChildrenString && (\n <ExternalLink className=\"ml-2 inline-block size-4\" />\n )}\n {isPageSection && <MoveRight className=\"ml-2 inline-block size-4\" />}\n </a>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Link.mjs","names":[],"sources":["../../../../src/components/Link/Link.tsx"],"sourcesContent":["import { getLocalizedUrl } from '@intlayer/core/localization';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ExternalLink, MoveRight } from 'lucide-react';\nimport {\n type AnchorHTMLAttributes,\n type DetailedHTMLProps,\n type FC,\n isValidElement,\n type ReactNode,\n} from 'react';\n\nexport type LinkVariant =\n | 'default'\n | 'invisible-link'\n | 'button'\n | 'button-outlined'\n | 'hoverable';\n\n/**\n * Color theme variants for Link component\n */\nexport type LinkColor =\n | 'primary'\n | 'secondary'\n | 'neutral'\n | 'light'\n | 'dark'\n | 'text'\n | 'text-inverse'\n | 'error'\n | 'success'\n | 'custom';\n\nexport type LinkRoundedSize =\n | 'none'\n | 'sm'\n | 'md'\n | 'lg'\n | 'xl'\n | '2xl'\n | '3xl'\n | 'full';\n\nexport type LinkSize = 'sm' | 'md' | 'lg' | 'xl' | 'custom';\n\nexport type LinkUnderlined = 'default' | 'true' | 'false';\n\nexport const linkVariants = cva(\n 'gap-3 transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n variant: {\n default:\n 'h-auto justify-start border-inherit bg-current/0 px-1 font-medium decoration-[1.5] underline-offset-5 hover:bg-current/0 hover:text-current/80 hover:underline hover:underline-offset-6',\n 'invisible-link':\n 'h-auto justify-start border-inherit bg-current/0 px-1',\n\n button:\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full bg-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text-opposite hover:bg-current/90 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n 'button-outlined':\n 'relative inline-flex min-h-8 cursor-pointer flex-row items-center justify-center gap-2 rounded-full border-[1.3px] border-current px-6 text-center font-medium text-sm text-text ring-0 *:text-text hover:bg-current/20 hover:ring-5 aria-selected:ring-5 aria-[current]:ring-5 max-md:py-2',\n\n hoverable:\n 'rounded-lg border-none bg-current/0 transition *:text-current! hover:bg-current/10 aria-[current]:bg-current/5',\n },\n roundedSize: {\n none: 'rounded-none',\n sm: 'rounded-lg [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl',\n md: 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n lg: 'rounded-2xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-3xl',\n xl: 'rounded-3xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-4xl',\n '2xl':\n 'rounded-4xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[2.5rem]',\n '3xl':\n 'rounded-[2.5rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[3rem]',\n full: 'rounded-full',\n },\n color: {\n primary: 'text-primary',\n secondary: 'text-secondary',\n neutral: 'text-neutral',\n light: 'text-white',\n dark: 'text-neutral-800',\n text: 'text-text',\n 'text-inverse': 'text-text-opposite',\n error: 'text-error',\n success: 'text-success',\n custom: '',\n },\n size: {\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n xl: 'text-xl',\n custom: '',\n },\n underlined: {\n default: '',\n true: 'underline',\n false: 'no-underline',\n },\n },\n // Compound variants handle height and padding\n compoundVariants: [\n // ---------------------------------------------------------\n // FIX START: Correctly Handle Contrast for TEXT_INVERSE\n // ---------------------------------------------------------\n {\n // Filled Button + Inverse Color (e.g., White Button):\n // We DO NOT override parent text color (it must remain 'text-opposite' so bg-current is white).\n // We ONLY override children to be 'text-text' (Dark) so they show up on white.\n variant: 'button',\n color: 'text-inverse',\n class: '*:text-text',\n },\n {\n // Outlined Button + Inverse Color (e.g., White Border):\n // Parent is 'text-opposite' (Border is white).\n // Children must also be 'text-opposite' (White text) to show on dark background.\n variant: 'button-outlined',\n color: 'text-inverse',\n class: 'text-text-opposite *:text-text-opposite',\n },\n\n // Min height and padding for button variants\n {\n variant: ['button', 'button-outlined'],\n size: 'sm',\n class: 'min-h-7 px-3 text-xs max-md:py-1',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'md',\n class: 'min-h-8 px-6 text-sm max-md:py-2',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'lg',\n class: 'min-h-10 px-8 text-lg max-md:py-3',\n },\n {\n variant: ['button', 'button-outlined'],\n size: 'xl',\n class: 'min-h-11 px-10 text-xl max-md:py-4',\n },\n // Ring color variants\n {\n variant: ['button', 'button-outlined'],\n color: 'primary',\n class: 'ring-primary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'secondary',\n class: 'ring-secondary/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'neutral',\n class: 'ring-neutral/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'light',\n class: 'ring-white/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'dark',\n class: 'ring-neutral-800/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text',\n class: 'ring-text/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'text-inverse',\n class: 'ring-text-opposite/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'error',\n class: 'ring-error/20',\n },\n {\n variant: ['button', 'button-outlined'],\n color: 'success',\n class: 'ring-success/20',\n },\n ],\n\n defaultVariants: {\n variant: 'default',\n roundedSize: 'md',\n underlined: 'default',\n size: 'custom',\n },\n }\n);\n\nexport type LinkProps = DetailedHTMLProps<\n AnchorHTMLAttributes<HTMLAnchorElement>,\n HTMLAnchorElement\n> &\n VariantProps<typeof linkVariants> & {\n label: string;\n isExternalLink?: boolean;\n isPageSection?: boolean;\n isActive?: boolean;\n locale?: LocalesValues;\n };\n\nexport const checkIsExternalLink = (\n {\n href,\n isExternalLink: isExternalLinkProp,\n }: Pick<LinkProps, 'href' | 'isExternalLink'>,\n url?: string\n): boolean => {\n // Explicit prop override takes precedence\n if (typeof isExternalLinkProp === 'boolean') {\n return isExternalLinkProp;\n }\n\n const isValidHref = typeof href === 'string' && href.trim() !== '';\n\n if (!isValidHref) return false;\n\n // Relative URLs (e.g., '/about') are always internal\n if (!/^https?:\\/\\//.test(href)) {\n return false;\n }\n\n // Compare base domains\n if (url) {\n try {\n const hrefHost = new URL(href).hostname;\n // Ensure the reference url has a protocol so URL() can parse it correctly\n const currentHost = new URL(\n url.startsWith('http') ? url : `https://${url}`\n ).hostname;\n\n // Extract the root domain (e.g., 'app.intlayer.org' -> 'intlayer.org')\n const getBaseDomain = (host: string) =>\n host.split('.').slice(-2).join('.');\n\n return getBaseDomain(hrefHost) !== getBaseDomain(currentHost);\n } catch {\n // If URL parsing fails for any reason, default to treating it as external\n return true;\n }\n }\n\n // Absolute URL with no comparison URL provided\n return true;\n};\n\nexport const isTextChildren = (children: ReactNode): boolean => {\n if (typeof children === 'string' || typeof children === 'number') {\n return true;\n }\n if (Array.isArray(children)) {\n return children.every(isTextChildren);\n }\n if (isValidElement(children)) {\n return isTextChildren(\n (children.props as { children?: ReactNode }).children\n );\n }\n return false;\n};\n\nexport const Link: FC<LinkProps> = (props) => {\n const {\n variant = 'default',\n color = 'custom',\n roundedSize,\n children,\n label,\n className,\n isActive,\n underlined,\n locale,\n size: sizeProp,\n isExternalLink: isExternalLinkProp,\n isPageSection: isPageSectionProp,\n href: hrefProp,\n rel: relProp,\n target: targetProp,\n ...otherProps\n } = props;\n\n const isButton = variant === 'button' || variant === 'button-outlined';\n const size = sizeProp ?? (isButton ? 'md' : 'custom');\n\n const isExternalLink = isExternalLinkProp ?? checkIsExternalLink(props);\n const isPageSection = isPageSectionProp ?? hrefProp?.startsWith('#') ?? false;\n\n const isChildrenString = isTextChildren(children);\n\n /**\n * External links always carry the full safety/SEO `rel`, so a caller cannot\n * accidentally strip `nofollow`. Internal links keep whatever the caller asked for.\n */\n const rel = isExternalLink ? 'noopener noreferrer nofollow' : relProp;\n\n const target = isExternalLink ? '_blank' : (targetProp ?? '_self');\n\n const resolvedHref =\n locale && hrefProp && !isExternalLink && !isPageSection\n ? getLocalizedUrl(hrefProp, locale)\n : hrefProp;\n\n const href = resolvedHref === '' ? undefined : resolvedHref;\n\n return (\n <a\n {...otherProps}\n href={href}\n aria-label={label}\n rel={rel}\n target={target}\n aria-current={isActive ? 'page' : undefined}\n suppressHydrationWarning\n className={cn(\n linkVariants({\n variant,\n color,\n roundedSize,\n underlined,\n size,\n className,\n })\n )}\n >\n {isButton && isChildrenString ? <span>{children}</span> : children}\n\n {isExternalLink && isChildrenString && (\n <ExternalLink className=\"ml-2 inline-block size-4\" />\n )}\n {isPageSection && <MoveRight className=\"ml-2 inline-block size-4\" />}\n </a>\n );\n};\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
4
3
  import { Button } from "../Button/Button.mjs";
5
4
  import { Container } from "../Container/index.mjs";
6
5
  import { DropDown } from "../DropDown/index.mjs";
7
6
  import { Input } from "../Input/Input.mjs";
8
7
  import { SwitchSelector } from "../SwitchSelector/SwitchSelector.mjs";
8
+ import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
9
9
  import { useLocaleSwitcherContent } from "./LocaleSwitcherContentContext.mjs";
10
10
  import { useMemo, useRef, useState } from "react";
11
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
- import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
5
- import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
6
4
  import { Button } from "../Button/Button.mjs";
7
5
  import { Container } from "../Container/index.mjs";
6
+ import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
7
+ import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
8
8
  import { H3 } from "../Headers/index.mjs";
9
9
  import { useEffect } from "react";
10
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
+ import { MaxHeightSmoother } from "../MaxHeightSmoother/index.mjs";
4
5
  import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
5
6
  import { useScrollDetection } from "../../hooks/useScrollDetection.mjs";
6
- import { MaxHeightSmoother } from "../MaxHeightSmoother/index.mjs";
7
7
  import { Burger } from "./Burger.mjs";
8
8
  import { useRef, useState } from "react";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils/cn.mjs";
4
- import { useItemSelector } from "../../hooks/useItemSelector.mjs";
5
4
  import { Button } from "../Button/Button.mjs";
5
+ import { useItemSelector } from "../../hooks/useItemSelector.mjs";
6
6
  import { useEffect, useRef } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { cva } from "class-variance-authority";
@@ -74,16 +74,16 @@ const Pagination = ({ currentPage, totalPages, onPageChange, showFirstLast = fal
74
74
  const isLastPage = currentPage === totalPages;
75
75
  const optionsRefs = useRef([]);
76
76
  const indicatorRef = useRef(null);
77
- const { choiceIndicatorPosition, calculatePosition } = useItemSelector(optionsRefs, {
77
+ const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(optionsRefs, {
78
78
  selector,
79
79
  isHoverable: true
80
80
  });
81
81
  useEffect(() => {
82
82
  const timer = setTimeout(() => {
83
- calculatePosition();
83
+ scheduleMeasurement();
84
84
  }, 300);
85
85
  return () => clearTimeout(timer);
86
- }, [currentPage, calculatePosition]);
86
+ }, [currentPage, scheduleMeasurement]);
87
87
  if (totalPages <= 1) return null;
88
88
  const handlePageChange = (page) => {
89
89
  if (!disabled && page >= 1 && page <= totalPages && page !== currentPage) onPageChange(page);
@@ -1 +1 @@
1
- {"version":3,"file":"Pagination.mjs","names":[],"sources":["../../../../src/components/Pagination/Pagination.tsx"],"sourcesContent":["'use client';\n\nimport { useItemSelector } from '@hooks/useItemSelector';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';\nimport {\n type ComponentProps,\n type FC,\n type HTMLAttributes,\n useEffect,\n useRef,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\n\nexport const paginationVariants = cva(\n 'flex items-center justify-center gap-1',\n {\n variants: {\n size: {\n sm: 'gap-1',\n md: 'gap-2',\n lg: 'gap-3',\n },\n color: {\n text: 'background-text',\n primary: 'background-primary',\n secondary: 'background-secondary',\n neutral: 'background-neutral',\n },\n variant: {\n default: '',\n bordered: 'rounded-lg border border-border p-2',\n ghost: 'bg-transparent',\n },\n },\n defaultVariants: {\n size: 'md',\n variant: 'default',\n },\n }\n);\n\nexport type PaginationSize = 'sm' | 'md' | 'lg';\n\nexport type PaginationVariant = 'default' | 'bordered' | 'ghost';\n\nexport type PaginationProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof paginationVariants> & {\n currentPage: number;\n totalPages: number;\n onPageChange: (page: number) => void;\n showFirstLast?: boolean;\n showPrevNext?: boolean;\n maxVisiblePages?: number;\n disabled?: boolean;\n };\n\nconst generatePageNumbers = (\n currentPage: number,\n totalPages: number,\n maxVisiblePages: number\n): (number | 'ellipsis')[] => {\n if (totalPages <= maxVisiblePages) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages: (number | 'ellipsis')[] = [];\n const halfVisible = Math.floor(maxVisiblePages / 2);\n\n pages.push(1);\n\n if (currentPage <= halfVisible + 2) {\n for (let i = 2; i <= Math.min(maxVisiblePages - 1, totalPages - 1); i++) {\n pages.push(i);\n }\n if (totalPages > maxVisiblePages) {\n pages.push('ellipsis');\n }\n if (totalPages > 1) {\n pages.push(totalPages);\n }\n } else if (currentPage >= totalPages - halfVisible - 1) {\n if (totalPages > maxVisiblePages) {\n pages.push('ellipsis');\n }\n for (\n let i = Math.max(2, totalPages - maxVisiblePages + 2);\n i <= totalPages;\n i++\n ) {\n pages.push(i);\n }\n } else {\n pages.push('ellipsis');\n const start = currentPage - halfVisible;\n const end = currentPage + halfVisible;\n for (let i = start; i <= end; i++) {\n pages.push(i);\n }\n pages.push('ellipsis');\n pages.push(totalPages);\n }\n\n return pages;\n};\n\nconst selector = (option: HTMLElement) =>\n option?.getAttribute('aria-current') === 'true';\n\nconst getButtonSize = (size?: PaginationSize | `${PaginationSize}` | null) => {\n if (size === 'sm') {\n return 'icon-sm';\n } else if (size === 'lg') {\n return 'icon-lg';\n } else {\n return 'icon-md';\n }\n};\n\nconst InputIndicator: FC<ComponentProps<'div'>> = (props) => (\n <div\n className=\"absolute top-0 z-0 h-full w-auto rounded-xl bg-text/20 ring-4 ring-text/10 transition-[left,width] duration-300 ease-in-out [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl motion-reduce:transition-none\"\n {...props}\n />\n);\n\nexport const Pagination: FC<PaginationProps> = ({\n currentPage,\n totalPages,\n onPageChange,\n showFirstLast = false,\n showPrevNext = true,\n maxVisiblePages = 5,\n disabled = false,\n size = 'md',\n variant = 'default',\n color = 'text',\n className,\n ...props\n}) => {\n const { goToNextPage, goToPreviousPage } = useIntlayer('pagination');\n\n const pageNumbers = generatePageNumbers(\n currentPage,\n totalPages,\n maxVisiblePages\n );\n\n const buttonSize = getButtonSize(size);\n const isFirstPage = currentPage === 1;\n const isLastPage = currentPage === totalPages;\n\n const optionsRefs = useRef<HTMLElement[]>([]);\n const indicatorRef = useRef<HTMLDivElement | null>(null);\n const { choiceIndicatorPosition, calculatePosition } = useItemSelector(\n optionsRefs,\n {\n selector,\n isHoverable: true,\n }\n );\n\n useEffect(() => {\n const timer = setTimeout(() => {\n calculatePosition();\n }, 300);\n\n return () => clearTimeout(timer);\n }, [currentPage, calculatePosition]);\n\n if (totalPages <= 1) return null;\n\n const handlePageChange = (page: number) => {\n if (!disabled && page >= 1 && page <= totalPages && page !== currentPage) {\n onPageChange(page);\n }\n };\n\n return (\n <div\n className={cn(paginationVariants({ size, variant }), className)}\n {...props}\n >\n <div className=\"relative flex items-center gap-1\">\n {choiceIndicatorPosition && (\n <InputIndicator style={choiceIndicatorPosition} ref={indicatorRef} />\n )}\n\n {showPrevNext && (\n <Button\n variant=\"outline\"\n size={buttonSize}\n color=\"text\"\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={disabled || isFirstPage}\n label={goToPreviousPage.value}\n Icon={ChevronLeft}\n ref={(el) => {\n if (el) optionsRefs.current[0] = el;\n }}\n className=\"min-w-0 px-2\"\n />\n )}\n\n <div className=\"flex items-center gap-1 max-md:gap-0.5\">\n {pageNumbers.map((page, index) => {\n if (page === 'ellipsis') {\n const isFirstEllipsis = index === pageNumbers.indexOf('ellipsis');\n const ellipsisKey = isFirstEllipsis\n ? 'ellipsis-start'\n : 'ellipsis-end';\n return (\n <div\n key={ellipsisKey}\n className=\"flex h-8 min-w-8 items-center justify-center px-1\"\n >\n <MoreHorizontal className=\"h-4 w-4 text-muted-foreground\" />\n </div>\n );\n }\n\n const isActive = page === currentPage;\n // Calculate ref index: offset by 1 if showPrevNext, then count only non-ellipsis items\n const refIndex =\n (showPrevNext ? 1 : 0) +\n pageNumbers.slice(0, index).filter((p) => p !== 'ellipsis')\n .length;\n\n return (\n <Button\n key={page}\n variant={isActive ? 'default' : 'outline'}\n size={buttonSize}\n color=\"text\"\n onClick={() => handlePageChange(page)}\n disabled={disabled}\n label={`Go to page ${page}`}\n aria-current={isActive ? 'true' : 'false'}\n ref={(el) => {\n if (el) optionsRefs.current[refIndex] = el;\n }}\n className={cn(\n 'flex aspect-square h-8 w-8 min-w-0 items-center justify-center p-0 text-sm',\n size === 'sm' && 'h-6 w-6 text-xs',\n size === 'lg' && 'size-10 text-base',\n isActive && 'font-semibold'\n )}\n >\n {page}\n </Button>\n );\n })}\n </div>\n\n {showPrevNext && (\n <Button\n variant=\"outline\"\n size={buttonSize}\n color=\"text\"\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={disabled || isLastPage}\n label={goToNextPage.value}\n Icon={ChevronRight}\n ref={(el) => {\n const lastRefIndex =\n (showPrevNext ? 1 : 0) +\n pageNumbers.filter((p) => p !== 'ellipsis').length;\n if (el) optionsRefs.current[lastRefIndex] = el;\n }}\n className=\"min-w-0 px-2\"\n />\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Pagination.mjs","names":[],"sources":["../../../../src/components/Pagination/Pagination.tsx"],"sourcesContent":["'use client';\n\nimport { useItemSelector } from '@hooks/useItemSelector';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';\nimport {\n type ComponentProps,\n type FC,\n type HTMLAttributes,\n useEffect,\n useRef,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button } from '../Button';\n\nexport const paginationVariants = cva(\n 'flex items-center justify-center gap-1',\n {\n variants: {\n size: {\n sm: 'gap-1',\n md: 'gap-2',\n lg: 'gap-3',\n },\n color: {\n text: 'background-text',\n primary: 'background-primary',\n secondary: 'background-secondary',\n neutral: 'background-neutral',\n },\n variant: {\n default: '',\n bordered: 'rounded-lg border border-border p-2',\n ghost: 'bg-transparent',\n },\n },\n defaultVariants: {\n size: 'md',\n variant: 'default',\n },\n }\n);\n\nexport type PaginationSize = 'sm' | 'md' | 'lg';\n\nexport type PaginationVariant = 'default' | 'bordered' | 'ghost';\n\nexport type PaginationProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof paginationVariants> & {\n currentPage: number;\n totalPages: number;\n onPageChange: (page: number) => void;\n showFirstLast?: boolean;\n showPrevNext?: boolean;\n maxVisiblePages?: number;\n disabled?: boolean;\n };\n\nconst generatePageNumbers = (\n currentPage: number,\n totalPages: number,\n maxVisiblePages: number\n): (number | 'ellipsis')[] => {\n if (totalPages <= maxVisiblePages) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages: (number | 'ellipsis')[] = [];\n const halfVisible = Math.floor(maxVisiblePages / 2);\n\n pages.push(1);\n\n if (currentPage <= halfVisible + 2) {\n for (let i = 2; i <= Math.min(maxVisiblePages - 1, totalPages - 1); i++) {\n pages.push(i);\n }\n if (totalPages > maxVisiblePages) {\n pages.push('ellipsis');\n }\n if (totalPages > 1) {\n pages.push(totalPages);\n }\n } else if (currentPage >= totalPages - halfVisible - 1) {\n if (totalPages > maxVisiblePages) {\n pages.push('ellipsis');\n }\n for (\n let i = Math.max(2, totalPages - maxVisiblePages + 2);\n i <= totalPages;\n i++\n ) {\n pages.push(i);\n }\n } else {\n pages.push('ellipsis');\n const start = currentPage - halfVisible;\n const end = currentPage + halfVisible;\n for (let i = start; i <= end; i++) {\n pages.push(i);\n }\n pages.push('ellipsis');\n pages.push(totalPages);\n }\n\n return pages;\n};\n\nconst selector = (option: HTMLElement) =>\n option?.getAttribute('aria-current') === 'true';\n\nconst getButtonSize = (size?: PaginationSize | `${PaginationSize}` | null) => {\n if (size === 'sm') {\n return 'icon-sm';\n } else if (size === 'lg') {\n return 'icon-lg';\n } else {\n return 'icon-md';\n }\n};\n\nconst InputIndicator: FC<ComponentProps<'div'>> = (props) => (\n <div\n className=\"absolute top-0 z-0 h-full w-auto rounded-xl bg-text/20 ring-4 ring-text/10 transition-[left,width] duration-300 ease-in-out [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl motion-reduce:transition-none\"\n {...props}\n />\n);\n\nexport const Pagination: FC<PaginationProps> = ({\n currentPage,\n totalPages,\n onPageChange,\n showFirstLast = false,\n showPrevNext = true,\n maxVisiblePages = 5,\n disabled = false,\n size = 'md',\n variant = 'default',\n color = 'text',\n className,\n ...props\n}) => {\n const { goToNextPage, goToPreviousPage } = useIntlayer('pagination');\n\n const pageNumbers = generatePageNumbers(\n currentPage,\n totalPages,\n maxVisiblePages\n );\n\n const buttonSize = getButtonSize(size);\n const isFirstPage = currentPage === 1;\n const isLastPage = currentPage === totalPages;\n\n const optionsRefs = useRef<HTMLElement[]>([]);\n const indicatorRef = useRef<HTMLDivElement | null>(null);\n const { choiceIndicatorPosition, scheduleMeasurement } = useItemSelector(\n optionsRefs,\n {\n selector,\n isHoverable: true,\n }\n );\n\n useEffect(() => {\n const timer = setTimeout(() => {\n scheduleMeasurement();\n }, 300);\n\n return () => clearTimeout(timer);\n }, [currentPage, scheduleMeasurement]);\n\n if (totalPages <= 1) return null;\n\n const handlePageChange = (page: number) => {\n if (!disabled && page >= 1 && page <= totalPages && page !== currentPage) {\n onPageChange(page);\n }\n };\n\n return (\n <div\n className={cn(paginationVariants({ size, variant }), className)}\n {...props}\n >\n <div className=\"relative flex items-center gap-1\">\n {choiceIndicatorPosition && (\n <InputIndicator style={choiceIndicatorPosition} ref={indicatorRef} />\n )}\n\n {showPrevNext && (\n <Button\n variant=\"outline\"\n size={buttonSize}\n color=\"text\"\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={disabled || isFirstPage}\n label={goToPreviousPage.value}\n Icon={ChevronLeft}\n ref={(el) => {\n if (el) optionsRefs.current[0] = el;\n }}\n className=\"min-w-0 px-2\"\n />\n )}\n\n <div className=\"flex items-center gap-1 max-md:gap-0.5\">\n {pageNumbers.map((page, index) => {\n if (page === 'ellipsis') {\n const isFirstEllipsis = index === pageNumbers.indexOf('ellipsis');\n const ellipsisKey = isFirstEllipsis\n ? 'ellipsis-start'\n : 'ellipsis-end';\n return (\n <div\n key={ellipsisKey}\n className=\"flex h-8 min-w-8 items-center justify-center px-1\"\n >\n <MoreHorizontal className=\"h-4 w-4 text-muted-foreground\" />\n </div>\n );\n }\n\n const isActive = page === currentPage;\n // Calculate ref index: offset by 1 if showPrevNext, then count only non-ellipsis items\n const refIndex =\n (showPrevNext ? 1 : 0) +\n pageNumbers.slice(0, index).filter((p) => p !== 'ellipsis')\n .length;\n\n return (\n <Button\n key={page}\n variant={isActive ? 'default' : 'outline'}\n size={buttonSize}\n color=\"text\"\n onClick={() => handlePageChange(page)}\n disabled={disabled}\n label={`Go to page ${page}`}\n aria-current={isActive ? 'true' : 'false'}\n ref={(el) => {\n if (el) optionsRefs.current[refIndex] = el;\n }}\n className={cn(\n 'flex aspect-square h-8 w-8 min-w-0 items-center justify-center p-0 text-sm',\n size === 'sm' && 'h-6 w-6 text-xs',\n size === 'lg' && 'size-10 text-base',\n isActive && 'font-semibold'\n )}\n >\n {page}\n </Button>\n );\n })}\n </div>\n\n {showPrevNext && (\n <Button\n variant=\"outline\"\n size={buttonSize}\n color=\"text\"\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={disabled || isLastPage}\n label={goToNextPage.value}\n Icon={ChevronRight}\n ref={(el) => {\n const lastRefIndex =\n (showPrevNext ? 1 : 0) +\n pageNumbers.filter((p) => p !== 'ellipsis').length;\n if (el) optionsRefs.current[lastRefIndex] = el;\n }}\n className=\"min-w-0 px-2\"\n />\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
+ import { useTheme } from "../../providers/ThemeProvider.mjs";
3
4
  import { cn } from "../../utils/cn.mjs";
4
- import { useIsDarkMode } from "../../hooks/useIsDarkMode.mjs";
5
5
  import { useEffect, useRef } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
 
@@ -153,7 +153,8 @@ const updateGridPoints = (grid, mouseX, mouseY, mouseActive, radius, strength) =
153
153
  */
154
154
  const SETTLE_THRESHOLD_PX = .05;
155
155
  const GridDistortionPattern = ({ width = 70, height = 70, radius = 280, strength = .12, lineColor, highlightColor, className, ...props }) => {
156
- const theme = useIsDarkMode() ? DARK_THEME : LIGHT_THEME;
156
+ const { resolvedTheme } = useTheme();
157
+ const theme = resolvedTheme === "dark" ? DARK_THEME : LIGHT_THEME;
157
158
  const resolvedLineColor = lineColor ?? theme.lineColor;
158
159
  const resolvedHighlightColor = highlightColor ?? theme.highlightColor;
159
160
  const svgRef = useRef(null);