@jbpark/ui-kit 5.0.0 → 5.1.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 (51) hide show
  1. package/dist/Space.mjs +2 -0
  2. package/dist/Space.mjs.map +1 -1
  3. package/dist/components/atoms/skeleton/skeleton.mjs +1 -1
  4. package/dist/components/atoms/skeleton/skeleton.mjs.map +1 -1
  5. package/dist/components/molecules/dropdown.mjs +1 -1
  6. package/dist/components/molecules/dropdown.mjs.map +1 -1
  7. package/dist/components/organisms/imperative-stack.mjs +6 -4
  8. package/dist/components/organisms/imperative-stack.mjs.map +1 -1
  9. package/dist/components/organisms/modal.d.mts.map +1 -1
  10. package/dist/components/organisms/modal.mjs +12 -7
  11. package/dist/components/organisms/modal.mjs.map +1 -1
  12. package/dist/components/organisms/toast.d.mts.map +1 -1
  13. package/dist/components/organisms/toast.mjs +3 -1
  14. package/dist/components/organisms/toast.mjs.map +1 -1
  15. package/dist/components/templates/grid/col.d.mts.map +1 -1
  16. package/dist/components/templates/grid/col.mjs +35 -15
  17. package/dist/components/templates/grid/col.mjs.map +1 -1
  18. package/dist/components/templates/grid/row-context.mjs +1 -2
  19. package/dist/components/templates/grid/row-context.mjs.map +1 -1
  20. package/dist/components/templates/grid/row.d.mts.map +1 -1
  21. package/dist/components/templates/grid/row.mjs +2 -9
  22. package/dist/components/templates/grid/row.mjs.map +1 -1
  23. package/dist/components/templates/layout/content.d.mts.map +1 -1
  24. package/dist/components/templates/layout/content.mjs +5 -1
  25. package/dist/components/templates/layout/content.mjs.map +1 -1
  26. package/dist/components/templates/layout/sider.mjs +22 -7
  27. package/dist/components/templates/layout/sider.mjs.map +1 -1
  28. package/dist/components/templates/page-header.d.mts.map +1 -1
  29. package/dist/components/templates/page-header.mjs +3 -1
  30. package/dist/components/templates/page-header.mjs.map +1 -1
  31. package/dist/core/badge.d.mts +1 -1
  32. package/dist/core/button.d.mts +1 -1
  33. package/dist/index.d.mts +2 -1
  34. package/dist/index.mjs +2 -1
  35. package/dist/providers/config/config.d.mts.map +1 -1
  36. package/dist/providers/config/config.mjs +7 -1
  37. package/dist/providers/config/config.mjs.map +1 -1
  38. package/dist/providers/config/context.d.mts.map +1 -1
  39. package/dist/providers/config/context.mjs +4 -1
  40. package/dist/providers/config/context.mjs.map +1 -1
  41. package/dist/providers/config/index.d.mts +2 -1
  42. package/dist/providers/config/registry.d.mts +14 -0
  43. package/dist/providers/config/registry.d.mts.map +1 -0
  44. package/dist/providers/config/registry.mjs +26 -0
  45. package/dist/providers/config/registry.mjs.map +1 -0
  46. package/dist/providers/config/types.d.mts +3 -0
  47. package/dist/providers/config/types.d.mts.map +1 -1
  48. package/dist/providers.d.mts +2 -1
  49. package/dist/providers.mjs +2 -1
  50. package/dist/style.css +40 -0
  51. package/package.json +1 -1
