@k8o/arte-odyssey 8.0.3 → 9.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 (132) hide show
  1. package/dist/components/buttons/button/button.d.mts +1 -1
  2. package/dist/components/buttons/button/button.mjs +1 -1
  3. package/dist/components/buttons/icon-button/icon-button.d.mts +1 -1
  4. package/dist/components/buttons/icon-button/icon-button.mjs +5 -16
  5. package/dist/components/data-display/accordion/accordion-button.mjs +2 -2
  6. package/dist/components/data-display/accordion/accordion-item.mjs +4 -1
  7. package/dist/components/data-display/accordion/accordion.mjs +1 -1
  8. package/dist/components/data-display/accordion/context.d.mts +1 -1
  9. package/dist/components/data-display/accordion/context.mjs +3 -12
  10. package/dist/components/data-display/avatar/avatar.d.mts +1 -1
  11. package/dist/components/data-display/avatar/avatar.mjs +2 -2
  12. package/dist/components/data-display/badge/badge.d.mts +1 -1
  13. package/dist/components/data-display/badge/badge.mjs +3 -3
  14. package/dist/components/data-display/card/card.mjs +2 -2
  15. package/dist/components/data-display/card/interactive-card.mjs +2 -2
  16. package/dist/components/data-display/card/type.d.mts +1 -1
  17. package/dist/components/data-display/code/code.d.mts +1 -1
  18. package/dist/components/data-display/code/code.mjs +4 -5
  19. package/dist/{helpers/color → components/data-display/code}/find-all-colors.d.mts +1 -1
  20. package/dist/{helpers/color → components/data-display/code}/find-all-colors.mjs +1 -1
  21. package/dist/components/data-display/heading/heading.d.mts +1 -1
  22. package/dist/components/data-display/heading/heading.mjs +7 -7
  23. package/dist/components/data-display/table/table.d.mts +12 -17
  24. package/dist/components/data-display/table/table.mjs +23 -21
  25. package/dist/components/feedback/alert/alert.d.mts +1 -1
  26. package/dist/components/feedback/alert/alert.mjs +2 -2
  27. package/dist/components/feedback/progress/progress.d.mts +1 -1
  28. package/dist/components/feedback/progress/progress.mjs +6 -7
  29. package/dist/components/feedback/skeleton/skeleton.d.mts +1 -1
  30. package/dist/components/feedback/skeleton/skeleton.mjs +2 -2
  31. package/dist/components/feedback/spinner/spinner.d.mts +1 -1
  32. package/dist/components/feedback/spinner/spinner.mjs +2 -2
  33. package/dist/components/feedback/toast/context.d.mts +2 -2
  34. package/dist/components/feedback/toast/context.mjs +6 -7
  35. package/dist/components/feedback/toast/provider.mjs +1 -1
  36. package/dist/components/form/autocomplete/autocomplete.d.mts +1 -1
  37. package/dist/components/form/autocomplete/autocomplete.mjs +75 -51
  38. package/dist/components/form/checkbox/checkbox.d.mts +1 -1
  39. package/dist/components/form/checkbox/checkbox.mjs +7 -4
  40. package/dist/components/form/checkbox-card/checkbox-card.d.mts +1 -1
  41. package/dist/components/form/checkbox-card/checkbox-card.mjs +10 -9
  42. package/dist/components/form/checkbox-group/checkbox-group.d.mts +1 -1
  43. package/dist/components/form/checkbox-group/index.d.mts +3 -3
  44. package/dist/components/form/file-field/file-field.d.mts +1 -1
  45. package/dist/components/form/file-field/file-field.mjs +4 -10
  46. package/dist/components/form/form/form.d.mts +1 -1
  47. package/dist/components/form/form/form.mjs +2 -3
  48. package/dist/{helpers/number → components/form/number-field}/cast.d.mts +1 -1
  49. package/dist/{helpers/number → components/form/number-field}/cast.mjs +2 -2
  50. package/dist/components/form/number-field/number-field.d.mts +1 -1
  51. package/dist/components/form/number-field/number-field.mjs +25 -24
  52. package/dist/components/form/password-input/password-input.d.mts +1 -1
  53. package/dist/components/form/password-input/password-input.mjs +3 -3
  54. package/dist/components/form/radio/radio.d.mts +1 -1
  55. package/dist/components/form/radio/radio.mjs +6 -4
  56. package/dist/components/form/radio-card/radio-card.d.mts +1 -1
  57. package/dist/components/form/radio-card/radio-card.mjs +11 -8
  58. package/dist/components/form/select/select.d.mts +1 -1
  59. package/dist/components/form/select/select.mjs +5 -4
  60. package/dist/components/form/slider/slider.d.mts +1 -1
  61. package/dist/components/form/slider/slider.mjs +15 -8
  62. package/dist/components/form/switch/switch.d.mts +1 -1
  63. package/dist/components/form/switch/switch.mjs +9 -6
  64. package/dist/components/form/text-field/text-field.d.mts +1 -1
  65. package/dist/components/form/text-field/text-field.mjs +1 -1
  66. package/dist/components/form/textarea/textarea.d.mts +1 -1
  67. package/dist/components/form/textarea/textarea.mjs +1 -1
  68. package/dist/components/icons/index.d.mts +3 -2
  69. package/dist/components/icons/index.mjs +3 -2
  70. package/dist/components/icons/logo.mjs +3 -3
  71. package/dist/components/icons/lucide.d.mts +2 -1
  72. package/dist/components/icons/lucide.mjs +6 -2
  73. package/dist/components/icons/qiita.mjs +4 -4
  74. package/dist/components/icons/twitter.mjs +1 -1
  75. package/dist/components/icons/vertical-writing.d.mts +7 -0
  76. package/dist/components/icons/vertical-writing.mjs +25 -0
  77. package/dist/components/index.d.mts +3 -2
  78. package/dist/components/index.mjs +3 -2
  79. package/dist/components/layout/separator/separator.d.mts +1 -1
  80. package/dist/components/layout/separator/separator.mjs +2 -5
  81. package/dist/components/navigation/anchor/anchor.d.mts +1 -1
  82. package/dist/components/navigation/anchor/anchor.mjs +1 -2
  83. package/dist/components/navigation/breadcrumb/breadcrumb.mjs +1 -1
  84. package/dist/components/navigation/pagination/pagination.mjs +12 -6
  85. package/dist/components/navigation/tabs/tabs.mjs +25 -25
  86. package/dist/components/overlays/dialog/dialog.mjs +3 -7
  87. package/dist/components/overlays/drawer/drawer.mjs +1 -1
  88. package/dist/components/overlays/dropdown-menu/dropdown-menu.mjs +1 -1
  89. package/dist/components/overlays/dropdown-menu/hooks.d.mts +2 -3
  90. package/dist/components/overlays/dropdown-menu/hooks.mjs +4 -9
  91. package/dist/components/overlays/list-box/hooks.d.mts +0 -1
  92. package/dist/components/overlays/list-box/hooks.mjs +3 -8
  93. package/dist/components/overlays/list-box/list-box.mjs +1 -1
  94. package/dist/components/overlays/modal/modal.mjs +9 -5
  95. package/dist/components/overlays/popover/hooks.d.mts +1 -3
  96. package/dist/components/overlays/popover/hooks.mjs +3 -8
  97. package/dist/components/overlays/popover/popover.mjs +8 -2
  98. package/dist/components/overlays/tooltip/tooltip.mjs +19 -6
  99. package/dist/helpers/chain.d.mts +5 -0
  100. package/dist/helpers/chain.mjs +29 -0
  101. package/dist/helpers/create-safe-context.d.mts +7 -0
  102. package/dist/helpers/create-safe-context.mjs +13 -0
  103. package/dist/helpers/index.d.mts +5 -8
  104. package/dist/helpers/index.mjs +5 -8
  105. package/dist/helpers/merge-props.d.mts +6 -0
  106. package/dist/helpers/merge-props.mjs +56 -0
  107. package/dist/hooks/click-away/index.mjs +1 -1
  108. package/dist/hooks/index.d.mts +2 -1
  109. package/dist/hooks/index.mjs +2 -1
  110. package/dist/hooks/writing-mode/index.d.mts +7 -0
  111. package/dist/hooks/writing-mode/index.mjs +22 -0
  112. package/dist/index.d.mts +8 -9
  113. package/dist/index.mjs +8 -9
  114. package/dist/internal/clamp.d.mts +4 -0
  115. package/dist/internal/clamp.mjs +15 -0
  116. package/dist/{helpers/number → internal}/to-precision.d.mts +1 -1
  117. package/dist/{helpers/number → internal}/to-precision.mjs +1 -1
  118. package/dist/styles/index.css +37 -0
  119. package/dist/styles/tokens.d.mts +12 -1
  120. package/dist/styles/tokens.mjs +25 -1
  121. package/docs/references/typography.md +29 -0
  122. package/package.json +2 -2
  123. package/dist/helpers/is-internal-route.d.mts +0 -4
  124. package/dist/helpers/is-internal-route.mjs +0 -12
  125. package/dist/helpers/number/between.d.mts +0 -4
  126. package/dist/helpers/number/between.mjs +0 -15
  127. package/dist/helpers/number/commalize.d.mts +0 -4
  128. package/dist/helpers/number/commalize.mjs +0 -20
  129. package/dist/helpers/number/index.d.mts +0 -5
  130. package/dist/helpers/number/index.mjs +0 -5
  131. package/dist/helpers/uuid-v4.d.mts +0 -4
  132. package/dist/helpers/uuid-v4.mjs +0 -28
