@k8o/arte-odyssey 8.0.2 → 9.0.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 (67) hide show
  1. package/dist/components/buttons/icon-button/icon-button.mjs +6 -17
  2. package/dist/components/data-display/accordion/context.d.mts +1 -1
  3. package/dist/components/data-display/accordion/context.mjs +3 -12
  4. package/dist/components/data-display/code/code.mjs +1 -1
  5. package/dist/{helpers/color → components/data-display/code}/find-all-colors.d.mts +1 -1
  6. package/dist/{helpers/color → components/data-display/code}/find-all-colors.mjs +1 -1
  7. package/dist/components/feedback/progress/progress.mjs +1 -1
  8. package/dist/components/feedback/toast/context.d.mts +2 -2
  9. package/dist/components/feedback/toast/context.mjs +6 -7
  10. package/dist/components/form/autocomplete/autocomplete.mjs +14 -13
  11. package/dist/components/form/checkbox/checkbox.mjs +7 -4
  12. package/dist/components/form/checkbox-card/checkbox-card.mjs +8 -7
  13. package/dist/components/form/checkbox-group/index.d.mts +3 -3
  14. package/dist/components/form/file-field/file-field.mjs +4 -10
  15. package/dist/{helpers/number → components/form/number-field}/cast.d.mts +1 -1
  16. package/dist/{helpers/number → components/form/number-field}/cast.mjs +2 -2
  17. package/dist/components/form/number-field/number-field.mjs +20 -19
  18. package/dist/components/form/radio/radio.mjs +6 -4
  19. package/dist/components/form/radio-card/radio-card.mjs +7 -5
  20. package/dist/components/form/switch/switch.mjs +7 -4
  21. package/dist/components/icons/index.d.mts +3 -2
  22. package/dist/components/icons/index.mjs +3 -2
  23. package/dist/components/icons/logo.mjs +3 -3
  24. package/dist/components/icons/lucide.d.mts +2 -1
  25. package/dist/components/icons/lucide.mjs +6 -2
  26. package/dist/components/icons/qiita.mjs +4 -4
  27. package/dist/components/icons/twitter.mjs +1 -1
  28. package/dist/components/icons/vertical-writing.d.mts +7 -0
  29. package/dist/components/icons/vertical-writing.mjs +25 -0
  30. package/dist/components/index.d.mts +3 -2
  31. package/dist/components/index.mjs +3 -2
  32. package/dist/components/navigation/anchor/anchor.mjs +1 -2
  33. package/dist/components/navigation/tabs/tabs.mjs +4 -13
  34. package/dist/components/overlays/dialog/dialog.mjs +3 -7
  35. package/dist/components/overlays/dropdown-menu/hooks.d.mts +2 -3
  36. package/dist/components/overlays/dropdown-menu/hooks.mjs +4 -9
  37. package/dist/components/overlays/list-box/hooks.d.mts +0 -1
  38. package/dist/components/overlays/list-box/hooks.mjs +3 -8
  39. package/dist/components/overlays/popover/hooks.d.mts +1 -3
  40. package/dist/components/overlays/popover/hooks.mjs +3 -8
  41. package/dist/components/overlays/tooltip/tooltip.mjs +19 -6
  42. package/dist/helpers/chain.d.mts +5 -0
  43. package/dist/helpers/chain.mjs +29 -0
  44. package/dist/helpers/create-safe-context.d.mts +7 -0
  45. package/dist/helpers/create-safe-context.mjs +13 -0
  46. package/dist/helpers/index.d.mts +5 -8
  47. package/dist/helpers/index.mjs +5 -8
  48. package/dist/helpers/merge-props.d.mts +6 -0
  49. package/dist/helpers/merge-props.mjs +56 -0
  50. package/dist/hooks/click-away/index.mjs +1 -1
  51. package/dist/index.d.mts +7 -9
  52. package/dist/index.mjs +7 -9
  53. package/dist/internal/clamp.d.mts +4 -0
  54. package/dist/internal/clamp.mjs +15 -0
  55. package/dist/{helpers/number → internal}/to-precision.d.mts +1 -1
  56. package/dist/{helpers/number → internal}/to-precision.mjs +1 -1
  57. package/package.json +5 -5
  58. package/dist/helpers/is-internal-route.d.mts +0 -4
  59. package/dist/helpers/is-internal-route.mjs +0 -12
  60. package/dist/helpers/number/between.d.mts +0 -4
  61. package/dist/helpers/number/between.mjs +0 -15
  62. package/dist/helpers/number/commalize.d.mts +0 -4
  63. package/dist/helpers/number/commalize.mjs +0 -20
  64. package/dist/helpers/number/index.d.mts +0 -5
  65. package/dist/helpers/number/index.mjs +0 -5
  66. package/dist/helpers/uuid-v4.d.mts +0 -4
  67. package/dist/helpers/uuid-v4.mjs +0 -28
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
3
  import { Tooltip } from "../../overlays/tooltip/tooltip.mjs";