package/dist/Space.mjs CHANGED
@@ -21,6 +21,7 @@ const Space = ({ loading, loader, children, size = "middle", orientation = "hori
21
21
  if (loading) return /* @__PURE__ */ jsx("div", {
22
22
  className: cn(hidden && "hidden", className),
23
23
  style,
24
+ hidden,
24
25
  ...props,
25
26
  children: loader || /* @__PURE__ */ jsx(Skeleton, {})
26
27
  });
@@ -33,6 +34,7 @@ const Space = ({ loading, loader, children, size = "middle", orientation = "hori
33
34
  gridTemplateColumns: grid?.cols ? `repeat(${grid.cols}, minmax(0, 1fr))` : void 0,
34
35
  ...style
35
36
  },
37
+ hidden,
36
38
  ...props,
37
39
  children: Children.map(children, (child, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [child, split && i < count - 1 && split] }))
38
40
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Space.mjs","names":[],"sources":["../src/components/molecules/space.tsx"],"sourcesContent":["import { Children } from 'react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport Skeleton from '../atoms/skeleton';\n\nexport interface Props extends React.ComponentPropsWithRef<'div'> {\n loading?: boolean;\n loader?: React.ReactNode;\n orientation?: 'horizontal' | 'vertical';\n size?: 'small' | 'middle' | 'large' | number | number[];\n align?: 'start' | 'end' | 'center' | 'baseline';\n wrap?: boolean;\n split?: React.ReactNode;\n grid?: {\n rows?: number;\n cols?: number;\n };\n}\n\nconst sizes: Record<string, number[]> = {\n small: [4, 4],\n middle: [8, 8],\n large: [16, 16],\n};\n\nconst aligns: Record<string, string> = {\n start: 'items-start text-start',\n end: 'items-end text-end',\n center: 'items-center text-center',\n baseline: 'items-baseline text-baseline',\n};\n\nconst Space = ({\n loading,\n loader,\n children,\n size = 'middle',\n orientation = 'horizontal',\n align = 'center',\n wrap = false,\n split,\n className,\n grid,\n hidden,\n style,\n ...props\n}: Props) => {\n const [x, y] =\n typeof size === 'string'\n ? sizes[size] || [0, 0]\n : Array.isArray(size)\n ? size\n : [size, size];\n const count = Children.count(children);\n\n if (loading) {\n return (\n <div\n className={cn(hidden && 'hidden', className)}\n style={style}\n {...props}\n >\n {loader || <Skeleton />}\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'flex flex-nowrap',\n orientation === 'horizontal' ? 'flex-row' : 'flex-col',\n wrap && 'flex-wrap',\n aligns[align],\n grid && 'grid',\n hidden && 'hidden',\n className,\n )}\n style={{\n columnGap: x,\n rowGap: y,\n gridTemplateRows: grid?.rows\n ? `repeat(${grid.rows}, minmax(0, 1fr))`\n : undefined,\n gridTemplateColumns: grid?.cols\n ? `repeat(${grid.cols}, minmax(0, 1fr))`\n : undefined,\n ...style,\n }}\n {...props}\n >\n {Children.map(children, (child, i) => (\n <>\n {child}\n {split && i < count - 1 && split}\n </>\n ))}\n </div>\n );\n};\n\nexport default Space;\n"],"mappings":";;;;;;AAoBA,MAAM,QAAkC;CACtC,OAAO,CAAC,GAAG,EAAE;CACb,QAAQ,CAAC,GAAG,EAAE;CACd,OAAO,CAAC,IAAI,GAAG;CAChB;AAED,MAAM,SAAiC;CACrC,OAAO;CACP,KAAK;CACL,QAAQ;CACR,UAAU;CACX;AAED,MAAM,SAAS,EACb,SACA,QACA,UACA,OAAO,UACP,cAAc,cACd,QAAQ,UACR,OAAO,OACP,OACA,WACA,MACA,QACA,OACA,GAAG,YACQ;CACX,MAAM,CAAC,GAAG,KACR,OAAO,SAAS,WACZ,MAAM,SAAS,CAAC,GAAG,EAAE,GACrB,MAAM,QAAQ,KAAK,GACjB,OACA,CAAC,MAAM,KAAK;CACpB,MAAM,QAAQ,SAAS,MAAM,SAAS;AAEtC,KAAI,QACF,QACE,oBAAC;EACC,WAAW,GAAG,UAAU,UAAU,UAAU;EACrC;EACP,GAAI;YAEH,UAAU,oBAAC,aAAW;GACnB;AAIV,QACE,oBAAC;EACC,WAAW,GACT,oBACA,gBAAgB,eAAe,aAAa,YAC5C,QAAQ,aACR,OAAO,QACP,QAAQ,QACR,UAAU,UACV,UACD;EACD,OAAO;GACL,WAAW;GACX,QAAQ;GACR,kBAAkB,MAAM,OACpB,UAAU,KAAK,KAAK,qBACpB;GACJ,qBAAqB,MAAM,OACvB,UAAU,KAAK,KAAK,qBACpB;GACJ,GAAG;GACJ;EACD,GAAI;YAEH,SAAS,IAAI,WAAW,OAAO,MAC9B,8CACG,OACA,SAAS,IAAI,QAAQ,KAAK,SAC1B,CACH;GACE"}
1
+ {"version":3,"file":"Space.mjs","names":[],"sources":["../src/components/molecules/space.tsx"],"sourcesContent":["import { Children } from 'react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport Skeleton from '../atoms/skeleton';\n\nexport interface Props extends React.ComponentPropsWithRef<'div'> {\n loading?: boolean;\n loader?: React.ReactNode;\n orientation?: 'horizontal' | 'vertical';\n size?: 'small' | 'middle' | 'large' | number | number[];\n align?: 'start' | 'end' | 'center' | 'baseline';\n wrap?: boolean;\n split?: React.ReactNode;\n grid?: {\n rows?: number;\n cols?: number;\n };\n}\n\nconst sizes: Record<string, number[]> = {\n small: [4, 4],\n middle: [8, 8],\n large: [16, 16],\n};\n\nconst aligns: Record<string, string> = {\n start: 'items-start text-start',\n end: 'items-end text-end',\n center: 'items-center text-center',\n baseline: 'items-baseline text-baseline',\n};\n\nconst Space = ({\n loading,\n loader,\n children,\n size = 'middle',\n orientation = 'horizontal',\n align = 'center',\n wrap = false,\n split,\n className,\n grid,\n hidden,\n style,\n ...props\n}: Props) => {\n const [x, y] =\n typeof size === 'string'\n ? sizes[size] || [0, 0]\n : Array.isArray(size)\n ? size\n : [size, size];\n const count = Children.count(children);\n\n if (loading) {\n return (\n <div\n className={cn(hidden && 'hidden', className)}\n style={style}\n hidden={hidden}\n {...props}\n >\n {loader || <Skeleton />}\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'flex flex-nowrap',\n orientation === 'horizontal' ? 'flex-row' : 'flex-col',\n wrap && 'flex-wrap',\n aligns[align],\n grid && 'grid',\n hidden && 'hidden',\n className,\n )}\n style={{\n columnGap: x,\n rowGap: y,\n gridTemplateRows: grid?.rows\n ? `repeat(${grid.rows}, minmax(0, 1fr))`\n : undefined,\n gridTemplateColumns: grid?.cols\n ? `repeat(${grid.cols}, minmax(0, 1fr))`\n : undefined,\n ...style,\n }}\n hidden={hidden}\n {...props}\n >\n {Children.map(children, (child, i) => (\n <>\n {child}\n {split && i < count - 1 && split}\n </>\n ))}\n </div>\n );\n};\n\nexport default Space;\n"],"mappings":";;;;;;AAoBA,MAAM,QAAkC;CACtC,OAAO,CAAC,GAAG,EAAE;CACb,QAAQ,CAAC,GAAG,EAAE;CACd,OAAO,CAAC,IAAI,GAAG;CAChB;AAED,MAAM,SAAiC;CACrC,OAAO;CACP,KAAK;CACL,QAAQ;CACR,UAAU;CACX;AAED,MAAM,SAAS,EACb,SACA,QACA,UACA,OAAO,UACP,cAAc,cACd,QAAQ,UACR,OAAO,OACP,OACA,WACA,MACA,QACA,OACA,GAAG,YACQ;CACX,MAAM,CAAC,GAAG,KACR,OAAO,SAAS,WACZ,MAAM,SAAS,CAAC,GAAG,EAAE,GACrB,MAAM,QAAQ,KAAK,GACjB,OACA,CAAC,MAAM,KAAK;CACpB,MAAM,QAAQ,SAAS,MAAM,SAAS;AAEtC,KAAI,QACF,QACE,oBAAC;EACC,WAAW,GAAG,UAAU,UAAU,UAAU;EACrC;EACC;EACR,GAAI;YAEH,UAAU,oBAAC,aAAW;GACnB;AAIV,QACE,oBAAC;EACC,WAAW,GACT,oBACA,gBAAgB,eAAe,aAAa,YAC5C,QAAQ,aACR,OAAO,QACP,QAAQ,QACR,UAAU,UACV,UACD;EACD,OAAO;GACL,WAAW;GACX,QAAQ;GACR,kBAAkB,MAAM,OACpB,UAAU,KAAK,KAAK,qBACpB;GACJ,qBAAqB,MAAM,OACvB,UAAU,KAAK,KAAK,qBACpB;GACJ,GAAG;GACJ;EACO;EACR,GAAI;YAEH,SAAS,IAAI,WAAW,OAAO,MAC9B,8CACG,OACA,SAAS,IAAI,QAAQ,KAAK,SAC1B,CACH;GACE"}
@@ -22,6 +22,7 @@ const Skeleton = ({ active = true, loading = true, avatar, size = "default", dir
22
22
  role: "status",
23
23
  "aria-label": locale.loading ?? DEFAULT_LOCALE.loading,
24
24
  className: cn("flex items-center gap-3", className),
25
+ style,
25
26
  ...props,
26
27
  children: [avatar && /* @__PURE__ */ jsx(Core, { className: cn("h-12 w-12", "rounded-xl", !active && "animate-none", classNames.avatar) }), /* @__PURE__ */ jsx("div", {
27
28
  className: cn("flex grow", direction === "vertical" && "flex-col", classNames.wrapper),
@@ -32,7 +33,6 @@ const Skeleton = ({ active = true, loading = true, avatar, size = "default", dir
32
33
  return /* @__PURE__ */ jsx(Core, {
33
34
  className: cn("rounded-md", !active && "animate-none", SIZES[size], classNames.item),
34
35
  style: {
35
- ...style,
36
36
  width: itemWidth,
37
37
  height: itemHeight
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"skeleton.mjs","names":["skeleton"],"sources":["../../../../src/components/atoms/skeleton/skeleton.tsx"],"sourcesContent":["'use client';\n\nimport { skeleton } from '@repo/ui/core';\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nconst { Skeleton: Core } = skeleton;\n\nconst getValueAtIndex = <T,>(value: T | T[], index: number) => {\n return Array.isArray(value) ? (value[index] ?? value[0]) : value;\n};\n\nexport interface Props extends React.ComponentPropsWithoutRef<'div'> {\n active?: boolean;\n loading?: boolean;\n avatar?: boolean;\n size?: 'default' | 'small' | 'large';\n count?: number;\n gap?: number;\n direction?: 'horizontal' | 'vertical';\n width?: string | number | (string | number)[];\n height?: string | number | (string | number)[];\n classNames?: {\n wrapper?: string;\n avatar?: string;\n item?: string;\n };\n}\n\nconst SIZES = {\n small: 'w-48 h-4',\n default: 'w-60 h-6',\n large: 'w-72 h-8',\n};\n\nconst Skeleton = ({\n active = true,\n loading = true,\n avatar,\n size = 'default',\n direction = 'vertical',\n count = 1,\n gap = 6,\n className,\n classNames = {},\n style,\n width,\n height,\n children,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n\n // Not wrapping `children` here (unlike Spin's overlay mode) — Skeleton\n // is meant to swap in arbitrary real content once loading finishes,\n // which can include elements with strict parent requirements (e.g. a\n // `<tr>`), so adding a wrapper div could produce invalid markup.\n // `className`/`style`/rest props only ever describe the skeleton's own\n // placeholder markup, so there's nothing for them to apply to here.\n if (!loading) {\n return children ?? null;\n }\n\n return (\n <div\n role=\"status\"\n aria-label={locale.loading ?? DEFAULT_LOCALE.loading}\n className={cn('flex items-center gap-3', className)}\n {...props}\n >\n {avatar && (\n <Core\n className={cn(\n 'h-12 w-12',\n 'rounded-xl',\n !active && 'animate-none',\n classNames.avatar,\n )}\n />\n )}\n <div\n className={cn(\n 'flex grow',\n direction === 'vertical' && 'flex-col',\n classNames.wrapper,\n )}\n style={{\n gap,\n }}\n >\n {Array.from({ length: count }).map((_, index) => {\n const itemWidth = getValueAtIndex(width, index);\n const itemHeight = getValueAtIndex(height, index);\n\n return (\n <Core\n key={index}\n className={cn(\n 'rounded-md',\n !active && 'animate-none',\n SIZES[size],\n classNames.item,\n //\n )}\n style={{\n ...style,\n width: itemWidth,\n height: itemHeight,\n }}\n />\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default Skeleton;\n"],"mappings":";;;;;;;;AAMA,MAAM,EAAE,UAAU,SAASA;AAE3B,MAAM,mBAAuB,OAAgB,UAAkB;AAC7D,QAAO,MAAM,QAAQ,MAAM,GAAI,MAAM,UAAU,MAAM,KAAM;;AAoB7D,MAAM,QAAQ;CACZ,OAAO;CACP,SAAS;CACT,OAAO;CACR;AAED,MAAM,YAAY,EAChB,SAAS,MACT,UAAU,MACV,QACA,OAAO,WACP,YAAY,YACZ,QAAQ,GACR,MAAM,GACN,WACA,aAAa,EAAE,EACf,OACA,OACA,QACA,UACA,GAAG,YACQ;CACX,MAAM,EAAE,WAAW,WAAW;AAQ9B,KAAI,CAAC,QACH,QAAO,YAAY;AAGrB,QACE,qBAAC;EACC,MAAK;EACL,cAAY,OAAO,WAAW,eAAe;EAC7C,WAAW,GAAG,2BAA2B,UAAU;EACnD,GAAI;aAEH,UACC,oBAAC,QACC,WAAW,GACT,aACA,cACA,CAAC,UAAU,gBACX,WAAW,OACZ,GACD,EAEJ,oBAAC;GACC,WAAW,GACT,aACA,cAAc,cAAc,YAC5B,WAAW,QACZ;GACD,OAAO,EACL,KACD;aAEA,MAAM,KAAK,EAAE,QAAQ,OAAO,CAAC,CAAC,KAAK,GAAG,UAAU;IAC/C,MAAM,YAAY,gBAAgB,OAAO,MAAM;IAC/C,MAAM,aAAa,gBAAgB,QAAQ,MAAM;AAEjD,WACE,oBAAC;KAEC,WAAW,GACT,cACA,CAAC,UAAU,gBACX,MAAM,OACN,WAAW,KAEZ;KACD,OAAO;MACL,GAAG;MACH,OAAO;MACP,QAAQ;MACT;OAZI,MAaL;KAEJ;IACE;GACF"}
1
+ {"version":3,"file":"skeleton.mjs","names":["skeleton"],"sources":["../../../../src/components/atoms/skeleton/skeleton.tsx"],"sourcesContent":["'use client';\n\nimport { skeleton } from '@repo/ui/core';\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nconst { Skeleton: Core } = skeleton;\n\nconst getValueAtIndex = <T,>(value: T | T[], index: number) => {\n return Array.isArray(value) ? (value[index] ?? value[0]) : value;\n};\n\nexport interface Props extends React.ComponentPropsWithoutRef<'div'> {\n active?: boolean;\n loading?: boolean;\n avatar?: boolean;\n size?: 'default' | 'small' | 'large';\n count?: number;\n gap?: number;\n direction?: 'horizontal' | 'vertical';\n width?: string | number | (string | number)[];\n height?: string | number | (string | number)[];\n classNames?: {\n wrapper?: string;\n avatar?: string;\n item?: string;\n };\n}\n\nconst SIZES = {\n small: 'w-48 h-4',\n default: 'w-60 h-6',\n large: 'w-72 h-8',\n};\n\nconst Skeleton = ({\n active = true,\n loading = true,\n avatar,\n size = 'default',\n direction = 'vertical',\n count = 1,\n gap = 6,\n className,\n classNames = {},\n style,\n width,\n height,\n children,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n\n // Not wrapping `children` here (unlike Spin's overlay mode) — Skeleton\n // is meant to swap in arbitrary real content once loading finishes,\n // which can include elements with strict parent requirements (e.g. a\n // `<tr>`), so adding a wrapper div could produce invalid markup.\n // `className`/`style`/rest props only ever describe the skeleton's own\n // placeholder markup, so there's nothing for them to apply to here.\n if (!loading) {\n return children ?? null;\n }\n\n return (\n <div\n role=\"status\"\n aria-label={locale.loading ?? DEFAULT_LOCALE.loading}\n className={cn('flex items-center gap-3', className)}\n style={style}\n {...props}\n >\n {avatar && (\n <Core\n className={cn(\n 'h-12 w-12',\n 'rounded-xl',\n !active && 'animate-none',\n classNames.avatar,\n )}\n />\n )}\n <div\n className={cn(\n 'flex grow',\n direction === 'vertical' && 'flex-col',\n classNames.wrapper,\n )}\n style={{\n gap,\n }}\n >\n {Array.from({ length: count }).map((_, index) => {\n const itemWidth = getValueAtIndex(width, index);\n const itemHeight = getValueAtIndex(height, index);\n\n return (\n <Core\n key={index}\n className={cn(\n 'rounded-md',\n !active && 'animate-none',\n SIZES[size],\n classNames.item,\n //\n )}\n style={{\n width: itemWidth,\n height: itemHeight,\n }}\n />\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default Skeleton;\n"],"mappings":";;;;;;;;AAMA,MAAM,EAAE,UAAU,SAASA;AAE3B,MAAM,mBAAuB,OAAgB,UAAkB;AAC7D,QAAO,MAAM,QAAQ,MAAM,GAAI,MAAM,UAAU,MAAM,KAAM;;AAoB7D,MAAM,QAAQ;CACZ,OAAO;CACP,SAAS;CACT,OAAO;CACR;AAED,MAAM,YAAY,EAChB,SAAS,MACT,UAAU,MACV,QACA,OAAO,WACP,YAAY,YACZ,QAAQ,GACR,MAAM,GACN,WACA,aAAa,EAAE,EACf,OACA,OACA,QACA,UACA,GAAG,YACQ;CACX,MAAM,EAAE,WAAW,WAAW;AAQ9B,KAAI,CAAC,QACH,QAAO,YAAY;AAGrB,QACE,qBAAC;EACC,MAAK;EACL,cAAY,OAAO,WAAW,eAAe;EAC7C,WAAW,GAAG,2BAA2B,UAAU;EAC5C;EACP,GAAI;aAEH,UACC,oBAAC,QACC,WAAW,GACT,aACA,cACA,CAAC,UAAU,gBACX,WAAW,OACZ,GACD,EAEJ,oBAAC;GACC,WAAW,GACT,aACA,cAAc,cAAc,YAC5B,WAAW,QACZ;GACD,OAAO,EACL,KACD;aAEA,MAAM,KAAK,EAAE,QAAQ,OAAO,CAAC,CAAC,KAAK,GAAG,UAAU;IAC/C,MAAM,YAAY,gBAAgB,OAAO,MAAM;IAC/C,MAAM,aAAa,gBAAgB,QAAQ,MAAM;AAEjD,WACE,oBAAC;KAEC,WAAW,GACT,cACA,CAAC,UAAU,gBACX,MAAM,OACN,WAAW,KAEZ;KACD,OAAO;MACL,OAAO;MACP,QAAQ;MACT;OAXI,MAYL;KAEJ;IACE;GACF"}
@@ -60,7 +60,7 @@ const Dropdown = ({ children, menu, trigger = "hover", open: _open, onOpenChange
60
60
  },
61
61
  children
62
62
  }), /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(motion.div, {
63
- className: cn("absolute top-full pt-2"),
63
+ className: cn("absolute top-full z-50 pt-2"),
64
64
  initial: {
65
65
  opacity: 0,
66
66
  y: -10
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown.mjs","names":[],"sources":["../../../src/components/molecules/dropdown.tsx"],"sourcesContent":["'use client';\n\nimport { useId, useRef } from 'react';\n\nimport { useControllableState } from '@jbpark/use-hooks';\nimport { AnimatePresence, motion } from 'motion/react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport Menu, { MenuProps } from './menu';\n\ntype ChangeEventHandler = (open: boolean) => void;\n\nexport interface Props extends React.ComponentPropsWithoutRef<'div'> {\n open?: boolean;\n trigger?: 'hover' | 'click';\n menu?: MenuProps;\n onOpenChange?: ChangeEventHandler;\n}\n\nconst Dropdown = ({\n children,\n menu,\n trigger = 'hover',\n open: _open,\n onOpenChange: _onOpenChange = () => {},\n className,\n onMouseEnter,\n onMouseLeave,\n ...props\n}: Props) => {\n const [open, onOpenChange] = useControllableState<boolean>({\n value: _open,\n defaultValue: false,\n onChange: _onOpenChange,\n });\n\n const isClickTrigger = trigger === 'click';\n const menuId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n\n return (\n <div\n {...props}\n ref={containerRef}\n className={cn(\n 'relative',\n 'inline-block cursor-pointer',\n className,\n //\n )}\n onMouseEnter={event => {\n onMouseEnter?.(event);\n if (isClickTrigger) {\n return;\n }\n onOpenChange(true);\n }}\n onMouseLeave={event => {\n onMouseLeave?.(event);\n if (isClickTrigger) {\n return;\n }\n onOpenChange(false);\n }}\n >\n <div\n role={isClickTrigger ? 'button' : undefined}\n tabIndex={isClickTrigger ? 0 : undefined}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={open ? menuId : undefined}\n onClick={() => {\n if (!isClickTrigger) {\n return;\n }\n onOpenChange(!open);\n }}\n onFocus={() => {\n if (isClickTrigger) {\n return;\n }\n onOpenChange(true);\n }}\n onBlur={e => {\n // Without this check, moving focus from the trigger into the\n // menu itself (e.g. via Tab) closed the dropdown before the\n // user could ever reach it — the mouse path avoided the same\n // bug only by way of onMouseDown's preventDefault below, which\n // does nothing for keyboard focus. Mirrors the containment\n // check menu/item/item.tsx already uses for the same reason.\n if (!containerRef.current?.contains(e.relatedTarget as Node)) {\n onOpenChange(false);\n }\n }}\n onKeyDown={e => {\n if (e.key === 'Escape') {\n onOpenChange(false);\n return;\n }\n if (isClickTrigger && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onOpenChange(!open);\n }\n }}\n >\n {children}\n </div>\n <AnimatePresence>\n {open && (\n <motion.div\n className={cn(\n 'absolute top-full pt-2',\n //\n )}\n initial={{ opacity: 0, y: -10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -10 }}\n transition={{ duration: 0.1 }}\n >\n <Menu\n {...menu}\n id={menuId}\n onMouseDown={e => {\n e.preventDefault();\n }}\n />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n};\n\nexport default Dropdown;\n"],"mappings":";;;;;;;;;;AAoBA,MAAM,YAAY,EAChB,UACA,MACA,UAAU,SACV,MAAM,OACN,cAAc,sBAAsB,IACpC,WACA,cACA,cACA,GAAG,YACQ;CACX,MAAM,CAAC,MAAM,gBAAgB,qBAA8B;EACzD,OAAO;EACP,cAAc;EACd,UAAU;EACX,CAAC;CAEF,MAAM,iBAAiB,YAAY;CACnC,MAAM,SAAS,OAAO;CACtB,MAAM,eAAe,OAAuB,KAAK;AAEjD,QACE,qBAAC;EACC,GAAI;EACJ,KAAK;EACL,WAAW,GACT,YACA,+BACA,UAED;EACD,eAAc,UAAS;AACrB,kBAAe,MAAM;AACrB,OAAI,eACF;AAEF,gBAAa,KAAK;;EAEpB,eAAc,UAAS;AACrB,kBAAe,MAAM;AACrB,OAAI,eACF;AAEF,gBAAa,MAAM;;aAGrB,oBAAC;GACC,MAAM,iBAAiB,WAAW;GAClC,UAAU,iBAAiB,IAAI;GAC/B,iBAAc;GACd,iBAAe;GACf,iBAAe,OAAO,SAAS;GAC/B,eAAe;AACb,QAAI,CAAC,eACH;AAEF,iBAAa,CAAC,KAAK;;GAErB,eAAe;AACb,QAAI,eACF;AAEF,iBAAa,KAAK;;GAEpB,SAAQ,MAAK;AAOX,QAAI,CAAC,aAAa,SAAS,SAAS,EAAE,cAAsB,CAC1D,cAAa,MAAM;;GAGvB,YAAW,MAAK;AACd,QAAI,EAAE,QAAQ,UAAU;AACtB,kBAAa,MAAM;AACnB;;AAEF,QAAI,mBAAmB,EAAE,QAAQ,WAAW,EAAE,QAAQ,MAAM;AAC1D,OAAE,gBAAgB;AAClB,kBAAa,CAAC,KAAK;;;GAItB;IACG,EACN,oBAAC,6BACE,QACC,oBAAC,OAAO;GACN,WAAW,GACT,yBAED;GACD,SAAS;IAAE,SAAS;IAAG,GAAG;IAAK;GAC/B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;GAC7B,MAAM;IAAE,SAAS;IAAG,GAAG;IAAK;GAC5B,YAAY,EAAE,UAAU,IAAK;aAE7B,oBAAC;IACC,GAAI;IACJ,IAAI;IACJ,cAAa,MAAK;AAChB,OAAE,gBAAgB;;KAEpB;IACS,GAEC;GACd"}
1
+ {"version":3,"file":"dropdown.mjs","names":[],"sources":["../../../src/components/molecules/dropdown.tsx"],"sourcesContent":["'use client';\n\nimport { useId, useRef } from 'react';\n\nimport { useControllableState } from '@jbpark/use-hooks';\nimport { AnimatePresence, motion } from 'motion/react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport Menu, { MenuProps } from './menu';\n\ntype ChangeEventHandler = (open: boolean) => void;\n\nexport interface Props extends React.ComponentPropsWithoutRef<'div'> {\n open?: boolean;\n trigger?: 'hover' | 'click';\n menu?: MenuProps;\n onOpenChange?: ChangeEventHandler;\n}\n\nconst Dropdown = ({\n children,\n menu,\n trigger = 'hover',\n open: _open,\n onOpenChange: _onOpenChange = () => {},\n className,\n onMouseEnter,\n onMouseLeave,\n ...props\n}: Props) => {\n const [open, onOpenChange] = useControllableState<boolean>({\n value: _open,\n defaultValue: false,\n onChange: _onOpenChange,\n });\n\n const isClickTrigger = trigger === 'click';\n const menuId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n\n return (\n <div\n {...props}\n ref={containerRef}\n className={cn(\n 'relative',\n 'inline-block cursor-pointer',\n className,\n //\n )}\n onMouseEnter={event => {\n onMouseEnter?.(event);\n if (isClickTrigger) {\n return;\n }\n onOpenChange(true);\n }}\n onMouseLeave={event => {\n onMouseLeave?.(event);\n if (isClickTrigger) {\n return;\n }\n onOpenChange(false);\n }}\n >\n <div\n role={isClickTrigger ? 'button' : undefined}\n tabIndex={isClickTrigger ? 0 : undefined}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={open ? menuId : undefined}\n onClick={() => {\n if (!isClickTrigger) {\n return;\n }\n onOpenChange(!open);\n }}\n onFocus={() => {\n if (isClickTrigger) {\n return;\n }\n onOpenChange(true);\n }}\n onBlur={e => {\n // Without this check, moving focus from the trigger into the\n // menu itself (e.g. via Tab) closed the dropdown before the\n // user could ever reach it — the mouse path avoided the same\n // bug only by way of onMouseDown's preventDefault below, which\n // does nothing for keyboard focus. Mirrors the containment\n // check menu/item/item.tsx already uses for the same reason.\n if (!containerRef.current?.contains(e.relatedTarget as Node)) {\n onOpenChange(false);\n }\n }}\n onKeyDown={e => {\n if (e.key === 'Escape') {\n onOpenChange(false);\n return;\n }\n if (isClickTrigger && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onOpenChange(!open);\n }\n }}\n >\n {children}\n </div>\n <AnimatePresence>\n {open && (\n <motion.div\n className={cn(\n 'absolute top-full z-50 pt-2',\n //\n )}\n initial={{ opacity: 0, y: -10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -10 }}\n transition={{ duration: 0.1 }}\n >\n <Menu\n {...menu}\n id={menuId}\n onMouseDown={e => {\n e.preventDefault();\n }}\n />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n );\n};\n\nexport default Dropdown;\n"],"mappings":";;;;;;;;;;AAoBA,MAAM,YAAY,EAChB,UACA,MACA,UAAU,SACV,MAAM,OACN,cAAc,sBAAsB,IACpC,WACA,cACA,cACA,GAAG,YACQ;CACX,MAAM,CAAC,MAAM,gBAAgB,qBAA8B;EACzD,OAAO;EACP,cAAc;EACd,UAAU;EACX,CAAC;CAEF,MAAM,iBAAiB,YAAY;CACnC,MAAM,SAAS,OAAO;CACtB,MAAM,eAAe,OAAuB,KAAK;AAEjD,QACE,qBAAC;EACC,GAAI;EACJ,KAAK;EACL,WAAW,GACT,YACA,+BACA,UAED;EACD,eAAc,UAAS;AACrB,kBAAe,MAAM;AACrB,OAAI,eACF;AAEF,gBAAa,KAAK;;EAEpB,eAAc,UAAS;AACrB,kBAAe,MAAM;AACrB,OAAI,eACF;AAEF,gBAAa,MAAM;;aAGrB,oBAAC;GACC,MAAM,iBAAiB,WAAW;GAClC,UAAU,iBAAiB,IAAI;GAC/B,iBAAc;GACd,iBAAe;GACf,iBAAe,OAAO,SAAS;GAC/B,eAAe;AACb,QAAI,CAAC,eACH;AAEF,iBAAa,CAAC,KAAK;;GAErB,eAAe;AACb,QAAI,eACF;AAEF,iBAAa,KAAK;;GAEpB,SAAQ,MAAK;AAOX,QAAI,CAAC,aAAa,SAAS,SAAS,EAAE,cAAsB,CAC1D,cAAa,MAAM;;GAGvB,YAAW,MAAK;AACd,QAAI,EAAE,QAAQ,UAAU;AACtB,kBAAa,MAAM;AACnB;;AAEF,QAAI,mBAAmB,EAAE,QAAQ,WAAW,EAAE,QAAQ,MAAM;AAC1D,OAAE,gBAAgB;AAClB,kBAAa,CAAC,KAAK;;;GAItB;IACG,EACN,oBAAC,6BACE,QACC,oBAAC,OAAO;GACN,WAAW,GACT,8BAED;GACD,SAAS;IAAE,SAAS;IAAG,GAAG;IAAK;GAC/B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;GAC7B,MAAM;IAAE,SAAS;IAAG,GAAG;IAAK;GAC5B,YAAY,EAAE,UAAU,IAAK;aAE7B,oBAAC;IACC,GAAI;IACJ,IAAI;IACJ,cAAa,MAAK;AAChB,OAAE,gBAAgB;;KAEpB;IACS,GAEC;GACd"}
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
 
3
+ import { ConfigSnapshotProvider, getRootConfigValue } from "../../providers/config/registry.mjs";
3
4
  import { useEffect, useState } from "react";
4
- import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
5
+ import { jsx } from "react/jsx-runtime";
5
6
  import { v4 } from "uuid";
6
7
  import { createRoot } from "react-dom/client";
7
8
 
@@ -22,9 +23,10 @@ function createImperativeStack({ createRootElement, StackItem }) {
22
23
  }
23
24
  return state;
24
25
  };
26
+ const resolveContainer = (container) => container || getRootConfigValue()?.getContainer() || document.body;
25
27
  const getRootElement = (container) => {
26
28
  if (!isBrowser) return null;
27
- const targetContainer = container || document.body;
29
+ const targetContainer = resolveContainer(container);
28
30
  let rootEl = rootElements.get(targetContainer);
29
31
  if (!rootEl) {
30
32
  rootEl = createRootElement();
@@ -43,14 +45,14 @@ function createImperativeStack({ createRootElement, StackItem }) {
43
45
  };
44
46
  }, [container]);
45
47
  const { stack } = getContainerState(container);
46
- return /* @__PURE__ */ jsx(Fragment$1, { children: stack.map(({ id, ...props }) => /* @__PURE__ */ jsx(StackItem, {
48
+ return /* @__PURE__ */ jsx(ConfigSnapshotProvider, { children: stack.map(({ id, ...props }) => /* @__PURE__ */ jsx(StackItem, {
47
49
  id,
48
50
  ...props
49
51
  }, id)) });
50
52
  };
51
53
  const render = (props) => {
52
54
  if (!isBrowser) return;
53
- const targetContainer = props.container || document.body;
55
+ const targetContainer = resolveContainer(props.container);
54
56
  const rootElement = getRootElement(targetContainer);
55
57
  if (!reactRoots.has(targetContainer)) {
56
58
  const root = createRoot(rootElement);
@@ -1 +1 @@
1
- {"version":3,"file":"imperative-stack.mjs","names":["uuid"],"sources":["../../../src/components/organisms/imperative-stack.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Root, createRoot } from 'react-dom/client';\n\nimport { v4 as uuid } from 'uuid';\n\n// Shared machinery behind Modal.info/success/error/warning/confirm and\n// Toast.info/success/error/warning — both used to independently\n// reimplement ~120 near-identical lines of this (container-scoped stack,\n// a React root per container, imperative render/destroy). The only parts\n// that actually differ between the two are the DOM node their stack\n// mounts into (position/ARIA) and which component renders each stack\n// entry — everything else here is generic over that.\n\nexport const isBrowser =\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\ntype ContainerState<TProps> = {\n stack: (TProps & { id: string })[];\n update: (() => void) | null;\n};\n\nexport interface ImperativeStack<\n TProps extends { id?: string; container?: HTMLElement },\n> {\n render: (props: TProps) => string | undefined;\n destroy: (id?: string) => void;\n getRootElement: (container?: HTMLElement) => HTMLElement | null;\n}\n\nexport interface ImperativeStackOptions<\n TProps extends { id?: string; container?: HTMLElement },\n> {\n /** Creates the DOM node this stack's React root mounts into — not yet\n * attached to the document; the factory appends it to the target\n * container itself. Called once per distinct container. */\n createRootElement: () => HTMLElement;\n /** Renders one stack entry — typically each caller's \"Static<Thing>\"\n * component, which owns its own createPortal(..., getRootElement()). */\n StackItem: React.ComponentType<TProps & { id: string }>;\n}\n\nexport function createImperativeStack<\n TProps extends { id?: string; container?: HTMLElement },\n>({\n createRootElement,\n StackItem,\n}: ImperativeStackOptions<TProps>): ImperativeStack<TProps> {\n const containerStates = new Map<HTMLElement, ContainerState<TProps>>();\n const rootElements = new WeakMap<HTMLElement, HTMLElement>();\n const reactRoots = new Map<HTMLElement, Root>();\n\n const getContainerState = (\n container: HTMLElement,\n ): ContainerState<TProps> => {\n let state = containerStates.get(container);\n\n if (!state) {\n state = { stack: [], update: null };\n containerStates.set(container, state);\n }\n\n return state;\n };\n\n const getRootElement = (container?: HTMLElement): HTMLElement | null => {\n if (!isBrowser) {\n return null;\n }\n\n const targetContainer = container || document.body;\n let rootEl = rootElements.get(targetContainer);\n\n if (!rootEl) {\n rootEl = createRootElement();\n targetContainer.appendChild(rootEl);\n rootElements.set(targetContainer, rootEl);\n }\n\n return rootEl;\n };\n\n const StackRenderer = ({ container }: { container: HTMLElement }) => {\n const [, forceUpdate] = useState({});\n\n useEffect(() => {\n const state = getContainerState(container);\n state.update = () => forceUpdate({});\n return () => {\n state.update = null;\n };\n }, [container]);\n\n const { stack } = getContainerState(container);\n\n return (\n <>\n {stack.map(({ id, ...props }) => (\n <StackItem key={id} id={id} {...(props as TProps)} />\n ))}\n </>\n );\n };\n\n const render = (props: TProps): string | undefined => {\n if (!isBrowser) {\n return;\n }\n\n const targetContainer = props.container || document.body;\n const rootElement = getRootElement(targetContainer)!;\n\n if (!reactRoots.has(targetContainer)) {\n const root = createRoot(rootElement);\n reactRoots.set(targetContainer, root);\n root.render(<StackRenderer container={targetContainer} />);\n }\n\n const state = getContainerState(targetContainer);\n const id = props.id || uuid();\n state.stack.push({ ...props, id });\n state.update?.();\n\n return id;\n };\n\n const destroy = (id?: string) => {\n if (!id) {\n containerStates.forEach(state => {\n state.stack = [];\n state.update?.();\n });\n reactRoots.forEach((root, container) => {\n root.unmount();\n rootElements.get(container)?.remove();\n rootElements.delete(container);\n });\n reactRoots.clear();\n containerStates.clear();\n return;\n }\n\n containerStates.forEach((state, container) => {\n if (!state.stack.some(item => item.id === id)) {\n return;\n }\n\n state.stack = state.stack.filter(item => item.id !== id);\n state.update?.();\n\n if (!state.stack.length) {\n reactRoots.get(container)?.unmount();\n reactRoots.delete(container);\n containerStates.delete(container);\n rootElements.get(container)?.remove();\n rootElements.delete(container);\n }\n });\n };\n\n return { render, destroy, getRootElement };\n}\n"],"mappings":";;;;;;;;AAeA,MAAa,YACX,OAAO,WAAW,eAAe,OAAO,aAAa;AA2BvD,SAAgB,sBAEd,EACA,mBACA,aAC0D;CAC1D,MAAM,kCAAkB,IAAI,KAA0C;CACtE,MAAM,+BAAe,IAAI,SAAmC;CAC5D,MAAM,6BAAa,IAAI,KAAwB;CAE/C,MAAM,qBACJ,cAC2B;EAC3B,IAAI,QAAQ,gBAAgB,IAAI,UAAU;AAE1C,MAAI,CAAC,OAAO;AACV,WAAQ;IAAE,OAAO,EAAE;IAAE,QAAQ;IAAM;AACnC,mBAAgB,IAAI,WAAW,MAAM;;AAGvC,SAAO;;CAGT,MAAM,kBAAkB,cAAgD;AACtE,MAAI,CAAC,UACH,QAAO;EAGT,MAAM,kBAAkB,aAAa,SAAS;EAC9C,IAAI,SAAS,aAAa,IAAI,gBAAgB;AAE9C,MAAI,CAAC,QAAQ;AACX,YAAS,mBAAmB;AAC5B,mBAAgB,YAAY,OAAO;AACnC,gBAAa,IAAI,iBAAiB,OAAO;;AAG3C,SAAO;;CAGT,MAAM,iBAAiB,EAAE,gBAA4C;EACnE,MAAM,GAAG,eAAe,SAAS,EAAE,CAAC;AAEpC,kBAAgB;GACd,MAAM,QAAQ,kBAAkB,UAAU;AAC1C,SAAM,eAAe,YAAY,EAAE,CAAC;AACpC,gBAAa;AACX,UAAM,SAAS;;KAEhB,CAAC,UAAU,CAAC;EAEf,MAAM,EAAE,UAAU,kBAAkB,UAAU;AAE9C,SACE,4CACG,MAAM,KAAK,EAAE,IAAI,GAAG,YACnB,oBAAC;GAAuB;GAAI,GAAK;KAAjB,GAAqC,CACrD,GACD;;CAIP,MAAM,UAAU,UAAsC;AACpD,MAAI,CAAC,UACH;EAGF,MAAM,kBAAkB,MAAM,aAAa,SAAS;EACpD,MAAM,cAAc,eAAe,gBAAgB;AAEnD,MAAI,CAAC,WAAW,IAAI,gBAAgB,EAAE;GACpC,MAAM,OAAO,WAAW,YAAY;AACpC,cAAW,IAAI,iBAAiB,KAAK;AACrC,QAAK,OAAO,oBAAC,iBAAc,WAAW,kBAAmB,CAAC;;EAG5D,MAAM,QAAQ,kBAAkB,gBAAgB;EAChD,MAAM,KAAK,MAAM,MAAMA,IAAM;AAC7B,QAAM,MAAM,KAAK;GAAE,GAAG;GAAO;GAAI,CAAC;AAClC,QAAM,UAAU;AAEhB,SAAO;;CAGT,MAAM,WAAW,OAAgB;AAC/B,MAAI,CAAC,IAAI;AACP,mBAAgB,SAAQ,UAAS;AAC/B,UAAM,QAAQ,EAAE;AAChB,UAAM,UAAU;KAChB;AACF,cAAW,SAAS,MAAM,cAAc;AACtC,SAAK,SAAS;AACd,iBAAa,IAAI,UAAU,EAAE,QAAQ;AACrC,iBAAa,OAAO,UAAU;KAC9B;AACF,cAAW,OAAO;AAClB,mBAAgB,OAAO;AACvB;;AAGF,kBAAgB,SAAS,OAAO,cAAc;AAC5C,OAAI,CAAC,MAAM,MAAM,MAAK,SAAQ,KAAK,OAAO,GAAG,CAC3C;AAGF,SAAM,QAAQ,MAAM,MAAM,QAAO,SAAQ,KAAK,OAAO,GAAG;AACxD,SAAM,UAAU;AAEhB,OAAI,CAAC,MAAM,MAAM,QAAQ;AACvB,eAAW,IAAI,UAAU,EAAE,SAAS;AACpC,eAAW,OAAO,UAAU;AAC5B,oBAAgB,OAAO,UAAU;AACjC,iBAAa,IAAI,UAAU,EAAE,QAAQ;AACrC,iBAAa,OAAO,UAAU;;IAEhC;;AAGJ,QAAO;EAAE;EAAQ;EAAS;EAAgB"}
1
+ {"version":3,"file":"imperative-stack.mjs","names":["uuid"],"sources":["../../../src/components/organisms/imperative-stack.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\nimport { Root, createRoot } from 'react-dom/client';\n\nimport { v4 as uuid } from 'uuid';\n\nimport { ConfigSnapshotProvider, getRootConfigValue } from '@repo/ui/providers';\n\n// Shared machinery behind Modal.info/success/error/warning/confirm and\n// Toast.info/success/error/warning — both used to independently\n// reimplement ~120 near-identical lines of this (container-scoped stack,\n// a React root per container, imperative render/destroy). The only parts\n// that actually differ between the two are the DOM node their stack\n// mounts into (position/ARIA) and which component renders each stack\n// entry — everything else here is generic over that.\n\nexport const isBrowser =\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\ntype ContainerState<TProps> = {\n stack: (TProps & { id: string })[];\n update: (() => void) | null;\n};\n\nexport interface ImperativeStack<\n TProps extends { id?: string; container?: HTMLElement },\n> {\n render: (props: TProps) => string | undefined;\n destroy: (id?: string) => void;\n getRootElement: (container?: HTMLElement) => HTMLElement | null;\n}\n\nexport interface ImperativeStackOptions<\n TProps extends { id?: string; container?: HTMLElement },\n> {\n /** Creates the DOM node this stack's React root mounts into — not yet\n * attached to the document; the factory appends it to the target\n * container itself. Called once per distinct container. */\n createRootElement: () => HTMLElement;\n /** Renders one stack entry — typically each caller's \"Static<Thing>\"\n * component, which owns its own createPortal(..., getRootElement()). */\n StackItem: React.ComponentType<TProps & { id: string }>;\n}\n\nexport function createImperativeStack<\n TProps extends { id?: string; container?: HTMLElement },\n>({\n createRootElement,\n StackItem,\n}: ImperativeStackOptions<TProps>): ImperativeStack<TProps> {\n const containerStates = new Map<HTMLElement, ContainerState<TProps>>();\n const rootElements = new WeakMap<HTMLElement, HTMLElement>();\n const reactRoots = new Map<HTMLElement, Root>();\n\n const getContainerState = (\n container: HTMLElement,\n ): ContainerState<TProps> => {\n let state = containerStates.get(container);\n\n if (!state) {\n state = { stack: [], update: null };\n containerStates.set(container, state);\n }\n\n return state;\n };\n\n // Shared by `render` and `getRootElement` so both agree on the same\n // target container whenever the caller doesn't pass one explicitly — the\n // stack's React root (created once, in `render`) and the DOM node a\n // `StackItem` (e.g. StaticToast) later portals into (calling\n // `getRootElement` again with the same, possibly-undefined `container`)\n // would otherwise resolve two different containers here, splitting the\n // React tree (correctly under the root Config, via ConfigSnapshotProvider\n // above) from the actual visible DOM (silently back under `document.body`,\n // outside the Config's themed wrapper — undoing this fix's whole point).\n const resolveContainer = (container?: HTMLElement): HTMLElement =>\n container || getRootConfigValue()?.getContainer() || document.body;\n\n const getRootElement = (container?: HTMLElement): HTMLElement | null => {\n if (!isBrowser) {\n return null;\n }\n\n const targetContainer = resolveContainer(container);\n let rootEl = rootElements.get(targetContainer);\n\n if (!rootEl) {\n rootEl = createRootElement();\n targetContainer.appendChild(rootEl);\n rootElements.set(targetContainer, rootEl);\n }\n\n return rootEl;\n };\n\n const StackRenderer = ({ container }: { container: HTMLElement }) => {\n const [, forceUpdate] = useState({});\n\n useEffect(() => {\n const state = getContainerState(container);\n state.update = () => forceUpdate({});\n return () => {\n state.update = null;\n };\n }, [container]);\n\n const { stack } = getContainerState(container);\n\n return (\n <ConfigSnapshotProvider>\n {stack.map(({ id, ...props }) => (\n <StackItem key={id} id={id} {...(props as TProps)} />\n ))}\n </ConfigSnapshotProvider>\n );\n };\n\n const render = (props: TProps): string | undefined => {\n if (!isBrowser) {\n return;\n }\n\n // An explicit `container` prop always wins (it may be chosen for\n // layout reasons unrelated to theming); otherwise mount into the root\n // Config's own themed wrapper element (if one renders — see\n // `needsWrapper` in config.tsx) so CSS custom properties and the\n // `.dark` class cascade to this stack via normal DOM inheritance, not\n // just `document.body` sitting as an unthemed sibling of it.\n const targetContainer = resolveContainer(props.container);\n const rootElement = getRootElement(targetContainer)!;\n\n if (!reactRoots.has(targetContainer)) {\n const root = createRoot(rootElement);\n reactRoots.set(targetContainer, root);\n root.render(<StackRenderer container={targetContainer} />);\n }\n\n const state = getContainerState(targetContainer);\n const id = props.id || uuid();\n state.stack.push({ ...props, id });\n state.update?.();\n\n return id;\n };\n\n const destroy = (id?: string) => {\n if (!id) {\n containerStates.forEach(state => {\n state.stack = [];\n state.update?.();\n });\n reactRoots.forEach((root, container) => {\n root.unmount();\n rootElements.get(container)?.remove();\n rootElements.delete(container);\n });\n reactRoots.clear();\n containerStates.clear();\n return;\n }\n\n containerStates.forEach((state, container) => {\n if (!state.stack.some(item => item.id === id)) {\n return;\n }\n\n state.stack = state.stack.filter(item => item.id !== id);\n state.update?.();\n\n if (!state.stack.length) {\n reactRoots.get(container)?.unmount();\n reactRoots.delete(container);\n containerStates.delete(container);\n rootElements.get(container)?.remove();\n rootElements.delete(container);\n }\n });\n };\n\n return { render, destroy, getRootElement };\n}\n"],"mappings":";;;;;;;;;AAiBA,MAAa,YACX,OAAO,WAAW,eAAe,OAAO,aAAa;AA2BvD,SAAgB,sBAEd,EACA,mBACA,aAC0D;CAC1D,MAAM,kCAAkB,IAAI,KAA0C;CACtE,MAAM,+BAAe,IAAI,SAAmC;CAC5D,MAAM,6BAAa,IAAI,KAAwB;CAE/C,MAAM,qBACJ,cAC2B;EAC3B,IAAI,QAAQ,gBAAgB,IAAI,UAAU;AAE1C,MAAI,CAAC,OAAO;AACV,WAAQ;IAAE,OAAO,EAAE;IAAE,QAAQ;IAAM;AACnC,mBAAgB,IAAI,WAAW,MAAM;;AAGvC,SAAO;;CAYT,MAAM,oBAAoB,cACxB,aAAa,oBAAoB,EAAE,cAAc,IAAI,SAAS;CAEhE,MAAM,kBAAkB,cAAgD;AACtE,MAAI,CAAC,UACH,QAAO;EAGT,MAAM,kBAAkB,iBAAiB,UAAU;EACnD,IAAI,SAAS,aAAa,IAAI,gBAAgB;AAE9C,MAAI,CAAC,QAAQ;AACX,YAAS,mBAAmB;AAC5B,mBAAgB,YAAY,OAAO;AACnC,gBAAa,IAAI,iBAAiB,OAAO;;AAG3C,SAAO;;CAGT,MAAM,iBAAiB,EAAE,gBAA4C;EACnE,MAAM,GAAG,eAAe,SAAS,EAAE,CAAC;AAEpC,kBAAgB;GACd,MAAM,QAAQ,kBAAkB,UAAU;AAC1C,SAAM,eAAe,YAAY,EAAE,CAAC;AACpC,gBAAa;AACX,UAAM,SAAS;;KAEhB,CAAC,UAAU,CAAC;EAEf,MAAM,EAAE,UAAU,kBAAkB,UAAU;AAE9C,SACE,oBAAC,oCACE,MAAM,KAAK,EAAE,IAAI,GAAG,YACnB,oBAAC;GAAuB;GAAI,GAAK;KAAjB,GAAqC,CACrD,GACqB;;CAI7B,MAAM,UAAU,UAAsC;AACpD,MAAI,CAAC,UACH;EASF,MAAM,kBAAkB,iBAAiB,MAAM,UAAU;EACzD,MAAM,cAAc,eAAe,gBAAgB;AAEnD,MAAI,CAAC,WAAW,IAAI,gBAAgB,EAAE;GACpC,MAAM,OAAO,WAAW,YAAY;AACpC,cAAW,IAAI,iBAAiB,KAAK;AACrC,QAAK,OAAO,oBAAC,iBAAc,WAAW,kBAAmB,CAAC;;EAG5D,MAAM,QAAQ,kBAAkB,gBAAgB;EAChD,MAAM,KAAK,MAAM,MAAMA,IAAM;AAC7B,QAAM,MAAM,KAAK;GAAE,GAAG;GAAO;GAAI,CAAC;AAClC,QAAM,UAAU;AAEhB,SAAO;;CAGT,MAAM,WAAW,OAAgB;AAC/B,MAAI,CAAC,IAAI;AACP,mBAAgB,SAAQ,UAAS;AAC/B,UAAM,QAAQ,EAAE;AAChB,UAAM,UAAU;KAChB;AACF,cAAW,SAAS,MAAM,cAAc;AACtC,SAAK,SAAS;AACd,iBAAa,IAAI,UAAU,EAAE,QAAQ;AACrC,iBAAa,OAAO,UAAU;KAC9B;AACF,cAAW,OAAO;AAClB,mBAAgB,OAAO;AACvB;;AAGF,kBAAgB,SAAS,OAAO,cAAc;AAC5C,OAAI,CAAC,MAAM,MAAM,MAAK,SAAQ,KAAK,OAAO,GAAG,CAC3C;AAGF,SAAM,QAAQ,MAAM,MAAM,QAAO,SAAQ,KAAK,OAAO,GAAG;AACxD,SAAM,UAAU;AAEhB,OAAI,CAAC,MAAM,MAAM,QAAQ;AACvB,eAAW,IAAI,UAAU,EAAE,SAAS;AACpC,eAAW,OAAO,UAAU;AAC5B,oBAAgB,OAAO,UAAU;AACjC,iBAAa,IAAI,UAAU,EAAE,QAAQ;AACrC,iBAAa,OAAO,UAAU;;IAEhC;;AAGJ,QAAO;EAAE;EAAQ;EAAS;EAAgB"}
@@ -1 +1 @@
1
- {"version":3,"file":"modal.d.mts","names":[],"sources":["../../../src/components/organisms/modal.tsx"],"mappings":";;;;UAgCiB,KAAA;EACf,IAAA;EACA,YAAA;EACA,QAAA;IAGM,SAAA,GAAY,KAAA,CAAM,SAAA;IAClB,QAAA;EAAA;EAEN,SAAA,GAAY,KAAA,CAAM,SAAA;EAClB,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,OAAA,GAAU,KAAA,CAAM,SAAA;EAChB,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,SAAA;EACA,UAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA;IACA,MAAA;EAAA;EAEF,KAAA,GAAQ,KAAA,CAAM,aAAA;EACd,MAAA;EACA,UAAA;EACA,SAAA,GAAY,WAAA;EACZ,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,IAAA;EACA,QAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,KAAA;EAC5B,IAAA;EACA,EAAA;EACA,IAAA,GAAO,KAAA,CAAM,SAAA;EACb,SAAA,GAAY,WAAA;AAAA;AAAA,cAGR,KAAA;EAAA;;;;;;;;;;;;;;;;;;KAkBH,KAAA,GAAK,kBAAA,CAAA,GAAA,CAAA,OAAA;;;cAoMa,WAAA;iBAEG,WAAA;eAEF,WAAA;iBAEE,WAAA;iBAEA,WAAA;AAAA"}
1
+ {"version":3,"file":"modal.d.mts","names":[],"sources":["../../../src/components/organisms/modal.tsx"],"mappings":";;;;UAiCiB,KAAA;EACf,IAAA;EACA,YAAA;EACA,QAAA;IAGM,SAAA,GAAY,KAAA,CAAM,SAAA;IAClB,QAAA;EAAA;EAEN,SAAA,GAAY,KAAA,CAAM,SAAA;EAClB,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,OAAA,GAAU,KAAA,CAAM,SAAA;EAChB,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,SAAA;EACA,UAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA;IACA,MAAA;EAAA;EAEF,KAAA,GAAQ,KAAA,CAAM,aAAA;EACd,MAAA;EACA,UAAA;EACA,SAAA,GAAY,WAAA;EACZ,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,IAAA;EACA,QAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,KAAA;EAC5B,IAAA;EACA,EAAA;EACA,IAAA,GAAO,KAAA,CAAM,SAAA;EACb,SAAA,GAAY,WAAA;AAAA;AAAA,cAGR,KAAA;EAAA;;;;;;;;;;;;;;;;;;KAkBH,KAAA,GAAK,kBAAA,CAAA,GAAA,CAAA,OAAA;;;cA4Ma,WAAA;iBAEG,WAAA;eAEF,WAAA;iBAEE,WAAA;iBAEA,WAAA;AAAA"}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils.mjs";
4
+ import { DEFAULT_LOCALE, useConfig } from "../../providers/config/context.mjs";
4
5
  import { dialog_exports } from "../../core/dialog.mjs";
5
6
  import Button from "../../Button.mjs";
6
7
  import { createImperativeStack, isBrowser } from "./imperative-stack.mjs";
@@ -11,7 +12,8 @@ import { createPortal } from "react-dom";
11
12
 
12
13
  //#region src/components/organisms/modal.tsx
13
14
  const { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } = dialog_exports;
14
- const Modal = ({ open = false, maskClosable = false, closable = false, closeIcon, className, classNames, style, title, content, footer, container, children, okText = "OK", cancelText = "Cancel", onOk, onCancel, ...props }) => {
15
+ const Modal = ({ open = false, maskClosable = false, closable = false, closeIcon, className, classNames, style, title, content, footer, container, children, okText, cancelText, onOk, onCancel, ...props }) => {
16
+ const { locale } = useConfig();
15
17
  if (typeof window === "undefined") return null;
16
18
  return /* @__PURE__ */ jsx(Dialog, {
17
19
  open,
@@ -45,11 +47,11 @@ const Modal = ({ open = false, maskClosable = false, closable = false, closeIcon
45
47
  className: cn("flex-row justify-end gap-x-2", classNames?.footer),
46
48
  children: footer || /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
47
49
  onClick: onOk,
48
- children: okText
50
+ children: okText ?? locale.ok ?? DEFAULT_LOCALE.ok
49
51
  }), /* @__PURE__ */ jsx(Button, {
50
52
  variant: "outlined",
51
53
  onClick: onCancel,
52
- children: cancelText
54
+ children: cancelText ?? locale.cancel ?? DEFAULT_LOCALE.cancel
53
55
  })] })
54
56
  })
55
57
  ]
@@ -63,8 +65,11 @@ const STATIC_ICONS = {
63
65
  warning: /* @__PURE__ */ jsx(OctagonAlert, { className: "text-yellow-400" }),
64
66
  confirm: /* @__PURE__ */ jsx(CircleQuestionMark, {})
65
67
  };
66
- const StaticModal = ({ id, type, title, content, okText = "OK", cancelText = "Cancel", container, icon, onOk, onCancel, ...props }) => {
68
+ const StaticModal = ({ id, type, title, content, okText, cancelText, container, icon, onOk, onCancel, ...props }) => {
69
+ const { locale } = useConfig();
67
70
  const [open, setOpen] = useState(true);
71
+ const resolvedOkText = okText ?? locale.ok ?? DEFAULT_LOCALE.ok;
72
+ const resolvedCancelText = cancelText ?? locale.cancel ?? DEFAULT_LOCALE.cancel;
68
73
  const closeModal = (callback) => {
69
74
  callback?.();
70
75
  setOpen(false);
@@ -78,16 +83,16 @@ const StaticModal = ({ id, type, title, content, okText = "OK", cancelText = "Ca
78
83
  variant: "outlined",
79
84
  className: "col-span-2",
80
85
  onClick: () => closeModal(onCancel),
81
- children: cancelText
86
+ children: resolvedCancelText
82
87
  }), /* @__PURE__ */ jsx(Button, {
83
88
  className: "col-span-3",
84
89
  onClick: () => closeModal(onOk),
85
- children: okText
90
+ children: resolvedOkText
86
91
  })]
87
92
  }) : /* @__PURE__ */ jsx(Button, {
88
93
  className: cn("grow"),
89
94
  onClick: () => closeModal(onOk),
90
- children: okText
95
+ children: resolvedOkText
91
96
  });
92
97
  if (!isBrowser) return null;
93
98
  return createPortal(/* @__PURE__ */ jsx(Modal, {
@@ -1 +1 @@
1
- {"version":3,"file":"modal.mjs","names":["dialog"],"sources":["../../../src/components/organisms/modal.tsx"],"sourcesContent":["'use client';\n\nimport React, { useState } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport {\n Check,\n CircleQuestionMark,\n Info,\n OctagonAlert,\n OctagonX,\n} from 'lucide-react';\n\nimport { dialog } from '@repo/ui/core';\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\nimport {\n type ImperativeStack,\n createImperativeStack,\n isBrowser,\n} from './imperative-stack';\n\nconst {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} = dialog;\n\nexport interface Props {\n open?: boolean;\n maskClosable?: boolean;\n closable?:\n | boolean\n | {\n closeIcon?: React.ReactNode;\n disabled?: boolean;\n };\n closeIcon?: React.ReactNode;\n title?: React.ReactNode;\n content?: React.ReactNode;\n footer?: React.ReactNode;\n className?: string;\n classNames?: {\n mask?: string;\n header?: string;\n body?: string;\n footer?: string;\n };\n style?: React.CSSProperties;\n okText?: string;\n cancelText?: string;\n container?: HTMLElement;\n children?: React.ReactNode;\n onOk?: () => void;\n onCancel?: () => void;\n}\n\ninterface StaticProps extends Props {\n type?: 'info' | 'success' | 'error' | 'warning' | 'confirm';\n id?: string;\n icon?: React.ReactNode;\n container?: HTMLElement;\n}\n\nconst Modal = ({\n open = false,\n maskClosable = false,\n closable = false,\n closeIcon,\n className,\n classNames,\n style,\n title,\n content,\n footer,\n container,\n children,\n okText = 'OK',\n cancelText = 'Cancel',\n onOk,\n onCancel,\n ...props\n}: Props) => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(open: boolean) => {\n if (!open) {\n onCancel?.();\n }\n }}\n {...props}\n >\n <DialogContent\n className={cn(\n 'rounded-lg p-4 max-sm:max-w-[80%]',\n 'border-none outline-none',\n className,\n )}\n classNames={{\n ...classNames,\n mask: cn('bg-black/60', classNames?.mask),\n }}\n style={style}\n closable={closable}\n closeIcon={closeIcon}\n container={container}\n onPointerDownOutside={event => {\n if (!maskClosable) {\n event.preventDefault();\n }\n }}\n >\n {/**\n * @todo [Dialog & AlertDialog] fix: can't get id correctly in shadow dom\n * @see https://github.com/radix-ui/primitives/pull/3384\n **/}\n <DialogHeader\n className={cn(\n classNames?.header,\n !title && 'hidden',\n //\n )}\n >\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription className=\"hidden\" />\n </DialogHeader>\n <div className={cn('break-all', classNames?.body)}>\n {children ?? content}\n </div>\n {footer !== null && (\n <DialogFooter\n className={cn('flex-row justify-end gap-x-2', classNames?.footer)}\n >\n {footer || (\n <>\n <Button onClick={onOk}>{okText}</Button>\n <Button variant=\"outlined\" onClick={onCancel}>\n {cancelText}\n </Button>\n </>\n )}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n );\n};\n\nconst STATIC_ICONS = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n confirm: <CircleQuestionMark />,\n};\n\nconst StaticModal = ({\n id,\n type,\n title,\n content,\n okText = 'OK',\n cancelText = 'Cancel',\n container,\n icon,\n onOk,\n onCancel,\n ...props\n}: StaticProps & { id: string }): React.ReactPortal | null => {\n const [open, setOpen] = useState(true);\n\n const closeModal = (callback?: () => void) => {\n callback?.();\n setOpen(false);\n setTimeout(() => {\n modalStack.destroy(id);\n }, 200);\n };\n\n const footer =\n type === 'confirm' ? (\n <div className=\"grid w-full grid-cols-5 gap-x-2\">\n <Button\n variant=\"outlined\"\n className=\"col-span-2\"\n onClick={() => closeModal(onCancel)}\n >\n {cancelText}\n </Button>\n <Button className=\"col-span-3\" onClick={() => closeModal(onOk)}>\n {okText}\n </Button>\n </div>\n ) : (\n <Button\n className={cn(\n 'grow',\n //\n )}\n onClick={() => closeModal(onOk)}\n >\n {okText}\n </Button>\n );\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <Modal\n open={open}\n footer={footer}\n className={cn(\n 'z-10000',\n 'box-content w-100',\n 'rounded-3xl p-4',\n 'gap-y-0',\n )}\n classNames={{\n mask: 'z-10000 bg-black/[.6]',\n body: cn(\n 'text-black-70 whitespace-pre-wrap text-center',\n content && 'mt-3',\n ),\n footer: 'mt-9',\n }}\n title={\n <p\n className={cn(\n 'text-center text-lg leading-normal whitespace-pre-wrap',\n 'flex items-center justify-center gap-x-2',\n //\n )}\n >\n {icon || (type && STATIC_ICONS[type])}\n {title}\n </p>\n }\n container={container}\n onCancel={() => closeModal(onCancel)}\n {...props}\n >\n {content}\n </Modal>,\n modalStack.getRootElement(container)!,\n );\n};\n\n// No `role`/`aria-modal` here — this element is only a mount point for the\n// imperative stack, not the dialog itself. Radix already renders its own\n// `role=\"dialog\"`/`aria-modal=\"true\"` on the actual `DialogContent` via\n// portal once a modal is open. Setting it here as well would (a) leave\n// `aria-modal=\"true\"` on the page permanently, even with zero modals open,\n// hiding the rest of the document from screen readers, and (b) duplicate\n// Radix's own dialog role once one is open.\nconst modalStack: ImperativeStack<StaticProps> =\n createImperativeStack<StaticProps>({\n createRootElement: () => {\n const el = document.createElement('div');\n el.style.zIndex = '10000';\n el.style.position = 'absolute';\n return el;\n },\n StackItem: StaticModal,\n });\n\nModal.destroy = modalStack.destroy;\n\nModal.destroyAll = () => {\n modalStack.destroy();\n};\n\nModal.info = (props: StaticProps) =>\n modalStack.render({ type: 'info', ...props });\nModal.success = (props: StaticProps) =>\n modalStack.render({ type: 'success', ...props });\nModal.error = (props: StaticProps) =>\n modalStack.render({ type: 'error', ...props });\nModal.warning = (props: StaticProps) =>\n modalStack.render({ type: 'warning', ...props });\nModal.confirm = (props: StaticProps) =>\n modalStack.render({ type: 'confirm', ...props });\n\nexport default Modal;\n"],"mappings":"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"}
1
+ {"version":3,"file":"modal.mjs","names":["dialog"],"sources":["../../../src/components/organisms/modal.tsx"],"sourcesContent":["'use client';\n\nimport React, { useState } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport {\n Check,\n CircleQuestionMark,\n Info,\n OctagonAlert,\n OctagonX,\n} from 'lucide-react';\n\nimport { dialog } from '@repo/ui/core';\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\nimport {\n type ImperativeStack,\n createImperativeStack,\n isBrowser,\n} from './imperative-stack';\n\nconst {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} = dialog;\n\nexport interface Props {\n open?: boolean;\n maskClosable?: boolean;\n closable?:\n | boolean\n | {\n closeIcon?: React.ReactNode;\n disabled?: boolean;\n };\n closeIcon?: React.ReactNode;\n title?: React.ReactNode;\n content?: React.ReactNode;\n footer?: React.ReactNode;\n className?: string;\n classNames?: {\n mask?: string;\n header?: string;\n body?: string;\n footer?: string;\n };\n style?: React.CSSProperties;\n okText?: string;\n cancelText?: string;\n container?: HTMLElement;\n children?: React.ReactNode;\n onOk?: () => void;\n onCancel?: () => void;\n}\n\ninterface StaticProps extends Props {\n type?: 'info' | 'success' | 'error' | 'warning' | 'confirm';\n id?: string;\n icon?: React.ReactNode;\n container?: HTMLElement;\n}\n\nconst Modal = ({\n open = false,\n maskClosable = false,\n closable = false,\n closeIcon,\n className,\n classNames,\n style,\n title,\n content,\n footer,\n container,\n children,\n okText,\n cancelText,\n onOk,\n onCancel,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n\n if (typeof window === 'undefined') {\n return null;\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(open: boolean) => {\n if (!open) {\n onCancel?.();\n }\n }}\n {...props}\n >\n <DialogContent\n className={cn(\n 'rounded-lg p-4 max-sm:max-w-[80%]',\n 'border-none outline-none',\n className,\n )}\n classNames={{\n ...classNames,\n mask: cn('bg-black/60', classNames?.mask),\n }}\n style={style}\n closable={closable}\n closeIcon={closeIcon}\n container={container}\n onPointerDownOutside={event => {\n if (!maskClosable) {\n event.preventDefault();\n }\n }}\n >\n {/**\n * @todo [Dialog & AlertDialog] fix: can't get id correctly in shadow dom\n * @see https://github.com/radix-ui/primitives/pull/3384\n **/}\n <DialogHeader\n className={cn(\n classNames?.header,\n !title && 'hidden',\n //\n )}\n >\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription className=\"hidden\" />\n </DialogHeader>\n <div className={cn('break-all', classNames?.body)}>\n {children ?? content}\n </div>\n {footer !== null && (\n <DialogFooter\n className={cn('flex-row justify-end gap-x-2', classNames?.footer)}\n >\n {footer || (\n <>\n <Button onClick={onOk}>\n {okText ?? locale.ok ?? DEFAULT_LOCALE.ok}\n </Button>\n <Button variant=\"outlined\" onClick={onCancel}>\n {cancelText ?? locale.cancel ?? DEFAULT_LOCALE.cancel}\n </Button>\n </>\n )}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n );\n};\n\nconst STATIC_ICONS = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n confirm: <CircleQuestionMark />,\n};\n\nconst StaticModal = ({\n id,\n type,\n title,\n content,\n okText,\n cancelText,\n container,\n icon,\n onOk,\n onCancel,\n ...props\n}: StaticProps & { id: string }): React.ReactPortal | null => {\n const { locale } = useConfig();\n const [open, setOpen] = useState(true);\n const resolvedOkText = okText ?? locale.ok ?? DEFAULT_LOCALE.ok;\n const resolvedCancelText =\n cancelText ?? locale.cancel ?? DEFAULT_LOCALE.cancel;\n\n const closeModal = (callback?: () => void) => {\n callback?.();\n setOpen(false);\n setTimeout(() => {\n modalStack.destroy(id);\n }, 200);\n };\n\n const footer =\n type === 'confirm' ? (\n <div className=\"grid w-full grid-cols-5 gap-x-2\">\n <Button\n variant=\"outlined\"\n className=\"col-span-2\"\n onClick={() => closeModal(onCancel)}\n >\n {resolvedCancelText}\n </Button>\n <Button className=\"col-span-3\" onClick={() => closeModal(onOk)}>\n {resolvedOkText}\n </Button>\n </div>\n ) : (\n <Button\n className={cn(\n 'grow',\n //\n )}\n onClick={() => closeModal(onOk)}\n >\n {resolvedOkText}\n </Button>\n );\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <Modal\n open={open}\n footer={footer}\n className={cn(\n 'z-10000',\n 'box-content w-100',\n 'rounded-3xl p-4',\n 'gap-y-0',\n )}\n classNames={{\n mask: 'z-10000 bg-black/[.6]',\n body: cn(\n 'text-black-70 whitespace-pre-wrap text-center',\n content && 'mt-3',\n ),\n footer: 'mt-9',\n }}\n title={\n <p\n className={cn(\n 'text-center text-lg leading-normal whitespace-pre-wrap',\n 'flex items-center justify-center gap-x-2',\n //\n )}\n >\n {icon || (type && STATIC_ICONS[type])}\n {title}\n </p>\n }\n container={container}\n onCancel={() => closeModal(onCancel)}\n {...props}\n >\n {content}\n </Modal>,\n modalStack.getRootElement(container)!,\n );\n};\n\n// No `role`/`aria-modal` here — this element is only a mount point for the\n// imperative stack, not the dialog itself. Radix already renders its own\n// `role=\"dialog\"`/`aria-modal=\"true\"` on the actual `DialogContent` via\n// portal once a modal is open. Setting it here as well would (a) leave\n// `aria-modal=\"true\"` on the page permanently, even with zero modals open,\n// hiding the rest of the document from screen readers, and (b) duplicate\n// Radix's own dialog role once one is open.\nconst modalStack: ImperativeStack<StaticProps> =\n createImperativeStack<StaticProps>({\n createRootElement: () => {\n const el = document.createElement('div');\n el.style.zIndex = '10000';\n el.style.position = 'absolute';\n return el;\n },\n StackItem: StaticModal,\n });\n\nModal.destroy = modalStack.destroy;\n\nModal.destroyAll = () => {\n modalStack.destroy();\n};\n\nModal.info = (props: StaticProps) =>\n modalStack.render({ type: 'info', ...props });\nModal.success = (props: StaticProps) =>\n modalStack.render({ type: 'success', ...props });\nModal.error = (props: StaticProps) =>\n modalStack.render({ type: 'error', ...props });\nModal.warning = (props: StaticProps) =>\n modalStack.render({ type: 'warning', ...props });\nModal.confirm = (props: StaticProps) =>\n modalStack.render({ type: 'confirm', ...props });\n\nexport default Modal;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"toast.d.mts","names":[],"sources":["../../../src/components/organisms/toast.tsx"],"mappings":";;;KAiBK,SAAA;AAAA,UAEY,KAAA;EACf,IAAA,GAAO,SAAA;EACP,KAAA,EAAO,KAAA,CAAM,SAAA;EACb,WAAA,GAAc,KAAA,CAAM,SAAA;EACpB,IAAA,GAAO,KAAA,CAAM,SAAA;EACb,QAAA;EACA,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,SAAA;EACA,OAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,KAAA;EAC5B,EAAA;EACA,QAAA;EACA,SAAA,GAAY,WAAA;AAAA;AAAA,cAUR,KAAA;EAAA;;;;;;;;;KASH,KAAA,GAAK,kBAAA,CAAA,GAAA,CAAA,OAAA;;;cA+Ga,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;iBAEtB,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;eAE3B,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;iBAEvB,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;AAAA;AAAA,KAR5C,cAAA,GAAiB,IAAA,CAAK,WAAA"}
1
+ {"version":3,"file":"toast.d.mts","names":[],"sources":["../../../src/components/organisms/toast.tsx"],"mappings":";;;KAkBK,SAAA;AAAA,UAEY,KAAA;EACf,IAAA,GAAO,SAAA;EACP,KAAA,EAAO,KAAA,CAAM,SAAA;EACb,WAAA,GAAc,KAAA,CAAM,SAAA;EACpB,IAAA,GAAO,KAAA,CAAM,SAAA;EACb,QAAA;EACA,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,SAAA;EACA,OAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,KAAA;EAC5B,EAAA;EACA,QAAA;EACA,SAAA,GAAY,WAAA;AAAA;AAAA,cAUR,KAAA;EAAA;;;;;;;;;KASH,KAAA,GAAK,kBAAA,CAAA,GAAA,CAAA,OAAA;;;cAoHa,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;iBAEtB,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;eAE3B,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;iBAEvB,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;AAAA;AAAA,KAR5C,cAAA,GAAiB,IAAA,CAAK,WAAA"}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { cn } from "../../utils.mjs";
4
+ import { DEFAULT_LOCALE, useConfig } from "../../providers/config/context.mjs";
4
5
  import Button from "../../Button.mjs";
5
6
  import { createImperativeStack, isBrowser } from "./imperative-stack.mjs";
6
7
  import { Check, Info, OctagonAlert, OctagonX, X } from "lucide-react";
@@ -17,6 +18,7 @@ const TYPE_ICONS = {
17
18
  warning: /* @__PURE__ */ jsx(OctagonAlert, { className: "text-yellow-400" })
18
19
  };
19
20
  const Toast = ({ type, title, description, icon, closable = true, action, className, onClose }) => {
21
+ const { locale } = useConfig();
20
22
  return /* @__PURE__ */ jsxs("div", {
21
23
  role: type === "error" ? "alert" : "status",
22
24
  className: cn("pointer-events-auto flex w-80 items-start gap-3 rounded-lg", "bg-background text-foreground border p-4 shadow-lg", className),
@@ -46,7 +48,7 @@ const Toast = ({ type, title, description, icon, closable = true, action, classN
46
48
  type: "text",
47
49
  shape: "circle",
48
50
  size: "small",
49
- "aria-label": "Close",
51
+ "aria-label": locale.close ?? DEFAULT_LOCALE.close,
50
52
  icon: /* @__PURE__ */ jsx(X, {}),
51
53
  onClick: onClose
52
54
  })
@@ -1 +1 @@
1
- {"version":3,"file":"toast.mjs","names":[],"sources":["../../../src/components/organisms/toast.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useTimeout } from '@jbpark/use-hooks';\nimport { Check, Info, OctagonAlert, OctagonX, X } from 'lucide-react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\nimport {\n type ImperativeStack,\n createImperativeStack,\n isBrowser,\n} from './imperative-stack';\n\ntype ToastType = 'info' | 'success' | 'error' | 'warning';\n\nexport interface Props {\n type?: ToastType;\n title: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n closable?: boolean;\n action?: React.ReactNode;\n className?: string;\n onClose?: () => void;\n}\n\ninterface StaticProps extends Props {\n id?: string;\n duration?: number;\n container?: HTMLElement;\n}\n\nconst TYPE_ICONS: Record<ToastType, React.ReactNode> = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n};\n\nconst Toast = ({\n type,\n title,\n description,\n icon,\n closable = true,\n action,\n className,\n onClose,\n}: Props) => {\n return (\n <div\n role={type === 'error' ? 'alert' : 'status'}\n className={cn(\n 'pointer-events-auto flex w-80 items-start gap-3 rounded-lg',\n 'bg-background text-foreground border p-4 shadow-lg',\n className,\n //\n )}\n >\n {(icon || type) && (\n <div className=\"mt-0.5 shrink-0 [&_svg]:size-5\">\n {icon || (type && TYPE_ICONS[type])}\n </div>\n )}\n <div className=\"flex-1 space-y-1\">\n <p className=\"text-sm font-medium\">{title}</p>\n {description && (\n <p className=\"text-muted-foreground text-sm\">{description}</p>\n )}\n {action && <div className=\"pt-1\">{action}</div>}\n </div>\n {closable && (\n <Button\n type=\"text\"\n shape=\"circle\"\n size=\"small\"\n aria-label=\"Close\"\n icon={<X />}\n onClick={onClose}\n />\n )}\n </div>\n );\n};\n\nconst DEFAULT_DURATION = 4000;\n\nconst StaticToast = ({\n id,\n duration = DEFAULT_DURATION,\n container,\n onClose,\n ...props\n}: StaticProps & { id: string }): React.ReactPortal | null => {\n const [visible, setVisible] = useState(true);\n\n const close = () => {\n onClose?.();\n setVisible(false);\n setTimeout(() => {\n toastStack.destroy(id);\n }, 200);\n };\n\n const { reset, clear } = useTimeout(close, duration || null);\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <div\n className={cn(\n 'transition-all duration-200',\n visible ? 'opacity-100' : 'translate-x-2 opacity-0',\n )}\n // Without this, a toast with a closable/action button (or one the\n // user is mid-read on) could auto-dismiss itself out from under\n // them — hovering or focusing anything inside pauses the timer,\n // moving away restarts it from the full duration.\n onMouseEnter={clear}\n onMouseLeave={reset}\n onFocus={clear}\n onBlur={reset}\n >\n <Toast {...props} onClose={close} />\n </div>,\n toastStack.getRootElement(container)!,\n );\n};\n\nconst toastStack: ImperativeStack<StaticProps> =\n createImperativeStack<StaticProps>({\n createRootElement: () => {\n const el = document.createElement('div');\n el.setAttribute('role', 'region');\n el.setAttribute('aria-label', 'Notifications');\n el.style.zIndex = '10000';\n el.style.position = 'fixed';\n el.style.right = '0';\n el.style.bottom = '0';\n el.style.display = 'flex';\n el.style.flexDirection = 'column-reverse';\n el.style.gap = '8px';\n el.style.padding = '16px';\n el.style.pointerEvents = 'none';\n return el;\n },\n StackItem: StaticToast,\n });\n\nToast.destroy = toastStack.destroy;\n\nToast.destroyAll = () => {\n toastStack.destroy();\n};\n\ntype TriggerOptions = Omit<StaticProps, 'type' | 'title'>;\n\nToast.info = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'info', title, ...props });\nToast.success = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'success', title, ...props });\nToast.error = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'error', title, ...props });\nToast.warning = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'warning', title, ...props });\n\nexport default Toast;\nexport type { StaticProps as ToastOptions };\n"],"mappings":";;;;;;;;;;;;AAoCA,MAAM,aAAiD;CACrD,MAAM,oBAAC,QAAK,WAAU,kBAAkB;CACxC,SAAS,oBAAC,SAAM,WAAU,mBAAmB;CAC7C,OAAO,oBAAC,YAAS,WAAU,iBAAiB;CAC5C,SAAS,oBAAC,gBAAa,WAAU,oBAAoB;CACtD;AAED,MAAM,SAAS,EACb,MACA,OACA,aACA,MACA,WAAW,MACX,QACA,WACA,cACW;AACX,QACE,qBAAC;EACC,MAAM,SAAS,UAAU,UAAU;EACnC,WAAW,GACT,8DACA,sDACA,UAED;;IAEC,QAAQ,SACR,oBAAC;IAAI,WAAU;cACZ,QAAS,QAAQ,WAAW;KACzB;GAER,qBAAC;IAAI,WAAU;;KACb,oBAAC;MAAE,WAAU;gBAAuB;OAAU;KAC7C,eACC,oBAAC;MAAE,WAAU;gBAAiC;OAAgB;KAE/D,UAAU,oBAAC;MAAI,WAAU;gBAAQ;OAAa;;KAC3C;GACL,YACC,oBAAC;IACC,MAAK;IACL,OAAM;IACN,MAAK;IACL,cAAW;IACX,MAAM,oBAAC,MAAI;IACX,SAAS;KACT;;GAEA;;AAIV,MAAM,mBAAmB;AAEzB,MAAM,eAAe,EACnB,IACA,WAAW,kBACX,WACA,SACA,GAAG,YACyD;CAC5D,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,cAAc;AAClB,aAAW;AACX,aAAW,MAAM;AACjB,mBAAiB;AACf,cAAW,QAAQ,GAAG;KACrB,IAAI;;CAGT,MAAM,EAAE,OAAO,UAAU,WAAW,OAAO,YAAY,KAAK;AAE5D,KAAI,CAAC,UACH,QAAO;AAGT,QAAO,aACL,oBAAC;EACC,WAAW,GACT,+BACA,UAAU,gBAAgB,0BAC3B;EAKD,cAAc;EACd,cAAc;EACd,SAAS;EACT,QAAQ;YAER,oBAAC;GAAM,GAAI;GAAO,SAAS;IAAS;GAChC,EACN,WAAW,eAAe,UAAU,CACrC;;AAGH,MAAM,aACJ,sBAAmC;CACjC,yBAAyB;EACvB,MAAM,KAAK,SAAS,cAAc,MAAM;AACxC,KAAG,aAAa,QAAQ,SAAS;AACjC,KAAG,aAAa,cAAc,gBAAgB;AAC9C,KAAG,MAAM,SAAS;AAClB,KAAG,MAAM,WAAW;AACpB,KAAG,MAAM,QAAQ;AACjB,KAAG,MAAM,SAAS;AAClB,KAAG,MAAM,UAAU;AACnB,KAAG,MAAM,gBAAgB;AACzB,KAAG,MAAM,MAAM;AACf,KAAG,MAAM,UAAU;AACnB,KAAG,MAAM,gBAAgB;AACzB,SAAO;;CAET,WAAW;CACZ,CAAC;AAEJ,MAAM,UAAU,WAAW;AAE3B,MAAM,mBAAmB;AACvB,YAAW,SAAS;;AAKtB,MAAM,QAAQ,OAAwB,UACpC,WAAW,OAAO;CAAE,MAAM;CAAQ;CAAO,GAAG;CAAO,CAAC;AACtD,MAAM,WAAW,OAAwB,UACvC,WAAW,OAAO;CAAE,MAAM;CAAW;CAAO,GAAG;CAAO,CAAC;AACzD,MAAM,SAAS,OAAwB,UACrC,WAAW,OAAO;CAAE,MAAM;CAAS;CAAO,GAAG;CAAO,CAAC;AACvD,MAAM,WAAW,OAAwB,UACvC,WAAW,OAAO;CAAE,MAAM;CAAW;CAAO,GAAG;CAAO,CAAC"}
1
+ {"version":3,"file":"toast.mjs","names":[],"sources":["../../../src/components/organisms/toast.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useTimeout } from '@jbpark/use-hooks';\nimport { Check, Info, OctagonAlert, OctagonX, X } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\nimport {\n type ImperativeStack,\n createImperativeStack,\n isBrowser,\n} from './imperative-stack';\n\ntype ToastType = 'info' | 'success' | 'error' | 'warning';\n\nexport interface Props {\n type?: ToastType;\n title: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n closable?: boolean;\n action?: React.ReactNode;\n className?: string;\n onClose?: () => void;\n}\n\ninterface StaticProps extends Props {\n id?: string;\n duration?: number;\n container?: HTMLElement;\n}\n\nconst TYPE_ICONS: Record<ToastType, React.ReactNode> = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n};\n\nconst Toast = ({\n type,\n title,\n description,\n icon,\n closable = true,\n action,\n className,\n onClose,\n}: Props) => {\n const { locale } = useConfig();\n\n return (\n <div\n role={type === 'error' ? 'alert' : 'status'}\n className={cn(\n 'pointer-events-auto flex w-80 items-start gap-3 rounded-lg',\n 'bg-background text-foreground border p-4 shadow-lg',\n className,\n //\n )}\n >\n {(icon || type) && (\n <div className=\"mt-0.5 shrink-0 [&_svg]:size-5\">\n {icon || (type && TYPE_ICONS[type])}\n </div>\n )}\n <div className=\"flex-1 space-y-1\">\n <p className=\"text-sm font-medium\">{title}</p>\n {description && (\n <p className=\"text-muted-foreground text-sm\">{description}</p>\n )}\n {action && <div className=\"pt-1\">{action}</div>}\n </div>\n {closable && (\n <Button\n type=\"text\"\n shape=\"circle\"\n size=\"small\"\n aria-label={locale.close ?? DEFAULT_LOCALE.close}\n icon={<X />}\n onClick={onClose}\n />\n )}\n </div>\n );\n};\n\nconst DEFAULT_DURATION = 4000;\n\nconst StaticToast = ({\n id,\n duration = DEFAULT_DURATION,\n container,\n onClose,\n ...props\n}: StaticProps & { id: string }): React.ReactPortal | null => {\n const [visible, setVisible] = useState(true);\n\n const close = () => {\n onClose?.();\n setVisible(false);\n setTimeout(() => {\n toastStack.destroy(id);\n }, 200);\n };\n\n const { reset, clear } = useTimeout(close, duration || null);\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <div\n className={cn(\n 'transition-all duration-200',\n visible ? 'opacity-100' : 'translate-x-2 opacity-0',\n )}\n // Without this, a toast with a closable/action button (or one the\n // user is mid-read on) could auto-dismiss itself out from under\n // them — hovering or focusing anything inside pauses the timer,\n // moving away restarts it from the full duration.\n onMouseEnter={clear}\n onMouseLeave={reset}\n onFocus={clear}\n onBlur={reset}\n >\n <Toast {...props} onClose={close} />\n </div>,\n toastStack.getRootElement(container)!,\n );\n};\n\nconst toastStack: ImperativeStack<StaticProps> =\n createImperativeStack<StaticProps>({\n createRootElement: () => {\n const el = document.createElement('div');\n el.setAttribute('role', 'region');\n // Plain DOM, created outside any React tree — no Config to read a\n // locale override from until #226 gives the imperative stack a way\n // to inherit one.\n el.setAttribute('aria-label', 'Notifications');\n el.style.zIndex = '10000';\n el.style.position = 'fixed';\n el.style.right = '0';\n el.style.bottom = '0';\n el.style.display = 'flex';\n el.style.flexDirection = 'column-reverse';\n el.style.gap = '8px';\n el.style.padding = '16px';\n el.style.pointerEvents = 'none';\n return el;\n },\n StackItem: StaticToast,\n });\n\nToast.destroy = toastStack.destroy;\n\nToast.destroyAll = () => {\n toastStack.destroy();\n};\n\ntype TriggerOptions = Omit<StaticProps, 'type' | 'title'>;\n\nToast.info = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'info', title, ...props });\nToast.success = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'success', title, ...props });\nToast.error = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'error', title, ...props });\nToast.warning = (title: React.ReactNode, props?: TriggerOptions) =>\n toastStack.render({ type: 'warning', title, ...props });\n\nexport default Toast;\nexport type { StaticProps as ToastOptions };\n"],"mappings":";;;;;;;;;;;;;AAqCA,MAAM,aAAiD;CACrD,MAAM,oBAAC,QAAK,WAAU,kBAAkB;CACxC,SAAS,oBAAC,SAAM,WAAU,mBAAmB;CAC7C,OAAO,oBAAC,YAAS,WAAU,iBAAiB;CAC5C,SAAS,oBAAC,gBAAa,WAAU,oBAAoB;CACtD;AAED,MAAM,SAAS,EACb,MACA,OACA,aACA,MACA,WAAW,MACX,QACA,WACA,cACW;CACX,MAAM,EAAE,WAAW,WAAW;AAE9B,QACE,qBAAC;EACC,MAAM,SAAS,UAAU,UAAU;EACnC,WAAW,GACT,8DACA,sDACA,UAED;;IAEC,QAAQ,SACR,oBAAC;IAAI,WAAU;cACZ,QAAS,QAAQ,WAAW;KACzB;GAER,qBAAC;IAAI,WAAU;;KACb,oBAAC;MAAE,WAAU;gBAAuB;OAAU;KAC7C,eACC,oBAAC;MAAE,WAAU;gBAAiC;OAAgB;KAE/D,UAAU,oBAAC;MAAI,WAAU;gBAAQ;OAAa;;KAC3C;GACL,YACC,oBAAC;IACC,MAAK;IACL,OAAM;IACN,MAAK;IACL,cAAY,OAAO,SAAS,eAAe;IAC3C,MAAM,oBAAC,MAAI;IACX,SAAS;KACT;;GAEA;;AAIV,MAAM,mBAAmB;AAEzB,MAAM,eAAe,EACnB,IACA,WAAW,kBACX,WACA,SACA,GAAG,YACyD;CAC5D,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,cAAc;AAClB,aAAW;AACX,aAAW,MAAM;AACjB,mBAAiB;AACf,cAAW,QAAQ,GAAG;KACrB,IAAI;;CAGT,MAAM,EAAE,OAAO,UAAU,WAAW,OAAO,YAAY,KAAK;AAE5D,KAAI,CAAC,UACH,QAAO;AAGT,QAAO,aACL,oBAAC;EACC,WAAW,GACT,+BACA,UAAU,gBAAgB,0BAC3B;EAKD,cAAc;EACd,cAAc;EACd,SAAS;EACT,QAAQ;YAER,oBAAC;GAAM,GAAI;GAAO,SAAS;IAAS;GAChC,EACN,WAAW,eAAe,UAAU,CACrC;;AAGH,MAAM,aACJ,sBAAmC;CACjC,yBAAyB;EACvB,MAAM,KAAK,SAAS,cAAc,MAAM;AACxC,KAAG,aAAa,QAAQ,SAAS;AAIjC,KAAG,aAAa,cAAc,gBAAgB;AAC9C,KAAG,MAAM,SAAS;AAClB,KAAG,MAAM,WAAW;AACpB,KAAG,MAAM,QAAQ;AACjB,KAAG,MAAM,SAAS;AAClB,KAAG,MAAM,UAAU;AACnB,KAAG,MAAM,gBAAgB;AACzB,KAAG,MAAM,MAAM;AACf,KAAG,MAAM,UAAU;AACnB,KAAG,MAAM,gBAAgB;AACzB,SAAO;;CAET,WAAW;CACZ,CAAC;AAEJ,MAAM,UAAU,WAAW;AAE3B,MAAM,mBAAmB;AACvB,YAAW,SAAS;;AAKtB,MAAM,QAAQ,OAAwB,UACpC,WAAW,OAAO;CAAE,MAAM;CAAQ;CAAO,GAAG;CAAO,CAAC;AACtD,MAAM,WAAW,OAAwB,UACvC,WAAW,OAAO;CAAE,MAAM;CAAW;CAAO,GAAG;CAAO,CAAC;AACzD,MAAM,SAAS,OAAwB,UACrC,WAAW,OAAO;CAAE,MAAM;CAAS;CAAO,GAAG;CAAO,CAAC;AACvD,MAAM,WAAW,OAAwB,UACvC,WAAW,OAAO;CAAE,MAAM;CAAW;CAAO,GAAG;CAAO,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"col.d.mts","names":[],"sources":["../../../../src/components/templates/grid/col.tsx"],"mappings":";;;UAUU,iBAAA;EACR,IAAA;EACA,MAAA;AAAA;AAAA,KAGG,WAAA,YAAuB,iBAAA;AAAA,UAEX,KAAA,SAAc,KAAA,CAAM,cAAA;EACnC,IAAA;EACA,MAAA;EACA,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,KAAA,GAAQ,WAAA;AAAA;AAAA,cAaJ,GAAA;EAAO,IAAA;EAAA,MAAA;EAAA,EAAA;EAAA,EAAA;EAAA,EAAA;EAAA,EAAA;EAAA,EAAA;EAAA,OAAA,GAAA;EAAA,SAAA;EAAA,KAAA;EAAA,QAAA;EAAA,GAAA;AAAA,GAaV,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"col.d.mts","names":[],"sources":["../../../../src/components/templates/grid/col.tsx"],"mappings":";;;UAUU,iBAAA;EACR,IAAA;EACA,MAAA;AAAA;AAAA,KAGG,WAAA,YAAuB,iBAAA;AAAA,UAEX,KAAA,SAAc,KAAA,CAAM,cAAA;EACnC,IAAA;EACA,MAAA;EACA,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,EAAA,GAAK,WAAA;EACL,KAAA,GAAQ,WAAA;AAAA;AAAA,cAsDJ,GAAA;EAAO,IAAA;EAAA,MAAA;EAAA,EAAA;EAAA,EAAA;EAAA,EAAA;EAAA,EAAA;EAAA,EAAA;EAAA,OAAA,GAAA;EAAA,SAAA;EAAA,KAAA;EAAA,QAAA;EAAA,GAAA;AAAA,GAaV,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -15,32 +15,52 @@ const BREAKPOINT_ORDER = [
15
15
  "2xl"
16
16
  ];
17
17
  const resolveColSpan = (value) => typeof value === "number" ? { span: value } : value;
18
+ const resolveEffectiveValuesByTier = (span, offset, responsiveProps) => {
19
+ let effectiveSpan = span;
20
+ let effectiveOffset = offset;
21
+ const spans = {};
22
+ const offsets = {};
23
+ for (const bp of BREAKPOINT_ORDER) {
24
+ const resolved = resolveColSpan(responsiveProps[bp]);
25
+ if (resolved?.span !== void 0) effectiveSpan = resolved.span;
26
+ if (resolved?.offset !== void 0) effectiveOffset = resolved.offset;
27
+ spans[bp] = effectiveSpan;
28
+ offsets[bp] = effectiveOffset;
29
+ }
30
+ return {
31
+ spans,
32
+ offsets
33
+ };
34
+ };
18
35
  const Col = ({ span = TOTAL_COLUMNS, offset = 0, xs, sm, md, lg, xl, "2xl": xxl, className, style, children, ...props }) => {
19
- const { gutterX, breakpoint } = useRowContext();
20
- const responsiveProps = {
36
+ const { gutterX } = useRowContext();
37
+ const { spans, offsets } = resolveEffectiveValuesByTier(span, offset, {
21
38
  xs,
22
39
  sm,
23
40
  md,
24
41
  lg,
25
42
  xl,
26
43
  "2xl": xxl
44
+ });
45
+ const cssVars = {
46
+ "--col-span-xs": spans.xs,
47
+ "--col-span-sm": spans.sm,
48
+ "--col-span-md": spans.md,
49
+ "--col-span-lg": spans.lg,
50
+ "--col-span-xl": spans.xl,
51
+ "--col-span-2xl": spans["2xl"],
52
+ "--col-offset-xs": offsets.xs,
53
+ "--col-offset-sm": offsets.sm,
54
+ "--col-offset-md": offsets.md,
55
+ "--col-offset-lg": offsets.lg,
56
+ "--col-offset-xl": offsets.xl,
57
+ "--col-offset-2xl": offsets["2xl"]
27
58
  };
28
- let effectiveSpan = span;
29
- let effectiveOffset = offset;
30
- for (const bp of BREAKPOINT_ORDER) {
31
- if (BREAKPOINT_ORDER.indexOf(bp) > BREAKPOINT_ORDER.indexOf(breakpoint)) break;
32
- const resolved = resolveColSpan(responsiveProps[bp]);
33
- if (resolved?.span !== void 0) effectiveSpan = resolved.span;
34
- if (resolved?.offset !== void 0) effectiveOffset = resolved.offset;
35
- }
36
- const widthPercent = effectiveSpan / TOTAL_COLUMNS * 100;
37
- const offsetPercent = effectiveOffset / TOTAL_COLUMNS * 100;
38
59
  return /* @__PURE__ */ jsx("div", {
60
+ "data-slot": "col",
39
61
  className: cn("box-border shrink-0 grow-0", className),
40
62
  style: {
41
- flexBasis: `${widthPercent}%`,
42
- maxWidth: `${widthPercent}%`,
43
- marginLeft: offsetPercent ? `${offsetPercent}%` : void 0,
63
+ ...cssVars,
44
64
  paddingLeft: gutterX ? gutterX / 2 : void 0,
45
65
  paddingRight: gutterX ? gutterX / 2 : void 0,
46
66
  ...style
@@ -1 +1 @@
1
- {"version":3,"file":"col.mjs","names":[],"sources":["../../../../src/components/templates/grid/col.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { type Breakpoint, useRowContext } from './row-context';\n\nconst TOTAL_COLUMNS = 24;\n\nconst BREAKPOINT_ORDER: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];\n\ninterface ResponsiveColSpan {\n span?: number;\n offset?: number;\n}\n\ntype ColSpanProp = number | ResponsiveColSpan;\n\nexport interface Props extends React.ComponentProps<'div'> {\n span?: number;\n offset?: number;\n xs?: ColSpanProp;\n sm?: ColSpanProp;\n md?: ColSpanProp;\n lg?: ColSpanProp;\n xl?: ColSpanProp;\n '2xl'?: ColSpanProp;\n}\n\nconst resolveColSpan = (value: ColSpanProp | undefined) =>\n typeof value === 'number' ? { span: value } : value;\n\n// 24-column system (matches Ant Design's Row/Col, a familiar mental model\n// for anyone coming from it) implemented with plain percentage widths\n// rather than Tailwind's col-span-* utilities — those utilities need a\n// literal, statically-scannable class per (breakpoint, span) combination,\n// and 6 breakpoints x 24 spans is 144 combinations, most of which would\n// never be used. Percentage math scales to any span/breakpoint\n// combination with one code path instead.\nconst Col = ({\n span = TOTAL_COLUMNS,\n offset = 0,\n xs,\n sm,\n md,\n lg,\n xl,\n '2xl': xxl,\n className,\n style,\n children,\n ...props\n}: Props) => {\n const { gutterX, breakpoint } = useRowContext();\n\n const responsiveProps: Partial<Record<Breakpoint, ColSpanProp | undefined>> =\n { xs, sm, md, lg, xl, '2xl': xxl };\n\n let effectiveSpan = span;\n let effectiveOffset = offset;\n\n // Mobile-first cascade: walk breakpoints up to (and including) the\n // current one, letting each defined prop override the last — so\n // `<Col span={24} md={12} />` is 24 below md and 12 from md upward, with\n // no need to also specify lg/xl/2xl for the value to keep applying.\n for (const bp of BREAKPOINT_ORDER) {\n if (BREAKPOINT_ORDER.indexOf(bp) > BREAKPOINT_ORDER.indexOf(breakpoint)) {\n break;\n }\n\n const resolved = resolveColSpan(responsiveProps[bp]);\n\n if (resolved?.span !== undefined) {\n effectiveSpan = resolved.span;\n }\n if (resolved?.offset !== undefined) {\n effectiveOffset = resolved.offset;\n }\n }\n\n const widthPercent = (effectiveSpan / TOTAL_COLUMNS) * 100;\n const offsetPercent = (effectiveOffset / TOTAL_COLUMNS) * 100;\n\n return (\n <div\n className={cn('box-border shrink-0 grow-0', className)}\n style={{\n flexBasis: `${widthPercent}%`,\n maxWidth: `${widthPercent}%`,\n marginLeft: offsetPercent ? `${offsetPercent}%` : undefined,\n paddingLeft: gutterX ? gutterX / 2 : undefined,\n paddingRight: gutterX ? gutterX / 2 : undefined,\n ...style,\n }}\n {...props}\n >\n {children}\n </div>\n );\n};\n\nexport default Col;\n"],"mappings":";;;;;;;AAMA,MAAM,gBAAgB;AAEtB,MAAM,mBAAiC;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAoB5E,MAAM,kBAAkB,UACtB,OAAO,UAAU,WAAW,EAAE,MAAM,OAAO,GAAG;AAShD,MAAM,OAAO,EACX,OAAO,eACP,SAAS,GACT,IACA,IACA,IACA,IACA,IACA,OAAO,KACP,WACA,OACA,UACA,GAAG,YACQ;CACX,MAAM,EAAE,SAAS,eAAe,eAAe;CAE/C,MAAM,kBACJ;EAAE;EAAI;EAAI;EAAI;EAAI;EAAI,OAAO;EAAK;CAEpC,IAAI,gBAAgB;CACpB,IAAI,kBAAkB;AAMtB,MAAK,MAAM,MAAM,kBAAkB;AACjC,MAAI,iBAAiB,QAAQ,GAAG,GAAG,iBAAiB,QAAQ,WAAW,CACrE;EAGF,MAAM,WAAW,eAAe,gBAAgB,IAAI;AAEpD,MAAI,UAAU,SAAS,OACrB,iBAAgB,SAAS;AAE3B,MAAI,UAAU,WAAW,OACvB,mBAAkB,SAAS;;CAI/B,MAAM,eAAgB,gBAAgB,gBAAiB;CACvD,MAAM,gBAAiB,kBAAkB,gBAAiB;AAE1D,QACE,oBAAC;EACC,WAAW,GAAG,8BAA8B,UAAU;EACtD,OAAO;GACL,WAAW,GAAG,aAAa;GAC3B,UAAU,GAAG,aAAa;GAC1B,YAAY,gBAAgB,GAAG,cAAc,KAAK;GAClD,aAAa,UAAU,UAAU,IAAI;GACrC,cAAc,UAAU,UAAU,IAAI;GACtC,GAAG;GACJ;EACD,GAAI;EAEH;GACG"}
1
+ {"version":3,"file":"col.mjs","names":[],"sources":["../../../../src/components/templates/grid/col.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { type Breakpoint, useRowContext } from './row-context';\n\nconst TOTAL_COLUMNS = 24;\n\nconst BREAKPOINT_ORDER: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];\n\ninterface ResponsiveColSpan {\n span?: number;\n offset?: number;\n}\n\ntype ColSpanProp = number | ResponsiveColSpan;\n\nexport interface Props extends React.ComponentProps<'div'> {\n span?: number;\n offset?: number;\n xs?: ColSpanProp;\n sm?: ColSpanProp;\n md?: ColSpanProp;\n lg?: ColSpanProp;\n xl?: ColSpanProp;\n '2xl'?: ColSpanProp;\n}\n\nconst resolveColSpan = (value: ColSpanProp | undefined) =>\n typeof value === 'number' ? { span: value } : value;\n\n// Mobile-first cascade, run once per breakpoint tier (not just the\n// \"current\" one) — each tier's entry is what a `<Col span={24} md={12} />`\n// effectively resolves to *at and above* that tier, letting each defined\n// prop override the last. `globals.css`'s `[data-slot='col']` rules read\n// one of these six values per tier, switched by a real `@media` query —\n// see the comment on the returned CSS custom properties below for why.\nconst resolveEffectiveValuesByTier = (\n span: number,\n offset: number,\n responsiveProps: Partial<Record<Breakpoint, ColSpanProp | undefined>>,\n) => {\n let effectiveSpan = span;\n let effectiveOffset = offset;\n const spans = {} as Record<Breakpoint, number>;\n const offsets = {} as Record<Breakpoint, number>;\n\n for (const bp of BREAKPOINT_ORDER) {\n const resolved = resolveColSpan(responsiveProps[bp]);\n\n if (resolved?.span !== undefined) {\n effectiveSpan = resolved.span;\n }\n if (resolved?.offset !== undefined) {\n effectiveOffset = resolved.offset;\n }\n\n spans[bp] = effectiveSpan;\n offsets[bp] = effectiveOffset;\n }\n\n return { spans, offsets };\n};\n\n// 24-column system (matches Ant Design's Row/Col, a familiar mental model\n// for anyone coming from it). The actual `flex-basis`/`max-width`/\n// `margin-left` math lives in `globals.css` (`[data-slot='col']`), driven\n// by real `@media` queries — this component's only job is resolving each\n// breakpoint tier's effective span/offset (pure prop math, no `window`\n// access) and handing them over as CSS custom properties. That's what\n// makes the responsive behavior work from the very first (server-rendered)\n// paint: previously this picked one \"current\" tier via `useResponsiveSize`\n// (which measures the viewport client-side, so it can't know the real\n// value until after mount) and baked only *that* into an inline style,\n// so SSR output — and the first client paint, before that measurement\n// effect ran — was always the same-as-`useResponsiveSize`'s SSR-safe 'xs'\n// guess, regardless of the real viewport. Handing over all six tiers and\n// letting CSS pick lets the browser resolve the right one immediately,\n// with no JS and no resize listener needed at all.\nconst Col = ({\n span = TOTAL_COLUMNS,\n offset = 0,\n xs,\n sm,\n md,\n lg,\n xl,\n '2xl': xxl,\n className,\n style,\n children,\n ...props\n}: Props) => {\n const { gutterX } = useRowContext();\n\n const responsiveProps: Partial<Record<Breakpoint, ColSpanProp | undefined>> =\n { xs, sm, md, lg, xl, '2xl': xxl };\n\n const { spans, offsets } = resolveEffectiveValuesByTier(\n span,\n offset,\n responsiveProps,\n );\n\n const cssVars = {\n '--col-span-xs': spans.xs,\n '--col-span-sm': spans.sm,\n '--col-span-md': spans.md,\n '--col-span-lg': spans.lg,\n '--col-span-xl': spans.xl,\n '--col-span-2xl': spans['2xl'],\n '--col-offset-xs': offsets.xs,\n '--col-offset-sm': offsets.sm,\n '--col-offset-md': offsets.md,\n '--col-offset-lg': offsets.lg,\n '--col-offset-xl': offsets.xl,\n '--col-offset-2xl': offsets['2xl'],\n } as React.CSSProperties;\n\n return (\n <div\n data-slot=\"col\"\n className={cn('box-border shrink-0 grow-0', className)}\n style={{\n ...cssVars,\n paddingLeft: gutterX ? gutterX / 2 : undefined,\n paddingRight: gutterX ? gutterX / 2 : undefined,\n ...style,\n }}\n {...props}\n >\n {children}\n </div>\n );\n};\n\nexport default Col;\n"],"mappings":";;;;;;;AAMA,MAAM,gBAAgB;AAEtB,MAAM,mBAAiC;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAoB5E,MAAM,kBAAkB,UACtB,OAAO,UAAU,WAAW,EAAE,MAAM,OAAO,GAAG;AAQhD,MAAM,gCACJ,MACA,QACA,oBACG;CACH,IAAI,gBAAgB;CACpB,IAAI,kBAAkB;CACtB,MAAM,QAAQ,EAAE;CAChB,MAAM,UAAU,EAAE;AAElB,MAAK,MAAM,MAAM,kBAAkB;EACjC,MAAM,WAAW,eAAe,gBAAgB,IAAI;AAEpD,MAAI,UAAU,SAAS,OACrB,iBAAgB,SAAS;AAE3B,MAAI,UAAU,WAAW,OACvB,mBAAkB,SAAS;AAG7B,QAAM,MAAM;AACZ,UAAQ,MAAM;;AAGhB,QAAO;EAAE;EAAO;EAAS;;AAkB3B,MAAM,OAAO,EACX,OAAO,eACP,SAAS,GACT,IACA,IACA,IACA,IACA,IACA,OAAO,KACP,WACA,OACA,UACA,GAAG,YACQ;CACX,MAAM,EAAE,YAAY,eAAe;CAKnC,MAAM,EAAE,OAAO,YAAY,6BACzB,MACA,QAJA;EAAE;EAAI;EAAI;EAAI;EAAI;EAAI,OAAO;EAAK,CAMnC;CAED,MAAM,UAAU;EACd,iBAAiB,MAAM;EACvB,iBAAiB,MAAM;EACvB,iBAAiB,MAAM;EACvB,iBAAiB,MAAM;EACvB,iBAAiB,MAAM;EACvB,kBAAkB,MAAM;EACxB,mBAAmB,QAAQ;EAC3B,mBAAmB,QAAQ;EAC3B,mBAAmB,QAAQ;EAC3B,mBAAmB,QAAQ;EAC3B,mBAAmB,QAAQ;EAC3B,oBAAoB,QAAQ;EAC7B;AAED,QACE,oBAAC;EACC,aAAU;EACV,WAAW,GAAG,8BAA8B,UAAU;EACtD,OAAO;GACL,GAAG;GACH,aAAa,UAAU,UAAU,IAAI;GACrC,cAAc,UAAU,UAAU,IAAI;GACtC,GAAG;GACJ;EACD,GAAI;EAEH;GACG"}
@@ -3,8 +3,7 @@ import { createContext, useContext } from "react";
3
3
  //#region src/components/templates/grid/row-context.ts
4
4
  const RowContext = createContext({
5
5
  gutterX: 0,
6
- gutterY: 0,
7
- breakpoint: "xs"
6
+ gutterY: 0
8
7
  });
9
8
  const useRowContext = () => useContext(RowContext);
10
9
 
@@ -1 +1 @@
1
- {"version":3,"file":"row-context.mjs","names":[],"sources":["../../../../src/components/templates/grid/row-context.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nexport type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport interface RowContextValue {\n gutterX: number;\n gutterY: number;\n breakpoint: Breakpoint;\n}\n\n// Defaults assume Col is used without a Row (not the intended usage, but\n// shouldn't crash) — 'xs' matches useResponsiveSize's own SSR-safe initial\n// guess (measures 0 width before mount), same mobile-first default Sider's\n// breakpoint feature already relies on.\nexport const RowContext = createContext<RowContextValue>({\n gutterX: 0,\n gutterY: 0,\n breakpoint: 'xs',\n});\n\nexport const useRowContext = () => useContext(RowContext);\n"],"mappings":";;;AAcA,MAAa,aAAa,cAA+B;CACvD,SAAS;CACT,SAAS;CACT,YAAY;CACb,CAAC;AAEF,MAAa,sBAAsB,WAAW,WAAW"}
1
+ {"version":3,"file":"row-context.mjs","names":[],"sources":["../../../../src/components/templates/grid/row-context.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\n\nexport type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport interface RowContextValue {\n gutterX: number;\n gutterY: number;\n}\n\n// Defaults assume Col is used without a Row (not the intended usage, but\n// shouldn't crash).\nexport const RowContext = createContext<RowContextValue>({\n gutterX: 0,\n gutterY: 0,\n});\n\nexport const useRowContext = () => useContext(RowContext);\n"],"mappings":";;;AAWA,MAAa,aAAa,cAA+B;CACvD,SAAS;CACT,SAAS;CACV,CAAC;AAEF,MAAa,sBAAsB,WAAW,WAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"row.d.mts","names":[],"sources":["../../../../src/components/templates/grid/row.tsx"],"mappings":";;;cAUM,aAAA;;;;;;cAOA,eAAA;;;;;;;;UASW,KAAA,SAAc,KAAA,CAAM,cAAA;EACnC,MAAA;EACA,KAAA,gBAAqB,aAAA;EACrB,OAAA,gBAAuB,eAAA;EACvB,IAAA;AAAA;AAAA,cAMI,GAAA;EAAO,MAAA;EAAA,KAAA;EAAA,OAAA;EAAA,IAAA;EAAA,SAAA;EAAA,KAAA;EAAA,QAAA;EAAA,GAAA;AAAA,GASV,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"row.d.mts","names":[],"sources":["../../../../src/components/templates/grid/row.tsx"],"mappings":";;;cAQM,aAAA;;;;;;cAOA,eAAA;;;;;;;;UASW,KAAA,SAAc,KAAA,CAAM,cAAA;EACnC,MAAA;EACA,KAAA,gBAAqB,aAAA;EACrB,OAAA,gBAAuB,eAAA;EACvB,IAAA;AAAA;AAAA,cAGI,GAAA;EAAO,MAAA;EAAA,KAAA;EAAA,OAAA;EAAA,IAAA;EAAA,SAAA;EAAA,KAAA;EAAA,QAAA;EAAA,GAAA;AAAA,GASV,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -4,7 +4,6 @@ import { cn } from "../../../utils.mjs";
4
4
  import { RowContext } from "./row-context.mjs";
5
5
  import { useMemo } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
- import { useResponsiveSize } from "@jbpark/use-hooks";
8
7
 
9
8
  //#region src/components/templates/grid/row.tsx
10
9
  const ALIGN_CLASSES = {
@@ -23,16 +22,10 @@ const JUSTIFY_CLASSES = {
23
22
  };
24
23
  const Row = ({ gutter = 0, align, justify, wrap = true, className, style, children, ...props }) => {
25
24
  const [gutterX, gutterY] = Array.isArray(gutter) ? gutter : [gutter, 0];
26
- const { breakpoint } = useResponsiveSize({ viewport: true });
27
25
  const contextValue = useMemo(() => ({
28
26
  gutterX,
29
- gutterY,
30
- breakpoint: breakpoint.current
31
- }), [
32
- gutterX,
33
- gutterY,
34
- breakpoint
35
- ]);
27
+ gutterY
28
+ }), [gutterX, gutterY]);
36
29
  return /* @__PURE__ */ jsx(RowContext.Provider, {
37
30
  value: contextValue,
38
31
  children: /* @__PURE__ */ jsx("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"row.mjs","names":[],"sources":["../../../../src/components/templates/grid/row.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\n\nimport { useResponsiveSize } from '@jbpark/use-hooks';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { RowContext } from './row-context';\n\nconst ALIGN_CLASSES = {\n top: 'items-start',\n middle: 'items-center',\n bottom: 'items-end',\n stretch: 'items-stretch',\n};\n\nconst JUSTIFY_CLASSES = {\n start: 'justify-start',\n end: 'justify-end',\n center: 'justify-center',\n 'space-around': 'justify-around',\n 'space-between': 'justify-between',\n 'space-evenly': 'justify-evenly',\n};\n\nexport interface Props extends React.ComponentProps<'div'> {\n gutter?: number | [number, number];\n align?: keyof typeof ALIGN_CLASSES;\n justify?: keyof typeof JUSTIFY_CLASSES;\n wrap?: boolean;\n}\n\n// The breakpoint tier is resolved once here (not per-Col) so N columns\n// share a single resize listener instead of each mounting its own via\n// useResponsiveSize — matters once a grid has more than a couple Cols.\nconst Row = ({\n gutter = 0,\n align,\n justify,\n wrap = true,\n className,\n style,\n children,\n ...props\n}: Props) => {\n const [gutterX, gutterY] = Array.isArray(gutter) ? gutter : [gutter, 0];\n const { breakpoint } = useResponsiveSize({ viewport: true });\n\n const contextValue = useMemo(\n () => ({ gutterX, gutterY, breakpoint: breakpoint.current }),\n [gutterX, gutterY, breakpoint],\n );\n\n return (\n <RowContext.Provider value={contextValue}>\n <div\n className={cn(\n 'flex',\n wrap && 'flex-wrap',\n align && ALIGN_CLASSES[align],\n justify && JUSTIFY_CLASSES[justify],\n className,\n //\n )}\n style={{\n // Negative margin + Col's matching padding is the classic\n // gutter technique — unlike a plain `gap`, it doesn't break the\n // percentage-width math when several Cols are meant to sum to\n // exactly 100% of the row. Vertical gutter has no such\n // constraint (it only affects spacing *between* wrapped rows),\n // so it uses native `row-gap` directly.\n marginLeft: gutterX ? -gutterX / 2 : undefined,\n marginRight: gutterX ? -gutterX / 2 : undefined,\n rowGap: gutterY || undefined,\n ...style,\n }}\n {...props}\n >\n {children}\n </div>\n </RowContext.Provider>\n );\n};\n\nexport default Row;\n"],"mappings":";;;;;;;;;AAUA,MAAM,gBAAgB;CACpB,KAAK;CACL,QAAQ;CACR,QAAQ;CACR,SAAS;CACV;AAED,MAAM,kBAAkB;CACtB,OAAO;CACP,KAAK;CACL,QAAQ;CACR,gBAAgB;CAChB,iBAAiB;CACjB,gBAAgB;CACjB;AAYD,MAAM,OAAO,EACX,SAAS,GACT,OACA,SACA,OAAO,MACP,WACA,OACA,UACA,GAAG,YACQ;CACX,MAAM,CAAC,SAAS,WAAW,MAAM,QAAQ,OAAO,GAAG,SAAS,CAAC,QAAQ,EAAE;CACvE,MAAM,EAAE,eAAe,kBAAkB,EAAE,UAAU,MAAM,CAAC;CAE5D,MAAM,eAAe,eACZ;EAAE;EAAS;EAAS,YAAY,WAAW;EAAS,GAC3D;EAAC;EAAS;EAAS;EAAW,CAC/B;AAED,QACE,oBAAC,WAAW;EAAS,OAAO;YAC1B,oBAAC;GACC,WAAW,GACT,QACA,QAAQ,aACR,SAAS,cAAc,QACvB,WAAW,gBAAgB,UAC3B,UAED;GACD,OAAO;IAOL,YAAY,UAAU,CAAC,UAAU,IAAI;IACrC,aAAa,UAAU,CAAC,UAAU,IAAI;IACtC,QAAQ,WAAW;IACnB,GAAG;IACJ;GACD,GAAI;GAEH;IACG;GACc"}
1
+ {"version":3,"file":"row.mjs","names":[],"sources":["../../../../src/components/templates/grid/row.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { RowContext } from './row-context';\n\nconst ALIGN_CLASSES = {\n top: 'items-start',\n middle: 'items-center',\n bottom: 'items-end',\n stretch: 'items-stretch',\n};\n\nconst JUSTIFY_CLASSES = {\n start: 'justify-start',\n end: 'justify-end',\n center: 'justify-center',\n 'space-around': 'justify-around',\n 'space-between': 'justify-between',\n 'space-evenly': 'justify-evenly',\n};\n\nexport interface Props extends React.ComponentProps<'div'> {\n gutter?: number | [number, number];\n align?: keyof typeof ALIGN_CLASSES;\n justify?: keyof typeof JUSTIFY_CLASSES;\n wrap?: boolean;\n}\n\nconst Row = ({\n gutter = 0,\n align,\n justify,\n wrap = true,\n className,\n style,\n children,\n ...props\n}: Props) => {\n const [gutterX, gutterY] = Array.isArray(gutter) ? gutter : [gutter, 0];\n\n const contextValue = useMemo(\n () => ({ gutterX, gutterY }),\n [gutterX, gutterY],\n );\n\n return (\n <RowContext.Provider value={contextValue}>\n <div\n className={cn(\n 'flex',\n wrap && 'flex-wrap',\n align && ALIGN_CLASSES[align],\n justify && JUSTIFY_CLASSES[justify],\n className,\n //\n )}\n style={{\n // Negative margin + Col's matching padding is the classic\n // gutter technique — unlike a plain `gap`, it doesn't break the\n // percentage-width math when several Cols are meant to sum to\n // exactly 100% of the row. Vertical gutter has no such\n // constraint (it only affects spacing *between* wrapped rows),\n // so it uses native `row-gap` directly.\n marginLeft: gutterX ? -gutterX / 2 : undefined,\n marginRight: gutterX ? -gutterX / 2 : undefined,\n rowGap: gutterY || undefined,\n ...style,\n }}\n {...props}\n >\n {children}\n </div>\n </RowContext.Provider>\n );\n};\n\nexport default Row;\n"],"mappings":";;;;;;;;AAQA,MAAM,gBAAgB;CACpB,KAAK;CACL,QAAQ;CACR,QAAQ;CACR,SAAS;CACV;AAED,MAAM,kBAAkB;CACtB,OAAO;CACP,KAAK;CACL,QAAQ;CACR,gBAAgB;CAChB,iBAAiB;CACjB,gBAAgB;CACjB;AASD,MAAM,OAAO,EACX,SAAS,GACT,OACA,SACA,OAAO,MACP,WACA,OACA,UACA,GAAG,YACQ;CACX,MAAM,CAAC,SAAS,WAAW,MAAM,QAAQ,OAAO,GAAG,SAAS,CAAC,QAAQ,EAAE;CAEvE,MAAM,eAAe,eACZ;EAAE;EAAS;EAAS,GAC3B,CAAC,SAAS,QAAQ,CACnB;AAED,QACE,oBAAC,WAAW;EAAS,OAAO;YAC1B,oBAAC;GACC,WAAW,GACT,QACA,QAAQ,aACR,SAAS,cAAc,QACvB,WAAW,gBAAgB,UAC3B,UAED;GACD,OAAO;IAOL,YAAY,UAAU,CAAC,UAAU,IAAI;IACrC,aAAa,UAAU,CAAC,UAAU,IAAI;IACtC,QAAQ,WAAW;IACnB,GAAG;IACJ;GACD,GAAI;GAEH;IACG;GACc"}
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.mts","names":[],"sources":["../../../../src/components/templates/layout/content.tsx"],"mappings":";;;UAIiB,KAAA,SAAc,KAAA,CAAM,cAAA;EACnC,OAAA;AAAA;AAAA,cAGI,OAAA;EAAW,QAAA;EAAA,SAAA;EAAA,OAAA;EAAA,GAAA;AAAA,GAAoD,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"content.d.mts","names":[],"sources":["../../../../src/components/templates/layout/content.tsx"],"mappings":";;;UAMiB,KAAA,SAAc,KAAA,CAAM,cAAA;EACnC,OAAA;AAAA;AAAA,cAGI,OAAA;EAAW,QAAA;EAAA,SAAA;EAAA,OAAA;EAAA,GAAA;AAAA,GAAoD,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../../../utils.mjs";
2
+ import { SiderRegistryContext } from "./sider-context.mjs";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  import { Slot } from "@radix-ui/react-slot";
4
5
 
@@ -7,7 +8,10 @@ const Content = ({ children, className, asChild = false, ...props }) => {
7
8
  return /* @__PURE__ */ jsx(asChild ? Slot : "main", {
8
9
  className: cn("min-w-0 shrink grow basis-auto", className),
9
10
  ...props,
10
- children
11
+ children: /* @__PURE__ */ jsx(SiderRegistryContext.Provider, {
12
+ value: null,
13
+ children
14
+ })
11
15
  });
12
16
  };
13
17
 
@@ -1 +1 @@
1
- {"version":3,"file":"content.mjs","names":[],"sources":["../../../../src/components/templates/layout/content.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot';\n\nimport { cn } from '@repo/ui/utils';\n\nexport interface Props extends React.ComponentProps<'main'> {\n asChild?: boolean;\n}\n\nconst Content = ({ children, className, asChild = false, ...props }: Props) => {\n // Only one <main> landmark should exist per page — a Layout nested\n // inside a page that already has its own <main> (or a Content nested\n // inside another Content) needs a way to opt out of rendering a second\n // one. asChild + Slot (same pattern as core/button.tsx and\n // core/badge.tsx) merges these props/classes onto the caller's own\n // element instead.\n const Comp = asChild ? Slot : 'main';\n\n return (\n <Comp\n className={cn(\n 'min-w-0 shrink grow basis-auto',\n // basis-auto (not basis-0) sizes Content from its actual content\n // height first, so flex-grow only adds extra space when content is\n // shorter than the viewport — when content is taller, Content\n // keeps its natural (larger) height instead of clipping to\n // available space and pushes Footer below it. min-h-0 is\n // intentionally omitted here: with basis-auto, the default\n // min-height:auto (content-based) is what makes that possible; on\n // the Sider row's cross axis it's harmless since min-height there\n // governs stretch, not main-axis growth.\n className,\n )}\n {...props}\n >\n {children}\n </Comp>\n );\n};\n\nexport default Content;\n"],"mappings":";;;;;AAQA,MAAM,WAAW,EAAE,UAAU,WAAW,UAAU,OAAO,GAAG,YAAmB;AAS7E,QACE,oBAHW,UAAU,OAAO;EAI1B,WAAW,GACT,kCAUA,UACD;EACD,GAAI;EAEH;GACI"}
1
+ {"version":3,"file":"content.mjs","names":[],"sources":["../../../../src/components/templates/layout/content.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { SiderRegistryContext } from './sider-context';\n\nexport interface Props extends React.ComponentProps<'main'> {\n asChild?: boolean;\n}\n\nconst Content = ({ children, className, asChild = false, ...props }: Props) => {\n // Only one <main> landmark should exist per page — a Layout nested\n // inside a page that already has its own <main> (or a Content nested\n // inside another Content) needs a way to opt out of rendering a second\n // one. asChild + Slot (same pattern as core/button.tsx and\n // core/badge.tsx) merges these props/classes onto the caller's own\n // element instead.\n const Comp = asChild ? Slot : 'main';\n\n return (\n <Comp\n className={cn(\n 'min-w-0 shrink grow basis-auto',\n // basis-auto (not basis-0) sizes Content from its actual content\n // height first, so flex-grow only adds extra space when content is\n // shorter than the viewport — when content is taller, Content\n // keeps its natural (larger) height instead of clipping to\n // available space and pushes Footer below it. min-h-0 is\n // intentionally omitted here: with basis-auto, the default\n // min-height:auto (content-based) is what makes that possible; on\n // the Sider row's cross axis it's harmless since min-height there\n // governs stretch, not main-axis growth.\n className,\n )}\n {...props}\n >\n {/* Reset the registry to null so a `Sider` used inside `Content`\n (e.g. a detail page's own sub-navigation) doesn't register\n against the ancestor `Layout` and flip its axis to flex-row —\n a genuinely nested `Layout` already gets its own fresh registry,\n this only guards the \"Sider with no intervening Layout\" case. */}\n <SiderRegistryContext.Provider value={null}>\n {children}\n </SiderRegistryContext.Provider>\n </Comp>\n );\n};\n\nexport default Content;\n"],"mappings":";;;;;;AAUA,MAAM,WAAW,EAAE,UAAU,WAAW,UAAU,OAAO,GAAG,YAAmB;AAS7E,QACE,oBAHW,UAAU,OAAO;EAI1B,WAAW,GACT,kCAUA,UACD;EACD,GAAI;YAOJ,oBAAC,qBAAqB;GAAS,OAAO;GACnC;IAC6B;GAC3B"}
@@ -4,7 +4,7 @@ import { cn } from "../../../utils.mjs";
4
4
  import { DEFAULT_LOCALE, useConfig } from "../../../providers/config/context.mjs";
5
5
  import { useRegisterSider } from "./sider-context.mjs";
6
6
  import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
7
- import { useId, useLayoutEffect } from "react";
7
+ import { useEffect, useId, useRef } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  import { useControllableState, useResponsiveSize } from "@jbpark/use-hooks";
10
10
 
@@ -29,15 +29,30 @@ const Sider = ({ children, className, classNames, style, width = 200, collapsedW
29
29
  });
30
30
  const { breakpoint: viewportBreakpoint } = useResponsiveSize({ viewport: true });
31
31
  const broken = breakpoint ? BREAKPOINT_ORDER.indexOf(viewportBreakpoint.current) < BREAKPOINT_ORDER.indexOf(breakpoint) : false;
32
- useLayoutEffect(() => {
32
+ const latestBrokenRef = useRef(broken);
33
+ const previousBrokenRef = useRef(null);
34
+ const microtaskScheduledRef = useRef(false);
35
+ const onBreakpointRef = useRef(onBreakpoint);
36
+ useEffect(() => {
37
+ onBreakpointRef.current = onBreakpoint;
38
+ });
39
+ useEffect(() => {
33
40
  if (!breakpoint) return;
34
- setCollapsed(broken);
35
- onBreakpoint?.(broken);
41
+ latestBrokenRef.current = broken;
42
+ if (microtaskScheduledRef.current) return;
43
+ microtaskScheduledRef.current = true;
44
+ queueMicrotask(() => {
45
+ microtaskScheduledRef.current = false;
46
+ const settledBroken = latestBrokenRef.current;
47
+ if (previousBrokenRef.current === settledBroken) return;
48
+ previousBrokenRef.current = settledBroken;
49
+ setCollapsed(settledBroken);
50
+ onBreakpointRef.current?.(settledBroken);
51
+ });
36
52
  }, [
37
53
  broken,
38
54
  breakpoint,
39
- setCollapsed,
40
- onBreakpoint
55
+ setCollapsed
41
56
  ]);
42
57
  const onTriggerClick = () => {
43
58
  setCollapsed(!collapsed);
@@ -54,7 +69,7 @@ const Sider = ({ children, className, classNames, style, width = 200, collapsedW
54
69
  id: contentId,
55
70
  className: "min-h-0 flex-1 overflow-auto",
56
71
  children
57
- }), collapsible && /* @__PURE__ */ jsx("button", {
72
+ }), collapsible && trigger !== null && /* @__PURE__ */ jsx("button", {
58
73
  type: "button",
59
74
  "aria-label": collapsed ? locale.expand ?? DEFAULT_LOCALE.expand : locale.collapse ?? DEFAULT_LOCALE.collapse,
60
75
  "aria-expanded": !collapsed,
@@ -1 +1 @@
1
- {"version":3,"file":"sider.mjs","names":[],"sources":["../../../../src/components/templates/layout/sider.tsx"],"sourcesContent":["'use client';\n\nimport { useId, useLayoutEffect } from 'react';\n\nimport { useControllableState, useResponsiveSize } from '@jbpark/use-hooks';\nimport { PanelLeftClose, PanelLeftOpen } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { useRegisterSider } from './sider-context';\n\ntype Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\n// Order matters here, not the values — used to compare the current\n// viewport tier against the configured `breakpoint` ordinally, since\n// `useResponsiveSize` reports which named tier the viewport is currently\n// in rather than a raw pixel width.\nconst BREAKPOINT_ORDER: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];\n\nexport interface Props extends Omit<\n React.ComponentProps<'aside'>,\n 'onCollapse'\n> {\n width?: number | string;\n collapsedWidth?: number | string;\n collapsible?: boolean;\n defaultCollapsed?: boolean;\n collapsed?: boolean;\n placement?: 'left' | 'right';\n reverseArrow?: boolean;\n trigger?: React.ReactNode;\n classNames?: {\n trigger?: string;\n };\n breakpoint?: Breakpoint;\n onCollapse?: (collapsed: boolean) => void;\n onBreakpoint?: (broken: boolean) => void;\n}\n\nconst toCssSize = (value: number | string) =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst Sider = ({\n children,\n className,\n classNames,\n style,\n width = 200,\n collapsedWidth = 80,\n collapsible = false,\n defaultCollapsed = false,\n collapsed: _collapsed,\n placement = 'left',\n reverseArrow = false,\n trigger,\n breakpoint,\n onCollapse,\n onBreakpoint,\n ...props\n}: Props) => {\n useRegisterSider();\n\n const { locale } = useConfig();\n const contentId = useId();\n const [collapsed, setCollapsed] = useControllableState<boolean>({\n value: _collapsed,\n defaultValue: defaultCollapsed,\n onChange: onCollapse,\n });\n\n const { breakpoint: viewportBreakpoint } = useResponsiveSize({\n viewport: true,\n });\n const broken = breakpoint\n ? BREAKPOINT_ORDER.indexOf(viewportBreakpoint.current) <\n BREAKPOINT_ORDER.indexOf(breakpoint)\n : false;\n\n useLayoutEffect(() => {\n if (!breakpoint) {\n return;\n }\n\n setCollapsed(broken);\n onBreakpoint?.(broken);\n }, [broken, breakpoint, setCollapsed, onBreakpoint]);\n\n const onTriggerClick = () => {\n setCollapsed(!collapsed);\n };\n\n // A right-placed Sider's collapse icon should default to pointing the\n // opposite way from a left-placed one (it's opening/closing a panel on\n // the other side of the content), so `placement` flips the effective\n // direction on top of whatever `reverseArrow` already requests —\n // `reverseArrow` still layers on as a manual override either way.\n const effectiveReverseArrow = reverseArrow !== (placement === 'right');\n const TriggerIcon =\n collapsed !== effectiveReverseArrow ? PanelLeftOpen : PanelLeftClose;\n\n return (\n <aside\n className={cn(\n // Below Header's z-50 (and every other floating/overlay primitive\n // in this library, which all use z-50) — Sider is a static layout\n // column, not floating chrome, and previously outranked Header\n // with an arbitrary z-100, covering it once Header stuck to the\n // viewport top on scroll.\n 'z-10 flex h-full shrink-0 flex-col overflow-hidden',\n 'transition-[width] duration-200',\n // Lets a Sider visually sit on the right without needing to\n // reorder JSX/children — useful since Sider's presence is\n // detected via context (sider-context.ts) rather than requiring\n // it to be a specific direct child in a specific position.\n placement === 'right' && 'order-last',\n className,\n //\n )}\n style={{\n width: toCssSize(collapsed ? collapsedWidth : width),\n ...style,\n }}\n {...props}\n >\n <div id={contentId} className=\"min-h-0 flex-1 overflow-auto\">\n {children}\n </div>\n {collapsible && (\n <button\n type=\"button\"\n aria-label={\n collapsed\n ? (locale.expand ?? DEFAULT_LOCALE.expand)\n : (locale.collapse ?? DEFAULT_LOCALE.collapse)\n }\n aria-expanded={!collapsed}\n aria-controls={contentId}\n onClick={onTriggerClick}\n className={cn(\n 'flex w-full shrink-0 items-center justify-center py-2',\n 'cursor-pointer transition-colors hover:bg-black/5',\n classNames?.trigger,\n //\n )}\n >\n {trigger === undefined ? <TriggerIcon className=\"size-4\" /> : trigger}\n </button>\n )}\n </aside>\n );\n};\n\nexport default Sider;\n"],"mappings":";;;;;;;;;;;AAkBA,MAAM,mBAAiC;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAsB5E,MAAM,aAAa,UACjB,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM;AAE7C,MAAM,SAAS,EACb,UACA,WACA,YACA,OACA,QAAQ,KACR,iBAAiB,IACjB,cAAc,OACd,mBAAmB,OACnB,WAAW,YACX,YAAY,QACZ,eAAe,OACf,SACA,YACA,YACA,cACA,GAAG,YACQ;AACX,mBAAkB;CAElB,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,YAAY,OAAO;CACzB,MAAM,CAAC,WAAW,gBAAgB,qBAA8B;EAC9D,OAAO;EACP,cAAc;EACd,UAAU;EACX,CAAC;CAEF,MAAM,EAAE,YAAY,uBAAuB,kBAAkB,EAC3D,UAAU,MACX,CAAC;CACF,MAAM,SAAS,aACX,iBAAiB,QAAQ,mBAAmB,QAAQ,GACpD,iBAAiB,QAAQ,WAAW,GACpC;AAEJ,uBAAsB;AACpB,MAAI,CAAC,WACH;AAGF,eAAa,OAAO;AACpB,iBAAe,OAAO;IACrB;EAAC;EAAQ;EAAY;EAAc;EAAa,CAAC;CAEpD,MAAM,uBAAuB;AAC3B,eAAa,CAAC,UAAU;;CAS1B,MAAM,cACJ,eAF4B,kBAAkB,cAAc,YAEtB,gBAAgB;AAExD,QACE,qBAAC;EACC,WAAW,GAMT,sDACA,mCAKA,cAAc,WAAW,cACzB,UAED;EACD,OAAO;GACL,OAAO,UAAU,YAAY,iBAAiB,MAAM;GACpD,GAAG;GACJ;EACD,GAAI;aAEJ,oBAAC;GAAI,IAAI;GAAW,WAAU;GAC3B;IACG,EACL,eACC,oBAAC;GACC,MAAK;GACL,cACE,YACK,OAAO,UAAU,eAAe,SAChC,OAAO,YAAY,eAAe;GAEzC,iBAAe,CAAC;GAChB,iBAAe;GACf,SAAS;GACT,WAAW,GACT,yDACA,qDACA,YAAY,QAEb;aAEA,YAAY,SAAY,oBAAC,eAAY,WAAU,WAAW,GAAG;IACvD;GAEL"}
1
+ {"version":3,"file":"sider.mjs","names":[],"sources":["../../../../src/components/templates/layout/sider.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useId, useRef } from 'react';\n\nimport { useControllableState, useResponsiveSize } from '@jbpark/use-hooks';\nimport { PanelLeftClose, PanelLeftOpen } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { useRegisterSider } from './sider-context';\n\ntype Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\n// Order matters here, not the values — used to compare the current\n// viewport tier against the configured `breakpoint` ordinally, since\n// `useResponsiveSize` reports which named tier the viewport is currently\n// in rather than a raw pixel width.\nconst BREAKPOINT_ORDER: Breakpoint[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];\n\nexport interface Props extends Omit<\n React.ComponentProps<'aside'>,\n 'onCollapse'\n> {\n width?: number | string;\n collapsedWidth?: number | string;\n collapsible?: boolean;\n defaultCollapsed?: boolean;\n collapsed?: boolean;\n placement?: 'left' | 'right';\n reverseArrow?: boolean;\n trigger?: React.ReactNode;\n classNames?: {\n trigger?: string;\n };\n breakpoint?: Breakpoint;\n onCollapse?: (collapsed: boolean) => void;\n onBreakpoint?: (broken: boolean) => void;\n}\n\nconst toCssSize = (value: number | string) =>\n typeof value === 'number' ? `${value}px` : value;\n\nconst Sider = ({\n children,\n className,\n classNames,\n style,\n width = 200,\n collapsedWidth = 80,\n collapsible = false,\n defaultCollapsed = false,\n collapsed: _collapsed,\n placement = 'left',\n reverseArrow = false,\n trigger,\n breakpoint,\n onCollapse,\n onBreakpoint,\n ...props\n}: Props) => {\n useRegisterSider();\n\n const { locale } = useConfig();\n const contentId = useId();\n const [collapsed, setCollapsed] = useControllableState<boolean>({\n value: _collapsed,\n defaultValue: defaultCollapsed,\n onChange: onCollapse,\n });\n\n const { breakpoint: viewportBreakpoint } = useResponsiveSize({\n viewport: true,\n });\n const broken = breakpoint\n ? BREAKPOINT_ORDER.indexOf(viewportBreakpoint.current) <\n BREAKPOINT_ORDER.indexOf(breakpoint)\n : false;\n\n const latestBrokenRef = useRef(broken);\n const previousBrokenRef = useRef<boolean | null>(null);\n const microtaskScheduledRef = useRef(false);\n const onBreakpointRef = useRef(onBreakpoint);\n\n useEffect(() => {\n onBreakpointRef.current = onBreakpoint;\n });\n\n // `useResponsiveSize`'s own initial measurement resolves through its own\n // layout effect, which (since it's called earlier in this component)\n // commits and — if the real viewport differs from its SSR-safe 'xs'\n // placeholder default — triggers a synchronous corrective re-render, all\n // before the browser paints. Both the placeholder-derived render and the\n // corrected one get their own effect pass (React doesn't coalesce\n // same-tick synchronous re-commits into a single effect flush), so\n // reacting to `broken` directly — in a layout or passive effect either\n // way — fires once with the wrong placeholder value, then again with the\n // corrected one.\n //\n // Coalescing through a microtask sidesteps that: every effect pass just\n // records its `broken` into a ref and schedules (at most once) a\n // microtask that reads whatever ended up latest once the synchronous\n // cascade has fully settled, and acts on that single, correct value.\n //\n // The `previousBrokenRef` guard is the separate fix for #227 point 3:\n // without it, an inline `onCollapse`/`onBreakpoint` (a fresh identity\n // every render) would change `setCollapsed`'s own identity and re-run\n // this effect on every unrelated parent re-render, reapplying `broken`\n // and undoing a user's manual toggle. Only a genuine change should act.\n useEffect(() => {\n if (!breakpoint) {\n return;\n }\n\n latestBrokenRef.current = broken;\n\n if (microtaskScheduledRef.current) {\n return;\n }\n microtaskScheduledRef.current = true;\n\n queueMicrotask(() => {\n microtaskScheduledRef.current = false;\n\n const settledBroken = latestBrokenRef.current;\n\n if (previousBrokenRef.current === settledBroken) {\n return;\n }\n\n previousBrokenRef.current = settledBroken;\n setCollapsed(settledBroken);\n onBreakpointRef.current?.(settledBroken);\n });\n }, [broken, breakpoint, setCollapsed]);\n\n const onTriggerClick = () => {\n setCollapsed(!collapsed);\n };\n\n // A right-placed Sider's collapse icon should default to pointing the\n // opposite way from a left-placed one (it's opening/closing a panel on\n // the other side of the content), so `placement` flips the effective\n // direction on top of whatever `reverseArrow` already requests —\n // `reverseArrow` still layers on as a manual override either way.\n const effectiveReverseArrow = reverseArrow !== (placement === 'right');\n const TriggerIcon =\n collapsed !== effectiveReverseArrow ? PanelLeftOpen : PanelLeftClose;\n\n return (\n <aside\n className={cn(\n // Below Header's z-50 (and every other floating/overlay primitive\n // in this library, which all use z-50) — Sider is a static layout\n // column, not floating chrome, and previously outranked Header\n // with an arbitrary z-100, covering it once Header stuck to the\n // viewport top on scroll.\n 'z-10 flex h-full shrink-0 flex-col overflow-hidden',\n 'transition-[width] duration-200',\n // Lets a Sider visually sit on the right without needing to\n // reorder JSX/children — useful since Sider's presence is\n // detected via context (sider-context.ts) rather than requiring\n // it to be a specific direct child in a specific position.\n placement === 'right' && 'order-last',\n className,\n //\n )}\n style={{\n width: toCssSize(collapsed ? collapsedWidth : width),\n ...style,\n }}\n {...props}\n >\n <div id={contentId} className=\"min-h-0 flex-1 overflow-auto\">\n {children}\n </div>\n {collapsible && trigger !== null && (\n <button\n type=\"button\"\n aria-label={\n collapsed\n ? (locale.expand ?? DEFAULT_LOCALE.expand)\n : (locale.collapse ?? DEFAULT_LOCALE.collapse)\n }\n aria-expanded={!collapsed}\n aria-controls={contentId}\n onClick={onTriggerClick}\n className={cn(\n 'flex w-full shrink-0 items-center justify-center py-2',\n 'cursor-pointer transition-colors hover:bg-black/5',\n classNames?.trigger,\n //\n )}\n >\n {trigger === undefined ? <TriggerIcon className=\"size-4\" /> : trigger}\n </button>\n )}\n </aside>\n );\n};\n\nexport default Sider;\n"],"mappings":";;;;;;;;;;;AAkBA,MAAM,mBAAiC;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAsB5E,MAAM,aAAa,UACjB,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM;AAE7C,MAAM,SAAS,EACb,UACA,WACA,YACA,OACA,QAAQ,KACR,iBAAiB,IACjB,cAAc,OACd,mBAAmB,OACnB,WAAW,YACX,YAAY,QACZ,eAAe,OACf,SACA,YACA,YACA,cACA,GAAG,YACQ;AACX,mBAAkB;CAElB,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,YAAY,OAAO;CACzB,MAAM,CAAC,WAAW,gBAAgB,qBAA8B;EAC9D,OAAO;EACP,cAAc;EACd,UAAU;EACX,CAAC;CAEF,MAAM,EAAE,YAAY,uBAAuB,kBAAkB,EAC3D,UAAU,MACX,CAAC;CACF,MAAM,SAAS,aACX,iBAAiB,QAAQ,mBAAmB,QAAQ,GACpD,iBAAiB,QAAQ,WAAW,GACpC;CAEJ,MAAM,kBAAkB,OAAO,OAAO;CACtC,MAAM,oBAAoB,OAAuB,KAAK;CACtD,MAAM,wBAAwB,OAAO,MAAM;CAC3C,MAAM,kBAAkB,OAAO,aAAa;AAE5C,iBAAgB;AACd,kBAAgB,UAAU;GAC1B;AAuBF,iBAAgB;AACd,MAAI,CAAC,WACH;AAGF,kBAAgB,UAAU;AAE1B,MAAI,sBAAsB,QACxB;AAEF,wBAAsB,UAAU;AAEhC,uBAAqB;AACnB,yBAAsB,UAAU;GAEhC,MAAM,gBAAgB,gBAAgB;AAEtC,OAAI,kBAAkB,YAAY,cAChC;AAGF,qBAAkB,UAAU;AAC5B,gBAAa,cAAc;AAC3B,mBAAgB,UAAU,cAAc;IACxC;IACD;EAAC;EAAQ;EAAY;EAAa,CAAC;CAEtC,MAAM,uBAAuB;AAC3B,eAAa,CAAC,UAAU;;CAS1B,MAAM,cACJ,eAF4B,kBAAkB,cAAc,YAEtB,gBAAgB;AAExD,QACE,qBAAC;EACC,WAAW,GAMT,sDACA,mCAKA,cAAc,WAAW,cACzB,UAED;EACD,OAAO;GACL,OAAO,UAAU,YAAY,iBAAiB,MAAM;GACpD,GAAG;GACJ;EACD,GAAI;aAEJ,oBAAC;GAAI,IAAI;GAAW,WAAU;GAC3B;IACG,EACL,eAAe,YAAY,QAC1B,oBAAC;GACC,MAAK;GACL,cACE,YACK,OAAO,UAAU,eAAe,SAChC,OAAO,YAAY,eAAe;GAEzC,iBAAe,CAAC;GAChB,iBAAe;GACf,SAAS;GACT,WAAW,GACT,yDACA,qDACA,YAAY,QAEb;aAEA,YAAY,SAAY,oBAAC,eAAY,WAAU,WAAW,GAAG;IACvD;GAEL"}
@@ -1 +1 @@
1
- {"version":3,"file":"page-header.d.mts","names":[],"sources":["../../../src/components/templates/page-header.tsx"],"mappings":";;;UAQiB,KAAA,SAAc,IAAA,CAAK,KAAA,CAAM,cAAA;EACxC,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,MAAA;EACA,UAAA;IACE,IAAA;IACA,KAAA;IACA,QAAA;IACA,KAAA;EAAA;AAAA;AAAA,cAaE,UAAA;EAAc,KAAA;EAAA,QAAA;EAAA,KAAA;EAAA,QAAA;EAAA,MAAA;EAAA,SAAA;EAAA,UAAA;EAAA,QAAA;EAAA,GAAA;AAAA,GAUjB,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"page-header.d.mts","names":[],"sources":["../../../src/components/templates/page-header.tsx"],"mappings":";;;UASiB,KAAA,SAAc,IAAA,CAAK,KAAA,CAAM,cAAA;EACxC,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,MAAA;EACA,UAAA;IACE,IAAA;IACA,KAAA;IACA,QAAA;IACA,KAAA;EAAA;AAAA;AAAA,cAOE,UAAA;EAAc,KAAA;EAAA,QAAA;EAAA,KAAA;EAAA,QAAA;EAAA,MAAA;EAAA,SAAA;EAAA,UAAA;EAAA,QAAA;EAAA,GAAA;AAAA,GAUjB,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -1,12 +1,14 @@
1
1
  'use client';
2
2
 
3
3
  import { cn, renderConditional } from "../../utils.mjs";
4
+ import { DEFAULT_LOCALE, useConfig } from "../../providers/config/context.mjs";
4
5
  import Button from "../../Button.mjs";
5
6
  import { ArrowLeft } from "lucide-react";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
 
8
9
  //#region src/components/templates/page-header.tsx
9
10
  const PageHeader = ({ title, subTitle, extra, backIcon, onBack, className, classNames, children, ...props }) => {
11
+ const { locale } = useConfig();
10
12
  return /* @__PURE__ */ jsxs("div", {
11
13
  className: cn("flex flex-col gap-2", className),
12
14
  ...props,
@@ -16,7 +18,7 @@ const PageHeader = ({ title, subTitle, extra, backIcon, onBack, className, class
16
18
  type: "text",
17
19
  shape: "circle",
18
20
  size: "small",
19
- "aria-label": "뒤로가기",
21
+ "aria-label": locale.back ?? DEFAULT_LOCALE.back,
20
22
  icon: backIcon || /* @__PURE__ */ jsx(ArrowLeft, {}),
21
23
  onClick: onBack,
22
24
  className: cn(classNames?.back)
@@ -1 +1 @@
1
- {"version":3,"file":"page-header.mjs","names":[],"sources":["../../../src/components/templates/page-header.tsx"],"sourcesContent":["'use client';\n\nimport { ArrowLeft } from 'lucide-react';\n\nimport { cn, renderConditional } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\nexport interface Props extends Omit<React.ComponentProps<'div'>, 'title'> {\n title?: React.ReactNode;\n subTitle?: React.ReactNode;\n extra?: React.ReactNode;\n backIcon?: React.ReactNode;\n onBack?: () => void;\n classNames?: {\n back?: string;\n title?: string;\n subTitle?: string;\n extra?: string;\n };\n}\n\n// Title + back button + extra action area — same shape as Drawer's header\n// (title/extra/closable), which this borrows the layout from wholesale;\n// PageHeader just swaps Drawer's close button for a back button.\n//\n// TODO: the back button's aria-label (\"뒤로가기\") is hardcoded the same\n// way every other component's was before #210 (Config's `locale` slot)\n// existed. Wire it up to `useConfig().locale` once that PR merges, adding\n// a `back` key to Locale/DEFAULT_LOCALE — left out here to avoid this PR\n// depending on an unmerged one.\nconst PageHeader = ({\n title,\n subTitle,\n extra,\n backIcon,\n onBack,\n className,\n classNames,\n children,\n ...props\n}: Props) => {\n return (\n <div className={cn('flex flex-col gap-2', className)} {...props}>\n <div className=\"flex items-start gap-2\">\n {onBack && (\n <Button\n type=\"text\"\n shape=\"circle\"\n size=\"small\"\n aria-label=\"뒤로가기\"\n icon={backIcon || <ArrowLeft />}\n onClick={onBack}\n className={cn(classNames?.back)}\n />\n )}\n <div className=\"flex flex-1 items-start justify-between gap-2\">\n <div className=\"flex flex-col gap-0.5\">\n {title && (\n <p className={cn('text-lg font-medium', classNames?.title)}>\n {title}\n </p>\n )}\n {subTitle && (\n <p\n className={cn(\n 'text-muted-foreground text-sm',\n classNames?.subTitle,\n )}\n >\n {subTitle}\n </p>\n )}\n </div>\n {renderConditional(extra, v => (\n <div className={cn('shrink-0', classNames?.extra)}>{v}</div>\n ))}\n </div>\n </div>\n {children}\n </div>\n );\n};\n\nexport default PageHeader;\n"],"mappings":";;;;;;;;AA+BA,MAAM,cAAc,EAClB,OACA,UACA,OACA,UACA,QACA,WACA,YACA,UACA,GAAG,YACQ;AACX,QACE,qBAAC;EAAI,WAAW,GAAG,uBAAuB,UAAU;EAAE,GAAI;aACxD,qBAAC;GAAI,WAAU;cACZ,UACC,oBAAC;IACC,MAAK;IACL,OAAM;IACN,MAAK;IACL,cAAW;IACX,MAAM,YAAY,oBAAC,cAAY;IAC/B,SAAS;IACT,WAAW,GAAG,YAAY,KAAK;KAC/B,EAEJ,qBAAC;IAAI,WAAU;eACb,qBAAC;KAAI,WAAU;gBACZ,SACC,oBAAC;MAAE,WAAW,GAAG,uBAAuB,YAAY,MAAM;gBACvD;OACC,EAEL,YACC,oBAAC;MACC,WAAW,GACT,iCACA,YAAY,SACb;gBAEA;OACC;MAEF,EACL,kBAAkB,QAAO,MACxB,oBAAC;KAAI,WAAW,GAAG,YAAY,YAAY,MAAM;eAAG;MAAQ,CAC5D;KACE;IACF,EACL;GACG"}
1
+ {"version":3,"file":"page-header.mjs","names":[],"sources":["../../../src/components/templates/page-header.tsx"],"sourcesContent":["'use client';\n\nimport { ArrowLeft } from 'lucide-react';\n\nimport { DEFAULT_LOCALE, useConfig } from '@repo/ui/providers';\nimport { cn, renderConditional } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\nexport interface Props extends Omit<React.ComponentProps<'div'>, 'title'> {\n title?: React.ReactNode;\n subTitle?: React.ReactNode;\n extra?: React.ReactNode;\n backIcon?: React.ReactNode;\n onBack?: () => void;\n classNames?: {\n back?: string;\n title?: string;\n subTitle?: string;\n extra?: string;\n };\n}\n\n// Title + back button + extra action area — same shape as Drawer's header\n// (title/extra/closable), which this borrows the layout from wholesale;\n// PageHeader just swaps Drawer's close button for a back button.\nconst PageHeader = ({\n title,\n subTitle,\n extra,\n backIcon,\n onBack,\n className,\n classNames,\n children,\n ...props\n}: Props) => {\n const { locale } = useConfig();\n\n return (\n <div className={cn('flex flex-col gap-2', className)} {...props}>\n <div className=\"flex items-start gap-2\">\n {onBack && (\n <Button\n type=\"text\"\n shape=\"circle\"\n size=\"small\"\n aria-label={locale.back ?? DEFAULT_LOCALE.back}\n icon={backIcon || <ArrowLeft />}\n onClick={onBack}\n className={cn(classNames?.back)}\n />\n )}\n <div className=\"flex flex-1 items-start justify-between gap-2\">\n <div className=\"flex flex-col gap-0.5\">\n {title && (\n <p className={cn('text-lg font-medium', classNames?.title)}>\n {title}\n </p>\n )}\n {subTitle && (\n <p\n className={cn(\n 'text-muted-foreground text-sm',\n classNames?.subTitle,\n )}\n >\n {subTitle}\n </p>\n )}\n </div>\n {renderConditional(extra, v => (\n <div className={cn('shrink-0', classNames?.extra)}>{v}</div>\n ))}\n </div>\n </div>\n {children}\n </div>\n );\n};\n\nexport default PageHeader;\n"],"mappings":";;;;;;;;;AA0BA,MAAM,cAAc,EAClB,OACA,UACA,OACA,UACA,QACA,WACA,YACA,UACA,GAAG,YACQ;CACX,MAAM,EAAE,WAAW,WAAW;AAE9B,QACE,qBAAC;EAAI,WAAW,GAAG,uBAAuB,UAAU;EAAE,GAAI;aACxD,qBAAC;GAAI,WAAU;cACZ,UACC,oBAAC;IACC,MAAK;IACL,OAAM;IACN,MAAK;IACL,cAAY,OAAO,QAAQ,eAAe;IAC1C,MAAM,YAAY,oBAAC,cAAY;IAC/B,SAAS;IACT,WAAW,GAAG,YAAY,KAAK;KAC/B,EAEJ,qBAAC;IAAI,WAAU;eACb,qBAAC;KAAI,WAAU;gBACZ,SACC,oBAAC;MAAE,WAAW,GAAG,uBAAuB,YAAY,MAAM;gBACvD;OACC,EAEL,YACC,oBAAC;MACC,WAAW,GACT,iCACA,YAAY,SACb;gBAEA;OACC;MAEF,EACL,kBAAkB,QAAO,MACxB,oBAAC;KAAI,WAAW,GAAG,YAAY,YAAY,MAAM;eAAG;MAAQ,CAC5D;KACE;IACF,EACL;GACG"}
@@ -8,7 +8,7 @@ declare namespace badge_d_exports {
8
8
  export { Badge, Props, badgeVariants };
9
9
  }
10
10
  declare const badgeVariants: (props?: ({
11
- variant?: "default" | "secondary" | "destructive" | "outline" | null | undefined;
11
+ variant?: "default" | "destructive" | "outline" | "secondary" | null | undefined;
12
12
  } & class_variance_authority_types0.ClassProp) | undefined) => string;
13
13
  type Props = React$1.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & {
14
14
  asChild?: boolean;
@@ -8,7 +8,7 @@ declare namespace button_d_exports {
8
8
  export { Button, Props, buttonVariants };
9
9
  }
10
10
  declare const buttonVariants: (props?: ({
11
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
11
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
12
12
  size?: "default" | "icon" | "xs" | "sm" | "lg" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
13
13
  } & class_variance_authority_types0.ClassProp) | undefined) => string;
14
14
  type Props = React$1.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
package/dist/index.d.mts CHANGED
@@ -51,5 +51,6 @@ import "./components/templates/index.mjs";
51
51
  import { ComponentSize, DefaultProps, Locale, ThemeConfig, ThemeToken } from "./providers/config/types.mjs";
52
52
  import { DEFAULT_LOCALE, useConfig } from "./providers/config/context.mjs";
53
53
  import { Config, Props as Props$3 } from "./providers/config/config.mjs";
54
+ import { ConfigSnapshotProvider, getRootConfigValue } from "./providers/config/registry.mjs";
54
55
  import "./providers.mjs";
55
- export { Button, Card, Props as CardProps, Checkbox, Col, Props$1 as ColProps, Collapse, Props$2 as CollapseProps, ColorPicker, ComponentSize, Config, Props$3 as ConfigProps, Container, Props$4 as ContainerProps, Props$5 as ContentProps, DEFAULT_LOCALE, DatePicker, DefaultProps, Drawer, Props$6 as DrawerProps, Dropdown, Props$7 as DropdownProps, Empty, Props$8 as EmptyProps, FloatButton, Props$9 as FooterProps, Props$10 as HeaderProps, Input, Layout, Props$11 as LayoutProps, List, Props$12 as ListProps, Locale, Marquees, Props$13 as MarqueesProps, Menu, Props$14 as MenuProps, Modal, Props$15 as ModalProps, PageHeader, Props$16 as PageHeaderProps, Popover, Progress, Radio, Result, Props$17 as ResultProps, Reveals, Props$18 as RevealsProps, Row, Props$19 as RowProps, Select, Props$20 as SiderProps, Skeleton, Space, Props$21 as SpaceProps, Spin, Splitter, Props$22 as SplitterProps, Swiper, Props$23 as SwiperProps, Switch, Tag, ThemeConfig, ThemeToken, Toast, StaticProps as ToastOptions, Typography, Upload, UploadFile, Props$24 as UploadProps, useConfig };
56
+ export { Button, Card, Props as CardProps, Checkbox, Col, Props$1 as ColProps, Collapse, Props$2 as CollapseProps, ColorPicker, ComponentSize, Config, Props$3 as ConfigProps, ConfigSnapshotProvider, Container, Props$4 as ContainerProps, Props$5 as ContentProps, DEFAULT_LOCALE, DatePicker, DefaultProps, Drawer, Props$6 as DrawerProps, Dropdown, Props$7 as DropdownProps, Empty, Props$8 as EmptyProps, FloatButton, Props$9 as FooterProps, Props$10 as HeaderProps, Input, Layout, Props$11 as LayoutProps, List, Props$12 as ListProps, Locale, Marquees, Props$13 as MarqueesProps, Menu, Props$14 as MenuProps, Modal, Props$15 as ModalProps, PageHeader, Props$16 as PageHeaderProps, Popover, Progress, Radio, Result, Props$17 as ResultProps, Reveals, Props$18 as RevealsProps, Row, Props$19 as RowProps, Select, Props$20 as SiderProps, Skeleton, Space, Props$21 as SpaceProps, Spin, Splitter, Props$22 as SplitterProps, Swiper, Props$23 as SwiperProps, Switch, Tag, ThemeConfig, ThemeToken, Toast, StaticProps as ToastOptions, Typography, Upload, UploadFile, Props$24 as UploadProps, getRootConfigValue, useConfig };
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import "./style.mjs";
2
2
  import { DEFAULT_LOCALE, useConfig } from "./providers/config/context.mjs";
3
+ import { ConfigSnapshotProvider, getRootConfigValue } from "./providers/config/registry.mjs";
3
4
  import Config from "./providers/config/config.mjs";
4
5
  import Button from "./Button.mjs";
5
6
  import Checkbox from "./components/atoms/checkbox/index.mjs";
@@ -38,4 +39,4 @@ import PageHeader from "./components/templates/page-header.mjs";
38
39
  import Row from "./components/templates/grid/row.mjs";
39
40
  import Col from "./components/templates/grid/col.mjs";
40
41
 
41
- export { Button, Card, Checkbox, Col, Collapse, ColorPicker, Config, Container, DEFAULT_LOCALE, DatePicker, Drawer, Dropdown, Empty, FloatButton, Input, Layout, List, Marquees, Menu, Modal, PageHeader, Popover, Progress, Radio, Result, Reveals, Row, Select, Skeleton, Space, Spin, Splitter, Swiper, Switch, Tag, Toast, Typography, Upload, useConfig };
42
+ export { Button, Card, Checkbox, Col, Collapse, ColorPicker, Config, ConfigSnapshotProvider, Container, DEFAULT_LOCALE, DatePicker, Drawer, Dropdown, Empty, FloatButton, Input, Layout, List, Marquees, Menu, Modal, PageHeader, Popover, Progress, Radio, Result, Reveals, Row, Select, Skeleton, Space, Spin, Splitter, Swiper, Switch, Tag, Toast, Typography, Upload, getRootConfigValue, useConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../../../src/providers/config/config.tsx"],"mappings":";;;;;UAgHU,KAAA;EACR,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,MAAA;EACT,YAAA,SAAqB,WAAA;EACrB,aAAA,GAAgB,aAAA;EAChB,SAAA;EACA,YAAA,GAAe,YAAA;EACf,SAAA;EACA,KAAA,GAAQ,KAAA,CAAM,aAAA;EACd,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA;AAAA,cAGZ,MAAA;EAAU,KAAA;EAAA,MAAA;EAAA,YAAA;EAAA,aAAA;EAAA,SAAA;EAAA,YAAA;EAAA,SAAA;EAAA,KAAA;EAAA;AAAA,GAUb,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../../src/providers/config/config.tsx"],"mappings":";;;;;UAuHU,KAAA;EACR,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,MAAA;EACT,YAAA,SAAqB,WAAA;EACrB,aAAA,GAAgB,aAAA;EAChB,SAAA;EACA,YAAA,GAAe,YAAA;EACf,SAAA;EACA,KAAA,GAAQ,KAAA,CAAM,aAAA;EACd,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA;AAAA,cAGZ,MAAA;EAAU,KAAA;EAAA,MAAA;EAAA,YAAA;EAAA,aAAA;EAAA,SAAA;EAAA,YAAA;EAAA,SAAA;EAAA,KAAA;EAAA;AAAA,GAUb,KAAA,KAAK,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -2,7 +2,8 @@
2
2
 
3
3
  import { cn } from "../../utils.mjs";
4
4
  import { Context, DEFAULT_LOCALE, useConfig } from "./context.mjs";
5
- import { useCallback, useMemo, useState, useSyncExternalStore } from "react";
5
+ import { registerRootConfig } from "./registry.mjs";
6
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from "react";
6
7
  import { jsx } from "react/jsx-runtime";
7
8
  import { DirectionProvider } from "@radix-ui/react-direction";
8
9
 
@@ -71,6 +72,7 @@ const buildCssVars = (token) => {
71
72
  const Config = ({ theme = {}, locale = {}, getContainer, componentSize, direction, defaultProps, className, style, children }) => {
72
73
  const parent = useConfig();
73
74
  const [wrapperEl, setWrapperEl] = useState(null);
75
+ const isRoot = !parent.isConfigured;
74
76
  const themeKey = JSON.stringify(theme);
75
77
  const parentThemeKey = JSON.stringify(parent.theme);
76
78
  const localeKey = JSON.stringify(locale);
@@ -137,6 +139,10 @@ const Config = ({ theme = {}, locale = {}, getContainer, componentSize, directio
137
139
  resolvedDirection,
138
140
  mergedDefaultProps
139
141
  ]);
142
+ useEffect(() => {
143
+ if (!isRoot) return;
144
+ return registerRootConfig(contextValue);
145
+ }, [isRoot, contextValue]);
140
146
  const content = /* @__PURE__ */ jsx(Context.Provider, {
141
147
  value: contextValue,
142
148
  children: needsWrapper ? /* @__PURE__ */ jsx("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../../src/providers/config/config.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport { DirectionProvider } from '@radix-ui/react-direction';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { Context, DEFAULT_LOCALE, useConfig } from './context';\nimport type {\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n} from './types';\n\nconst DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';\n\nconst subscribeToSystemColorScheme = (callback: () => void) => {\n if (typeof window === 'undefined') {\n return () => {};\n }\n\n const mql = window.matchMedia(DARK_MEDIA_QUERY);\n mql.addEventListener('change', callback);\n return () => mql.removeEventListener('change', callback);\n};\n\nconst getSystemPrefersDark = () =>\n typeof window !== 'undefined' && window.matchMedia(DARK_MEDIA_QUERY).matches;\n\n// No `window` during SSR, so there's no way to know the visitor's actual\n// preference before hydration — assume light (the conservative default)\n// and let useSyncExternalStore correct it client-side once matchMedia is\n// available, same flash-of-incorrect-guess tradeoff already accepted for\n// Sider's breakpoint prop elsewhere in this library.\nconst getServerSnapshot = () => false;\n\nconst useSystemPrefersDark = () =>\n useSyncExternalStore(\n subscribeToSystemColorScheme,\n getSystemPrefersDark,\n getServerSnapshot,\n );\n\nconst tokenToCssVar: Record<keyof ThemeToken, string> = {\n colorPrimary: '--primary',\n colorPrimaryForeground: '--primary-foreground',\n colorBackground: '--background',\n colorForeground: '--foreground',\n colorCard: '--card',\n colorCardForeground: '--card-foreground',\n colorPopover: '--popover',\n colorPopoverForeground: '--popover-foreground',\n colorSecondary: '--secondary',\n colorSecondaryForeground: '--secondary-foreground',\n colorMuted: '--muted',\n colorMutedForeground: '--muted-foreground',\n colorAccent: '--accent',\n colorAccentForeground: '--accent-foreground',\n colorDestructive: '--destructive',\n colorBorder: '--border',\n colorInput: '--input',\n colorRing: '--ring',\n borderRadius: '--radius',\n\n btnBackground: '--btn-bg',\n btnBackgroundHover: '--btn-bg-hover',\n btnBackgroundActive: '--btn-bg-active',\n btnBorder: '--btn-border',\n btnForeground: '--btn-fg',\n\n sidebar: '--sidebar',\n sidebarForeground: '--sidebar-foreground',\n sidebarPrimary: '--sidebar-primary',\n sidebarPrimaryForeground: '--sidebar-primary-foreground',\n sidebarAccent: '--sidebar-accent',\n sidebarAccentForeground: '--sidebar-accent-foreground',\n sidebarBorder: '--sidebar-border',\n sidebarRing: '--sidebar-ring',\n\n chart1: '--chart-1',\n chart2: '--chart-2',\n chart3: '--chart-3',\n chart4: '--chart-4',\n chart5: '--chart-5',\n\n fontSans: '--font-sans',\n fontMono: '--font-mono',\n};\n\nconst buildCssVars = (token?: ThemeToken): React.CSSProperties => {\n if (!token) {\n return {};\n }\n\n const vars: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(token)) {\n if (value === undefined) {\n continue;\n }\n const cssVar = tokenToCssVar[key as keyof ThemeToken];\n if (cssVar) {\n vars[cssVar] = value;\n }\n }\n\n return vars as React.CSSProperties;\n};\n\ninterface Props {\n theme?: ThemeConfig;\n locale?: Locale;\n getContainer?: () => HTMLElement;\n componentSize?: ComponentSize;\n direction?: 'ltr' | 'rtl';\n defaultProps?: DefaultProps;\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n}\n\nconst Config = ({\n theme = {},\n locale = {},\n getContainer,\n componentSize,\n direction,\n defaultProps,\n className,\n style,\n children,\n}: Props) => {\n const parent = useConfig();\n const [wrapperEl, setWrapperEl] = useState<HTMLDivElement | null>(null);\n\n // `theme` is typically a fresh inline object literal at the call site\n // (and `parent.theme` inherits the same problem from its own Config\n // ancestor), so depending on them directly meant mergedTheme/contextValue\n // got a new identity on every render regardless of whether the actual\n // values changed — every useConfig() consumer in the tree re-rendered on\n // every render of whatever's above this Config, which for the common\n // app-root usage is everything. Depend on a serialized key instead so\n // the memo only invalidates when the content actually changes; these\n // are small, string/boolean-only objects, so JSON.stringify is cheap.\n const themeKey = JSON.stringify(theme);\n const parentThemeKey = JSON.stringify(parent.theme);\n const localeKey = JSON.stringify(locale);\n const parentLocaleKey = JSON.stringify(parent.locale);\n\n const mergedTheme = useMemo<ThemeConfig>(\n () => ({\n ...parent.theme,\n ...theme,\n token: {\n ...parent.theme.token,\n ...theme.token,\n },\n darkToken: {\n ...parent.theme.darkToken,\n ...theme.darkToken,\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentThemeKey, themeKey],\n );\n\n const mergedLocale = useMemo<Locale>(\n () => ({\n ...parent.locale,\n ...locale,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentLocaleKey, localeKey],\n );\n\n const systemPrefersDark = useSystemPrefersDark();\n const isDarkActive =\n mergedTheme.dark === 'system'\n ? systemPrefersDark\n : mergedTheme.dark === 'dark';\n\n // `darkToken` overrides individual keys of `token` (not a full swap)\n // only while dark mode is actually active.\n const cssVars = useMemo(\n () =>\n buildCssVars(\n isDarkActive\n ? { ...mergedTheme.token, ...mergedTheme.darkToken }\n : mergedTheme.token,\n ),\n [mergedTheme.token, mergedTheme.darkToken, isDarkActive],\n );\n\n const hasCssVars = Object.keys(cssVars).length > 0;\n const hasDarkMode = mergedTheme.dark !== undefined;\n const needsWrapper =\n hasCssVars || hasDarkMode || !!className || !!style || !!direction;\n\n // Resolution order: an explicit `getContainer` prop on this Config wins;\n // otherwise, if this Config renders its own themed wrapper below, that's\n // the default (portaled content appended into it still inherits its CSS\n // custom properties and matches the `.dark` selector — `display:\n // contents` only removes the element from the layout box tree, not from\n // the DOM/cascade); otherwise defer to the parent Config's resolution,\n // so an inner Config that only sets e.g. `locale` doesn't shadow an\n // outer one's theme container.\n const resolvedGetContainer = useCallback((): HTMLElement | undefined => {\n if (getContainer) {\n return getContainer();\n }\n if (needsWrapper && wrapperEl) {\n return wrapperEl;\n }\n return parent.getContainer();\n }, [getContainer, needsWrapper, wrapperEl, parent]);\n\n const resolvedComponentSize = componentSize ?? parent.componentSize;\n const resolvedDirection = direction ?? parent.direction;\n\n const defaultPropsKey = JSON.stringify(defaultProps);\n const parentDefaultPropsKey = JSON.stringify(parent.defaultProps);\n\n const mergedDefaultProps = useMemo<DefaultProps | undefined>(\n () =>\n parent.defaultProps || defaultProps\n ? { ...parent.defaultProps, ...defaultProps }\n : undefined,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentDefaultPropsKey, defaultPropsKey],\n );\n\n const contextValue = useMemo(\n () => ({\n theme: mergedTheme,\n locale: mergedLocale,\n getContainer: resolvedGetContainer,\n componentSize: resolvedComponentSize,\n direction: resolvedDirection,\n defaultProps: mergedDefaultProps,\n isConfigured: true,\n }),\n [\n mergedTheme,\n mergedLocale,\n resolvedGetContainer,\n resolvedComponentSize,\n resolvedDirection,\n mergedDefaultProps,\n ],\n );\n\n const content = (\n <Context.Provider value={contextValue}>\n {needsWrapper ? (\n <div\n ref={setWrapperEl}\n dir={direction}\n className={cn(isDarkActive && 'dark', className)}\n style={{\n ...(hasCssVars ? cssVars : undefined),\n ...style,\n // Keeps this element out of the layout box tree — so it\n // doesn't intercept flex/grid child relationships or break\n // height chains like min-h-screen — while custom properties\n // and the `dark` class selector still cascade to children\n // normally (inheritance and box generation are separate\n // mechanisms in CSS). Note this means className/style\n // shouldn't be used here for anything that needs a real box\n // (padding, background, borders); Config's className/style are\n // meant for theming hooks (the `dark` class, arbitrary\n // selector hooks, custom properties), not layout — spread\n // after the caller's `style` so it can't be overridden into\n // something that reintroduces the layout-breaking wrapper this\n // was added to fix in the first place.\n display: 'contents',\n }}\n >\n {children}\n </div>\n ) : (\n children\n )}\n </Context.Provider>\n );\n\n // DirectionProvider is Radix's own mechanism (@radix-ui/react-direction)\n // for propagating `dir` to every primitive that calls its useDirection()\n // internally — Select, Menu (Dropdown/Menu), Accordion (Collapse),\n // RadioGroup, ScrollArea all pick up RTL automatically this way, no\n // per-component wiring needed. Only wraps when this Config's own\n // `direction` prop is set (not the merely-inherited resolved value) —\n // context already cascades on its own, so re-wrapping at every nested\n // Config that doesn't override direction would just be redundant.\n // Components with hardcoded LTR-only layout logic that Radix's Direction\n // context doesn't reach (Popover placement, Splitter, Sider, Drawer)\n // aren't covered by this and would need dedicated follow-up work.\n return direction ? (\n <DirectionProvider dir={direction}>{content}</DirectionProvider>\n ) : (\n content\n );\n};\n\nexport default Config;\nexport { useConfig, DEFAULT_LOCALE };\nexport type {\n Props,\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../../src/providers/config/config.tsx"],"sourcesContent":["'use client';\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from 'react';\n\nimport { DirectionProvider } from '@radix-ui/react-direction';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { Context, DEFAULT_LOCALE, useConfig } from './context';\nimport { registerRootConfig } from './registry';\nimport type {\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n} from './types';\n\nconst DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';\n\nconst subscribeToSystemColorScheme = (callback: () => void) => {\n if (typeof window === 'undefined') {\n return () => {};\n }\n\n const mql = window.matchMedia(DARK_MEDIA_QUERY);\n mql.addEventListener('change', callback);\n return () => mql.removeEventListener('change', callback);\n};\n\nconst getSystemPrefersDark = () =>\n typeof window !== 'undefined' && window.matchMedia(DARK_MEDIA_QUERY).matches;\n\n// No `window` during SSR, so there's no way to know the visitor's actual\n// preference before hydration — assume light (the conservative default)\n// and let useSyncExternalStore correct it client-side once matchMedia is\n// available, same flash-of-incorrect-guess tradeoff already accepted for\n// Sider's breakpoint prop elsewhere in this library.\nconst getServerSnapshot = () => false;\n\nconst useSystemPrefersDark = () =>\n useSyncExternalStore(\n subscribeToSystemColorScheme,\n getSystemPrefersDark,\n getServerSnapshot,\n );\n\nconst tokenToCssVar: Record<keyof ThemeToken, string> = {\n colorPrimary: '--primary',\n colorPrimaryForeground: '--primary-foreground',\n colorBackground: '--background',\n colorForeground: '--foreground',\n colorCard: '--card',\n colorCardForeground: '--card-foreground',\n colorPopover: '--popover',\n colorPopoverForeground: '--popover-foreground',\n colorSecondary: '--secondary',\n colorSecondaryForeground: '--secondary-foreground',\n colorMuted: '--muted',\n colorMutedForeground: '--muted-foreground',\n colorAccent: '--accent',\n colorAccentForeground: '--accent-foreground',\n colorDestructive: '--destructive',\n colorBorder: '--border',\n colorInput: '--input',\n colorRing: '--ring',\n borderRadius: '--radius',\n\n btnBackground: '--btn-bg',\n btnBackgroundHover: '--btn-bg-hover',\n btnBackgroundActive: '--btn-bg-active',\n btnBorder: '--btn-border',\n btnForeground: '--btn-fg',\n\n sidebar: '--sidebar',\n sidebarForeground: '--sidebar-foreground',\n sidebarPrimary: '--sidebar-primary',\n sidebarPrimaryForeground: '--sidebar-primary-foreground',\n sidebarAccent: '--sidebar-accent',\n sidebarAccentForeground: '--sidebar-accent-foreground',\n sidebarBorder: '--sidebar-border',\n sidebarRing: '--sidebar-ring',\n\n chart1: '--chart-1',\n chart2: '--chart-2',\n chart3: '--chart-3',\n chart4: '--chart-4',\n chart5: '--chart-5',\n\n fontSans: '--font-sans',\n fontMono: '--font-mono',\n};\n\nconst buildCssVars = (token?: ThemeToken): React.CSSProperties => {\n if (!token) {\n return {};\n }\n\n const vars: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(token)) {\n if (value === undefined) {\n continue;\n }\n const cssVar = tokenToCssVar[key as keyof ThemeToken];\n if (cssVar) {\n vars[cssVar] = value;\n }\n }\n\n return vars as React.CSSProperties;\n};\n\ninterface Props {\n theme?: ThemeConfig;\n locale?: Locale;\n getContainer?: () => HTMLElement;\n componentSize?: ComponentSize;\n direction?: 'ltr' | 'rtl';\n defaultProps?: DefaultProps;\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n}\n\nconst Config = ({\n theme = {},\n locale = {},\n getContainer,\n componentSize,\n direction,\n defaultProps,\n className,\n style,\n children,\n}: Props) => {\n const parent = useConfig();\n const [wrapperEl, setWrapperEl] = useState<HTMLDivElement | null>(null);\n\n // Only a Config with no Config ancestor of its own registers for the\n // imperative stack (Modal.*/Toast.*) to inherit — see registry.tsx.\n const isRoot = !parent.isConfigured;\n\n // `theme` is typically a fresh inline object literal at the call site\n // (and `parent.theme` inherits the same problem from its own Config\n // ancestor), so depending on them directly meant mergedTheme/contextValue\n // got a new identity on every render regardless of whether the actual\n // values changed — every useConfig() consumer in the tree re-rendered on\n // every render of whatever's above this Config, which for the common\n // app-root usage is everything. Depend on a serialized key instead so\n // the memo only invalidates when the content actually changes; these\n // are small, string/boolean-only objects, so JSON.stringify is cheap.\n const themeKey = JSON.stringify(theme);\n const parentThemeKey = JSON.stringify(parent.theme);\n const localeKey = JSON.stringify(locale);\n const parentLocaleKey = JSON.stringify(parent.locale);\n\n const mergedTheme = useMemo<ThemeConfig>(\n () => ({\n ...parent.theme,\n ...theme,\n token: {\n ...parent.theme.token,\n ...theme.token,\n },\n darkToken: {\n ...parent.theme.darkToken,\n ...theme.darkToken,\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentThemeKey, themeKey],\n );\n\n const mergedLocale = useMemo<Locale>(\n () => ({\n ...parent.locale,\n ...locale,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentLocaleKey, localeKey],\n );\n\n const systemPrefersDark = useSystemPrefersDark();\n const isDarkActive =\n mergedTheme.dark === 'system'\n ? systemPrefersDark\n : mergedTheme.dark === 'dark';\n\n // `darkToken` overrides individual keys of `token` (not a full swap)\n // only while dark mode is actually active.\n const cssVars = useMemo(\n () =>\n buildCssVars(\n isDarkActive\n ? { ...mergedTheme.token, ...mergedTheme.darkToken }\n : mergedTheme.token,\n ),\n [mergedTheme.token, mergedTheme.darkToken, isDarkActive],\n );\n\n const hasCssVars = Object.keys(cssVars).length > 0;\n const hasDarkMode = mergedTheme.dark !== undefined;\n const needsWrapper =\n hasCssVars || hasDarkMode || !!className || !!style || !!direction;\n\n // Resolution order: an explicit `getContainer` prop on this Config wins;\n // otherwise, if this Config renders its own themed wrapper below, that's\n // the default (portaled content appended into it still inherits its CSS\n // custom properties and matches the `.dark` selector — `display:\n // contents` only removes the element from the layout box tree, not from\n // the DOM/cascade); otherwise defer to the parent Config's resolution,\n // so an inner Config that only sets e.g. `locale` doesn't shadow an\n // outer one's theme container.\n const resolvedGetContainer = useCallback((): HTMLElement | undefined => {\n if (getContainer) {\n return getContainer();\n }\n if (needsWrapper && wrapperEl) {\n return wrapperEl;\n }\n return parent.getContainer();\n }, [getContainer, needsWrapper, wrapperEl, parent]);\n\n const resolvedComponentSize = componentSize ?? parent.componentSize;\n const resolvedDirection = direction ?? parent.direction;\n\n const defaultPropsKey = JSON.stringify(defaultProps);\n const parentDefaultPropsKey = JSON.stringify(parent.defaultProps);\n\n const mergedDefaultProps = useMemo<DefaultProps | undefined>(\n () =>\n parent.defaultProps || defaultProps\n ? { ...parent.defaultProps, ...defaultProps }\n : undefined,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentDefaultPropsKey, defaultPropsKey],\n );\n\n const contextValue = useMemo(\n () => ({\n theme: mergedTheme,\n locale: mergedLocale,\n getContainer: resolvedGetContainer,\n componentSize: resolvedComponentSize,\n direction: resolvedDirection,\n defaultProps: mergedDefaultProps,\n isConfigured: true,\n }),\n [\n mergedTheme,\n mergedLocale,\n resolvedGetContainer,\n resolvedComponentSize,\n resolvedDirection,\n mergedDefaultProps,\n ],\n );\n\n useEffect(() => {\n if (!isRoot) {\n return;\n }\n return registerRootConfig(contextValue);\n }, [isRoot, contextValue]);\n\n const content = (\n <Context.Provider value={contextValue}>\n {needsWrapper ? (\n <div\n ref={setWrapperEl}\n dir={direction}\n className={cn(isDarkActive && 'dark', className)}\n style={{\n ...(hasCssVars ? cssVars : undefined),\n ...style,\n // Keeps this element out of the layout box tree — so it\n // doesn't intercept flex/grid child relationships or break\n // height chains like min-h-screen — while custom properties\n // and the `dark` class selector still cascade to children\n // normally (inheritance and box generation are separate\n // mechanisms in CSS). Note this means className/style\n // shouldn't be used here for anything that needs a real box\n // (padding, background, borders); Config's className/style are\n // meant for theming hooks (the `dark` class, arbitrary\n // selector hooks, custom properties), not layout — spread\n // after the caller's `style` so it can't be overridden into\n // something that reintroduces the layout-breaking wrapper this\n // was added to fix in the first place.\n display: 'contents',\n }}\n >\n {children}\n </div>\n ) : (\n children\n )}\n </Context.Provider>\n );\n\n // DirectionProvider is Radix's own mechanism (@radix-ui/react-direction)\n // for propagating `dir` to every primitive that calls its useDirection()\n // internally — Select, Menu (Dropdown/Menu), Accordion (Collapse),\n // RadioGroup, ScrollArea all pick up RTL automatically this way, no\n // per-component wiring needed. Only wraps when this Config's own\n // `direction` prop is set (not the merely-inherited resolved value) —\n // context already cascades on its own, so re-wrapping at every nested\n // Config that doesn't override direction would just be redundant.\n // Components with hardcoded LTR-only layout logic that Radix's Direction\n // context doesn't reach (Popover placement, Splitter, Sider, Drawer)\n // aren't covered by this and would need dedicated follow-up work.\n return direction ? (\n <DirectionProvider dir={direction}>{content}</DirectionProvider>\n ) : (\n content\n );\n};\n\nexport default Config;\nexport { useConfig, DEFAULT_LOCALE };\nexport type {\n Props,\n ComponentSize,\n DefaultProps,\n Locale,\n ThemeConfig,\n ThemeToken,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.mts","names":[],"sources":["../../../src/providers/config/context.ts"],"mappings":";;;;cAMa,cAAA,EAAgB,QAAA,CAAS,MAAA;AAAA,cAgBzB,SAAA,QAAS,YAAA"}
1
+ {"version":3,"file":"context.d.mts","names":[],"sources":["../../../src/providers/config/context.ts"],"mappings":";;;;cAMa,cAAA,EAAgB,QAAA,CAAS,MAAA;AAAA,cAmBzB,SAAA,QAAS,YAAA"}
@@ -9,7 +9,10 @@ const DEFAULT_LOCALE = {
9
9
  backToTop: "맨 위로",
10
10
  loading: "로딩 중",
11
11
  expand: "펼치기",
12
- collapse: "접기"
12
+ collapse: "접기",
13
+ back: "뒤로가기",
14
+ ok: "OK",
15
+ cancel: "Cancel"
13
16
  };
14
17
  const Context = createContext({
15
18
  theme: {},
@@ -1 +1 @@
1
- {"version":3,"file":"context.mjs","names":[],"sources":["../../../src/providers/config/context.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext } from 'react';\n\nimport type { ContextValue, Locale } from './types';\n\nexport const DEFAULT_LOCALE: Required<Locale> = {\n close: '닫기',\n clear: '지우기',\n backToTop: '맨 위로',\n loading: '로딩 중',\n expand: '펼치기',\n collapse: '접기',\n};\n\nexport const Context = createContext<ContextValue>({\n theme: {},\n locale: DEFAULT_LOCALE,\n getContainer: () => undefined,\n isConfigured: false,\n});\n\nexport const useConfig = () => useContext(Context);\n"],"mappings":";;;;;AAMA,MAAa,iBAAmC;CAC9C,OAAO;CACP,OAAO;CACP,WAAW;CACX,SAAS;CACT,QAAQ;CACR,UAAU;CACX;AAED,MAAa,UAAU,cAA4B;CACjD,OAAO,EAAE;CACT,QAAQ;CACR,oBAAoB;CACpB,cAAc;CACf,CAAC;AAEF,MAAa,kBAAkB,WAAW,QAAQ"}
1
+ {"version":3,"file":"context.mjs","names":[],"sources":["../../../src/providers/config/context.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext } from 'react';\n\nimport type { ContextValue, Locale } from './types';\n\nexport const DEFAULT_LOCALE: Required<Locale> = {\n close: '닫기',\n clear: '지우기',\n backToTop: '맨 위로',\n loading: '로딩 중',\n expand: '펼치기',\n collapse: '접기',\n back: '뒤로가기',\n ok: 'OK',\n cancel: 'Cancel',\n};\n\nexport const Context = createContext<ContextValue>({\n theme: {},\n locale: DEFAULT_LOCALE,\n getContainer: () => undefined,\n isConfigured: false,\n});\n\nexport const useConfig = () => useContext(Context);\n"],"mappings":";;;;;AAMA,MAAa,iBAAmC;CAC9C,OAAO;CACP,OAAO;CACP,WAAW;CACX,SAAS;CACT,QAAQ;CACR,UAAU;CACV,MAAM;CACN,IAAI;CACJ,QAAQ;CACT;AAED,MAAa,UAAU,cAA4B;CACjD,OAAO,EAAE;CACT,QAAQ;CACR,oBAAoB;CACpB,cAAc;CACf,CAAC;AAEF,MAAa,kBAAkB,WAAW,QAAQ"}
@@ -1,3 +1,4 @@
1
1
  import { ComponentSize, DefaultProps, Locale, ThemeConfig, ThemeToken } from "./types.mjs";
2
2
  import { DEFAULT_LOCALE, useConfig } from "./context.mjs";
3
- import { Config, Props } from "./config.mjs";
3
+ import { Config, Props } from "./config.mjs";
4
+ import { ConfigSnapshotProvider, getRootConfigValue } from "./registry.mjs";
@@ -0,0 +1,14 @@
1
+ import { ContextValue } from "./types.mjs";
2
+ import * as react from "react";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/providers/config/registry.d.ts
6
+ declare const getRootConfigValue: () => ContextValue | null;
7
+ declare const ConfigSnapshotProvider: ({
8
+ children
9
+ }: {
10
+ children: React.ReactNode;
11
+ }) => string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime0.JSX.Element | null | undefined;
12
+ //#endregion
13
+ export { ConfigSnapshotProvider, getRootConfigValue };
14
+ //# sourceMappingURL=registry.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.mts","names":[],"sources":["../../../src/providers/config/registry.tsx"],"mappings":";;;;;cAkCa,kBAAA,QAAyB,YAAA;AAAA,cAOzB,sBAAA;EAA0B;AAAA;EAGrC,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA,2CACjB,QAAA,CAD0B,KAAA,CAC1B,SAAA,IAAA,OAAA,sCAAA,KAAA,CAAA,WAAA,GAAA,KAAA,CAAA,YAAA,mBAAA,KAAA,CAAA,qBAAA,SAAA,QAAA,CAAA,KAAA,CAAA,SAAA,wBAAA,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { Context } from "./context.mjs";
4
+ import { jsx } from "react/jsx-runtime";
5
+
6
+ //#region src/providers/config/registry.tsx
7
+ let currentValue = null;
8
+ const registerRootConfig = (value) => {
9
+ currentValue = value;
10
+ return () => {
11
+ if (currentValue === value) currentValue = null;
12
+ };
13
+ };
14
+ const getRootConfigValue = () => currentValue;
15
+ const ConfigSnapshotProvider = ({ children }) => {
16
+ const value = getRootConfigValue();
17
+ if (!value) return children;
18
+ return /* @__PURE__ */ jsx(Context.Provider, {
19
+ value,
20
+ children
21
+ });
22
+ };
23
+
24
+ //#endregion
25
+ export { ConfigSnapshotProvider, getRootConfigValue, registerRootConfig };
26
+ //# sourceMappingURL=registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.mjs","names":[],"sources":["../../../src/providers/config/registry.tsx"],"sourcesContent":["'use client';\n\nimport { Context } from './context';\nimport type { ContextValue } from './types';\n\n// The imperative stack (Modal.*/Toast.*, see organisms/imperative-stack.tsx)\n// renders into its own React root, outside any <Config> ancestor — there's\n// no call-site component to read useConfig() from, since these are plain\n// function calls, not JSX. This module-level registry is how that root\n// finds a Config to inherit anyway: the app's root Config registers its\n// current context value here, and the stack re-wraps itself with it as a\n// snapshot at render time (not a live subscription — a Modal/Toast that's\n// already open won't pick up a Config change made after it was created,\n// same \"reflects state at creation\" behavior as everything else about the\n// imperative API being a one-shot call).\n//\n// Only the root Config (no Config ancestor of its own — see\n// `isRoot` in config.tsx) registers. A section-local nested Config (used\n// for one-off theming of part of a page) intentionally does not — an app-\n// wide `Modal.confirm()` call has no natural association with whichever\n// nested Config happened to render most recently, so it should inherit\n// the app's actual root configuration instead.\nlet currentValue: ContextValue | null = null;\n\nexport const registerRootConfig = (value: ContextValue): (() => void) => {\n currentValue = value;\n\n return () => {\n if (currentValue === value) {\n currentValue = null;\n }\n };\n};\n\nexport const getRootConfigValue = (): ContextValue | null => currentValue;\n\n// Wraps the imperative stack's rendered items with whatever the root\n// Config's snapshot currently is, so useConfig() inside Modal/Toast (and\n// anything they render) resolves theme/locale/componentSize/etc instead of\n// the raw context default. A no-op passthrough when no root Config has\n// registered (e.g. no <Config> anywhere in the app).\nexport const ConfigSnapshotProvider = ({\n children,\n}: {\n children: React.ReactNode;\n}) => {\n const value = getRootConfigValue();\n\n if (!value) {\n return children;\n }\n\n return <Context.Provider value={value}>{children}</Context.Provider>;\n};\n"],"mappings":";;;;;;AAsBA,IAAI,eAAoC;AAExC,MAAa,sBAAsB,UAAsC;AACvE,gBAAe;AAEf,cAAa;AACX,MAAI,iBAAiB,MACnB,gBAAe;;;AAKrB,MAAa,2BAAgD;AAO7D,MAAa,0BAA0B,EACrC,eAGI;CACJ,MAAM,QAAQ,oBAAoB;AAElC,KAAI,CAAC,MACH,QAAO;AAGT,QAAO,oBAAC,QAAQ;EAAgB;EAAQ;GAA4B"}
@@ -52,6 +52,9 @@ interface Locale {
52
52
  loading?: string;
53
53
  expand?: string;
54
54
  collapse?: string;
55
+ back?: string;
56
+ ok?: string;
57
+ cancel?: string;
55
58
  }
56
59
  type ComponentSize = 'small' | 'middle' | 'large';
57
60
  type DefaultProps = Record<string, Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/providers/config/types.ts"],"mappings":";UAAiB,UAAA;EAEf,YAAA;EACA,sBAAA;EACA,eAAA;EACA,eAAA;EACA,SAAA;EACA,mBAAA;EACA,YAAA;EACA,sBAAA;EACA,cAAA;EACA,wBAAA;EACA,UAAA;EACA,oBAAA;EACA,WAAA;EACA,qBAAA;EACA,gBAAA;EACA,WAAA;EACA,UAAA;EACA,SAAA;EASA,YAAA;EAIA,aAAA;EACA,kBAAA;EACA,mBAAA;EACA,SAAA;EACA,aAAA;EAGA,OAAA;EACA,iBAAA;EACA,cAAA;EACA,wBAAA;EACA,aAAA;EACA,uBAAA;EACA,aAAA;EACA,WAAA;EAGA,MAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EAGA,QAAA;EACA,QAAA;AAAA;AAAA,UAGe,WAAA;EACf,KAAA,GAAQ,UAAA;EAKR,SAAA,GAAY,UAAA;EASZ,IAAA;AAAA;AAAA,UAQe,MAAA;EACf,KAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;EACA,MAAA;EACA,QAAA;AAAA;AAAA,KAMU,aAAA;AAAA,KAYA,YAAA,GAAe,MAAA,SAAe,MAAA;AAAA,UAEzB,YAAA;EACf,KAAA,EAAO,WAAA;EACP,MAAA,EAAQ,MAAA;EACR,SAAA;EAUA,YAAA,QAAoB,WAAA;EACpB,aAAA,GAAgB,aAAA;EAChB,YAAA,GAAe,YAAA;EAKf,YAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/providers/config/types.ts"],"mappings":";UAAiB,UAAA;EAEf,YAAA;EACA,sBAAA;EACA,eAAA;EACA,eAAA;EACA,SAAA;EACA,mBAAA;EACA,YAAA;EACA,sBAAA;EACA,cAAA;EACA,wBAAA;EACA,UAAA;EACA,oBAAA;EACA,WAAA;EACA,qBAAA;EACA,gBAAA;EACA,WAAA;EACA,UAAA;EACA,SAAA;EASA,YAAA;EAIA,aAAA;EACA,kBAAA;EACA,mBAAA;EACA,SAAA;EACA,aAAA;EAGA,OAAA;EACA,iBAAA;EACA,cAAA;EACA,wBAAA;EACA,aAAA;EACA,uBAAA;EACA,aAAA;EACA,WAAA;EAGA,MAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EACA,MAAA;EAGA,QAAA;EACA,QAAA;AAAA;AAAA,UAGe,WAAA;EACf,KAAA,GAAQ,UAAA;EAKR,SAAA,GAAY,UAAA;EASZ,IAAA;AAAA;AAAA,UAQe,MAAA;EACf,KAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;EACA,MAAA;EACA,QAAA;EACA,IAAA;EACA,EAAA;EACA,MAAA;AAAA;AAAA,KAMU,aAAA;AAAA,KAYA,YAAA,GAAe,MAAA,SAAe,MAAA;AAAA,UAEzB,YAAA;EACf,KAAA,EAAO,WAAA;EACP,MAAA,EAAQ,MAAA;EACR,SAAA;EAUA,YAAA,QAAoB,WAAA;EACpB,aAAA,GAAgB,aAAA;EAChB,YAAA,GAAe,YAAA;EAKf,YAAA;AAAA"}
@@ -1,5 +1,6 @@
1
1
  import { ComponentSize, DefaultProps, Locale, ThemeConfig, ThemeToken } from "./providers/config/types.mjs";
2
2
  import { DEFAULT_LOCALE, useConfig } from "./providers/config/context.mjs";
3
3
  import { Config, Props } from "./providers/config/config.mjs";
4
+ import { ConfigSnapshotProvider, getRootConfigValue } from "./providers/config/registry.mjs";
4
5
  import "./providers/config/index.mjs";
5
- export { type ComponentSize, Config, type Props as ConfigProps, DEFAULT_LOCALE, type DefaultProps, type Locale, type ThemeConfig, type ThemeToken, useConfig };
6
+ export { type ComponentSize, Config, type Props as ConfigProps, ConfigSnapshotProvider, DEFAULT_LOCALE, type DefaultProps, type Locale, type ThemeConfig, type ThemeToken, getRootConfigValue, useConfig };
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_LOCALE, useConfig } from "./providers/config/context.mjs";
2
+ import { ConfigSnapshotProvider, getRootConfigValue } from "./providers/config/registry.mjs";
2
3
  import Config from "./providers/config/config.mjs";
3
4
 
4
- export { Config, DEFAULT_LOCALE, useConfig };
5
+ export { Config, ConfigSnapshotProvider, DEFAULT_LOCALE, getRootConfigValue, useConfig };
package/dist/style.css CHANGED
@@ -3599,6 +3599,46 @@
3599
3599
  [data-slot='button'][data-color='gold'] {
3600
3600
  --btn-bg: #f59e0b;
3601
3601
  }
3602
+ [data-slot='col'] {
3603
+ flex-basis: calc(var(--col-span-xs) / 24 * 100%);
3604
+ max-width: calc(var(--col-span-xs) / 24 * 100%);
3605
+ margin-left: calc(var(--col-offset-xs, 0) / 24 * 100%);
3606
+ }
3607
+ @media (width >= 40rem) {
3608
+ [data-slot='col'] {
3609
+ flex-basis: calc(var(--col-span-sm) / 24 * 100%);
3610
+ max-width: calc(var(--col-span-sm) / 24 * 100%);
3611
+ margin-left: calc(var(--col-offset-sm, 0) / 24 * 100%);
3612
+ }
3613
+ }
3614
+ @media (width >= 48rem) {
3615
+ [data-slot='col'] {
3616
+ flex-basis: calc(var(--col-span-md) / 24 * 100%);
3617
+ max-width: calc(var(--col-span-md) / 24 * 100%);
3618
+ margin-left: calc(var(--col-offset-md, 0) / 24 * 100%);
3619
+ }
3620
+ }
3621
+ @media (width >= 64rem) {
3622
+ [data-slot='col'] {
3623
+ flex-basis: calc(var(--col-span-lg) / 24 * 100%);
3624
+ max-width: calc(var(--col-span-lg) / 24 * 100%);
3625
+ margin-left: calc(var(--col-offset-lg, 0) / 24 * 100%);
3626
+ }
3627
+ }
3628
+ @media (width >= 80rem) {
3629
+ [data-slot='col'] {
3630
+ flex-basis: calc(var(--col-span-xl) / 24 * 100%);
3631
+ max-width: calc(var(--col-span-xl) / 24 * 100%);
3632
+ margin-left: calc(var(--col-offset-xl, 0) / 24 * 100%);
3633
+ }
3634
+ }
3635
+ @media (width >= 96rem) {
3636
+ [data-slot='col'] {
3637
+ flex-basis: calc(var(--col-span-2xl) / 24 * 100%);
3638
+ max-width: calc(var(--col-span-2xl) / 24 * 100%);
3639
+ margin-left: calc(var(--col-offset-2xl, 0) / 24 * 100%);
3640
+ }
3641
+ }
3602
3642
  }
3603
3643
  @property --tw-translate-x {
3604
3644
  syntax: "*";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbpark/ui-kit",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "Modern React UI component library built with TypeScript, Tailwind CSS, and Radix UI. Featuring atoms, molecules, organisms and layout templates for building beautiful interfaces.",
5
5
  "keywords": [
6
6
  "react",