@@ -1,17 +1,16 @@
1
1
  "use client";
2
- import { uuidV4 } from "../../../helpers/uuid-v4.mjs";
3
- import { createContext, use, useCallback } from "react";
2
+ import { createSafeContext } from "../../../helpers/create-safe-context.mjs";
3
+ import { useCallback } from "react";
4
4
  //#region src/components/feedback/toast/context.ts
5
5
  const MAX_TOAST_COUNT = 5;
6
- const SetToastContext = createContext(void 0);
6
+ const [SetToastContext, useSetToast] = createSafeContext("useToast must be used within a ToastProvider");
7
7
  const useToast = () => {
8
- const setToasts = use(SetToastContext);
9
- if (!setToasts) throw new Error("useToast must be used within a ToastProvider");
8
+ const setToasts = useSetToast();
10
9
  return {
11
10
  onOpen: useCallback((status, message) => {
12
11
  setToasts((prev) => {
13
12
  return [...prev, {
14
- id: uuidV4(),
13
+ id: crypto.randomUUID(),
15
14
  status,
16
15
  message
17
16
  }].slice(-MAX_TOAST_COUNT);
@@ -26,4 +25,4 @@ const useToast = () => {
26
25
  };
27
26
  };
28
27
  //#endregion
29
- export { SetToastContext, useToast };
28
+ export { SetToastContext, useSetToast, useToast };
@@ -54,7 +54,7 @@ const ToastProvider = ({ children, portalRef = null, position = "fixed" }) => {
54
54
  children: [children, container ? createPortal(/* @__PURE__ */ jsx("section", {
55
55
  "aria-label": "通知",
56
56
  "aria-live": "polite",
57
- className: cn("absolute bottom-3 z-50 flex w-full flex-col items-center justify-center gap-4", position === "fixed" && "fixed", position === "absolute" && "absolute"),
57
+ className: cn("absolute bottom-3 z-toast flex w-full flex-col items-center justify-center gap-4", position === "fixed" && "fixed", position === "absolute" && "absolute"),
58
58
  children: /* @__PURE__ */ jsx(AnimatePresence, {
59
59
  initial: false,
60
60
  children: toasts.map((toast) => /* @__PURE__ */ jsx(motion$1.div, {
@@ -6,7 +6,7 @@ type BaseProps = {
6
6
  id: string;
7
7
  invalid?: boolean;
8
8
  options: readonly Option[];
9
- } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'role' | 'className' | 'value' | 'onChange' | 'defaultValue' | 'children' | 'id' | 'autoComplete' | 'aria-autocomplete' | 'aria-controls' | 'aria-expanded'>;
9
+ } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'role' | 'className' | 'style' | 'value' | 'onChange' | 'defaultValue' | 'children' | 'id' | 'autoComplete' | 'aria-autocomplete' | 'aria-controls' | 'aria-expanded'>;
10
10
  type ControlledProps = {
11
11
  value: string[];
12
12
  onChange: (value: string[]) => void;
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
+ import { useDisclosure } from "../../../hooks/disclosure/index.mjs";
3
4
  import { CloseIcon } from "../../icons/lucide.mjs";
4
5
  import { IconButton } from "../../buttons/icon-button/icon-button.mjs";
5
6
  import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
@@ -7,6 +8,7 @@ import { useDeferredDebounce } from "../../../hooks/deferred-debounce/index.mjs"
7
8
  import { useCallback, useEffect, useRef, useState } from "react";
8
9
  import { useFormStatus } from "react-dom";
9
10
  import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { autoUpdate, flip, offset, size, useFloating } from "@floating-ui/react";
10
12
  //#region src/components/form/autocomplete/autocomplete.tsx
11
13
  const Autocomplete = ({ id, name, invalid = false, disabled = false, required = false, options, value, defaultValue, onChange, ...rest }) => {
12
14
  const [currentValue, handleChange] = useControllableState({
@@ -15,18 +17,37 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
15
17
  onChange
16
18
  });
17
19
  const ref = useRef(null);
18
- const [open, setOpen] = useState(false);
20
+ const { isOpen, open, close } = useDisclosure();
19
21
  const [text, setText] = useState("");
20
22
  const [selectIndex, setSelectIndex] = useState();
23
+ const { refs, floatingStyles } = useFloating({
24
+ strategy: "fixed",
25
+ placement: "bottom-start",
26
+ open: isOpen,
27
+ whileElementsMounted: autoUpdate,
28
+ middleware: [
29
+ offset(4),
30
+ flip({
31
+ fallbackAxisSideDirection: "end",
32
+ padding: 8
33
+ }),
34
+ size({ apply: ({ rects, elements }) => {
35
+ const referenceEl = elements.reference instanceof HTMLElement ? elements.reference : null;
36
+ const isVertical = referenceEl !== null && getComputedStyle(referenceEl).writingMode.startsWith("vertical");
37
+ Object.assign(elements.floating.style, { inlineSize: `${isVertical ? rects.reference.height : rects.reference.width}px` });
38
+ } })
39
+ ],
40
+ transform: false
41
+ });
21
42
  const [deferredText, isPending] = useDeferredDebounce(text);
22
43
  const filteredOptions = options.filter((option) => option.label.includes(deferredText));
23
44
  const { pending: formPending } = useFormStatus();
24
45
  const disabledResolved = disabled || formPending;
25
46
  const reset = useCallback(() => {
26
47
  setText("");
27
- setOpen(false);
48
+ close();
28
49
  setSelectIndex(void 0);
29
- }, []);
50
+ }, [close]);
30
51
  useEffect(() => {
31
52
  const handleClick = (e) => {
32
53
  if (e.target instanceof Node && ref.current?.contains(e.target) === true) return;
@@ -37,9 +58,13 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
37
58
  document.removeEventListener("click", handleClick);
38
59
  };
39
60
  }, [reset]);
61
+ const setReferenceRef = (node) => {
62
+ ref.current = node;
63
+ refs.setReference(node);
64
+ };
40
65
  return /* @__PURE__ */ jsxs("div", {
41
- className: cn("relative w-full rounded-xl border border-border-base bg-bg-base", "focus-within:border-transparent focus-within:outline-hidden focus-within:ring-2 focus-within:ring-border-info", "has-aria-invalid:border-border-error", "has-disabled:cursor-not-allowed has-disabled:border-border-mute has-disabled:bg-bg-mute hover:has-disabled:has-hover:bg-bg-mute"),
42
- ref,
66
+ className: cn("relative rounded-xl border border-border-base bg-bg-base inline-full", "focus-within:border-transparent focus-within:outline-hidden focus-within:ring-2 focus-within:ring-border-info", "has-aria-invalid:border-border-error", "has-disabled:cursor-not-allowed has-disabled:border-border-mute has-disabled:bg-bg-mute hover:has-disabled:has-hover:bg-bg-mute"),
67
+ ref: setReferenceRef,
43
68
  children: [
44
69
  name !== void 0 && name !== "" ? currentValue.map((selectedValue) => /* @__PURE__ */ jsx("input", {
45
70
  name,
@@ -69,8 +94,8 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
69
94
  }), /* @__PURE__ */ jsx("input", {
70
95
  ...rest,
71
96
  "aria-autocomplete": "list",
72
- "aria-controls": open ? `${id}_listbox` : void 0,
73
- "aria-expanded": open,
97
+ "aria-controls": isOpen ? `${id}_listbox` : void 0,
98
+ "aria-expanded": isOpen,
74
99
  "aria-invalid": invalid,
75
100
  "aria-required": required,
76
101
  autoComplete: "off",
@@ -79,19 +104,19 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
79
104
  id,
80
105
  onBlur: (e) => {
81
106
  if (e.relatedTarget?.id.startsWith(`${id}_option_`) === true) return;
82
- setOpen(false);
107
+ close();
83
108
  },
84
109
  onChange: (e) => {
85
- setOpen(true);
110
+ open();
86
111
  setText(e.target.value);
87
112
  setSelectIndex(void 0);
88
113
  },
89
114
  onClick: () => {
90
- if (open && text.length === 0) {
91
- setOpen(false);
115
+ if (isOpen && text.length === 0) {
116
+ close();
92
117
  return;
93
118
  }
94
- setOpen(true);
119
+ open();
95
120
  setSelectIndex(void 0);
96
121
  },
97
122
  onKeyDown: (e) => {
@@ -101,7 +126,7 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
101
126
  return;
102
127
  }
103
128
  if (e.key === "ArrowDown") {
104
- setOpen(true);
129
+ open();
105
130
  setSelectIndex((prev) => {
106
131
  if (prev === void 0) return 0;
107
132
  return Math.min(prev + 1, options.length - 1);
@@ -109,7 +134,7 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
109
134
  return;
110
135
  }
111
136
  if (e.key === "ArrowUp") {
112
- setOpen(true);
137
+ open();
113
138
  setSelectIndex((prev) => {
114
139
  if (prev === void 0) return 0;
115
140
  return Math.max(prev - 1, 0);
@@ -147,43 +172,42 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
147
172
  children: /* @__PURE__ */ jsx(CloseIcon, { size: "sm" })
148
173
  })]
149
174
  }),
150
- /* @__PURE__ */ jsx("div", {
151
- className: "relative w-full",
152
- children: open && /* @__PURE__ */ jsx("div", {
153
- className: "bg-bg-raised absolute top-1 z-10 w-full rounded-xl shadow-md",
154
- role: "presentation",
155
- children: /* @__PURE__ */ jsxs("ul", {
156
- "aria-busy": isPending || void 0,
157
- className: cn("max-h-96 py-2 transition-opacity", isPending && "opacity-60"),
158
- id: `${id}_listbox`,
159
- children: [filteredOptions.length === 0 && /* @__PURE__ */ jsx("li", {
160
- className: "text-fg-mute px-3 py-2",
161
- children: "該当なし"
162
- }), filteredOptions.map((option, idx) => {
163
- const selected = currentValue.includes(option.value);
164
- return /* @__PURE__ */ jsx("li", {
165
- className: cn("cursor-pointer px-3 py-2 transition-colors", selected && "bg-primary-bg-subtle text-primary-fg", selectIndex === idx && !selected && "bg-bg-subtle", selectIndex === idx && selected && "bg-primary-bg-mute text-primary-fg"),
166
- id: `${id}_option_${option.value}`,
167
- onClick: (e) => {
168
- e.stopPropagation();
169
- reset();
170
- if (selected) {
171
- handleChange(currentValue.filter((v) => v !== option.value));
172
- return;
173
- }
174
- handleChange([...currentValue, option.value]);
175
- },
176
- onKeyDown: (e) => {
177
- e.preventDefault();
178
- },
179
- onMouseEnter: () => {
180
- setSelectIndex(idx);
181
- },
182
- tabIndex: -1,
183
- children: option.label
184
- }, option.value);
185
- })]
186
- })
175
+ isOpen && /* @__PURE__ */ jsx("div", {
176
+ className: "bg-bg-raised z-10 rounded-xl shadow-md",
177
+ ref: refs.setFloating,
178
+ role: "presentation",
179
+ style: floatingStyles,
180
+ children: /* @__PURE__ */ jsxs("ul", {
181
+ "aria-busy": isPending || void 0,
182
+ className: cn("max-h-96 py-2 transition-opacity vertical:max-h-none vertical:max-w-96", isPending && "opacity-60"),
183
+ id: `${id}_listbox`,
184
+ children: [filteredOptions.length === 0 && /* @__PURE__ */ jsx("li", {
185
+ className: "text-fg-mute px-3 py-2",
186
+ children: "該当なし"
187
+ }), filteredOptions.map((option, idx) => {
188
+ const selected = currentValue.includes(option.value);
189
+ return /* @__PURE__ */ jsx("li", {
190
+ className: cn("cursor-pointer px-3 py-2 transition-colors", selected && "bg-primary-bg-subtle text-primary-fg", selectIndex === idx && !selected && "bg-bg-subtle", selectIndex === idx && selected && "bg-primary-bg-mute text-primary-fg"),
191
+ id: `${id}_option_${option.value}`,
192
+ onClick: (e) => {
193
+ e.stopPropagation();
194
+ reset();
195
+ if (selected) {
196
+ handleChange(currentValue.filter((v) => v !== option.value));
197
+ return;
198
+ }
199
+ handleChange([...currentValue, option.value]);
200
+ },
201
+ onKeyDown: (e) => {
202
+ e.preventDefault();
203
+ },
204
+ onMouseEnter: () => {
205
+ setSelectIndex(idx);
206
+ },
207
+ tabIndex: -1,
208
+ children: option.label
209
+ }, option.value);
210
+ })]
187
211
  })
188
212
  })
189
213
  ]
@@ -4,7 +4,7 @@ import { ChangeEventHandler, FC, InputHTMLAttributes } from "react";
4
4
  type BaseProps = {
5
5
  itemValue?: string;
6
6
  label: string;
7
- } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className' | 'value' | 'onChange' | 'defaultChecked' | 'checked' | 'children'>;
7
+ } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className' | 'style' | 'value' | 'onChange' | 'defaultChecked' | 'checked' | 'children'>;
8
8
  type ControlledProps = {
9
9
  value: boolean;
10
10
  onChange: ChangeEventHandler<HTMLInputElement>;
@@ -1,22 +1,25 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
3
  import { CheckIcon } from "../../icons/lucide.mjs";
4
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
4
5
  import { useCheckboxGroupContext } from "../checkbox-group/checkbox-group.mjs";
5
- import { useState } from "react";
6
6
  import { useFormStatus } from "react-dom";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/components/form/checkbox/checkbox.tsx
9
9
  const Checkbox = ({ name, itemValue, disabled = false, label, value, defaultChecked, onChange, ...rest }) => {
10
10
  const groupContext = useCheckboxGroupContext();
11
11
  const { pending } = useFormStatus();
12
- const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);
12
+ const [internalChecked, setInternalChecked] = useControllableState({
13
+ value,
14
+ defaultValue: defaultChecked ?? false
15
+ });
13
16
  const groupItemValue = itemValue ?? "";
14
17
  if (groupContext && (itemValue === void 0 || itemValue === "")) throw new Error("Checkbox inside CheckboxGroup requires itemValue");
15
18
  const isControlled = value !== void 0;
16
19
  const disabledResolved = disabled || groupContext?.disabled === true || pending;
17
- const checked = groupContext ? groupContext.currentValue.includes(groupItemValue) : isControlled ? value : internalChecked;
20
+ const checked = groupContext ? groupContext.currentValue.includes(groupItemValue) : internalChecked;
18
21
  const setChecked = (nextChecked) => {
19
- if (!isControlled) setInternalChecked(nextChecked);
22
+ setInternalChecked(nextChecked);
20
23
  onChange?.({ target: { checked: nextChecked } });
21
24
  };
22
25
  return /* @__PURE__ */ jsxs("label", {
@@ -11,7 +11,7 @@ type CheckboxCardOption = Readonly<{
11
11
  type BaseProps = {
12
12
  invalid?: boolean;
13
13
  options: readonly CheckboxCardOption[];
14
- } & Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'className' | 'children' | 'onChange' | 'defaultValue'>;
14
+ } & Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'className' | 'style' | 'children' | 'onChange' | 'defaultValue'>;
15
15
  type ControlledProps = {
16
16
  value: string[];
17
17
  onChange: (value: string[]) => void;
@@ -1,22 +1,23 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
3
  import { CheckIcon } from "../../icons/lucide.mjs";
4
- import { useId, useState } from "react";
4
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
5
+ import { useId } from "react";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region src/components/form/checkbox-card/checkbox-card.tsx
7
8
  const CheckboxCard = ({ name, disabled = false, invalid = false, options, value, defaultValue, onChange, ...rest }) => {
8
9
  const groupId = useId();
9
- const [internalValue, setInternalValue] = useState(defaultValue ?? []);
10
- const isControlled = value !== void 0;
11
- const selectedValues = isControlled ? value : internalValue;
10
+ const [selectedValues, setSelectedValues] = useControllableState({
11
+ value,
12
+ defaultValue: defaultValue ?? [],
13
+ onChange
14
+ });
12
15
  const handleToggle = (nextValue, checked) => {
13
- const nextValues = checked ? [...selectedValues, nextValue] : selectedValues.filter((item) => item !== nextValue);
14
- if (!isControlled) setInternalValue(nextValues);
15
- onChange?.(nextValues);
16
+ setSelectedValues(checked ? [...selectedValues, nextValue] : selectedValues.filter((item) => item !== nextValue));
16
17
  };
17
18
  return /* @__PURE__ */ jsx("fieldset", {
18
19
  ...rest,
19
- className: cn("m-0 w-full min-w-0 border-0 p-0", "grid gap-3", disabled && "opacity-70"),
20
+ className: cn("m-0 min-w-0 border-0 p-0 inline-full", "grid gap-3", disabled && "opacity-70"),
20
21
  children: options.map((option) => {
21
22
  const checked = selectedValues.includes(option.value);
22
23
  const optionDisabled = disabled || option.disabled === true;
@@ -24,7 +25,7 @@ const CheckboxCard = ({ name, disabled = false, invalid = false, options, value,
24
25
  const hasVisual = option.visual !== void 0 && option.visual !== null;
25
26
  const optionId = `${groupId}-${option.value}`;
26
27
  return /* @__PURE__ */ jsxs("label", {
27
- className: cn("flex w-full min-w-0 rounded-xl border bg-bg-base p-4 text-left transition-colors", "has-[input:focus-visible]:outline-hidden has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-border-info", checked && "border-primary-border bg-primary-bg-subtle hover:bg-primary-bg-mute", invalid ? "border-border-error" : !checked && "border-border-mute hover:bg-bg-subtle", optionDisabled && "cursor-not-allowed border-border-mute bg-bg-subtle text-fg-mute"),
28
+ className: cn("flex min-w-0 rounded-xl border bg-bg-base p-4 text-left transition-colors inline-full", "has-[input:focus-visible]:outline-hidden has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-border-info", checked && "border-primary-border bg-primary-bg-subtle hover:bg-primary-bg-mute", invalid ? "border-border-error" : !checked && "border-border-mute hover:bg-bg-subtle", optionDisabled && "cursor-not-allowed border-border-mute bg-bg-subtle text-fg-mute"),
28
29
  id: optionId,
29
30
  children: [
30
31
  /* @__PURE__ */ jsx("input", {
@@ -12,7 +12,7 @@ type RootBaseProps = PropsWithChildren<{
12
12
  invalid?: boolean;
13
13
  required?: boolean;
14
14
  name: string;
15
- } & Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'className' | 'onChange' | 'defaultValue' | 'name'>>;
15
+ } & Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'className' | 'style' | 'onChange' | 'defaultValue' | 'name'>>;
16
16
  type RootControlledProps = {
17
17
  value: string[];
18
18
  onChange: (value: string[]) => void;
@@ -5,7 +5,7 @@ declare const CheckboxGroup: _$react.FC<{
5
5
  invalid?: boolean;
6
6
  required?: boolean;
7
7
  name: string;
8
- } & Omit<_$react.FieldsetHTMLAttributes<HTMLFieldSetElement>, "name" | "defaultValue" | "className" | "onChange"> & {
8
+ } & Omit<_$react.FieldsetHTMLAttributes<HTMLFieldSetElement>, "defaultValue" | "onChange" | "className" | "style" | "name"> & {
9
9
  children?: _$react.ReactNode | undefined;
10
10
  } & ({
11
11
  value: string[];
@@ -20,7 +20,7 @@ declare const CheckboxGroup: _$react.FC<{
20
20
  invalid?: boolean;
21
21
  required?: boolean;
22
22
  name: string;
23
- } & Omit<_$react.FieldsetHTMLAttributes<HTMLFieldSetElement>, "name" | "defaultValue" | "className" | "onChange"> & {
23
+ } & Omit<_$react.FieldsetHTMLAttributes<HTMLFieldSetElement>, "defaultValue" | "onChange" | "className" | "style" | "name"> & {
24
24
  children?: _$react.ReactNode | undefined;
25
25
  } & ({
26
26
  value: string[];
@@ -35,7 +35,7 @@ declare const CheckboxGroup: _$react.FC<{
35
35
  Item: _$react.FC<{
36
36
  itemValue?: string;
37
37
  label: string;
38
- } & Omit<_$react.InputHTMLAttributes<HTMLInputElement>, "children" | "defaultChecked" | "className" | "onChange" | "type" | "value" | "checked"> & ({
38
+ } & Omit<_$react.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "type" | "className" | "style" | "checked" | "defaultChecked" | "children"> & ({
39
39
  value: boolean;
40
40
  onChange: _$react.ChangeEventHandler<HTMLInputElement>;
41
41
  defaultChecked?: never;
@@ -8,7 +8,7 @@ type RootProps = PropsWithChildren<{
8
8
  defaultValue?: File[];
9
9
  onChange?: ChangeEventHandler<HTMLInputElement>;
10
10
  webkitDirectory?: boolean;
11
- } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className' | 'onChange' | 'defaultValue' | 'value' | 'children'>>;
11
+ } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className' | 'style' | 'onChange' | 'defaultValue' | 'value' | 'children'>>;
12
12
  declare const FileField: {
13
13
  readonly Root: ({
14
14
  children,
@@ -1,18 +1,12 @@
1
1
  "use client";
2
- import { uuidV4 } from "../../../helpers/uuid-v4.mjs";
2
+ import { createSafeContext } from "../../../helpers/create-safe-context.mjs";
3
3
  import { CloseIcon } from "../../icons/lucide.mjs";
4
4
  import { IconButton } from "../../buttons/icon-button/icon-button.mjs";
5
- import { createContext, use, useCallback, useId, useMemo, useRef, useState } from "react";
5
+ import { useCallback, useId, useMemo, useRef, useState } from "react";
6
6
  import { useFormStatus } from "react-dom";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/components/form/file-field/file-field.tsx
9
- const FileFieldContext = createContext(null);
10
- const FileFieldProvider = FileFieldContext;
11
- const useFileFieldContext = () => {
12
- const fileField = use(FileFieldContext);
13
- if (!fileField) throw new Error("useFileFieldContext must be used within a FileField.Root");
14
- return fileField;
15
- };
9
+ const [FileFieldProvider, useFileFieldContext] = createSafeContext("useFileFieldContext must be used within a FileField.Root");
16
10
  const Root = ({ children, disabled = false, invalid = false, required = false, multiple = false, maxFiles, onChange, webkitDirectory = false, ...rest }) => {
17
11
  const generatedId = useId();
18
12
  const inputRef = useRef(null);
@@ -23,7 +17,7 @@ const Root = ({ children, disabled = false, invalid = false, required = false, m
23
17
  onChange?.(event);
24
18
  const newFiles = Array.from(event.target.files ?? []).map((file) => ({
25
19
  file,
26
- id: uuidV4()
20
+ id: crypto.randomUUID()
27
21
  }));
28
22
  setAcceptedFiles(multiple || webkitDirectory ? [...acceptedFiles, ...newFiles].slice(0, maxFiles ?? Number.POSITIVE_INFINITY) : newFiles.slice(0, 1));
29
23
  }, [
@@ -4,7 +4,7 @@ import { FC, FormHTMLAttributes, ReactNode } from "react";
4
4
  type Props = {
5
5
  action?: ((formData: FormData) => void | Promise<void>) | string;
6
6
  children: ReactNode;
7
- } & Omit<FormHTMLAttributes<HTMLFormElement>, 'action' | 'children'>;
7
+ } & Omit<FormHTMLAttributes<HTMLFormElement>, 'action' | 'children' | 'className' | 'style'>;
8
8
  declare const Form: FC<Props>;
9
9
  //#endregion
10
10
  export { Form };
@@ -1,10 +1,9 @@
1
1
  "use client";
2
- import { cn } from "../../../helpers/cn.mjs";
3
2
  import { jsx } from "react/jsx-runtime";
4
3
  //#region src/components/form/form/form.tsx
5
- const Form = ({ action, className, children, ...rest }) => /* @__PURE__ */ jsx("form", {
4
+ const Form = ({ action, children, ...rest }) => /* @__PURE__ */ jsx("form", {
6
5
  action,
7
- className: cn("flex flex-col gap-6", className),
6
+ className: "flex flex-col gap-6",
8
7
  ...rest,
9
8
  children
10
9
  });
@@ -1,4 +1,4 @@
1
- //#region src/helpers/number/cast.d.ts
1
+ //#region src/components/form/number-field/cast.d.ts
2
2
  declare const cast: (value: string, step: number, precision?: number) => number;
3
3
  //#endregion
4
4
  export { cast };
@@ -1,5 +1,5 @@
1
- import { toPrecision } from "./to-precision.mjs";
2
- //#region src/helpers/number/cast.ts
1
+ import { toPrecision } from "../../../internal/to-precision.mjs";
2
+ //#region src/components/form/number-field/cast.ts
3
3
  const FLOATING_POINT_REGEX = /^[Ee0-9+\-.]$/;
4
4
  const isInvalidCharacter = (value) => FLOATING_POINT_REGEX.test(value);
5
5
  const sanitize = (value) => value.split("").filter((char) => isInvalidCharacter(char)).join("");
@@ -4,7 +4,7 @@ import { FC, InputHTMLAttributes } from "react";
4
4
  type BaseProps = {
5
5
  invalid?: boolean;
6
6
  precision?: number;
7
- } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'role' | 'className' | 'value' | 'onChange' | 'defaultValue' | 'children' | 'step' | 'min' | 'max'> & {
7
+ } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'role' | 'className' | 'style' | 'value' | 'onChange' | 'defaultValue' | 'children' | 'step' | 'min' | 'max'> & {
8
8
  step?: number;
9
9
  min?: number;
10
10
  max?: number;
@@ -1,31 +1,32 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
3
  import { ChevronIcon } from "../../icons/lucide.mjs";
4
- import { between } from "../../../helpers/number/between.mjs";
5
- import { toPrecision } from "../../../helpers/number/to-precision.mjs";
6
- import { cast } from "../../../helpers/number/cast.mjs";
4
+ import { toPrecision } from "../../../internal/to-precision.mjs";
5
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
6
+ import { clamp } from "../../../internal/clamp.mjs";
7
+ import { cast } from "./cast.mjs";
7
8
  import { useState } from "react";
8
9
  import { useFormStatus } from "react-dom";
9
10
  import { jsx, jsxs } from "react/jsx-runtime";
10
11
  //#region src/components/form/number-field/number-field.tsx
11
12
  const NumberField = ({ invalid = false, disabled = false, required = false, value, defaultValue, onChange, step = 1, precision = 0, max = 9007199254740991, min = -9007199254740991, ...rest }) => {
12
- const isControlled = value !== void 0;
13
- const initialValue = defaultValue ?? value ?? 0;
14
- const [internalValue, setInternalValue] = useState(initialValue);
15
- const [displayValue, setDisplayValue] = useState(initialValue.toFixed(precision));
16
- const [prevValue, setPrevValue] = useState(initialValue);
13
+ const [currentValue, setCurrentValue] = useControllableState({
14
+ value,
15
+ defaultValue: defaultValue ?? 0,
16
+ onChange
17
+ });
18
+ const [displayValue, setDisplayValue] = useState(() => currentValue.toFixed(precision));
19
+ const [prevValue, setPrevValue] = useState(currentValue);
17
20
  const { pending } = useFormStatus();
18
- const currentValue = isControlled ? value : internalValue;
19
- if (isControlled && value !== prevValue) {
20
- setDisplayValue(value.toFixed(precision));
21
- setPrevValue(value);
21
+ if (currentValue !== prevValue) {
22
+ setDisplayValue(currentValue.toFixed(precision));
23
+ setPrevValue(currentValue);
22
24
  }
23
25
  const handleChange = (newValue) => {
24
- if (!isControlled) setInternalValue(newValue);
25
- onChange?.(newValue);
26
+ setCurrentValue(newValue);
26
27
  };
27
28
  return /* @__PURE__ */ jsxs("div", {
28
- className: cn("relative flex h-12 w-full items-center justify-between gap-2 rounded-xl border border-border-base bg-bg-base", "focus-within:border-transparent focus-within:outline-hidden focus-within:ring-2 focus-within:ring-border-info", "has-aria-invalid:border-border-error", "has-disabled:cursor-not-allowed has-disabled:border-border-mute has-disabled:bg-bg-mute hover:has-disabled:has-hover:bg-bg-mute"),
29
+ className: cn("relative flex h-12 w-full items-center justify-between gap-2 rounded-xl border border-border-base bg-bg-base vertical:h-auto vertical:w-12", "focus-within:border-transparent focus-within:outline-hidden focus-within:ring-2 focus-within:ring-border-info", "has-aria-invalid:border-border-error", "has-disabled:cursor-not-allowed has-disabled:border-border-mute has-disabled:bg-bg-mute hover:has-disabled:has-hover:bg-bg-mute"),
29
30
  children: [/* @__PURE__ */ jsx("input", {
30
31
  autoComplete: "off",
31
32
  autoCorrect: "off",
@@ -36,11 +37,11 @@ const NumberField = ({ invalid = false, disabled = false, required = false, valu
36
37
  "aria-valuemax": max,
37
38
  "aria-valuemin": min,
38
39
  "aria-valuenow": currentValue,
39
- className: cn("grow bg-transparent pr-8 pl-3 focus-visible:outline-hidden size-full", "disabled:cursor-not-allowed", "read-only:cursor-not-allowed"),
40
+ className: cn("grow bg-transparent pe-8 ps-3 focus-visible:outline-hidden size-full", "disabled:cursor-not-allowed", "read-only:cursor-not-allowed"),
40
41
  disabled,
41
42
  readOnly: pending || void 0,
42
43
  onBlur: () => {
43
- const newValue = between(cast(displayValue, precision), min, max);
44
+ const newValue = clamp(cast(displayValue, precision), min, max);
44
45
  handleChange(newValue);
45
46
  setDisplayValue(newValue.toFixed(precision));
46
47
  },
@@ -50,12 +51,12 @@ const NumberField = ({ invalid = false, disabled = false, required = false, valu
50
51
  },
51
52
  onKeyDown: (e) => {
52
53
  if (e.key === "ArrowUp") {
53
- const newValue = between(toPrecision(cast(displayValue, precision) + step, precision), min, max);
54
+ const newValue = clamp(toPrecision(cast(displayValue, precision) + step, precision), min, max);
54
55
  handleChange(newValue);
55
56
  setDisplayValue(newValue.toFixed(precision));
56
57
  }
57
58
  if (e.key === "ArrowDown") {
58
- const newValue = between(toPrecision(cast(displayValue, precision) - step, precision), min, max);
59
+ const newValue = clamp(toPrecision(cast(displayValue, precision) - step, precision), min, max);
59
60
  handleChange(newValue);
60
61
  setDisplayValue(newValue.toFixed(precision));
61
62
  }
@@ -66,12 +67,12 @@ const NumberField = ({ invalid = false, disabled = false, required = false, valu
66
67
  value: displayValue
67
68
  }), /* @__PURE__ */ jsxs("div", {
68
69
  "aria-hidden": "true",
69
- className: "absolute right-1 flex h-full flex-col py-1",
70
+ className: "vertical:inset-e-auto vertical:bottom-1 vertical:h-auto vertical:w-full vertical:flex-row vertical:px-1 vertical:py-0 absolute end-1 flex h-full flex-col py-1",
70
71
  children: [/* @__PURE__ */ jsxs("button", {
71
- className: cn("flex w-6 grow items-center justify-center rounded-md text-fg-mute transition-colors", "hover:bg-bg-mute hover:text-fg-base", "disabled:cursor-not-allowed disabled:text-fg-mute hover:disabled:bg-transparent"),
72
+ className: cn("flex w-6 grow items-center justify-center rounded-md text-fg-mute transition-colors vertical:h-6 vertical:w-auto", "hover:bg-bg-mute hover:text-fg-base", "disabled:cursor-not-allowed disabled:text-fg-mute hover:disabled:bg-transparent"),
72
73
  disabled: disabled || pending,
73
74
  onClick: () => {
74
- const newValue = between(toPrecision(cast(displayValue, precision) + step, precision), min, max);
75
+ const newValue = clamp(toPrecision(cast(displayValue, precision) + step, precision), min, max);
75
76
  handleChange(newValue);
76
77
  setDisplayValue(newValue.toFixed(precision));
77
78
  },
@@ -85,10 +86,10 @@ const NumberField = ({ invalid = false, disabled = false, required = false, valu
85
86
  size: "sm"
86
87
  })]
87
88
  }), /* @__PURE__ */ jsxs("button", {
88
- className: cn("flex w-6 grow items-center justify-center rounded-md text-fg-mute transition-colors", "hover:bg-bg-mute hover:text-fg-base", "disabled:cursor-not-allowed disabled:text-fg-mute hover:disabled:bg-transparent"),
89
+ className: cn("flex w-6 grow items-center justify-center rounded-md text-fg-mute transition-colors vertical:h-6 vertical:w-auto", "hover:bg-bg-mute hover:text-fg-base", "disabled:cursor-not-allowed disabled:text-fg-mute hover:disabled:bg-transparent"),
89
90
  disabled: disabled || pending,
90
91
  onClick: () => {
91
- const newValue = between(toPrecision(cast(displayValue, precision) - step, precision), min, max);
92
+ const newValue = clamp(toPrecision(cast(displayValue, precision) - step, precision), min, max);
92
93
  handleChange(newValue);
93
94
  setDisplayValue(newValue.toFixed(precision));
94
95
  },
@@ -5,7 +5,7 @@ type Props = {
5
5
  invalid?: boolean;
6
6
  showLabel?: string;
7
7
  hideLabel?: string;
8
- } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className'>;
8
+ } & Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className' | 'style'>;
9
9
  declare const PasswordInput: FC<Props>;
10
10
  //#endregion
11
11
  export { PasswordInput };
@@ -9,18 +9,18 @@ const PasswordInput = ({ invalid = false, autoComplete = "current-password", sho
9
9
  const { isOpen: isVisible, toggle: toggleVisible } = useDisclosure();
10
10
  const { pending } = useFormStatus();
11
11
  return /* @__PURE__ */ jsxs("div", {
12
- className: "relative w-full",
12
+ className: cn("relative flex w-full items-center rounded-xl border border-border-base bg-bg-base vertical:h-full vertical:w-auto", invalid && "border-border-error", (disabled || pending) && "cursor-not-allowed border-border-mute bg-bg-mute", "focus-within:border-transparent focus-within:outline-hidden focus-within:ring-2 focus-within:ring-border-info"),
13
13
  children: [/* @__PURE__ */ jsx("input", {
14
14
  "aria-invalid": invalid,
15
15
  autoComplete,
16
- className: cn("w-full rounded-xl border border-border-base bg-bg-base px-3 py-2 pr-12", "aria-invalid:border-border-error", "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute hover:disabled:bg-bg-mute", "read-only:cursor-not-allowed read-only:bg-bg-subtle", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
16
+ className: cn("w-full grow bg-transparent px-3 py-2 focus-visible:outline-hidden vertical:h-full vertical:w-auto", "disabled:cursor-not-allowed", "read-only:cursor-not-allowed read-only:bg-bg-subtle"),
17
17
  disabled,
18
18
  readOnly: pending || readOnly,
19
19
  type: isVisible ? "text" : "password",
20
20
  ...rest
21
21
  }), /* @__PURE__ */ jsx("button", {
22
22
  "aria-label": isVisible ? hideLabel : showLabel,
23
- className: cn("absolute top-1/2 right-2 inline-flex -translate-y-1/2 items-center justify-center rounded-md p-1 text-fg-mute transition-colors", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", !disabled && !pending && "hover:bg-bg-mute hover:text-fg-base", (disabled || pending) && "cursor-not-allowed text-fg-mute/70"),
23
+ className: cn("me-2 inline-flex shrink-0 items-center justify-center rounded-md p-1 text-fg-mute transition-colors", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", !disabled && !pending && "hover:bg-bg-mute hover:text-fg-base", (disabled || pending) && "cursor-not-allowed text-fg-mute/70"),
24
24
  disabled: disabled || pending,
25
25
  onClick: toggleVisible,
26
26
  type: "button",