4
+ import { chain } from "../../../helpers/chain.mjs";
4
5
  import { mergeRefs } from "../../../helpers/merge-refs.mjs";
5
6
  import { useTransition } from "react";
6
7
  import { useFormStatus } from "react-dom";
@@ -10,7 +11,7 @@ const joinIds = (...ids) => {
10
11
  const filtered = ids.filter(Boolean);
11
12
  return filtered.length === 0 ? void 0 : filtered.join(" ");
12
13
  };
13
- const IconButton = ({ ref, size = "md", bg = "transparent", label, tooltipPlacement = "bottom", tooltipDisabled = false, children, onAction, onClick, onMouseEnter, onMouseLeave, onFocus, onBlur, disabled, renderItem, "aria-describedby": describedBy, ...props }) => {
14
+ const IconButton = ({ ref, size = "md", bg = "transparent", label, tooltipPlacement = "top", tooltipDisabled = false, children, onAction, onClick, onMouseEnter, onMouseLeave, onFocus, onBlur, disabled, renderItem, "aria-describedby": describedBy, ...props }) => {
14
15
  const [transitionPending, startTransition] = useTransition();
15
16
  const { pending: formPending } = useFormStatus();
16
17
  const isPending = transitionPending || formPending;
@@ -66,23 +67,11 @@ const IconButton = ({ ref, size = "md", bg = "transparent", label, tooltipPlacem
66
67
  "aria-label": label,
67
68
  className,
68
69
  disabled: isDisabled,
69
- onBlur: (e) => {
70
- triggerProps.onBlur(e);
71
- onBlur?.(e);
72
- },
70
+ onBlur: chain(triggerProps.onBlur, onBlur),
73
71
  onClick: handleClick,
74
- onFocus: (e) => {
75
- triggerProps.onFocus(e);
76
- onFocus?.(e);
77
- },
78
- onMouseEnter: (e) => {
79
- triggerProps.onMouseEnter(e);
80
- onMouseEnter?.(e);
81
- },
82
- onMouseLeave: (e) => {
83
- triggerProps.onMouseLeave(e);
84
- onMouseLeave?.(e);
85
- },
72
+ onFocus: chain(triggerProps.onFocus, onFocus),
73
+ onMouseEnter: chain(triggerProps.onMouseEnter, onMouseEnter),
74
+ onMouseLeave: chain(triggerProps.onMouseLeave, onMouseLeave),
86
75
  ref: mergeRefs(ref, triggerProps.ref),
87
76
  type: "button",
88
77
  children
@@ -2,9 +2,9 @@ import { FC, PropsWithChildren } from "react";
2
2
 
3
3
  //#region src/components/data-display/accordion/context.d.ts
4
4
  type ToggleOpen = () => void;
5
- declare const useOpen: () => boolean;
6
5
  declare const useToggleOpen: () => ToggleOpen;
7
6
  declare const useItemId: () => string;
7
+ declare const useOpen: () => boolean;
8
8
  declare const AccordionItemProvider: FC<PropsWithChildren<{
9
9
  defaultOpen?: boolean;
10
10
  id: string;
@@ -1,22 +1,13 @@
1
1
  "use client";
2
2
  import { useDisclosure } from "../../../hooks/disclosure/index.mjs";
3
+ import { createSafeContext } from "../../../helpers/create-safe-context.mjs";
3
4
  import { createContext, use } from "react";
4
5
  import { jsx } from "react/jsx-runtime";
5
6
  //#region src/components/data-display/accordion/context.tsx
6
7
  const OpenContext = createContext(false);
7
- const ToggleOpenContext = createContext(void 0);
8
- const ItemIdContext = createContext(void 0);
8
+ const [ToggleOpenContext, useToggleOpen] = createSafeContext("useToggleOpen must be used within AccordionProvider");
9
+ const [ItemIdContext, useItemId] = createSafeContext("useItemId must be used within AccordionProvider");
9
10
  const useOpen = () => use(OpenContext);
10
- const useToggleOpen = () => {
11
- const toggleOpen = use(ToggleOpenContext);
12
- if (!toggleOpen) throw new Error("useToggleOpen must be used within AccordionProvider");
13
- return toggleOpen;
14
- };
15
- const useItemId = () => {
16
- const id = use(ItemIdContext);
17
- if (id === void 0 || id === "") throw new Error("useItemId must be used within AccordionProvider");
18
- return id;
19
- };
20
11
  const AccordionItemProvider = ({ defaultOpen = false, id, children }) => {
21
12
  const { isOpen, toggle } = useDisclosure(defaultOpen);
22
13
  return /* @__PURE__ */ jsx(OpenContext, {
@@ -1,5 +1,5 @@
1
1
  import { cn } from "../../../helpers/cn.mjs";
2
- import { findAllColors } from "../../../helpers/color/find-all-colors.mjs";
2
+ import { findAllColors } from "./find-all-colors.mjs";
3
3
  import { Fragment } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region src/components/data-display/code/code.tsx
@@ -1,4 +1,4 @@
1
- //#region src/helpers/color/find-all-colors.d.ts
1
+ //#region src/components/data-display/code/find-all-colors.d.ts
2
2
  type ColorMatch = {
3
3
  color: string;
4
4
  start: number;
@@ -1,4 +1,4 @@
1
- //#region src/helpers/color/find-all-colors.ts
1
+ //#region src/components/data-display/code/find-all-colors.ts
2
2
  const extractFunctionContent = (source, funcName) => {
3
3
  const funcPattern = new RegExp(`${funcName}\\s*\\(`, "gi");
4
4
  const matches = [];
@@ -1,5 +1,5 @@
1
1
  import { cn } from "../../../helpers/cn.mjs";
2
- import { toPrecision } from "../../../helpers/number/to-precision.mjs";
2
+ import { toPrecision } from "../../../internal/to-precision.mjs";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  //#region src/components/feedback/progress/progress.tsx
5
5
  const Progress = ({ progress, maxProgress, minProgress = 0, label, className, ...rest }) => /* @__PURE__ */ jsx("div", {
@@ -8,11 +8,11 @@ type ToastType = {
8
8
  status: Status;
9
9
  message: string;
10
10
  };
11
- declare const SetToastContext: _$react.Context<Dispatch<SetStateAction<ToastType[]>> | undefined>;
11
+ declare const SetToastContext: _$react.Context<Dispatch<SetStateAction<ToastType[]>> | null>, useSetToast: () => Dispatch<SetStateAction<ToastType[]>>;
12
12
  declare const useToast: () => {
13
13
  onOpen: (status: Status, message: string) => void;
14
14
  onClose: (id: string) => void;
15
15
  onCloseAll: () => void;
16
16
  };
17
17
  //#endregion
18
- export { SetToastContext, ToastType, useToast };
18
+ export { SetToastContext, ToastType, useSetToast, useToast };
@@ -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 };
@@ -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";
@@ -15,7 +16,7 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
15
16
  onChange
16
17
  });
17
18
  const ref = useRef(null);
18
- const [open, setOpen] = useState(false);
19
+ const { isOpen, open, close } = useDisclosure();
19
20
  const [text, setText] = useState("");
20
21
  const [selectIndex, setSelectIndex] = useState();
21
22
  const [deferredText, isPending] = useDeferredDebounce(text);
@@ -24,9 +25,9 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
24
25
  const disabledResolved = disabled || formPending;
25
26
  const reset = useCallback(() => {
26
27
  setText("");
27
- setOpen(false);
28
+ close();
28
29
  setSelectIndex(void 0);
29
- }, []);
30
+ }, [close]);
30
31
  useEffect(() => {
31
32
  const handleClick = (e) => {
32
33
  if (e.target instanceof Node && ref.current?.contains(e.target) === true) return;
@@ -69,8 +70,8 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
69
70
  }), /* @__PURE__ */ jsx("input", {
70
71
  ...rest,
71
72
  "aria-autocomplete": "list",
72
- "aria-controls": open ? `${id}_listbox` : void 0,
73
- "aria-expanded": open,
73
+ "aria-controls": isOpen ? `${id}_listbox` : void 0,
74
+ "aria-expanded": isOpen,
74
75
  "aria-invalid": invalid,
75
76
  "aria-required": required,
76
77
  autoComplete: "off",
@@ -79,19 +80,19 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
79
80
  id,
80
81
  onBlur: (e) => {
81
82
  if (e.relatedTarget?.id.startsWith(`${id}_option_`) === true) return;
82
- setOpen(false);
83
+ close();
83
84
  },
84
85
  onChange: (e) => {
85
- setOpen(true);
86
+ open();
86
87
  setText(e.target.value);
87
88
  setSelectIndex(void 0);
88
89
  },
89
90
  onClick: () => {
90
- if (open && text.length === 0) {
91
- setOpen(false);
91
+ if (isOpen && text.length === 0) {
92
+ close();
92
93
  return;
93
94
  }
94
- setOpen(true);
95
+ open();
95
96
  setSelectIndex(void 0);
96
97
  },
97
98
  onKeyDown: (e) => {
@@ -101,7 +102,7 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
101
102
  return;
102
103
  }
103
104
  if (e.key === "ArrowDown") {
104
- setOpen(true);
105
+ open();
105
106
  setSelectIndex((prev) => {
106
107
  if (prev === void 0) return 0;
107
108
  return Math.min(prev + 1, options.length - 1);
@@ -109,7 +110,7 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
109
110
  return;
110
111
  }
111
112
  if (e.key === "ArrowUp") {
112
- setOpen(true);
113
+ open();
113
114
  setSelectIndex((prev) => {
114
115
  if (prev === void 0) return 0;
115
116
  return Math.max(prev - 1, 0);
@@ -149,7 +150,7 @@ const Autocomplete = ({ id, name, invalid = false, disabled = false, required =
149
150
  }),
150
151
  /* @__PURE__ */ jsx("div", {
151
152
  className: "relative w-full",
152
- children: open && /* @__PURE__ */ jsx("div", {
153
+ children: isOpen && /* @__PURE__ */ jsx("div", {
153
154
  className: "bg-bg-raised absolute top-1 z-10 w-full rounded-xl shadow-md",
154
155
  role: "presentation",
155
156
  children: /* @__PURE__ */ jsxs("ul", {
@@ -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", {
@@ -1,18 +1,19 @@
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,
@@ -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>, "defaultValue" | "onChange" | "name" | "className"> & {
8
+ } & Omit<_$react.FieldsetHTMLAttributes<HTMLFieldSetElement>, "className" | "onChange" | "defaultValue" | "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>, "defaultValue" | "onChange" | "name" | "className"> & {
23
+ } & Omit<_$react.FieldsetHTMLAttributes<HTMLFieldSetElement>, "className" | "onChange" | "defaultValue" | "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>, "value" | "onChange" | "type" | "checked" | "defaultChecked" | "className" | "children"> & ({
38
+ } & Omit<_$react.InputHTMLAttributes<HTMLInputElement>, "type" | "className" | "value" | "onChange" | "children" | "checked" | "defaultChecked"> & ({
39
39
  value: boolean;
40
40
  onChange: _$react.ChangeEventHandler<HTMLInputElement>;
41
41
  defaultChecked?: never;
@@ -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
  }, [
@@ -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("");
@@ -1,28 +1,29 @@
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
29
  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"),
@@ -40,7 +41,7 @@ const NumberField = ({ invalid = false, disabled = false, required = false, valu
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
  }
@@ -71,7 +72,7 @@ const NumberField = ({ invalid = false, disabled = false, required = false, valu
71
72
  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
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
  },
@@ -88,7 +89,7 @@ const NumberField = ({ invalid = false, disabled = false, required = false, valu
88
89
  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
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
  },
@@ -1,17 +1,19 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
- import { useState } from "react";
3
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
4
4
  import { useFormStatus } from "react-dom";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/components/form/radio/radio.tsx
7
7
  const Radio = ({ "aria-labelledby": labelledbyId, name, disabled = false, value, defaultValue, onChange, options, ...rest }) => {
8
- const [internalValue, setInternalValue] = useState(defaultValue);
8
+ const [selectedValue, setSelectedValue] = useControllableState({
9
+ value,
10
+ defaultValue
11
+ });
9
12
  const { pending } = useFormStatus();
10
13
  const isControlled = value !== void 0;
11
- const selectedValue = isControlled ? value : internalValue;
12
14
  const disabledResolved = disabled || pending;
13
15
  const selectValue = (nextValue) => {
14
- if (!isControlled) setInternalValue(nextValue);
16
+ setSelectedValue(nextValue);
15
17
  onChange?.({ target: { value: nextValue } });
16
18
  };
17
19
  return /* @__PURE__ */ jsx("div", {
@@ -1,16 +1,18 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
- import { useId, useRef, useState } from "react";
3
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
4
+ import { useId, useRef } from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
6
  //#region src/components/form/radio-card/radio-card.tsx
6
7
  const RadioCard = ({ "aria-labelledby": labelledbyId, name, disabled = false, invalid = false, options, value, defaultValue, onChange, ...rest }) => {
7
8
  const groupId = useId();
8
9
  const buttonRefs = useRef([]);
9
- const [internalValue, setInternalValue] = useState(defaultValue ?? options[0]?.value);
10
- const isControlled = value !== void 0;
11
- const currentValue = isControlled ? value : internalValue;
10
+ const [currentValue, setCurrentValue] = useControllableState({
11
+ value,
12
+ defaultValue: defaultValue ?? options[0]?.value
13
+ });
12
14
  const selectValue = (nextValue) => {
13
- if (!isControlled) setInternalValue(nextValue);
15
+ setCurrentValue(nextValue);
14
16
  onChange?.({ target: { value: nextValue } });
15
17
  };
16
18
  const focusIndex = (index) => {
@@ -1,16 +1,19 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
- import { useId, useState } from "react";
3
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
4
+ import { useId } from "react";
4
5
  import { useFormStatus } from "react-dom";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region src/components/form/switch/switch.tsx
7
8
  const Switch = ({ value, defaultChecked, id, disabled = false, invalid = false, required = false, label, onChange, ...rest }) => {
8
9
  const generatedId = useId();
9
10
  const inputId = id ?? generatedId;
10
- const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);
11
+ const [isSelected, setSelected] = useControllableState({
12
+ value,
13
+ defaultValue: defaultChecked ?? false
14
+ });
11
15
  const { pending } = useFormStatus();
12
16
  const isControlled = value !== void 0;
13
- const isSelected = isControlled ? value : internalChecked;
14
17
  const disabledResolved = disabled || pending;
15
18
  return /* @__PURE__ */ jsxs("label", {
16
19
  className: cn("inline-flex w-fit items-center gap-3", disabledResolved ? "cursor-not-allowed text-fg-mute" : "cursor-pointer"),
@@ -27,7 +30,7 @@ const Switch = ({ value, defaultChecked, id, disabled = false, invalid = false,
27
30
  disabled: disabledResolved,
28
31
  id: inputId,
29
32
  onChange: (event) => {
30
- if (!isControlled) setInternalChecked(event.target.checked);
33
+ setSelected(event.target.checked);
31
34
  onChange?.(event);
32
35
  },
33
36
  required,
@@ -1,7 +1,8 @@
1
1
  import { ArteOdyssey } from "./arte-odyssey.mjs";
2
2
  import { GitHubIcon } from "./github-mark.mjs";
3
3
  import { Logo, LogoIcon } from "./logo.mjs";
4
- import { AIIcon, AccessibilityIcon, AlertIcon, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GoodIcon, HistoryIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, UpdateDateIcon, ViewIcon, ViewOffIcon } from "./lucide.mjs";
4
+ import { AIIcon, AccessibilityIcon, AlertIcon, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GoodIcon, HistoryIcon, HorizontalWritingIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, UpdateDateIcon, ViewIcon, ViewOffIcon } from "./lucide.mjs";
5
5
  import { QiitaIcon } from "./qiita.mjs";
6
6
  import { TwitterIcon } from "./twitter.mjs";
7
- export { AIIcon, AccessibilityIcon, AlertIcon, ArteOdyssey, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GitHubIcon, GoodIcon, HistoryIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, QiitaIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon };
7
+ import { VerticalWritingIcon } from "./vertical-writing.mjs";
8
+ export { AIIcon, AccessibilityIcon, AlertIcon, ArteOdyssey, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GitHubIcon, GoodIcon, HistoryIcon, HorizontalWritingIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, QiitaIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, TwitterIcon, UpdateDateIcon, VerticalWritingIcon, ViewIcon, ViewOffIcon };
@@ -1,7 +1,8 @@
1
1
  import { ArteOdyssey } from "./arte-odyssey.mjs";
2
2
  import { GitHubIcon } from "./github-mark.mjs";
3
3
  import { Logo, LogoIcon } from "./logo.mjs";
4
- import { AIIcon, AccessibilityIcon, AlertIcon, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GoodIcon, HistoryIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, UpdateDateIcon, ViewIcon, ViewOffIcon } from "./lucide.mjs";
4
+ import { AIIcon, AccessibilityIcon, AlertIcon, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GoodIcon, HistoryIcon, HorizontalWritingIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, UpdateDateIcon, ViewIcon, ViewOffIcon } from "./lucide.mjs";
5
5
  import { QiitaIcon } from "./qiita.mjs";
6
6
  import { TwitterIcon } from "./twitter.mjs";
7
- export { AIIcon, AccessibilityIcon, AlertIcon, ArteOdyssey, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GitHubIcon, GoodIcon, HistoryIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, QiitaIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon };
7
+ import { VerticalWritingIcon } from "./vertical-writing.mjs";
8
+ export { AIIcon, AccessibilityIcon, AlertIcon, ArteOdyssey, AtomIcon, BadIcon, BlogIcon, BoringIcon, CheckIcon, ChevronIcon, CloseIcon, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, DifficultIcon, EasyIcon, ExternalLinkIcon, FormIcon, GitHubIcon, GoodIcon, HistoryIcon, HorizontalWritingIcon, InformativeIcon, InterestingIcon, LightModeIcon, LinkIcon, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, NavigationMenuIcon, NewsIcon, PaletteIcon, PlusIcon, PrepareIcon, PublishDateIcon, QiitaIcon, RSSIcon, SendIcon, ShallowIcon, ShieldCheckIcon, SlideIcon, SparklesIcon, SubscribeIcon, TableIcon, TagIcon, TwitterIcon, UpdateDateIcon, VerticalWritingIcon, ViewIcon, ViewOffIcon };