@jbpark/ui-kit 6.0.1 → 6.2.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.
- package/dist/Button.d.mts +21 -22
- package/dist/Button.d.mts.map +1 -1
- package/dist/Button.mjs +33 -10
- package/dist/Button.mjs.map +1 -1
- package/dist/Space.d.mts.map +1 -1
- package/dist/Space.mjs +6 -2
- package/dist/Space.mjs.map +1 -1
- package/dist/Tag.d.mts +17 -3
- package/dist/Tag.d.mts.map +1 -1
- package/dist/Tag.mjs +14 -2
- package/dist/Tag.mjs.map +1 -1
- package/dist/components/atoms/checkbox/group.mjs +2 -2
- package/dist/components/atoms/checkbox/group.mjs.map +1 -1
- package/dist/components/atoms/index.d.mts +2 -1
- package/dist/components/atoms/radio/group.mjs +1 -1
- package/dist/components/atoms/radio/group.mjs.map +1 -1
- package/dist/components/atoms/slider/index.mjs +7 -0
- package/dist/components/atoms/slider/index.mjs.map +1 -0
- package/dist/components/atoms/slider/slider.d.mts +17 -0
- package/dist/components/atoms/slider/slider.d.mts.map +1 -0
- package/dist/components/atoms/slider/slider.mjs +36 -0
- package/dist/components/atoms/slider/slider.mjs.map +1 -0
- package/dist/components/molecules/menu/item/item.mjs +1 -1
- package/dist/components/molecules/menu/item/item.mjs.map +1 -1
- package/dist/components/molecules/upload.mjs +1 -1
- package/dist/components/molecules/upload.mjs.map +1 -1
- package/dist/components/organisms/modal.d.mts +9 -1
- package/dist/components/organisms/modal.d.mts.map +1 -1
- package/dist/components/organisms/modal.mjs +14 -10
- package/dist/components/organisms/modal.mjs.map +1 -1
- package/dist/components/organisms/toast.d.mts +10 -4
- package/dist/components/organisms/toast.d.mts.map +1 -1
- package/dist/components/organisms/toast.mjs +10 -9
- package/dist/components/organisms/toast.mjs.map +1 -1
- package/dist/core/index.d.mts +1 -0
- package/dist/core/slider.d.mts +2 -0
- package/dist/core/slider.mjs +54 -0
- package/dist/core/slider.mjs.map +1 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/lib/colors.d.mts +12 -0
- package/dist/lib/colors.d.mts.map +1 -0
- package/dist/lib/colors.mjs +28 -0
- package/dist/lib/colors.mjs.map +1 -0
- package/dist/style.css +95 -0
- package/package.json +2 -1
package/dist/Button.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { PresetColor } from "./lib/colors.mjs";
|
|
1
2
|
import * as React$1 from "react";
|
|
2
3
|
//#region src/components/atoms/button.d.ts
|
|
3
4
|
type NativeButtonProps = React$1.ComponentProps<'button'> & {
|
|
4
5
|
asChild?: boolean;
|
|
5
6
|
};
|
|
6
|
-
type PresetColors = 'blue' | 'purple' | 'cyan' | 'green' | 'magenta' | 'pink' | 'red' | 'orange' | 'yellow' | 'volcano' | 'geekblue' | 'lime' | 'gold';
|
|
7
7
|
interface Props extends Omit<NativeButtonProps, 'size' | 'variant' | 'type'> {
|
|
8
8
|
icon?: React$1.ReactNode;
|
|
9
9
|
block?: boolean;
|
|
@@ -11,31 +11,26 @@ interface Props extends Omit<NativeButtonProps, 'size' | 'variant' | 'type'> {
|
|
|
11
11
|
disabled?: boolean;
|
|
12
12
|
size?: 'small' | 'middle' | 'large';
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Syntactic sugar for a `(color, variant)` pair. **Will follow `variant` and
|
|
15
|
+
* `color` if provided** — `type` only fills in the axes you leave unset.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
21
|
-
* `
|
|
22
|
-
* `
|
|
17
|
+
* | `type` | equivalent to |
|
|
18
|
+
* | --------- | -------------------------------------- |
|
|
19
|
+
* | `primary` | `color="primary" variant="solid"` |
|
|
20
|
+
* | `default` | `color="default" variant="outlined"` |
|
|
21
|
+
* | `dashed` | `color="default" variant="dashed"` |
|
|
22
|
+
* | `text` | `color="default" variant="text"` |
|
|
23
|
+
* | `link` | `color="primary" variant="link"` |
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* | `default` | `outlined` |
|
|
29
|
-
* | `dashed` | `dashed` |
|
|
30
|
-
* | `text` | `text` |
|
|
31
|
-
* | `link` | `link` |
|
|
32
|
-
*
|
|
33
|
-
* Passing both still lets `variant` win.
|
|
25
|
+
* So `<Button type="primary">` is the same as
|
|
26
|
+
* `<Button color="primary" variant="solid">`, and
|
|
27
|
+
* `<Button type="primary" variant="filled">` keeps the primary color but
|
|
28
|
+
* renders filled. `danger` still overrides the resolved color.
|
|
34
29
|
*/
|
|
35
30
|
type?: 'primary' | 'default' | 'dashed' | 'text' | 'link';
|
|
36
31
|
/**
|
|
37
|
-
* Visual fill style
|
|
38
|
-
*
|
|
32
|
+
* Visual fill style, independent of `color`. Defaults to `'outlined'`.
|
|
33
|
+
* Takes precedence over whatever `type` would have supplied.
|
|
39
34
|
*/
|
|
40
35
|
variant?: 'solid' | 'outlined' | 'dashed' | 'filled' | 'text' | 'link';
|
|
41
36
|
/**
|
|
@@ -45,7 +40,11 @@ interface Props extends Omit<NativeButtonProps, 'size' | 'variant' | 'type'> {
|
|
|
45
40
|
*/
|
|
46
41
|
htmlType?: 'button' | 'submit' | 'reset';
|
|
47
42
|
shape?: 'default' | 'circle' | 'round';
|
|
48
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Color, independent of `variant`. Defaults to `'default'`, or to whatever
|
|
45
|
+
* `type` maps to when `type` is set. `danger` overrides this.
|
|
46
|
+
*/
|
|
47
|
+
color?: PresetColor | 'default' | 'primary' | 'danger';
|
|
49
48
|
loading?: boolean | {
|
|
50
49
|
icon: React$1.ReactNode;
|
|
51
50
|
};
|
package/dist/Button.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.d.mts","names":[],"sources":["../src/components/atoms/button.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Button.d.mts","names":[],"sources":["../src/components/atoms/button.tsx"],"mappings":";;;KAaK,oBAAoB,QAAM;EAC7B;;UAGe,cAAc,KAC7B;EAGA,OAAO,QAAM;EACb;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;EAkBA;;;;;EAKA;;;;;;EAMA;EACA;;;;;EAKA,QAAQ;EACR;IAAsB,MAAM,QAAM;;;cAqF9B,WAAU,MAAA,WAAA,MAAA,SAAA,UAAA,MAAA,OAAA,OAAA,OAAA,UAAA,SAAA,QAAA,SAAA,UAAA,gBAAA,SAiBb,UAAK,QAAA,IAAA"}
|
package/dist/Button.mjs
CHANGED
|
@@ -17,12 +17,34 @@ const variantClasses = {
|
|
|
17
17
|
text: cn("bg-transparent text-foreground", "hover:bg-accent"),
|
|
18
18
|
link: cn("bg-transparent text-primary", "underline-offset-4 hover:underline", "hover:bg-primary/10")
|
|
19
19
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
/**
|
|
21
|
+
* antd-style syntactic sugar: each `type` expands to a `(color, variant)` pair.
|
|
22
|
+
* Explicit `color`/`variant` props win over whatever the `type` maps to, so
|
|
23
|
+
* `type` only fills in the axes the caller left unset.
|
|
24
|
+
*
|
|
25
|
+
* Mirrors antd: `<Button type="primary">` ≡ `<Button color="primary" variant="solid">`.
|
|
26
|
+
*/
|
|
27
|
+
const typeToColorVariant = {
|
|
28
|
+
primary: {
|
|
29
|
+
color: "primary",
|
|
30
|
+
variant: "solid"
|
|
31
|
+
},
|
|
32
|
+
default: {
|
|
33
|
+
color: "default",
|
|
34
|
+
variant: "outlined"
|
|
35
|
+
},
|
|
36
|
+
dashed: {
|
|
37
|
+
color: "default",
|
|
38
|
+
variant: "dashed"
|
|
39
|
+
},
|
|
40
|
+
text: {
|
|
41
|
+
color: "default",
|
|
42
|
+
variant: "text"
|
|
43
|
+
},
|
|
44
|
+
link: {
|
|
45
|
+
color: "primary",
|
|
46
|
+
variant: "link"
|
|
47
|
+
}
|
|
26
48
|
};
|
|
27
49
|
const sizesClasses = {
|
|
28
50
|
small: "h-6 px-3 text-sm",
|
|
@@ -39,15 +61,16 @@ const shapesClasses = {
|
|
|
39
61
|
circle: "rounded-full",
|
|
40
62
|
round: "rounded-2xl"
|
|
41
63
|
};
|
|
42
|
-
const Button = ({ icon, className, type, variant, htmlType = "button", size, color
|
|
64
|
+
const Button = ({ icon, className, type, variant, htmlType = "button", size, color, shape, block = false, disabled, loading, danger, asChild = false, children, onMouseDown, ...props }) => {
|
|
43
65
|
const { componentSize, defaultProps } = useConfig();
|
|
44
66
|
const buttonDefaults = defaultProps?.button;
|
|
45
67
|
const resolvedSize = size ?? componentSize ?? "middle";
|
|
46
68
|
const resolvedShape = shape ?? buttonDefaults?.shape ?? "default";
|
|
47
69
|
const iconOnly = icon && !children;
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
70
|
+
const typeDefaults = type ? typeToColorVariant[type] : void 0;
|
|
71
|
+
const resolvedVariant = variant ?? typeDefaults?.variant ?? buttonDefaults?.variant ?? "outlined";
|
|
72
|
+
const computedColor = danger ? "danger" : color ?? typeDefaults?.color ?? buttonDefaults?.color ?? "default";
|
|
73
|
+
const colored = computedColor !== "default";
|
|
51
74
|
const isLoading = !!loading;
|
|
52
75
|
const Comp = asChild ? Slot : "button";
|
|
53
76
|
const displayIcon = loading ? typeof loading === "object" ? loading.icon : /* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin" }) : icon;
|
package/dist/Button.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.mjs","names":[],"sources":["../src/components/atoms/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { LoaderCircle } from 'lucide-react';\n\nimport { useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { INTERACTIVE_CHASSIS } from '../../lib/chassis';\n\ntype NativeButtonProps = React.ComponentProps<'button'> & {\n asChild?: boolean;\n};\n\ntype PresetColors =\n | 'blue'\n | 'purple'\n | 'cyan'\n | 'green'\n | 'magenta'\n | 'pink'\n | 'red'\n | 'orange'\n | 'yellow'\n | 'volcano'\n | 'geekblue'\n | 'lime'\n | 'gold';\n\nexport interface Props extends Omit<\n NativeButtonProps,\n 'size' | 'variant' | 'type'\n> {\n icon?: React.ReactNode;\n block?: boolean;\n danger?: boolean;\n disabled?: boolean;\n size?: 'small' | 'middle' | 'large';\n /**\n * @deprecated Use `variant` instead. `type` is an alias kept for backwards\n * compatibility and will be removed in the next major.\n *\n * The two props address the same axis, and 3 of the 5 values (`dashed`,\n * `text`, `link`) are already spelled identically — `<Button type=\"text\">`\n * and `<Button variant=\"text\">` render the same thing. The only values\n * `type` contributes are the aliases `primary` → `solid` and\n * `default` → `outlined`. `variant` additionally offers `filled`, which\n * `type` cannot express.\n *\n * Migration:\n * | `type` | `variant` |\n * | --------- | ----------- |\n * | `primary` | `solid` |\n * | `default` | `outlined` |\n * | `dashed` | `dashed` |\n * | `text` | `text` |\n * | `link` | `link` |\n *\n * Passing both still lets `variant` win.\n */\n type?: 'primary' | 'default' | 'dashed' | 'text' | 'link';\n /**\n * Visual fill style. This is the canonical prop — prefer it over `type`.\n * Defaults to `'outlined'` (matching the legacy `type=\"default\"`).\n */\n variant?: 'solid' | 'outlined' | 'dashed' | 'filled' | 'text' | 'link';\n /**\n * Native `<button>` `type` (`button`/`submit`/`reset`), kept separate\n * from the antd-style `type` above. Defaults to `'button'` so a Button\n * placed inside a `<form>` doesn't submit it unless explicitly opted in.\n */\n htmlType?: 'button' | 'submit' | 'reset';\n shape?: 'default' | 'circle' | 'round';\n color?: PresetColors | 'default' | 'primary' | 'danger';\n loading?: boolean | { icon: React.ReactNode };\n}\n\n// Chassis absorbed from core/button's cva base (#278 ③ / #294 Phase 4). The\n// shared focus/aria set lives in INTERACTIVE_CHASSIS; a Button additionally\n// disables and sizes its icons. Kept as constants so the absorb relocates this\n// treatment rather than dropping it (the #177 class of silent regression the\n// #300/#301 net guards).\nconst BUTTON_CHASSIS = cn(\n INTERACTIVE_CHASSIS,\n 'outline-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n);\n\n// Presentation the atom builds on. The size/variant/shape classes below still\n// override the incidental leftovers exactly as they did when this wrapped\n// core/button, so the rendered output is unchanged (verified against a\n// pre-refactor render matrix).\nconst BUTTON_BASE = cn(\n BUTTON_CHASSIS,\n 'gap-2 whitespace-nowrap font-medium transition-all',\n 'has-[>svg]:px-3',\n);\n\nconst variantClasses: Record<string, string> = {\n // solid used to inherit its fill from core/button's default variant; carry it\n // explicitly now that the primitive is absorbed. A colored solid overrides\n // this with the --btn-* custom properties further down.\n solid: cn('bg-primary text-primary-foreground', 'hover:bg-primary/90'),\n outlined: cn(\n 'border border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n dashed: cn(\n 'border border-dashed border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n filled: cn('bg-muted text-foreground', 'hover:bg-muted/80'),\n text: cn('bg-transparent text-foreground', 'hover:bg-accent'),\n link: cn(\n 'bg-transparent text-primary',\n 'underline-offset-4 hover:underline',\n 'hover:bg-primary/10',\n ),\n};\n\nconst typeToVariant: Record<string, string> = {\n primary: 'solid',\n default: 'outlined',\n dashed: 'dashed',\n text: 'text',\n link: 'link',\n};\n\nconst sizesClasses: Record<string, string> = {\n small: 'h-6 px-3 text-sm',\n middle: 'h-8 px-4 text-base',\n large: 'h-10 px-5 text-lg',\n};\n\nconst iconClasses: Record<string, string> = {\n small: `size-6 [&_svg:not([class*='size-'])]:size-3`,\n middle: `size-8 [&_svg:not([class*='size-'])]:size-4`,\n large: `size-10 [&_svg:not([class*='size-'])]:size-5`,\n};\n\nconst shapesClasses = {\n default: 'rounded-sm',\n circle: 'rounded-full',\n round: 'rounded-2xl',\n};\n\nconst Button = ({\n icon,\n className,\n type,\n variant,\n htmlType = 'button',\n size,\n color = 'default',\n shape,\n block = false,\n disabled,\n loading,\n danger,\n asChild = false,\n children,\n onMouseDown,\n ...props\n}: Props) => {\n const { componentSize, defaultProps } = useConfig();\n const buttonDefaults = defaultProps?.button as\n Partial<Pick<Props, 'shape'>> | undefined;\n const resolvedSize = size ?? componentSize ?? 'middle';\n const resolvedShape = shape ?? buttonDefaults?.shape ?? 'default';\n const iconOnly = icon && !children;\n const computedColor = danger ? 'danger' : color;\n const colored = computedColor && computedColor !== 'default';\n // `variant` is canonical; `type` is a deprecated alias resolved through\n // typeToVariant. The default lives here (not on the `type` parameter) so the\n // deprecated prop stays off the default path — a Button with neither prop\n // resolves straight to 'outlined'.\n const resolvedVariant =\n variant ?? (type ? typeToVariant[type] : undefined) ?? 'outlined';\n const isLoading = !!loading;\n\n const Comp = asChild ? Slot : 'button';\n\n const displayIcon = loading ? (\n typeof loading === 'object' ? (\n loading.icon\n ) : (\n <LoaderCircle className=\"animate-spin\" />\n )\n ) : (\n icon\n );\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(\n BUTTON_BASE,\n 'inline-flex items-center justify-center gap-x-2',\n 'rounded-lg',\n 'cursor-pointer',\n 'h-auto py-0',\n 'transition-all',\n variantClasses[resolvedVariant],\n sizesClasses[resolvedSize],\n iconOnly && ['p-0', iconClasses[resolvedSize]],\n shapesClasses[resolvedShape],\n block && 'w-full',\n colored &&\n (resolvedVariant === 'solid'\n ? [\n 'bg-(--btn-bg)',\n 'hover:bg-(--btn-bg-hover)',\n 'active:bg-(--btn-bg-active)',\n 'text-(--btn-fg)',\n 'border-(--btn-border)',\n ]\n : [\n 'text-(--btn-bg)',\n 'border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n //\n ]),\n className,\n //\n )}\n type={htmlType}\n disabled={disabled || isLoading}\n aria-busy={isLoading}\n data-color={computedColor}\n onMouseDown={e => {\n onMouseDown?.(e);\n }}\n {...props}\n >\n {displayIcon}\n {children}\n </Comp>\n );\n};\n\nexport default Button;\n"],"mappings":";;;;;;;;;AAoFA,MAAM,iBAAiB,GACrB,qBACA,gBACA,oDACA,mFACF;AAMA,MAAM,cAAc,GAClB,gBACA,sDACA,iBACF;AAEA,MAAM,iBAAyC;CAI7C,OAAO,GAAG,sCAAsC,qBAAqB;CACrE,UAAU,GACR,yEACA,iCACA,iBACF;CACA,QAAQ,GACN,uFACA,iCACA,iBACF;CACA,QAAQ,GAAG,4BAA4B,mBAAmB;CAC1D,MAAM,GAAG,kCAAkC,iBAAiB;CAC5D,MAAM,GACJ,+BACA,sCACA,qBACF;AACF;AAEA,MAAM,gBAAwC;CAC5C,SAAS;CACT,SAAS;CACT,QAAQ;CACR,MAAM;CACN,MAAM;AACR;AAEA,MAAM,eAAuC;CAC3C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,cAAsC;CAC1C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,gBAAgB;CACpB,SAAS;CACT,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,UAAU,EACd,MACA,WACA,MACA,SACA,WAAW,UACX,MACA,QAAQ,WACR,OACA,QAAQ,OACR,UACA,SACA,QACA,UAAU,OACV,UACA,aACA,GAAG,YACQ;CACX,MAAM,EAAE,eAAe,iBAAiB,UAAU;CAClD,MAAM,iBAAiB,cAAc;CAErC,MAAM,eAAe,QAAQ,iBAAiB;CAC9C,MAAM,gBAAgB,SAAS,gBAAgB,SAAS;CACxD,MAAM,WAAW,QAAQ,CAAC;CAC1B,MAAM,gBAAgB,SAAS,WAAW;CAC1C,MAAM,UAAU,iBAAiB,kBAAkB;CAKnD,MAAM,kBACJ,YAAY,OAAO,cAAc,QAAQ,KAAA,MAAc;CACzD,MAAM,YAAY,CAAC,CAAC;CAEpB,MAAM,OAAO,UAAU,OAAO;CAE9B,MAAM,cAAc,UAClB,OAAO,YAAY,WACjB,QAAQ,OAER,oBAAC,cAAD,EAAc,WAAU,eAAgB,CAAA,IAG1C;CAGF,OACE,qBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,aACA,mDACA,cACA,kBACA,eACA,kBACA,eAAe,kBACf,aAAa,eACb,YAAY,CAAC,OAAO,YAAY,aAAa,GAC7C,cAAc,gBACd,SAAS,UACT,YACG,oBAAoB,UACjB;GACE;GACA;GACA;GACA;GACA;EACF,IACA,CACE,mBACA,gEAEF,IACN,SAEF;EACA,MAAM;EACN,UAAU,YAAY;EACtB,aAAW;EACX,cAAY;EACZ,cAAa,MAAK;GAChB,cAAc,CAAC;EACjB;EACA,GAAI;EAtCN,UAAA,CAwCG,aACA,QACG;;AAEV"}
|
|
1
|
+
{"version":3,"file":"Button.mjs","names":[],"sources":["../src/components/atoms/button.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\nimport { LoaderCircle } from 'lucide-react';\n\nimport { useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { INTERACTIVE_CHASSIS } from '../../lib/chassis';\nimport { type PresetColor } from '../../lib/colors';\n\ntype NativeButtonProps = React.ComponentProps<'button'> & {\n asChild?: boolean;\n};\n\nexport interface Props extends Omit<\n NativeButtonProps,\n 'size' | 'variant' | 'type'\n> {\n icon?: React.ReactNode;\n block?: boolean;\n danger?: boolean;\n disabled?: boolean;\n size?: 'small' | 'middle' | 'large';\n /**\n * Syntactic sugar for a `(color, variant)` pair. **Will follow `variant` and\n * `color` if provided** — `type` only fills in the axes you leave unset.\n *\n * | `type` | equivalent to |\n * | --------- | -------------------------------------- |\n * | `primary` | `color=\"primary\" variant=\"solid\"` |\n * | `default` | `color=\"default\" variant=\"outlined\"` |\n * | `dashed` | `color=\"default\" variant=\"dashed\"` |\n * | `text` | `color=\"default\" variant=\"text\"` |\n * | `link` | `color=\"primary\" variant=\"link\"` |\n *\n * So `<Button type=\"primary\">` is the same as\n * `<Button color=\"primary\" variant=\"solid\">`, and\n * `<Button type=\"primary\" variant=\"filled\">` keeps the primary color but\n * renders filled. `danger` still overrides the resolved color.\n */\n type?: 'primary' | 'default' | 'dashed' | 'text' | 'link';\n /**\n * Visual fill style, independent of `color`. Defaults to `'outlined'`.\n * Takes precedence over whatever `type` would have supplied.\n */\n variant?: 'solid' | 'outlined' | 'dashed' | 'filled' | 'text' | 'link';\n /**\n * Native `<button>` `type` (`button`/`submit`/`reset`), kept separate\n * from the antd-style `type` above. Defaults to `'button'` so a Button\n * placed inside a `<form>` doesn't submit it unless explicitly opted in.\n */\n htmlType?: 'button' | 'submit' | 'reset';\n shape?: 'default' | 'circle' | 'round';\n /**\n * Color, independent of `variant`. Defaults to `'default'`, or to whatever\n * `type` maps to when `type` is set. `danger` overrides this.\n */\n color?: PresetColor | 'default' | 'primary' | 'danger';\n loading?: boolean | { icon: React.ReactNode };\n}\n\n// Chassis absorbed from core/button's cva base (#278 ③ / #294 Phase 4). The\n// shared focus/aria set lives in INTERACTIVE_CHASSIS; a Button additionally\n// disables and sizes its icons. Kept as constants so the absorb relocates this\n// treatment rather than dropping it (the #177 class of silent regression the\n// #300/#301 net guards).\nconst BUTTON_CHASSIS = cn(\n INTERACTIVE_CHASSIS,\n 'outline-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n);\n\n// Presentation the atom builds on. The size/variant/shape classes below still\n// override the incidental leftovers exactly as they did when this wrapped\n// core/button, so the rendered output is unchanged (verified against a\n// pre-refactor render matrix).\nconst BUTTON_BASE = cn(\n BUTTON_CHASSIS,\n 'gap-2 whitespace-nowrap font-medium transition-all',\n 'has-[>svg]:px-3',\n);\n\nconst variantClasses: Record<string, string> = {\n // solid used to inherit its fill from core/button's default variant; carry it\n // explicitly now that the primitive is absorbed. A colored solid overrides\n // this with the --btn-* custom properties further down.\n solid: cn('bg-primary text-primary-foreground', 'hover:bg-primary/90'),\n outlined: cn(\n 'border border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n dashed: cn(\n 'border border-dashed border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n 'bg-background text-foreground',\n 'hover:bg-accent',\n ),\n filled: cn('bg-muted text-foreground', 'hover:bg-muted/80'),\n text: cn('bg-transparent text-foreground', 'hover:bg-accent'),\n link: cn(\n 'bg-transparent text-primary',\n 'underline-offset-4 hover:underline',\n 'hover:bg-primary/10',\n ),\n};\n\n/**\n * antd-style syntactic sugar: each `type` expands to a `(color, variant)` pair.\n * Explicit `color`/`variant` props win over whatever the `type` maps to, so\n * `type` only fills in the axes the caller left unset.\n *\n * Mirrors antd: `<Button type=\"primary\">` ≡ `<Button color=\"primary\" variant=\"solid\">`.\n */\nconst typeToColorVariant: Record<\n NonNullable<Props['type']>,\n { color: NonNullable<Props['color']>; variant: NonNullable<Props['variant']> }\n> = {\n primary: { color: 'primary', variant: 'solid' },\n default: { color: 'default', variant: 'outlined' },\n dashed: { color: 'default', variant: 'dashed' },\n text: { color: 'default', variant: 'text' },\n link: { color: 'primary', variant: 'link' },\n};\n\nconst sizesClasses: Record<string, string> = {\n small: 'h-6 px-3 text-sm',\n middle: 'h-8 px-4 text-base',\n large: 'h-10 px-5 text-lg',\n};\n\nconst iconClasses: Record<string, string> = {\n small: `size-6 [&_svg:not([class*='size-'])]:size-3`,\n middle: `size-8 [&_svg:not([class*='size-'])]:size-4`,\n large: `size-10 [&_svg:not([class*='size-'])]:size-5`,\n};\n\nconst shapesClasses = {\n default: 'rounded-sm',\n circle: 'rounded-full',\n round: 'rounded-2xl',\n};\n\nconst Button = ({\n icon,\n className,\n type,\n variant,\n htmlType = 'button',\n size,\n color,\n shape,\n block = false,\n disabled,\n loading,\n danger,\n asChild = false,\n children,\n onMouseDown,\n ...props\n}: Props) => {\n const { componentSize, defaultProps } = useConfig();\n const buttonDefaults = defaultProps?.button as\n Partial<Pick<Props, 'shape' | 'variant' | 'color'>> | undefined;\n const resolvedSize = size ?? componentSize ?? 'middle';\n const resolvedShape = shape ?? buttonDefaults?.shape ?? 'default';\n const iconOnly = icon && !children;\n // `type` is syntactic sugar that expands to a (color, variant) pair; explicit\n // `color`/`variant` win over it, so `type` only fills the axes left unset.\n // A `Config` default sits below the call-site `type` (which is more specific)\n // but above the library built-in. `danger` still trumps everything, matching\n // antd. Defaults live here rather than on the parameters so `type` gets a\n // chance to supply them first.\n const typeDefaults = type ? typeToColorVariant[type] : undefined;\n const resolvedVariant =\n variant ?? typeDefaults?.variant ?? buttonDefaults?.variant ?? 'outlined';\n const computedColor = danger\n ? 'danger'\n : (color ?? typeDefaults?.color ?? buttonDefaults?.color ?? 'default');\n const colored = computedColor !== 'default';\n const isLoading = !!loading;\n\n const Comp = asChild ? Slot : 'button';\n\n const displayIcon = loading ? (\n typeof loading === 'object' ? (\n loading.icon\n ) : (\n <LoaderCircle className=\"animate-spin\" />\n )\n ) : (\n icon\n );\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(\n BUTTON_BASE,\n 'inline-flex items-center justify-center gap-x-2',\n 'rounded-lg',\n 'cursor-pointer',\n 'h-auto py-0',\n 'transition-all',\n variantClasses[resolvedVariant],\n sizesClasses[resolvedSize],\n iconOnly && ['p-0', iconClasses[resolvedSize]],\n shapesClasses[resolvedShape],\n block && 'w-full',\n colored &&\n (resolvedVariant === 'solid'\n ? [\n 'bg-(--btn-bg)',\n 'hover:bg-(--btn-bg-hover)',\n 'active:bg-(--btn-bg-active)',\n 'text-(--btn-fg)',\n 'border-(--btn-border)',\n ]\n : [\n 'text-(--btn-bg)',\n 'border-[color-mix(in_oklch,var(--btn-border),transparent_50%)]',\n //\n ]),\n className,\n //\n )}\n type={htmlType}\n disabled={disabled || isLoading}\n aria-busy={isLoading}\n data-color={computedColor}\n onMouseDown={e => {\n onMouseDown?.(e);\n }}\n {...props}\n >\n {displayIcon}\n {children}\n </Comp>\n );\n};\n\nexport default Button;\n"],"mappings":";;;;;;;;;AAqEA,MAAM,iBAAiB,GACrB,qBACA,gBACA,oDACA,mFACF;AAMA,MAAM,cAAc,GAClB,gBACA,sDACA,iBACF;AAEA,MAAM,iBAAyC;CAI7C,OAAO,GAAG,sCAAsC,qBAAqB;CACrE,UAAU,GACR,yEACA,iCACA,iBACF;CACA,QAAQ,GACN,uFACA,iCACA,iBACF;CACA,QAAQ,GAAG,4BAA4B,mBAAmB;CAC1D,MAAM,GAAG,kCAAkC,iBAAiB;CAC5D,MAAM,GACJ,+BACA,sCACA,qBACF;AACF;;;;;;;;AASA,MAAM,qBAGF;CACF,SAAS;EAAE,OAAO;EAAW,SAAS;CAAQ;CAC9C,SAAS;EAAE,OAAO;EAAW,SAAS;CAAW;CACjD,QAAQ;EAAE,OAAO;EAAW,SAAS;CAAS;CAC9C,MAAM;EAAE,OAAO;EAAW,SAAS;CAAO;CAC1C,MAAM;EAAE,OAAO;EAAW,SAAS;CAAO;AAC5C;AAEA,MAAM,eAAuC;CAC3C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,cAAsC;CAC1C,OAAO;CACP,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,gBAAgB;CACpB,SAAS;CACT,QAAQ;CACR,OAAO;AACT;AAEA,MAAM,UAAU,EACd,MACA,WACA,MACA,SACA,WAAW,UACX,MACA,OACA,OACA,QAAQ,OACR,UACA,SACA,QACA,UAAU,OACV,UACA,aACA,GAAG,YACQ;CACX,MAAM,EAAE,eAAe,iBAAiB,UAAU;CAClD,MAAM,iBAAiB,cAAc;CAErC,MAAM,eAAe,QAAQ,iBAAiB;CAC9C,MAAM,gBAAgB,SAAS,gBAAgB,SAAS;CACxD,MAAM,WAAW,QAAQ,CAAC;CAO1B,MAAM,eAAe,OAAO,mBAAmB,QAAQ,KAAA;CACvD,MAAM,kBACJ,WAAW,cAAc,WAAW,gBAAgB,WAAW;CACjE,MAAM,gBAAgB,SAClB,WACC,SAAS,cAAc,SAAS,gBAAgB,SAAS;CAC9D,MAAM,UAAU,kBAAkB;CAClC,MAAM,YAAY,CAAC,CAAC;CAEpB,MAAM,OAAO,UAAU,OAAO;CAE9B,MAAM,cAAc,UAClB,OAAO,YAAY,WACjB,QAAQ,OAER,oBAAC,cAAD,EAAc,WAAU,eAAgB,CAAA,IAG1C;CAGF,OACE,qBAAC,MAAD;EACE,aAAU;EACV,WAAW,GACT,aACA,mDACA,cACA,kBACA,eACA,kBACA,eAAe,kBACf,aAAa,eACb,YAAY,CAAC,OAAO,YAAY,aAAa,GAC7C,cAAc,gBACd,SAAS,UACT,YACG,oBAAoB,UACjB;GACE;GACA;GACA;GACA;GACA;EACF,IACA,CACE,mBACA,gEAEF,IACN,SAEF;EACA,MAAM;EACN,UAAU,YAAY;EACtB,aAAW;EACX,cAAY;EACZ,cAAa,MAAK;GAChB,cAAc,CAAC;EACjB;EACA,GAAI;EAtCN,UAAA,CAwCG,aACA,QACG;;AAEV"}
|
package/dist/Space.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Space.d.mts","names":[],"sources":["../src/components/molecules/space.tsx"],"mappings":";
|
|
1
|
+
{"version":3,"file":"Space.d.mts","names":[],"sources":["../src/components/molecules/space.tsx"],"mappings":";UASiB,cAAc,MAAM;EACnC;EACA,SAAS,MAAM;EACf;EACA;EACA;EACA;EACA,QAAQ,MAAM;EACd;IACE;IACA;;;cAiBE,UAAS,SAAA,QAAA,UAAA,MAAA,aAAA,OAAA,MAAA,OAAA,WAAA,MAAA,QAAA,UAAA,SAcZ,0BAAK,IAAA"}
|
package/dist/Space.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { cn } from "./utils.mjs";
|
|
3
|
+
import { useConfig } from "./providers/config/context.mjs";
|
|
2
4
|
import Skeleton from "./components/atoms/skeleton/index.mjs";
|
|
3
5
|
import { Children } from "react";
|
|
4
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -14,8 +16,10 @@ const aligns = {
|
|
|
14
16
|
center: "items-center text-center",
|
|
15
17
|
baseline: "items-baseline text-baseline"
|
|
16
18
|
};
|
|
17
|
-
const Space = ({ loading, loader, children, size
|
|
18
|
-
const
|
|
19
|
+
const Space = ({ loading, loader, children, size, orientation = "horizontal", align = "center", wrap = false, split, className, grid, hidden, style, ...props }) => {
|
|
20
|
+
const { componentSize } = useConfig();
|
|
21
|
+
const resolvedSize = size ?? componentSize ?? "middle";
|
|
22
|
+
const [x, y] = typeof resolvedSize === "string" ? sizes[resolvedSize] || [0, 0] : Array.isArray(resolvedSize) ? resolvedSize : [resolvedSize, resolvedSize];
|
|
19
23
|
const count = Children.count(children);
|
|
20
24
|
if (loading) return /* @__PURE__ */ jsx("div", {
|
|
21
25
|
className: cn(hidden && "hidden", className),
|
package/dist/Space.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Space.mjs","names":[],"sources":["../src/components/molecules/space.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"Space.mjs","names":[],"sources":["../src/components/molecules/space.tsx"],"sourcesContent":["'use client';\n\nimport { Children } from 'react';\n\nimport { useConfig } from '@repo/ui/providers';\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,\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 { componentSize } = useConfig();\n // `size` overrides the global `componentSize` from `Config`, which in turn\n // falls back to `middle` — the same resolution order `Button` uses so a single\n // `<Config componentSize>` sizes both. Numeric/array gaps bypass the token.\n const resolvedSize = size ?? componentSize ?? 'middle';\n const [x, y] =\n typeof resolvedSize === 'string'\n ? sizes[resolvedSize] || [0, 0]\n : Array.isArray(resolvedSize)\n ? resolvedSize\n : [resolvedSize, resolvedSize];\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":";;;;;;;AAuBA,MAAM,QAAkC;CACtC,OAAO,CAAC,GAAG,CAAC;CACZ,QAAQ,CAAC,GAAG,CAAC;CACb,OAAO,CAAC,IAAI,EAAE;AAChB;AAEA,MAAM,SAAiC;CACrC,OAAO;CACP,KAAK;CACL,QAAQ;CACR,UAAU;AACZ;AAEA,MAAM,SAAS,EACb,SACA,QACA,UACA,MACA,cAAc,cACd,QAAQ,UACR,OAAO,OACP,OACA,WACA,MACA,QACA,OACA,GAAG,YACQ;CACX,MAAM,EAAE,kBAAkB,UAAU;CAIpC,MAAM,eAAe,QAAQ,iBAAiB;CAC9C,MAAM,CAAC,GAAG,KACR,OAAO,iBAAiB,WACpB,MAAM,iBAAiB,CAAC,GAAG,CAAC,IAC5B,MAAM,QAAQ,YAAY,IACxB,eACA,CAAC,cAAc,YAAY;CACnC,MAAM,QAAQ,SAAS,MAAM,QAAQ;CAErC,IAAI,SACF,OACE,oBAAC,OAAD;EACE,WAAW,GAAG,UAAU,UAAU,SAAS;EACpC;EACC;EACR,GAAI;EAEH,UAAA,UAAU,oBAAC,UAAD,CAAW,CAAA;CACnB,CAAA;CAIT,OACE,oBAAC,OAAD;EACE,WAAW,GACT,oBACA,gBAAgB,eAAe,aAAa,YAC5C,QAAQ,aACR,OAAO,QACP,QAAQ,QACR,UAAU,UACV,SACF;EACA,OAAO;GACL,WAAW;GACX,QAAQ;GACR,kBAAkB,MAAM,OACpB,UAAU,KAAK,KAAK,qBACpB,KAAA;GACJ,qBAAqB,MAAM,OACvB,UAAU,KAAK,KAAK,qBACpB,KAAA;GACJ,GAAG;EACL;EACQ;EACR,GAAI;EAEH,UAAA,SAAS,IAAI,WAAW,OAAO,MAC9B,qBAAA,YAAA,EAAA,UAAA,CACG,OACA,SAAS,IAAI,QAAQ,KAAK,KAC3B,EAAA,CAAA,CACH;CACE,CAAA;AAET"}
|
package/dist/Tag.d.mts
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
import { PresetColor } from "./lib/colors.mjs";
|
|
1
2
|
import * as React$1 from "react";
|
|
2
3
|
//#region src/components/atoms/tag.d.ts
|
|
3
4
|
type NativeSpanProps = React$1.ComponentProps<'span'> & {
|
|
4
5
|
asChild?: boolean;
|
|
5
6
|
};
|
|
6
|
-
interface Props extends Omit<NativeSpanProps, 'variant'> {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
interface Props extends Omit<NativeSpanProps, 'variant' | 'color'> {
|
|
8
|
+
/**
|
|
9
|
+
* Fill style, aligned with `Button`'s vocabulary. `'default'` is a
|
|
10
|
+
* **deprecated alias** for `'filled'` — it still renders identically, but
|
|
11
|
+
* prefer `'filled'` going forward (#320).
|
|
12
|
+
*
|
|
13
|
+
* @remarks `'default'` will be removed in a future major.
|
|
14
|
+
*/
|
|
15
|
+
variant?: 'filled' | 'outlined' | 'default';
|
|
16
|
+
/**
|
|
17
|
+
* Colour, sharing `Button`'s preset palette. The named states
|
|
18
|
+
* (`primary`/`success`/`warning`/`danger`) keep their semantic look; the
|
|
19
|
+
* shared presets (`blue`, `red`, `gold`, …) are backed by the same
|
|
20
|
+
* `data-color` system as `Button` (#320).
|
|
21
|
+
*/
|
|
22
|
+
color?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | PresetColor;
|
|
9
23
|
}
|
|
10
24
|
declare const Tag: ({ className, variant, color, asChild, children, ...props }: Props) => React$1.JSX.Element;
|
|
11
25
|
//#endregion
|
package/dist/Tag.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tag.d.mts","names":[],"sources":["../src/components/atoms/tag.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Tag.d.mts","names":[],"sources":["../src/components/atoms/tag.tsx"],"mappings":";;;KAYK,kBAAkB,QAAM;EAC3B;;UAGe,cAAc,KAAK;;;;;;;;EAQlC;;;;;;;EAOA,mEAC6D;;cAgDzD,QAAO,WAAA,SAAA,OAAA,SAAA,aAAA,SAOV,UAAK,QAAA,IAAA"}
|
package/dist/Tag.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { cn } from "./utils.mjs";
|
|
3
|
+
import { useConfig } from "./providers/config/context.mjs";
|
|
2
4
|
import { INTERACTIVE_CHASSIS } from "./lib/chassis.mjs";
|
|
5
|
+
import { isPresetColor } from "./lib/colors.mjs";
|
|
3
6
|
import "react";
|
|
4
7
|
import { Slot } from "@radix-ui/react-slot";
|
|
5
8
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -19,10 +22,19 @@ const outlinedColorClasses = {
|
|
|
19
22
|
warning: "border-yellow-500 text-yellow-600 dark:text-yellow-400",
|
|
20
23
|
danger: "border-destructive text-destructive"
|
|
21
24
|
};
|
|
22
|
-
const
|
|
25
|
+
const presetFillClasses = cn("border-transparent", "bg-[color-mix(in_oklch,var(--tag-bg),transparent_90%)]", "text-(--tag-bg)");
|
|
26
|
+
const presetOutlinedClasses = cn("border-(--tag-bg) text-(--tag-bg)");
|
|
27
|
+
const Tag = ({ className, variant, color, asChild = false, children, ...props }) => {
|
|
28
|
+
const { defaultProps } = useConfig();
|
|
29
|
+
const tagDefaults = defaultProps?.tag;
|
|
30
|
+
const resolvedVariant = variant ?? tagDefaults?.variant ?? "filled";
|
|
31
|
+
const resolvedColor = color ?? tagDefaults?.color ?? "default";
|
|
32
|
+
const isOutlined = resolvedVariant === "outlined";
|
|
33
|
+
const preset = isPresetColor(resolvedColor);
|
|
23
34
|
return /* @__PURE__ */ jsx(asChild ? Slot : "span", {
|
|
24
35
|
"data-slot": "badge",
|
|
25
|
-
|
|
36
|
+
"data-color": preset ? resolvedColor : void 0,
|
|
37
|
+
className: cn(TAG_BASE, "rounded-full px-2.5 py-1", "text-xs font-medium", preset ? isOutlined ? presetOutlinedClasses : presetFillClasses : isOutlined ? outlinedColorClasses[resolvedColor] : fillColorClasses[resolvedColor], className),
|
|
26
38
|
...props,
|
|
27
39
|
children
|
|
28
40
|
});
|
package/dist/Tag.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tag.mjs","names":[],"sources":["../src/components/atoms/tag.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"Tag.mjs","names":[],"sources":["../src/components/atoms/tag.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { Slot } from '@radix-ui/react-slot';\n\nimport { useConfig } from '@repo/ui/providers';\nimport { cn } from '@repo/ui/utils';\n\nimport { INTERACTIVE_CHASSIS } from '../../lib/chassis';\nimport { type PresetColor, isPresetColor } from '../../lib/colors';\n\ntype NativeSpanProps = React.ComponentProps<'span'> & {\n asChild?: boolean;\n};\n\nexport interface Props extends Omit<NativeSpanProps, 'variant' | 'color'> {\n /**\n * Fill style, aligned with `Button`'s vocabulary. `'default'` is a\n * **deprecated alias** for `'filled'` — it still renders identically, but\n * prefer `'filled'` going forward (#320).\n *\n * @remarks `'default'` will be removed in a future major.\n */\n variant?: 'filled' | 'outlined' | 'default';\n /**\n * Colour, sharing `Button`'s preset palette. The named states\n * (`primary`/`success`/`warning`/`danger`) keep their semantic look; the\n * shared presets (`blue`, `red`, `gold`, …) are backed by the same\n * `data-color` system as `Button` (#320).\n */\n color?:\n 'default' | 'primary' | 'success' | 'warning' | 'danger' | PresetColor;\n}\n\n// Base absorbed from core/badge's cva base + its `outline` variant, which Tag\n// always rendered as (#278 ③ / #294 Phase 4). The shared focus/aria/centering\n// set lives in INTERACTIVE_CHASSIS; a Tag adds badge geometry and its svg\n// sizing but — unlike Button — no `disabled:*` (a Tag is not disableable). The\n// rounded/padding leftovers are overridden by the atom classes below exactly as\n// before, so the rendered output is unchanged (verified against a pre-refactor\n// render matrix).\nconst TAG_BASE = cn(\n INTERACTIVE_CHASSIS,\n 'border w-fit whitespace-nowrap overflow-hidden',\n 'text-xs font-medium gap-1 transition-[color,box-shadow]',\n '[&>svg]:size-3 [&>svg]:pointer-events-none',\n 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',\n);\n\nconst fillColorClasses: Record<string, string> = {\n default: 'border-transparent bg-muted text-muted-foreground',\n primary: 'border-transparent bg-primary/10 text-primary',\n success:\n 'border-transparent bg-green-500/10 text-green-600 dark:text-green-400',\n warning:\n 'border-transparent bg-yellow-500/10 text-yellow-600 dark:text-yellow-400',\n danger: 'border-transparent bg-destructive/10 text-destructive',\n};\n\nconst outlinedColorClasses: Record<string, string> = {\n default: 'border-border text-foreground',\n primary: 'border-primary text-primary',\n success: 'border-green-500 text-green-600 dark:text-green-400',\n warning: 'border-yellow-500 text-yellow-600 dark:text-yellow-400',\n danger: 'border-destructive text-destructive',\n};\n\n// Preset colours resolve their hue from the shared `--tag-bg` custom property\n// (globals.css, keyed on `data-color`) rather than a per-colour Tailwind class,\n// so `Tag` and `Button` stay in lock-step. Fill uses a 10% tint behind the\n// full-strength hue; outlined borders and texts it directly.\nconst presetFillClasses = cn(\n 'border-transparent',\n 'bg-[color-mix(in_oklch,var(--tag-bg),transparent_90%)]',\n 'text-(--tag-bg)',\n);\n\nconst presetOutlinedClasses = cn('border-(--tag-bg) text-(--tag-bg)');\n\nconst Tag = ({\n className,\n variant,\n color,\n asChild = false,\n children,\n ...props\n}: Props) => {\n const { defaultProps } = useConfig();\n const tagDefaults = defaultProps?.tag as\n Partial<Pick<Props, 'variant' | 'color'>> | undefined;\n // An explicit prop wins over a `Config` default, which wins over the built-in\n // — the same resolution order `Button` uses.\n const resolvedVariant = variant ?? tagDefaults?.variant ?? 'filled';\n const resolvedColor = color ?? tagDefaults?.color ?? 'default';\n // `'default'` is the pre-#320 spelling of `'filled'`; fold it in so the alias\n // renders identically.\n const isOutlined = resolvedVariant === 'outlined';\n const preset = isPresetColor(resolvedColor);\n const Comp = asChild ? Slot : 'span';\n\n return (\n <Comp\n data-slot=\"badge\"\n // Only preset colours drive the `--tag-*` system; the named states keep\n // their bespoke classes, so existing usage renders byte-identical.\n data-color={preset ? resolvedColor : undefined}\n className={cn(\n TAG_BASE,\n 'rounded-full px-2.5 py-1',\n 'text-xs font-medium',\n preset\n ? isOutlined\n ? presetOutlinedClasses\n : presetFillClasses\n : isOutlined\n ? outlinedColorClasses[resolvedColor]\n : fillColorClasses[resolvedColor],\n className,\n //\n )}\n {...props}\n >\n {children}\n </Comp>\n );\n};\n\nexport default Tag;\n"],"mappings":";;;;;;;;;AA0CA,MAAM,WAAW,GACf,qBACA,kDACA,2DACA,8CACA,wEACF;AAEA,MAAM,mBAA2C;CAC/C,SAAS;CACT,SAAS;CACT,SACE;CACF,SACE;CACF,QAAQ;AACV;AAEA,MAAM,uBAA+C;CACnD,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,QAAQ;AACV;AAMA,MAAM,oBAAoB,GACxB,sBACA,0DACA,iBACF;AAEA,MAAM,wBAAwB,GAAG,mCAAmC;AAEpE,MAAM,OAAO,EACX,WACA,SACA,OACA,UAAU,OACV,UACA,GAAG,YACQ;CACX,MAAM,EAAE,iBAAiB,UAAU;CACnC,MAAM,cAAc,cAAc;CAIlC,MAAM,kBAAkB,WAAW,aAAa,WAAW;CAC3D,MAAM,gBAAgB,SAAS,aAAa,SAAS;CAGrD,MAAM,aAAa,oBAAoB;CACvC,MAAM,SAAS,cAAc,aAAa;CAG1C,OACE,oBAHW,UAAU,OAAO,QAG5B;EACE,aAAU;EAGV,cAAY,SAAS,gBAAgB,KAAA;EACrC,WAAW,GACT,UACA,4BACA,uBACA,SACI,aACE,wBACA,oBACF,aACE,qBAAqB,iBACrB,iBAAiB,gBACvB,SAEF;EACA,GAAI;EAEH;CACG,CAAA;AAEV"}
|
|
@@ -19,11 +19,11 @@ const Group = ({ orientation = "vertical", placement = "left", className, classN
|
|
|
19
19
|
};
|
|
20
20
|
return /* @__PURE__ */ jsx("ul", {
|
|
21
21
|
...props,
|
|
22
|
-
className: cn("m-0 list-none p-0", orientation === "vertical"
|
|
22
|
+
className: cn("m-0 flex list-none gap-2 p-0", orientation === "vertical" && "flex-col", className),
|
|
23
23
|
children: options.map((item, index) => {
|
|
24
24
|
const checked = value.includes(item.value);
|
|
25
25
|
return /* @__PURE__ */ jsx("li", {
|
|
26
|
-
className: cn("flex", classNames?.wrapper),
|
|
26
|
+
className: cn("m-0 flex", classNames?.wrapper),
|
|
27
27
|
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
28
28
|
placement,
|
|
29
29
|
className: cn(classNames?.item),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group.mjs","names":[],"sources":["../../../../src/components/atoms/checkbox/group.tsx"],"sourcesContent":["'use client';\n\nimport { useControllableState } from '@jbpark/use-hooks';\n\nimport { cn } from '@repo/ui/utils';\n\nimport {\n type Option,\n type OptionGroupClassNames,\n type OptionValue,\n type Options,\n normalizeOptions,\n warnOnDuplicateOptionValues,\n} from '../option-group';\nimport Checkbox from './checkbox';\n\nexport type { OptionValue };\n\nexport interface Props extends Omit<\n React.ComponentPropsWithoutRef<'ul'>,\n 'onChange' | 'defaultValue' | 'value'\n> {\n options?: Options;\n orientation?: 'vertical' | 'horizontal';\n placement?: 'left' | 'right';\n classNames?: OptionGroupClassNames;\n defaultValue?: OptionValue[];\n value?: OptionValue[];\n /** Native form field name, shared across every option's checkbox. */\n name?: string;\n disabled?: boolean;\n onChange?: (values: OptionValue[]) => void;\n}\n\nconst Group = ({\n orientation = 'vertical',\n placement = 'left',\n className,\n classNames = {},\n options: _options = [],\n name,\n disabled,\n defaultValue,\n value: _value,\n onChange: _onChange = () => {},\n ...props\n}: Props) => {\n const [value, setValue] = useControllableState<OptionValue[]>({\n value: _value,\n defaultValue: defaultValue ?? [],\n onChange: _onChange,\n });\n\n const options: Option[] = normalizeOptions(_options);\n warnOnDuplicateOptionValues(options, 'Checkbox.Group');\n\n const onChange = (checked: boolean, optionValue: OptionValue) => {\n const nextValue = checked\n ? [...value, optionValue]\n : value.filter(v => v !== optionValue);\n\n setValue(nextValue);\n };\n\n return (\n <ul\n {...props}\n className={cn(\n 'm-0 list-none p-0',\n orientation === 'vertical'
|
|
1
|
+
{"version":3,"file":"group.mjs","names":[],"sources":["../../../../src/components/atoms/checkbox/group.tsx"],"sourcesContent":["'use client';\n\nimport { useControllableState } from '@jbpark/use-hooks';\n\nimport { cn } from '@repo/ui/utils';\n\nimport {\n type Option,\n type OptionGroupClassNames,\n type OptionValue,\n type Options,\n normalizeOptions,\n warnOnDuplicateOptionValues,\n} from '../option-group';\nimport Checkbox from './checkbox';\n\nexport type { OptionValue };\n\nexport interface Props extends Omit<\n React.ComponentPropsWithoutRef<'ul'>,\n 'onChange' | 'defaultValue' | 'value'\n> {\n options?: Options;\n orientation?: 'vertical' | 'horizontal';\n placement?: 'left' | 'right';\n classNames?: OptionGroupClassNames;\n defaultValue?: OptionValue[];\n value?: OptionValue[];\n /** Native form field name, shared across every option's checkbox. */\n name?: string;\n disabled?: boolean;\n onChange?: (values: OptionValue[]) => void;\n}\n\nconst Group = ({\n orientation = 'vertical',\n placement = 'left',\n className,\n classNames = {},\n options: _options = [],\n name,\n disabled,\n defaultValue,\n value: _value,\n onChange: _onChange = () => {},\n ...props\n}: Props) => {\n const [value, setValue] = useControllableState<OptionValue[]>({\n value: _value,\n defaultValue: defaultValue ?? [],\n onChange: _onChange,\n });\n\n const options: Option[] = normalizeOptions(_options);\n warnOnDuplicateOptionValues(options, 'Checkbox.Group');\n\n const onChange = (checked: boolean, optionValue: OptionValue) => {\n const nextValue = checked\n ? [...value, optionValue]\n : value.filter(v => v !== optionValue);\n\n setValue(nextValue);\n };\n\n return (\n <ul\n {...props}\n className={cn(\n // Spacing comes from `gap`, not `space-y`'s child margins, so the\n // per-item `m-0` reset below can neutralise host list styles without\n // also wiping out the group's own spacing.\n 'm-0 flex list-none gap-2 p-0',\n orientation === 'vertical' && 'flex-col',\n className,\n )}\n >\n {options.map((item: Option, index) => {\n const checked = value.includes(item.value);\n\n return (\n <li\n key={`${index}-${String(item.value)}`}\n // `m-0` completes the list reset the `ul` above starts (#253) —\n // see Radio.Group for the host rule this guards against.\n className={cn('m-0 flex', classNames?.wrapper)}\n >\n <Checkbox\n placement={placement}\n className={cn(classNames?.item)}\n name={name}\n value={item.value}\n checked={checked}\n disabled={disabled || item.disabled}\n onChange={checked => onChange(checked, item.value)}\n >\n {item.label}\n </Checkbox>\n </li>\n );\n })}\n </ul>\n );\n};\n\nexport default Group;\n"],"mappings":";;;;;;;AAkCA,MAAM,SAAS,EACb,cAAc,YACd,YAAY,QACZ,WACA,aAAa,CAAC,GACd,SAAS,WAAW,CAAC,GACrB,MACA,UACA,cACA,OAAO,QACP,UAAU,kBAAkB,CAAC,GAC7B,GAAG,YACQ;CACX,MAAM,CAAC,OAAO,YAAY,qBAAoC;EAC5D,OAAO;EACP,cAAc,gBAAgB,CAAC;EAC/B,UAAU;CACZ,CAAC;CAED,MAAM,UAAoB,iBAAiB,QAAQ;CACnD,4BAA4B,SAAS,gBAAgB;CAErD,MAAM,YAAY,SAAkB,gBAA6B;EAC/D,MAAM,YAAY,UACd,CAAC,GAAG,OAAO,WAAW,IACtB,MAAM,QAAO,MAAK,MAAM,WAAW;EAEvC,SAAS,SAAS;CACpB;CAEA,OACE,oBAAC,MAAD;EACE,GAAI;EACJ,WAAW,GAIT,gCACA,gBAAgB,cAAc,YAC9B,SACF;EAEC,UAAA,QAAQ,KAAK,MAAc,UAAU;GACpC,MAAM,UAAU,MAAM,SAAS,KAAK,KAAK;GAEzC,OACE,oBAAC,MAAD;IAIE,WAAW,GAAG,YAAY,YAAY,OAAO;IAE7C,UAAA,oBAAC,UAAD;KACa;KACX,WAAW,GAAG,YAAY,IAAI;KACxB;KACN,OAAO,KAAK;KACH;KACT,UAAU,YAAY,KAAK;KAC3B,WAAU,YAAW,SAAS,SAAS,KAAK,KAAK;KAEhD,UAAA,KAAK;IACE,CAAA;GACR,GAhBG,GAAG,MAAM,GAAG,OAAO,KAAK,KAAK,GAgBhC;EAER,CAAC;CACC,CAAA;AAER"}
|
|
@@ -11,6 +11,7 @@ import Progress from "./progress.mjs";
|
|
|
11
11
|
import Radio from "./radio/index.mjs";
|
|
12
12
|
import Select from "./select.mjs";
|
|
13
13
|
import Skeleton from "./skeleton/index.mjs";
|
|
14
|
+
import Slider from "./slider/slider.mjs";
|
|
14
15
|
import Spin from "./spin.mjs";
|
|
15
16
|
import Switch from "./switch.mjs";
|
|
16
|
-
export { Button, Checkbox, ColorPicker, DatePicker, FloatButton, Input, Popover, Progress, Radio, Select, Skeleton, Spin, Switch, Tag, Typography };
|
|
17
|
+
export { Button, Checkbox, ColorPicker, DatePicker, FloatButton, Input, Popover, Progress, Radio, Select, Skeleton, Slider, Spin, Switch, Tag, Typography };
|
|
@@ -37,7 +37,7 @@ const RadioGroup = ({ orientation = "horizontal", placement = "left", className,
|
|
|
37
37
|
children: options.map((item, index) => {
|
|
38
38
|
const checked = value === item.value;
|
|
39
39
|
return /* @__PURE__ */ jsx("li", {
|
|
40
|
-
className: cn("flex", classNames?.wrapper),
|
|
40
|
+
className: cn("m-0 flex", classNames?.wrapper),
|
|
41
41
|
children: isButton ? /* @__PURE__ */ jsx(Item, {
|
|
42
42
|
asChild: true,
|
|
43
43
|
value: String(item.value),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"group.mjs","names":["radio"],"sources":["../../../../src/components/atoms/radio/group.tsx"],"sourcesContent":["'use client';\n\nimport { useId } from 'react';\n\nimport { useControllableState } from '@jbpark/use-hooks';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { radio } from '../../../core';\nimport Button from '../button';\nimport {\n type Option,\n type OptionGroupClassNames,\n type OptionValue,\n type Options,\n normalizeOptions,\n warnOnDuplicateOptionValues,\n} from '../option-group';\nimport { RadioGroupContext } from './context';\nimport Radio from './radio';\n\nconst { RadioGroup: Core, RadioGroupItem: Item } = radio;\n\nexport type { OptionValue };\n\nexport interface Props extends Omit<\n React.ComponentPropsWithoutRef<'ul'>,\n 'onChange' | 'defaultValue' | 'value'\n> {\n options?: Options;\n orientation?: 'vertical' | 'horizontal';\n placement?: 'left' | 'right';\n classNames?: OptionGroupClassNames;\n defaultValue?: OptionValue;\n value?: OptionValue;\n /** Native form field name — participates in `FormData` when set. */\n name?: string;\n optionType?: 'default' | 'button';\n buttonStyle?: 'solid' | 'outlined';\n size?: 'small' | 'middle' | 'large';\n disabled?: boolean;\n onChange?: (value: OptionValue) => void;\n}\n\nconst RadioGroup = ({\n orientation = 'horizontal',\n placement = 'left',\n className,\n classNames = {},\n options: _options = [],\n name,\n optionType = 'default',\n buttonStyle = 'solid',\n size,\n disabled,\n defaultValue,\n value: _value,\n onChange: _onChange = () => {},\n ...props\n}: Props) => {\n const [value, setValue] = useControllableState<OptionValue>({\n value: _value,\n defaultValue,\n onChange: _onChange,\n });\n\n const options: Option[] = normalizeOptions(_options);\n warnOnDuplicateOptionValues(options, 'Radio.Group');\n\n const isButton = optionType === 'button';\n\n const onChange = (checked: boolean, optionValue: OptionValue) => {\n if (!checked) {\n return;\n }\n setValue(optionValue);\n };\n\n // A single Radix radiogroup root owns every option so that arrow keys move\n // focus across the whole group. `id` stays index-derived (for DOM\n // id/label pairing), but Radix itself now addresses/submits items by\n // their real (stringified) `value` — see radio.tsx.\n const baseId = useId();\n const getOptionId = (index: number) => `${baseId}-${index}`;\n const checkedItemValue = value === undefined ? '' : String(value);\n\n const onValueChange = (itemValue: string) => {\n const item = options.find(option => String(option.value) === itemValue);\n\n if (!item) {\n return;\n }\n\n onChange(true, item.value);\n };\n\n const list = (\n <ul\n {...props}\n className={cn(\n 'm-0 flex list-none gap-4 p-0',\n className,\n orientation === 'vertical' && 'flex-col',\n isButton && 'gap-0',\n )}\n >\n {options.map((item: Option, index) => {\n const checked = value === item.value;\n\n return (\n <li\n key={`${index}-${String(item.value)}`}\n className={cn('flex', classNames?.wrapper)}\n >\n {isButton ? (\n // `asChild` gives the Button real radio semantics (role,\n // aria-checked, roving tabindex, arrow-key nav, click/keyboard\n // selection via the shared Core's onValueChange below) instead\n // of a plain list of buttons with none of that.\n <Item\n asChild\n value={String(item.value)}\n disabled={disabled || item.disabled}\n >\n <Button\n variant={checked ? buttonStyle : 'outlined'}\n size={size}\n disabled={disabled || item.disabled}\n className={cn(\n // The `solid` variant (the checked item, by default)\n // renders no border at all while `outlined` (every\n // unchecked item) renders a 1px one — reserving the\n // border unconditionally keeps every option's box the\n // same size regardless of which variant it's in.\n 'rounded-none border',\n checked &&\n (buttonStyle === 'outlined'\n ? 'border-primary'\n : 'border-transparent'),\n index === 0 &&\n (orientation === 'vertical'\n ? 'rounded-t-lg'\n : 'rounded-l-lg'),\n index === options.length - 1 &&\n (orientation === 'vertical'\n ? 'rounded-b-lg'\n : 'rounded-r-lg'),\n )}\n >\n {item.label}\n </Button>\n </Item>\n ) : (\n <Radio\n id={getOptionId(index)}\n placement={placement}\n className={cn(classNames?.item)}\n value={item.value}\n checked={checked}\n disabled={disabled || item.disabled}\n onChange={checked => onChange(checked, item.value)}\n >\n {item.label}\n </Radio>\n )}\n </li>\n );\n })}\n </ul>\n );\n\n return (\n <RadioGroupContext.Provider value={true}>\n <Core\n className=\"block\"\n name={name}\n value={checkedItemValue}\n onValueChange={onValueChange}\n >\n {list}\n </Core>\n </RadioGroupContext.Provider>\n );\n};\n\nexport default RadioGroup;\n"],"mappings":";;;;;;;;;;;AAqBA,MAAM,EAAE,YAAY,MAAM,gBAAgB,SAASA;AAuBnD,MAAM,cAAc,EAClB,cAAc,cACd,YAAY,QACZ,WACA,aAAa,CAAC,GACd,SAAS,WAAW,CAAC,GACrB,MACA,aAAa,WACb,cAAc,SACd,MACA,UACA,cACA,OAAO,QACP,UAAU,kBAAkB,CAAC,GAC7B,GAAG,YACQ;CACX,MAAM,CAAC,OAAO,YAAY,qBAAkC;EAC1D,OAAO;EACP;EACA,UAAU;CACZ,CAAC;CAED,MAAM,UAAoB,iBAAiB,QAAQ;CACnD,4BAA4B,SAAS,aAAa;CAElD,MAAM,WAAW,eAAe;CAEhC,MAAM,YAAY,SAAkB,gBAA6B;EAC/D,IAAI,CAAC,SACH;EAEF,SAAS,WAAW;CACtB;CAMA,MAAM,SAAS,MAAM;CACrB,MAAM,eAAe,UAAkB,GAAG,OAAO,GAAG;CACpD,MAAM,mBAAmB,UAAU,KAAA,IAAY,KAAK,OAAO,KAAK;CAEhE,MAAM,iBAAiB,cAAsB;EAC3C,MAAM,OAAO,QAAQ,MAAK,WAAU,OAAO,OAAO,KAAK,MAAM,SAAS;EAEtE,IAAI,CAAC,MACH;EAGF,SAAS,MAAM,KAAK,KAAK;CAC3B;CAEA,MAAM,OACJ,oBAAC,MAAD;EACE,GAAI;EACJ,WAAW,GACT,gCACA,WACA,gBAAgB,cAAc,YAC9B,YAAY,OACd;EAEC,UAAA,QAAQ,KAAK,MAAc,UAAU;GACpC,MAAM,UAAU,UAAU,KAAK;GAE/B,OACE,oBAAC,MAAD;
|
|
1
|
+
{"version":3,"file":"group.mjs","names":["radio"],"sources":["../../../../src/components/atoms/radio/group.tsx"],"sourcesContent":["'use client';\n\nimport { useId } from 'react';\n\nimport { useControllableState } from '@jbpark/use-hooks';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { radio } from '../../../core';\nimport Button from '../button';\nimport {\n type Option,\n type OptionGroupClassNames,\n type OptionValue,\n type Options,\n normalizeOptions,\n warnOnDuplicateOptionValues,\n} from '../option-group';\nimport { RadioGroupContext } from './context';\nimport Radio from './radio';\n\nconst { RadioGroup: Core, RadioGroupItem: Item } = radio;\n\nexport type { OptionValue };\n\nexport interface Props extends Omit<\n React.ComponentPropsWithoutRef<'ul'>,\n 'onChange' | 'defaultValue' | 'value'\n> {\n options?: Options;\n orientation?: 'vertical' | 'horizontal';\n placement?: 'left' | 'right';\n classNames?: OptionGroupClassNames;\n defaultValue?: OptionValue;\n value?: OptionValue;\n /** Native form field name — participates in `FormData` when set. */\n name?: string;\n optionType?: 'default' | 'button';\n buttonStyle?: 'solid' | 'outlined';\n size?: 'small' | 'middle' | 'large';\n disabled?: boolean;\n onChange?: (value: OptionValue) => void;\n}\n\nconst RadioGroup = ({\n orientation = 'horizontal',\n placement = 'left',\n className,\n classNames = {},\n options: _options = [],\n name,\n optionType = 'default',\n buttonStyle = 'solid',\n size,\n disabled,\n defaultValue,\n value: _value,\n onChange: _onChange = () => {},\n ...props\n}: Props) => {\n const [value, setValue] = useControllableState<OptionValue>({\n value: _value,\n defaultValue,\n onChange: _onChange,\n });\n\n const options: Option[] = normalizeOptions(_options);\n warnOnDuplicateOptionValues(options, 'Radio.Group');\n\n const isButton = optionType === 'button';\n\n const onChange = (checked: boolean, optionValue: OptionValue) => {\n if (!checked) {\n return;\n }\n setValue(optionValue);\n };\n\n // A single Radix radiogroup root owns every option so that arrow keys move\n // focus across the whole group. `id` stays index-derived (for DOM\n // id/label pairing), but Radix itself now addresses/submits items by\n // their real (stringified) `value` — see radio.tsx.\n const baseId = useId();\n const getOptionId = (index: number) => `${baseId}-${index}`;\n const checkedItemValue = value === undefined ? '' : String(value);\n\n const onValueChange = (itemValue: string) => {\n const item = options.find(option => String(option.value) === itemValue);\n\n if (!item) {\n return;\n }\n\n onChange(true, item.value);\n };\n\n const list = (\n <ul\n {...props}\n className={cn(\n 'm-0 flex list-none gap-4 p-0',\n className,\n orientation === 'vertical' && 'flex-col',\n isButton && 'gap-0',\n )}\n >\n {options.map((item: Option, index) => {\n const checked = value === item.value;\n\n return (\n <li\n key={`${index}-${String(item.value)}`}\n // `m-0` completes the list reset the `ul` above starts (#253):\n // without preflight, hosts that style prose lists still reach the\n // items — Docusaurus/Infima's `.markdown li + li` gives every item\n // but the first a `margin-top`, which in horizontal orientation\n // drops options 2..n by that much and stretches the first one,\n // making it look misaligned.\n className={cn('m-0 flex', classNames?.wrapper)}\n >\n {isButton ? (\n // `asChild` gives the Button real radio semantics (role,\n // aria-checked, roving tabindex, arrow-key nav, click/keyboard\n // selection via the shared Core's onValueChange below) instead\n // of a plain list of buttons with none of that.\n <Item\n asChild\n value={String(item.value)}\n disabled={disabled || item.disabled}\n >\n <Button\n variant={checked ? buttonStyle : 'outlined'}\n size={size}\n disabled={disabled || item.disabled}\n className={cn(\n // The `solid` variant (the checked item, by default)\n // renders no border at all while `outlined` (every\n // unchecked item) renders a 1px one — reserving the\n // border unconditionally keeps every option's box the\n // same size regardless of which variant it's in.\n 'rounded-none border',\n checked &&\n (buttonStyle === 'outlined'\n ? 'border-primary'\n : 'border-transparent'),\n index === 0 &&\n (orientation === 'vertical'\n ? 'rounded-t-lg'\n : 'rounded-l-lg'),\n index === options.length - 1 &&\n (orientation === 'vertical'\n ? 'rounded-b-lg'\n : 'rounded-r-lg'),\n )}\n >\n {item.label}\n </Button>\n </Item>\n ) : (\n <Radio\n id={getOptionId(index)}\n placement={placement}\n className={cn(classNames?.item)}\n value={item.value}\n checked={checked}\n disabled={disabled || item.disabled}\n onChange={checked => onChange(checked, item.value)}\n >\n {item.label}\n </Radio>\n )}\n </li>\n );\n })}\n </ul>\n );\n\n return (\n <RadioGroupContext.Provider value={true}>\n <Core\n className=\"block\"\n name={name}\n value={checkedItemValue}\n onValueChange={onValueChange}\n >\n {list}\n </Core>\n </RadioGroupContext.Provider>\n );\n};\n\nexport default RadioGroup;\n"],"mappings":";;;;;;;;;;;AAqBA,MAAM,EAAE,YAAY,MAAM,gBAAgB,SAASA;AAuBnD,MAAM,cAAc,EAClB,cAAc,cACd,YAAY,QACZ,WACA,aAAa,CAAC,GACd,SAAS,WAAW,CAAC,GACrB,MACA,aAAa,WACb,cAAc,SACd,MACA,UACA,cACA,OAAO,QACP,UAAU,kBAAkB,CAAC,GAC7B,GAAG,YACQ;CACX,MAAM,CAAC,OAAO,YAAY,qBAAkC;EAC1D,OAAO;EACP;EACA,UAAU;CACZ,CAAC;CAED,MAAM,UAAoB,iBAAiB,QAAQ;CACnD,4BAA4B,SAAS,aAAa;CAElD,MAAM,WAAW,eAAe;CAEhC,MAAM,YAAY,SAAkB,gBAA6B;EAC/D,IAAI,CAAC,SACH;EAEF,SAAS,WAAW;CACtB;CAMA,MAAM,SAAS,MAAM;CACrB,MAAM,eAAe,UAAkB,GAAG,OAAO,GAAG;CACpD,MAAM,mBAAmB,UAAU,KAAA,IAAY,KAAK,OAAO,KAAK;CAEhE,MAAM,iBAAiB,cAAsB;EAC3C,MAAM,OAAO,QAAQ,MAAK,WAAU,OAAO,OAAO,KAAK,MAAM,SAAS;EAEtE,IAAI,CAAC,MACH;EAGF,SAAS,MAAM,KAAK,KAAK;CAC3B;CAEA,MAAM,OACJ,oBAAC,MAAD;EACE,GAAI;EACJ,WAAW,GACT,gCACA,WACA,gBAAgB,cAAc,YAC9B,YAAY,OACd;EAEC,UAAA,QAAQ,KAAK,MAAc,UAAU;GACpC,MAAM,UAAU,UAAU,KAAK;GAE/B,OACE,oBAAC,MAAD;IAQE,WAAW,GAAG,YAAY,YAAY,OAAO;IAE5C,UAAA,WAKC,oBAAC,MAAD;KACE,SAAA;KACA,OAAO,OAAO,KAAK,KAAK;KACxB,UAAU,YAAY,KAAK;KAE3B,UAAA,oBAAC,QAAD;MACE,SAAS,UAAU,cAAc;MAC3B;MACN,UAAU,YAAY,KAAK;MAC3B,WAAW,GAMT,uBACA,YACG,gBAAgB,aACb,mBACA,uBACN,UAAU,MACP,gBAAgB,aACb,iBACA,iBACN,UAAU,QAAQ,SAAS,MACxB,gBAAgB,aACb,iBACA,eACR;MAEC,UAAA,KAAK;KACA,CAAA;IACJ,CAAA,IAEN,oBAAC,OAAD;KACE,IAAI,YAAY,KAAK;KACV;KACX,WAAW,GAAG,YAAY,IAAI;KAC9B,OAAO,KAAK;KACH;KACT,UAAU,YAAY,KAAK;KAC3B,WAAU,YAAW,SAAS,SAAS,KAAK,KAAK;KAEhD,UAAA,KAAK;IACD,CAAA;GAEP,GA5DG,GAAG,MAAM,GAAG,OAAO,KAAK,KAAK,GA4DhC;EAER,CAAC;CACC,CAAA;CAGN,OACE,oBAAC,kBAAkB,UAAnB;EAA4B,OAAO;EACjC,UAAA,oBAAC,MAAD;GACE,WAAU;GACJ;GACN,OAAO;GACQ;GAEd,UAAA;EACG,CAAA;CACoB,CAAA;AAEhC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/atoms/slider/index.ts"],"sourcesContent":["import Slider, { type Props, type SliderValue } from './slider';\n\nexport default Slider;\nexport type { Props, SliderValue };\n"],"mappings":";;AAEA,IAAA,iBAAe"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/components/atoms/slider/slider.d.ts
|
|
2
|
+
/** A single thumb (`number`) or a two-thumb range (`[min, max]`). */
|
|
3
|
+
type SliderValue = number | number[];
|
|
4
|
+
interface Props extends Omit<React.ComponentPropsWithoutRef<'span'>, 'onChange' | 'defaultValue' | 'dir'> {
|
|
5
|
+
value?: SliderValue;
|
|
6
|
+
defaultValue?: SliderValue;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
orientation?: 'horizontal' | 'vertical';
|
|
12
|
+
onChange?: (value: SliderValue) => void;
|
|
13
|
+
}
|
|
14
|
+
declare const Slider: ({ value: _value, defaultValue, min, max, step, disabled, orientation, onChange: _onChange, className, ...props }: Props) => import("react").JSX.Element;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { Props, SliderValue, Slider as default };
|
|
17
|
+
//# sourceMappingURL=slider.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slider.d.mts","names":[],"sources":["../../../../src/components/atoms/slider/slider.tsx"],"mappings":";;KAWY;UAEK,cAAc,KAC7B,MAAM;EAGN,QAAQ;EACR,eAAe;EACf;EACA;EACA;EACA;EACA;EACA,YAAY,OAAO;;cAWf,WAAU,OAAA,QAAA,cAAA,KAAA,KAAA,MAAA,UAAA,aAAA,UAAA,WAAA,cAAA,SAWb,0BAAK,IAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../../utils.mjs";
|
|
3
|
+
import { slider_exports } from "../../../core/slider.mjs";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { useControllableState } from "@jbpark/use-hooks";
|
|
6
|
+
//#region src/components/atoms/slider/slider.tsx
|
|
7
|
+
const { Slider: Core } = slider_exports;
|
|
8
|
+
const toArray = (value) => {
|
|
9
|
+
if (value === void 0) return;
|
|
10
|
+
return Array.isArray(value) ? value : [value];
|
|
11
|
+
};
|
|
12
|
+
const Slider = ({ value: _value, defaultValue, min = 0, max = 100, step = 1, disabled, orientation = "horizontal", onChange: _onChange = () => {}, className, ...props }) => {
|
|
13
|
+
const isRange = Array.isArray(_value ?? defaultValue);
|
|
14
|
+
const [value, onChange] = useControllableState({
|
|
15
|
+
value: toArray(_value),
|
|
16
|
+
defaultValue: toArray(defaultValue) ?? [min],
|
|
17
|
+
onChange: (next) => {
|
|
18
|
+
_onChange(isRange ? next : next[0] ?? min);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return /* @__PURE__ */ jsx(Core, {
|
|
22
|
+
value,
|
|
23
|
+
min,
|
|
24
|
+
max,
|
|
25
|
+
step,
|
|
26
|
+
disabled,
|
|
27
|
+
orientation,
|
|
28
|
+
className: cn(className),
|
|
29
|
+
onValueChange: onChange,
|
|
30
|
+
...props
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { Slider as default };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=slider.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slider.mjs","names":["slider"],"sources":["../../../../src/components/atoms/slider/slider.tsx"],"sourcesContent":["'use client';\n\nimport { useControllableState } from '@jbpark/use-hooks';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { slider } from '../../../core';\n\nconst { Slider: Core } = slider;\n\n/** A single thumb (`number`) or a two-thumb range (`[min, max]`). */\nexport type SliderValue = number | number[];\n\nexport interface Props extends Omit<\n React.ComponentPropsWithoutRef<'span'>,\n 'onChange' | 'defaultValue' | 'dir'\n> {\n value?: SliderValue;\n defaultValue?: SliderValue;\n min?: number;\n max?: number;\n step?: number;\n disabled?: boolean;\n orientation?: 'horizontal' | 'vertical';\n onChange?: (value: SliderValue) => void;\n}\n\nconst toArray = (value: SliderValue | undefined): number[] | undefined => {\n if (value === undefined) {\n return undefined;\n }\n\n return Array.isArray(value) ? value : [value];\n};\n\nconst Slider = ({\n value: _value,\n defaultValue,\n min = 0,\n max = 100,\n step = 1,\n disabled,\n orientation = 'horizontal',\n onChange: _onChange = () => {},\n className,\n ...props\n}: Props) => {\n // A `number[]` value/defaultValue means the consumer wants a range; a bare\n // `number` means a single thumb. Radix works in `number[]` throughout, so we\n // normalise on the way in and hand the original shape back through `onChange`.\n const isRange = Array.isArray(_value ?? defaultValue);\n\n const [value, onChange] = useControllableState<number[]>({\n value: toArray(_value),\n defaultValue: toArray(defaultValue) ?? [min],\n onChange: next => {\n _onChange(isRange ? next : (next[0] ?? min));\n },\n });\n\n return (\n <Core\n value={value}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n orientation={orientation}\n className={cn(className)}\n onValueChange={onChange}\n {...props}\n />\n );\n};\n\nexport default Slider;\n"],"mappings":";;;;;;AAQA,MAAM,EAAE,QAAQ,SAASA;AAmBzB,MAAM,WAAW,UAAyD;CACxE,IAAI,UAAU,KAAA,GACZ;CAGF,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAC9C;AAEA,MAAM,UAAU,EACd,OAAO,QACP,cACA,MAAM,GACN,MAAM,KACN,OAAO,GACP,UACA,cAAc,cACd,UAAU,kBAAkB,CAAC,GAC7B,WACA,GAAG,YACQ;CAIX,MAAM,UAAU,MAAM,QAAQ,UAAU,YAAY;CAEpD,MAAM,CAAC,OAAO,YAAY,qBAA+B;EACvD,OAAO,QAAQ,MAAM;EACrB,cAAc,QAAQ,YAAY,KAAK,CAAC,GAAG;EAC3C,WAAU,SAAQ;GAChB,UAAU,UAAU,OAAQ,KAAK,MAAM,GAAI;EAC7C;CACF,CAAC;CAED,OACE,oBAAC,MAAD;EACS;EACF;EACA;EACC;EACI;EACG;EACb,WAAW,GAAG,SAAS;EACvB,eAAe;EACf,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -70,7 +70,7 @@ const Item = ({ itemKey, keyPath, root = false, index, mode, label, children, se
|
|
|
70
70
|
return /* @__PURE__ */ jsxs("li", {
|
|
71
71
|
ref: itemRef,
|
|
72
72
|
role: "none",
|
|
73
|
-
className: cn("group", "relative", root && isHorizontal ? "inline-block" : "block"),
|
|
73
|
+
className: cn("group", "relative", "m-0", root && isHorizontal ? "inline-block" : "block"),
|
|
74
74
|
onMouseEnter: () => {
|
|
75
75
|
if (isInline) return;
|
|
76
76
|
setOpen(true);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item.mjs","names":[],"sources":["../../../../../src/components/molecules/menu/item/item.tsx"],"sourcesContent":["'use client';\n\nimport { useRef, useState } from 'react';\n\nimport { ChevronDown } from 'lucide-react';\nimport { AnimatePresence, motion } from 'motion/react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { MENU_CLASSNAMES, MenuItem, Props } from '../menu';\nimport Label from './label';\n\ninterface ItemProps extends Props, MenuItem {\n itemKey: React.Key;\n keyPath: React.Key[];\n root?: boolean;\n index: number;\n}\nconst OFFSET: [number, number] = [8, 8];\nconst INLINE_OFFSET = 16;\n\nconst STYLES = {\n hover: {\n classes: ['hover:underline', 'hover:underline-offset-8'],\n },\n open: {\n classes: ['underline', 'underline-offset-8'],\n },\n} as const;\n\nconst getItemClasses = (\n root: boolean,\n isHorizontal: boolean,\n isInline: boolean,\n hasChildren: boolean,\n customClasses?: string,\n) =>\n cn(\n 'px-4 py-2',\n 'cursor-pointer',\n 'whitespace-nowrap',\n root && isHorizontal ? 'inline-flex' : 'flex',\n ...(root\n ? [\n 'h-full',\n 'rounded-none border-transparent',\n hasChildren && 'hover:border-current',\n isHorizontal ? 'border-b-2' : 'border-r-2',\n isInline && 'border-none',\n ]\n : ['rounded-md', ...STYLES.hover.classes]),\n customClasses,\n );\n\nconst Item = ({\n itemKey,\n keyPath,\n root = false,\n index,\n mode,\n label,\n children,\n selectionMap,\n classNames,\n styles,\n offset = OFFSET,\n inlineOffset = INLINE_OFFSET,\n onClick,\n onSelect,\n ...props\n}: ItemProps) => {\n const [open, setOpen] = useState<boolean>(false);\n\n const itemRef = useRef<HTMLLIElement | null>(null);\n\n const isHorizontal = mode === 'horizontal';\n const isInline = mode === 'inline';\n\n const [left, top] = offset;\n\n const selected = selectionMap?.get(itemKey) ?? false;\n\n const item = {\n ...props,\n label,\n key: itemKey,\n children,\n };\n\n const itemClassNames = getItemClasses(\n root,\n isHorizontal,\n isInline,\n !!children?.length,\n root ? classNames?.rootItem : classNames?.item,\n );\n\n const handleSelect = (domEvent: React.SyntheticEvent) => {\n domEvent.stopPropagation();\n\n if (isInline) {\n setOpen(!open);\n }\n\n // Next.js's types pull in React's `react/experimental` augmentation\n // (adding `useOptimistic`'s internal key symbol to `React.Key`), which\n // widens `itemKey`/`item.key` here beyond what `ClickEventHandler`\n // expects when this file is type-checked as part of a Next.js app —\n // real menu item keys are always string/number, never that symbol.\n const params = {\n domEvent,\n key: itemKey as Exclude<React.Key, symbol>,\n keyPath,\n item: item as unknown as MenuItem,\n };\n\n onClick?.(params);\n\n if (!selected) {\n onSelect?.(params);\n }\n };\n\n // Roving tabindex (WAI-ARIA menu pattern): only the first item in each\n // menu/submenu level is a Tab stop (below); arrow keys move real DOM\n // focus between sibling <li>s within that same level, which is what\n // ends up mattering for actual keyboard use even though — unlike a\n // fully state-tracked implementation — focus resets to the first item\n // if the user Tabs out and back in rather than resuming where they left\n // off.\n //\n // Deliberately not `useKeyPress` (unlike dropdown.tsx's trigger and\n // radio.tsx/checkbox.tsx's icon mode, #229's action item 5): that hook\n // is a static combo -> handler binding, one listener per combo. This\n // handler is a single stateful branch — Enter/Space select, Escape\n // conditionally closes a submenu and stops the event from also closing\n // an ancestor Dropdown, and the arrow-key branch reads `itemRef` at\n // fire time to focus a *sibling* `<li>`. Splitting that into 3-4\n // separate useKeyPress calls (each its own listener) per menu item\n // wouldn't fix a bug — the existing logic is already correct — it would\n // just multiply listeners across every item in a menu for no benefit.\n const handleKeyDown = (domEvent: React.KeyboardEvent) => {\n if (domEvent.key === 'Enter' || domEvent.key === ' ') {\n domEvent.preventDefault();\n handleSelect(domEvent);\n return;\n }\n\n if (domEvent.key === 'Escape') {\n if (children?.length && open) {\n domEvent.stopPropagation();\n setOpen(false);\n }\n return;\n }\n\n const isNext = isHorizontal\n ? domEvent.key === 'ArrowRight'\n : domEvent.key === 'ArrowDown';\n const isPrev = isHorizontal\n ? domEvent.key === 'ArrowLeft'\n : domEvent.key === 'ArrowUp';\n\n if (!isNext && !isPrev) {\n return;\n }\n\n domEvent.preventDefault();\n const sibling = isNext\n ? itemRef.current?.nextElementSibling\n : itemRef.current?.previousElementSibling;\n sibling?.querySelector<HTMLElement>('[role=\"menuitem\"]')?.focus();\n };\n\n const childrenContainerClassNames = cn(\n isInline ? 'relative overflow-hidden' : 'absolute min-w-[200px]',\n !isInline && root && isHorizontal\n ? 'left-0 top-full pt-2 pl-0'\n : !isInline\n ? 'left-full top-0 pl-2 pt-0'\n : 'pl-4',\n );\n\n const childrenContainerStyle = isInline\n ? {\n paddingLeft: inlineOffset,\n }\n : root && isHorizontal\n ? {\n paddingTop: top,\n }\n : {\n paddingLeft: left,\n };\n\n return (\n <li\n ref={itemRef}\n role=\"none\"\n className={cn(\n 'group',\n 'relative',\n root && isHorizontal ? 'inline-block' : 'block',\n )}\n onMouseEnter={() => {\n if (isInline) {\n return;\n }\n setOpen(true);\n }}\n onMouseLeave={() => {\n if (isInline) {\n return;\n }\n setOpen(false);\n }}\n onFocus={() => {\n if (isInline) {\n return;\n }\n setOpen(true);\n }}\n onBlur={e => {\n if (isInline) {\n return;\n }\n if (!itemRef.current?.contains(e.relatedTarget as Node)) {\n setOpen(false);\n }\n }}\n >\n <div\n role=\"menuitem\"\n tabIndex={index === 0 ? 0 : -1}\n aria-expanded={children?.length ? open : undefined}\n aria-haspopup={children?.length ? 'menu' : undefined}\n aria-current={selected ? true : undefined}\n className={cn(\n itemClassNames,\n ...(selected\n ? root\n ? ['border-current']\n : [...STYLES.open.classes, ...STYLES.hover.classes]\n : root && !!children?.length\n ? ['group-hover:border-current']\n : []),\n //\n )}\n style={root ? styles?.rootItem : styles?.item}\n onClick={handleSelect}\n onKeyDown={handleKeyDown}\n >\n <div\n className={cn(\n 'w-full',\n 'inline-flex items-center justify-between',\n //\n )}\n >\n <Label className={cn(classNames?.label)} style={styles?.label}>\n {label}\n </Label>\n {!root && !isInline && !!children?.length && (\n <ChevronDown className={cn('-rotate-90')} />\n )}\n {isInline && !!children?.length && (\n <ChevronDown\n className={cn(\n 'transition-all',\n open ? '-rotate-180' : 'rotate-0',\n )}\n />\n )}\n </div>\n </div>\n <AnimatePresence>\n {!!children?.length && open && (\n <motion.div\n className={cn(\n childrenContainerClassNames,\n //\n )}\n style={childrenContainerStyle}\n initial={isInline ? { height: 0, opacity: 0 } : { opacity: 0 }}\n animate={isInline ? { height: 'auto', opacity: 1 } : { opacity: 1 }}\n exit={isInline ? { height: 0, opacity: 0 } : { opacity: 0 }}\n transition={{ duration: 0.2 }}\n >\n <ul\n role=\"menu\"\n className={cn(\n MENU_CLASSNAMES,\n isInline && 'shadow-none',\n classNames?.subMenu,\n //\n )}\n style={styles?.subMenu}\n >\n {children.map((child, childIndex) => (\n <Item\n {...child}\n key={child.key}\n itemKey={child.key}\n keyPath={[...keyPath, child.key]}\n index={childIndex}\n selectionMap={selectionMap}\n mode={mode}\n classNames={classNames}\n styles={styles}\n offset={offset}\n inlineOffset={inlineOffset}\n onClick={onClick}\n onSelect={onSelect}\n />\n ))}\n </ul>\n </motion.div>\n )}\n </AnimatePresence>\n </li>\n );\n};\n\nexport default Item;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"item.mjs","names":[],"sources":["../../../../../src/components/molecules/menu/item/item.tsx"],"sourcesContent":["'use client';\n\nimport { useRef, useState } from 'react';\n\nimport { ChevronDown } from 'lucide-react';\nimport { AnimatePresence, motion } from 'motion/react';\n\nimport { cn } from '@repo/ui/utils';\n\nimport { MENU_CLASSNAMES, MenuItem, Props } from '../menu';\nimport Label from './label';\n\ninterface ItemProps extends Props, MenuItem {\n itemKey: React.Key;\n keyPath: React.Key[];\n root?: boolean;\n index: number;\n}\nconst OFFSET: [number, number] = [8, 8];\nconst INLINE_OFFSET = 16;\n\nconst STYLES = {\n hover: {\n classes: ['hover:underline', 'hover:underline-offset-8'],\n },\n open: {\n classes: ['underline', 'underline-offset-8'],\n },\n} as const;\n\nconst getItemClasses = (\n root: boolean,\n isHorizontal: boolean,\n isInline: boolean,\n hasChildren: boolean,\n customClasses?: string,\n) =>\n cn(\n 'px-4 py-2',\n 'cursor-pointer',\n 'whitespace-nowrap',\n root && isHorizontal ? 'inline-flex' : 'flex',\n ...(root\n ? [\n 'h-full',\n 'rounded-none border-transparent',\n hasChildren && 'hover:border-current',\n isHorizontal ? 'border-b-2' : 'border-r-2',\n isInline && 'border-none',\n ]\n : ['rounded-md', ...STYLES.hover.classes]),\n customClasses,\n );\n\nconst Item = ({\n itemKey,\n keyPath,\n root = false,\n index,\n mode,\n label,\n children,\n selectionMap,\n classNames,\n styles,\n offset = OFFSET,\n inlineOffset = INLINE_OFFSET,\n onClick,\n onSelect,\n ...props\n}: ItemProps) => {\n const [open, setOpen] = useState<boolean>(false);\n\n const itemRef = useRef<HTMLLIElement | null>(null);\n\n const isHorizontal = mode === 'horizontal';\n const isInline = mode === 'inline';\n\n const [left, top] = offset;\n\n const selected = selectionMap?.get(itemKey) ?? false;\n\n const item = {\n ...props,\n label,\n key: itemKey,\n children,\n };\n\n const itemClassNames = getItemClasses(\n root,\n isHorizontal,\n isInline,\n !!children?.length,\n root ? classNames?.rootItem : classNames?.item,\n );\n\n const handleSelect = (domEvent: React.SyntheticEvent) => {\n domEvent.stopPropagation();\n\n if (isInline) {\n setOpen(!open);\n }\n\n // Next.js's types pull in React's `react/experimental` augmentation\n // (adding `useOptimistic`'s internal key symbol to `React.Key`), which\n // widens `itemKey`/`item.key` here beyond what `ClickEventHandler`\n // expects when this file is type-checked as part of a Next.js app —\n // real menu item keys are always string/number, never that symbol.\n const params = {\n domEvent,\n key: itemKey as Exclude<React.Key, symbol>,\n keyPath,\n item: item as unknown as MenuItem,\n };\n\n onClick?.(params);\n\n if (!selected) {\n onSelect?.(params);\n }\n };\n\n // Roving tabindex (WAI-ARIA menu pattern): only the first item in each\n // menu/submenu level is a Tab stop (below); arrow keys move real DOM\n // focus between sibling <li>s within that same level, which is what\n // ends up mattering for actual keyboard use even though — unlike a\n // fully state-tracked implementation — focus resets to the first item\n // if the user Tabs out and back in rather than resuming where they left\n // off.\n //\n // Deliberately not `useKeyPress` (unlike dropdown.tsx's trigger and\n // radio.tsx/checkbox.tsx's icon mode, #229's action item 5): that hook\n // is a static combo -> handler binding, one listener per combo. This\n // handler is a single stateful branch — Enter/Space select, Escape\n // conditionally closes a submenu and stops the event from also closing\n // an ancestor Dropdown, and the arrow-key branch reads `itemRef` at\n // fire time to focus a *sibling* `<li>`. Splitting that into 3-4\n // separate useKeyPress calls (each its own listener) per menu item\n // wouldn't fix a bug — the existing logic is already correct — it would\n // just multiply listeners across every item in a menu for no benefit.\n const handleKeyDown = (domEvent: React.KeyboardEvent) => {\n if (domEvent.key === 'Enter' || domEvent.key === ' ') {\n domEvent.preventDefault();\n handleSelect(domEvent);\n return;\n }\n\n if (domEvent.key === 'Escape') {\n if (children?.length && open) {\n domEvent.stopPropagation();\n setOpen(false);\n }\n return;\n }\n\n const isNext = isHorizontal\n ? domEvent.key === 'ArrowRight'\n : domEvent.key === 'ArrowDown';\n const isPrev = isHorizontal\n ? domEvent.key === 'ArrowLeft'\n : domEvent.key === 'ArrowUp';\n\n if (!isNext && !isPrev) {\n return;\n }\n\n domEvent.preventDefault();\n const sibling = isNext\n ? itemRef.current?.nextElementSibling\n : itemRef.current?.previousElementSibling;\n sibling?.querySelector<HTMLElement>('[role=\"menuitem\"]')?.focus();\n };\n\n const childrenContainerClassNames = cn(\n isInline ? 'relative overflow-hidden' : 'absolute min-w-[200px]',\n !isInline && root && isHorizontal\n ? 'left-0 top-full pt-2 pl-0'\n : !isInline\n ? 'left-full top-0 pl-2 pt-0'\n : 'pl-4',\n );\n\n const childrenContainerStyle = isInline\n ? {\n paddingLeft: inlineOffset,\n }\n : root && isHorizontal\n ? {\n paddingTop: top,\n }\n : {\n paddingLeft: left,\n };\n\n return (\n <li\n ref={itemRef}\n role=\"none\"\n className={cn(\n 'group',\n 'relative',\n // `m-0` completes MENU_CLASSNAMES' list reset — see Radio.Group for\n // the host rule (`.markdown li + li`) this guards against.\n 'm-0',\n root && isHorizontal ? 'inline-block' : 'block',\n )}\n onMouseEnter={() => {\n if (isInline) {\n return;\n }\n setOpen(true);\n }}\n onMouseLeave={() => {\n if (isInline) {\n return;\n }\n setOpen(false);\n }}\n onFocus={() => {\n if (isInline) {\n return;\n }\n setOpen(true);\n }}\n onBlur={e => {\n if (isInline) {\n return;\n }\n if (!itemRef.current?.contains(e.relatedTarget as Node)) {\n setOpen(false);\n }\n }}\n >\n <div\n role=\"menuitem\"\n tabIndex={index === 0 ? 0 : -1}\n aria-expanded={children?.length ? open : undefined}\n aria-haspopup={children?.length ? 'menu' : undefined}\n aria-current={selected ? true : undefined}\n className={cn(\n itemClassNames,\n ...(selected\n ? root\n ? ['border-current']\n : [...STYLES.open.classes, ...STYLES.hover.classes]\n : root && !!children?.length\n ? ['group-hover:border-current']\n : []),\n //\n )}\n style={root ? styles?.rootItem : styles?.item}\n onClick={handleSelect}\n onKeyDown={handleKeyDown}\n >\n <div\n className={cn(\n 'w-full',\n 'inline-flex items-center justify-between',\n //\n )}\n >\n <Label className={cn(classNames?.label)} style={styles?.label}>\n {label}\n </Label>\n {!root && !isInline && !!children?.length && (\n <ChevronDown className={cn('-rotate-90')} />\n )}\n {isInline && !!children?.length && (\n <ChevronDown\n className={cn(\n 'transition-all',\n open ? '-rotate-180' : 'rotate-0',\n )}\n />\n )}\n </div>\n </div>\n <AnimatePresence>\n {!!children?.length && open && (\n <motion.div\n className={cn(\n childrenContainerClassNames,\n //\n )}\n style={childrenContainerStyle}\n initial={isInline ? { height: 0, opacity: 0 } : { opacity: 0 }}\n animate={isInline ? { height: 'auto', opacity: 1 } : { opacity: 1 }}\n exit={isInline ? { height: 0, opacity: 0 } : { opacity: 0 }}\n transition={{ duration: 0.2 }}\n >\n <ul\n role=\"menu\"\n className={cn(\n MENU_CLASSNAMES,\n isInline && 'shadow-none',\n classNames?.subMenu,\n //\n )}\n style={styles?.subMenu}\n >\n {children.map((child, childIndex) => (\n <Item\n {...child}\n key={child.key}\n itemKey={child.key}\n keyPath={[...keyPath, child.key]}\n index={childIndex}\n selectionMap={selectionMap}\n mode={mode}\n classNames={classNames}\n styles={styles}\n offset={offset}\n inlineOffset={inlineOffset}\n onClick={onClick}\n onSelect={onSelect}\n />\n ))}\n </ul>\n </motion.div>\n )}\n </AnimatePresence>\n </li>\n );\n};\n\nexport default Item;\n"],"mappings":"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"}
|