@k8o/arte-odyssey 4.2.1 → 5.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 (63) hide show
  1. package/dist/components/buttons/button/button.mjs +1 -1
  2. package/dist/components/buttons/link-button/link-button.mjs +1 -1
  3. package/dist/components/data-display/accordion/context.mjs +5 -6
  4. package/dist/components/data-display/card/card.mjs +1 -1
  5. package/dist/components/data-display/card/interactive-card.mjs +1 -1
  6. package/dist/components/form/autocomplete/autocomplete.mjs +8 -9
  7. package/dist/components/form/checkbox-card/checkbox-card.mjs +1 -1
  8. package/dist/components/form/checkbox-group/checkbox-group.mjs +8 -7
  9. package/dist/components/form/file-field/file-field.mjs +1 -1
  10. package/dist/components/form/number-field/number-field.mjs +3 -3
  11. package/dist/components/form/password-input/password-input.mjs +4 -6
  12. package/dist/components/form/radio-card/radio-card.mjs +1 -1
  13. package/dist/components/form/select/select.mjs +1 -1
  14. package/dist/components/form/slider/slider.mjs +7 -10
  15. package/dist/components/form/text-field/text-field.mjs +1 -1
  16. package/dist/components/form/textarea/textarea.mjs +1 -1
  17. package/dist/components/navigation/tabs/tabs.mjs +1 -1
  18. package/dist/components/overlays/modal/modal.mjs +1 -1
  19. package/dist/components/overlays/popover/hooks.mjs +3 -2
  20. package/dist/components/overlays/popover/popover.mjs +8 -16
  21. package/dist/hooks/breakpoint/index.d.mts +5 -0
  22. package/dist/hooks/breakpoint/index.mjs +24 -0
  23. package/dist/hooks/click-away/index.d.mts +1 -1
  24. package/dist/hooks/click-away/index.mjs +7 -5
  25. package/dist/hooks/controllable-state/index.d.mts +13 -0
  26. package/dist/hooks/controllable-state/index.mjs +21 -0
  27. package/dist/hooks/debounce/index.d.mts +6 -0
  28. package/dist/hooks/debounce/index.mjs +35 -0
  29. package/dist/hooks/disclosure/index.d.mts +10 -0
  30. package/dist/hooks/disclosure/index.mjs +14 -0
  31. package/dist/hooks/hover/index.d.mts +12 -0
  32. package/dist/hooks/hover/index.mjs +15 -0
  33. package/dist/hooks/index.d.mts +11 -1
  34. package/dist/hooks/index.mjs +11 -1
  35. package/dist/hooks/intersection-observer/index.d.mts +3 -0
  36. package/dist/hooks/intersection-observer/index.mjs +3 -0
  37. package/dist/hooks/intersection-observer/use-in-view.d.mts +12 -0
  38. package/dist/hooks/intersection-observer/use-in-view.mjs +17 -0
  39. package/dist/hooks/intersection-observer/use-intersection-observer.d.mts +12 -0
  40. package/dist/hooks/intersection-observer/use-intersection-observer.mjs +33 -0
  41. package/dist/hooks/local-storage/index.mjs +5 -4
  42. package/dist/hooks/resize/index.d.mts +1 -1
  43. package/dist/hooks/resize/index.mjs +2 -3
  44. package/dist/hooks/scroll-direction/index.mjs +47 -22
  45. package/dist/hooks/scroll-lock/index.d.mts +8 -0
  46. package/dist/hooks/scroll-lock/index.mjs +44 -0
  47. package/dist/hooks/session-storage/index.d.mts +4 -0
  48. package/dist/hooks/session-storage/index.mjs +46 -0
  49. package/dist/hooks/throttle/index.d.mts +6 -0
  50. package/dist/hooks/throttle/index.mjs +53 -0
  51. package/dist/hooks/window-size/index.mjs +26 -19
  52. package/dist/index.d.mts +11 -1
  53. package/dist/index.mjs +11 -1
  54. package/dist/styles/index.css +186 -137
  55. package/docs/GUIDE.md +52 -33
  56. package/docs/references/color.md +71 -36
  57. package/docs/references/components.md +555 -181
  58. package/docs/references/helpers.md +120 -0
  59. package/docs/references/hooks.md +295 -0
  60. package/docs/references/interaction-design.md +24 -6
  61. package/docs/references/spatial-design.md +69 -33
  62. package/docs/references/typography.md +31 -17
  63. package/package.json +18 -18
@@ -3,7 +3,7 @@ import { jsxs } from "react/jsx-runtime";
3
3
  //#region src/components/buttons/button/button.tsx
4
4
  const Button = ({ ref, children, type = "button", size = "md", color = "primary", variant = "contained", disabled = false, fullWidth = false, onClick, startIcon, endIcon, ...rest }) => {
5
5
  return /* @__PURE__ */ jsxs("button", {
6
- className: cn("cursor-pointer rounded-lg border-2 text-center font-bold transition-colors", {
6
+ className: cn("cursor-pointer rounded-full border-2 text-center font-bold transition-colors", {
7
7
  "border-transparent bg-primary-bg text-fg hover:bg-primary-bg/90 active:bg-primary-bg/80": variant === "contained" && color === "primary",
8
8
  "border-transparent bg-bg-subtle text-fg-base hover:bg-bg-mute active:bg-bg-emphasize": variant === "contained" && color === "gray",
9
9
  "cursor-not-allowed opacity-35 hover:bg-primary-bg active:bg-primary-bg": disabled && variant === "contained",
@@ -12,7 +12,7 @@ const LinkButton = ({ children, size = "md", color = "primary", variant = "conta
12
12
  };
13
13
  return renderAnchor({
14
14
  href,
15
- className: cn("rounded-lg border-2 text-center font-bold transition-colors", {
15
+ className: cn("rounded-full border-2 text-center font-bold transition-colors", {
16
16
  "border-transparent bg-primary-bg text-fg hover:bg-primary-bg/90 active:bg-primary-bg/80": variant === "contained" && color === "primary",
17
17
  "border-transparent bg-bg-subtle text-fg-base hover:bg-bg-mute active:bg-bg-emphasize": variant === "contained" && color === "gray",
18
18
  "border-primary-border bg-bg-base text-primary-fg hover:bg-bg-subtle active:bg-bg-emphasize": variant === "outlined" && color === "primary",
@@ -1,6 +1,7 @@
1
1
  "use client";
2
+ import { useDisclosure } from "../../../hooks/disclosure/index.mjs";
2
3
  import { jsx } from "react/jsx-runtime";
3
- import { createContext, use, useCallback, useState } from "react";
4
+ import { createContext, use } from "react";
4
5
  //#region src/components/data-display/accordion/context.tsx
5
6
  const OpenContext = createContext(false);
6
7
  const ToggleOpenContext = createContext(void 0);
@@ -17,13 +18,11 @@ const useItemId = () => {
17
18
  return id;
18
19
  };
19
20
  const AccordionItemProvider = ({ defaultOpen = false, id, children }) => {
20
- const [open, setOpen] = useState(defaultOpen);
21
+ const { isOpen, toggle } = useDisclosure(defaultOpen);
21
22
  return /* @__PURE__ */ jsx(OpenContext, {
22
- value: open,
23
+ value: isOpen,
23
24
  children: /* @__PURE__ */ jsx(ToggleOpenContext, {
24
- value: useCallback(() => {
25
- setOpen((open) => !open);
26
- }, []),
25
+ value: toggle,
27
26
  children: /* @__PURE__ */ jsx(ItemIdContext, {
28
27
  value: id,
29
28
  children
@@ -2,7 +2,7 @@ import { cn } from "../../../helpers/cn.mjs";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  //#region src/components/data-display/card/card.tsx
4
4
  const Card = ({ children, variant = "primary", width = "full", appearance = "shadow" }) => /* @__PURE__ */ jsx("div", {
5
- className: cn("rounded-lg", appearance === "shadow" && "shadow-sm", appearance === "bordered" && "border border-border-mute", width === "full" && "w-full", width === "fit" && "w-fit", variant === "primary" && "bg-bg-base", variant === "secondary" && "bg-bg-mute"),
5
+ className: cn("rounded-xl", appearance === "shadow" && "shadow-sm", appearance === "bordered" && "border border-border-mute", width === "full" && "w-full", width === "fit" && "w-fit", variant === "primary" && "bg-bg-base", variant === "secondary" && "bg-bg-mute"),
6
6
  children
7
7
  });
8
8
  //#endregion
@@ -2,7 +2,7 @@ import { cn } from "../../../helpers/cn.mjs";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  //#region src/components/data-display/card/interactive-card.tsx
4
4
  const InteractiveCard = ({ children, variant = "primary", width = "full", appearance = "shadow" }) => /* @__PURE__ */ jsx("div", {
5
- className: cn("rounded-lg transition-transform hover:scale-[1.02] active:scale-[0.98]", appearance === "shadow" && "shadow-sm", appearance === "bordered" && "border border-border-mute", width === "full" && "w-full", width === "fit" && "w-fit", variant === "primary" && "bg-bg-base", variant === "secondary" && "bg-bg-mute"),
5
+ className: cn("rounded-xl transition-transform hover:scale-[1.02] active:scale-[0.98]", appearance === "shadow" && "shadow-sm", appearance === "bordered" && "border border-border-mute", width === "full" && "w-full", width === "fit" && "w-fit", variant === "primary" && "bg-bg-base", variant === "secondary" && "bg-bg-mute"),
6
6
  children
7
7
  });
8
8
  //#endregion
@@ -2,21 +2,20 @@
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
3
  import { IconButton } from "../../buttons/icon-button/icon-button.mjs";
4
4
  import { CloseIcon } from "../../icons/lucide.mjs";
5
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  import { useCallback, useEffect, useRef, useState } from "react";
7
8
  //#region src/components/form/autocomplete/autocomplete.tsx
8
9
  const Autocomplete = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired, options, value, defaultValue, onChange }) => {
9
- const [internalValue, setInternalValue] = useState(defaultValue || []);
10
- const isControlled = value !== void 0;
11
- const currentValue = isControlled ? value : internalValue;
10
+ const [currentValue, handleChange] = useControllableState({
11
+ value,
12
+ defaultValue: defaultValue || [],
13
+ onChange
14
+ });
12
15
  const ref = useRef(null);
13
16
  const [open, setOpen] = useState(false);
14
17
  const [text, setText] = useState("");
15
18
  const [selectIndex, setSelectIndex] = useState();
16
- const handleChange = useCallback((newValue) => {
17
- if (!isControlled) setInternalValue(newValue);
18
- onChange?.(newValue);
19
- }, [isControlled, onChange]);
20
19
  const filteredOptions = options.filter((option) => option.label.includes(text));
21
20
  const reset = useCallback(() => {
22
21
  setText("");
@@ -34,7 +33,7 @@ const Autocomplete = ({ id, name, describedbyId, isInvalid, isDisabled, isRequir
34
33
  };
35
34
  }, [reset]);
36
35
  return /* @__PURE__ */ jsxs("div", {
37
- className: cn("relative w-full rounded-lg 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 has-disabled:has-hover:hover:bg-bg-mute"),
36
+ 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 has-disabled:has-hover:hover:bg-bg-mute"),
38
37
  ref,
39
38
  children: [
40
39
  name ? currentValue.map((selectedValue) => /* @__PURE__ */ jsx("input", {
@@ -143,7 +142,7 @@ const Autocomplete = ({ id, name, describedbyId, isInvalid, isDisabled, isRequir
143
142
  /* @__PURE__ */ jsx("div", {
144
143
  className: "relative w-full",
145
144
  children: open && /* @__PURE__ */ jsx("div", {
146
- className: "absolute top-1 z-10 w-full rounded-lg border border-border-mute bg-bg-base shadow-md",
145
+ className: "absolute top-1 z-10 w-full rounded-xl border border-border-mute bg-bg-base shadow-md",
147
146
  role: "presentation",
148
147
  children: /* @__PURE__ */ jsxs("ul", {
149
148
  className: "max-h-96 py-2",
@@ -22,7 +22,7 @@ const CheckboxCard = ({ labelId, name, isDisabled, isInvalid = false, options, v
22
22
  const disabled = isDisabled || option.disabled;
23
23
  const optionId = `${groupId}-${option.value}`;
24
24
  return /* @__PURE__ */ jsxs("label", {
25
- className: cn("flex w-full min-w-0 rounded-lg 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-border-info bg-bg-subtle", isInvalid ? "border-border-error" : "border-border-mute hover:bg-bg-mute", disabled && "cursor-not-allowed border-border-mute bg-bg-subtle text-fg-mute"),
25
+ 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-border-info bg-bg-subtle", isInvalid ? "border-border-error" : "border-border-mute hover:bg-bg-mute", disabled && "cursor-not-allowed border-border-mute bg-bg-subtle text-fg-mute"),
26
26
  id: optionId,
27
27
  children: [
28
28
  /* @__PURE__ */ jsx("input", {
@@ -1,18 +1,19 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
3
4
  import { jsx } from "react/jsx-runtime";
4
- import { createContext, use, useState } from "react";
5
+ import { createContext, use } from "react";
5
6
  //#region src/components/form/checkbox-group/checkbox-group.tsx
6
7
  const CheckboxGroupContext = createContext(void 0);
7
8
  const useCheckboxGroupContext = () => use(CheckboxGroupContext);
8
9
  const Root = ({ children, describedbyId, defaultValue, isDisabled = false, isInvalid = false, isRequired = false, labelId, name, onChange, value }) => {
9
- const isControlled = value !== void 0;
10
- const [internalValue, setInternalValue] = useState(defaultValue ?? []);
11
- const currentValue = isControlled ? value : internalValue;
10
+ const [currentValue, setCurrentValue] = useControllableState({
11
+ value,
12
+ defaultValue: defaultValue ?? [],
13
+ onChange
14
+ });
12
15
  const toggleValue = (targetValue) => {
13
- const nextValue = currentValue.includes(targetValue) ? currentValue.filter((item) => item !== targetValue) : [...currentValue, targetValue];
14
- if (!isControlled) setInternalValue(nextValue);
15
- onChange?.(nextValue);
16
+ setCurrentValue(currentValue.includes(targetValue) ? currentValue.filter((item) => item !== targetValue) : [...currentValue, targetValue]);
16
17
  };
17
18
  return /* @__PURE__ */ jsx("fieldset", {
18
19
  "aria-describedby": describedbyId,
@@ -100,7 +100,7 @@ const ItemList = ({ showWebkitRelativePath, clearable }) => {
100
100
  const onDelete = () => onFileDelete(id);
101
101
  const sizeInKB = (file.size / 1024).toFixed(2);
102
102
  return /* @__PURE__ */ jsxs("li", {
103
- className: "flex items-center justify-between rounded-lg border border-border-base bg-bg-base px-3 py-2",
103
+ className: "flex items-center justify-between rounded-xl border border-border-base bg-bg-base px-3 py-2",
104
104
  children: [/* @__PURE__ */ jsxs("div", {
105
105
  className: "flex flex-col gap-1",
106
106
  children: [/* @__PURE__ */ jsx("span", {
@@ -23,7 +23,7 @@ const NumberField = ({ id, name, describedbyId, isInvalid, isDisabled, isRequire
23
23
  onChange?.(newValue);
24
24
  };
25
25
  return /* @__PURE__ */ jsxs("div", {
26
- className: cn("relative flex h-12 w-full items-center justify-between gap-2 rounded-lg 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 has-disabled:has-hover:hover:bg-bg-mute"),
26
+ 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 has-disabled:has-hover:hover:bg-bg-mute"),
27
27
  children: [/* @__PURE__ */ jsx("input", {
28
28
  "aria-describedby": describedbyId,
29
29
  "aria-invalid": isInvalid,
@@ -68,7 +68,7 @@ const NumberField = ({ id, name, describedbyId, isInvalid, isDisabled, isRequire
68
68
  "aria-hidden": "true",
69
69
  className: "absolute right-0 flex h-full flex-col",
70
70
  children: [/* @__PURE__ */ jsxs("button", {
71
- className: cn("flex w-6 grow items-center justify-center rounded-tr-lg border-border-base border-b border-l bg-bg-mute", "disabled:cursor-not-allowed"),
71
+ className: cn("flex w-6 grow items-center justify-center rounded-tr-xl border-border-base border-b border-l bg-bg-mute", "disabled:cursor-not-allowed"),
72
72
  disabled: isDisabled,
73
73
  onClick: () => {
74
74
  const newValue = between(toPrecision(cast(displayValue, precision) + step, precision), min, max);
@@ -82,7 +82,7 @@ const NumberField = ({ id, name, describedbyId, isInvalid, isDisabled, isRequire
82
82
  children: "増やす"
83
83
  }), /* @__PURE__ */ jsx(PlusIcon, { size: "sm" })]
84
84
  }), /* @__PURE__ */ jsxs("button", {
85
- className: cn("flex w-6 grow items-center justify-center rounded-br-lg border-border-base border-l bg-bg-mute", "disabled:cursor-not-allowed"),
85
+ className: cn("flex w-6 grow items-center justify-center rounded-br-xl border-border-base border-l bg-bg-mute", "disabled:cursor-not-allowed"),
86
86
  disabled: isDisabled,
87
87
  onClick: () => {
88
88
  const newValue = between(toPrecision(cast(displayValue, precision) - step, precision), min, max);
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
3
  import { ViewIcon, ViewOffIcon } from "../../icons/lucide.mjs";
4
+ import { useDisclosure } from "../../../hooks/disclosure/index.mjs";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
- import { useState } from "react";
6
6
  //#region src/components/form/password-input/password-input.tsx
7
7
  const PasswordInput = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired, placeholder, autoComplete = "current-password", showLabel = "Show password", hideLabel = "Hide password", defaultValue, value, onChange }) => {
8
- const [isVisible, setIsVisible] = useState(false);
8
+ const { isOpen: isVisible, toggle: toggleVisible } = useDisclosure();
9
9
  return /* @__PURE__ */ jsxs("div", {
10
10
  className: "relative w-full",
11
11
  children: [/* @__PURE__ */ jsx("input", {
@@ -13,7 +13,7 @@ const PasswordInput = ({ id, name, describedbyId, isInvalid, isDisabled, isRequi
13
13
  "aria-invalid": isInvalid,
14
14
  "aria-required": isRequired,
15
15
  autoComplete,
16
- className: cn("w-full rounded-lg 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 disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
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 disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
17
17
  defaultValue,
18
18
  disabled: isDisabled,
19
19
  id,
@@ -27,9 +27,7 @@ const PasswordInput = ({ id, name, describedbyId, isInvalid, isDisabled, isRequi
27
27
  "aria-label": isVisible ? hideLabel : showLabel,
28
28
  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", !isDisabled && "hover:bg-bg-mute hover:text-fg-base", isDisabled && "cursor-not-allowed text-fg-mute/70"),
29
29
  disabled: isDisabled,
30
- onClick: () => {
31
- setIsVisible((current) => !current);
32
- },
30
+ onClick: toggleVisible,
33
31
  type: "button",
34
32
  children: isVisible ? /* @__PURE__ */ jsx(ViewOffIcon, { size: "sm" }) : /* @__PURE__ */ jsx(ViewIcon, { size: "sm" })
35
33
  })]
@@ -36,7 +36,7 @@ const RadioCard = ({ labelId, name, isDisabled, isInvalid = false, options, valu
36
36
  return /* @__PURE__ */ jsxs("button", {
37
37
  "aria-describedby": option.description ? `${optionId}-description` : void 0,
38
38
  "aria-pressed": checked,
39
- className: cn("flex w-full min-w-0 rounded-lg border bg-bg-base p-4 text-left transition-colors", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", checked && "border-border-info bg-bg-subtle", isInvalid ? "border-border-error" : "border-border-mute hover:bg-bg-mute", disabled && "cursor-not-allowed border-border-mute bg-bg-subtle text-fg-mute"),
39
+ className: cn("flex w-full min-w-0 rounded-xl border bg-bg-base p-4 text-left transition-colors", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", checked && "border-border-info bg-bg-subtle", isInvalid ? "border-border-error" : "border-border-mute hover:bg-bg-mute", disabled && "cursor-not-allowed border-border-mute bg-bg-subtle text-fg-mute"),
40
40
  disabled,
41
41
  id: optionId,
42
42
  onClick: () => {
@@ -9,7 +9,7 @@ const Select = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired, op
9
9
  "aria-describedby": describedbyId,
10
10
  "aria-invalid": isInvalid,
11
11
  "aria-required": isRequired,
12
- className: cn("w-full appearance-none rounded-lg border border-border-base bg-bg-base px-3 py-2 text-fg-base", "aria-invalid:border-border-error", "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
12
+ className: cn("w-full appearance-none rounded-xl border border-border-base bg-bg-base px-3 py-2 text-fg-base", "aria-invalid:border-border-error", "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
13
13
  defaultValue,
14
14
  disabled: isDisabled,
15
15
  id,
@@ -1,19 +1,17 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
+ import { useControllableState } from "../../../hooks/controllable-state/index.mjs";
3
4
  import { jsx } from "react/jsx-runtime";
4
- import { useState } from "react";
5
5
  //#region src/components/form/slider/slider.tsx
6
6
  const Slider = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired, value, defaultValue, onChange, step = 1, max = 100, min = 0 }) => {
7
- const isControlled = value !== void 0;
8
- const [internalValue, setInternalValue] = useState(defaultValue ?? value ?? min);
9
- const currentValue = isControlled ? value : internalValue;
7
+ const [currentValue, handleChange] = useControllableState({
8
+ value,
9
+ defaultValue: defaultValue ?? min,
10
+ onChange
11
+ });
10
12
  const range = Math.max(max - min, 1);
11
13
  const progress = (currentValue - min) / range * 100;
12
14
  const style = { "--slider-progress": `${Math.min(Math.max(progress, 0), 100)}%` };
13
- const handleChange = (newValue) => {
14
- if (!isControlled) setInternalValue(newValue);
15
- onChange?.(newValue);
16
- };
17
15
  return /* @__PURE__ */ jsx("div", {
18
16
  className: cn("relative flex h-8 w-full items-center", "before:absolute before:inset-x-0 before:h-2 before:rounded-full before:bg-bg-mute", "after:absolute after:left-0 after:h-2 after:rounded-full after:bg-primary-bg", "after:w-(--slider-progress)", isInvalid && "after:bg-bg-error", isDisabled && "opacity-50"),
19
17
  style,
@@ -24,7 +22,6 @@ const Slider = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired, va
24
22
  "aria-valuemin": min,
25
23
  "aria-valuenow": currentValue,
26
24
  className: cn("relative z-10 h-8 w-full appearance-none bg-transparent", "focus:outline-none", "disabled:cursor-not-allowed", "[&::-webkit-slider-runnable-track]:h-2 [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-transparent", "[&::-webkit-slider-thumb]:mt-[-4px] [&::-webkit-slider-thumb]:size-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-border-base [&::-webkit-slider-thumb]:bg-bg-base [&::-webkit-slider-thumb]:shadow-xs", "[&:focus-visible::-webkit-slider-thumb]:border-transparent [&:focus-visible::-webkit-slider-thumb]:ring-2 [&:focus-visible::-webkit-slider-thumb]:ring-border-info", "[&::-moz-range-track]:h-2 [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-transparent", "[&::-moz-range-progress]:h-2 [&::-moz-range-progress]:rounded-full [&::-moz-range-progress]:bg-transparent", "[&::-moz-range-thumb]:size-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border [&::-moz-range-thumb]:border-border-base [&::-moz-range-thumb]:bg-bg-base [&::-moz-range-thumb]:shadow-xs", "[&:focus-visible::-moz-range-thumb]:border-transparent [&:focus-visible::-moz-range-thumb]:ring-2 [&:focus-visible::-moz-range-thumb]:ring-border-info", isInvalid && "[&::-moz-range-thumb]:border-border-error [&::-webkit-slider-thumb]:border-border-error [&:focus-visible::-moz-range-thumb]:ring-border-error [&:focus-visible::-webkit-slider-thumb]:ring-border-error"),
27
- defaultValue: isControlled ? void 0 : defaultValue,
28
25
  disabled: isDisabled,
29
26
  id,
30
27
  max,
@@ -36,7 +33,7 @@ const Slider = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired, va
36
33
  required: isRequired,
37
34
  step,
38
35
  type: "range",
39
- value: isControlled ? value : void 0
36
+ value: currentValue
40
37
  })
41
38
  });
42
39
  };
@@ -6,7 +6,7 @@ const TextField = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired,
6
6
  "aria-describedby": describedbyId,
7
7
  "aria-invalid": isInvalid,
8
8
  "aria-required": isRequired,
9
- className: cn("w-full rounded-lg border border-border-base bg-bg-base px-3 py-2", "aria-invalid:border-border-error", "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
9
+ className: cn("w-full rounded-xl border border-border-base bg-bg-base px-3 py-2", "aria-invalid:border-border-error", "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
10
10
  defaultValue,
11
11
  disabled: isDisabled,
12
12
  id,
@@ -15,7 +15,7 @@ const Textarea = ({ id, name, describedbyId, isInvalid, isDisabled, isRequired,
15
15
  "aria-describedby": describedbyId,
16
16
  "aria-invalid": isInvalid,
17
17
  "aria-required": isRequired,
18
- className: cn("w-full resize-none rounded-lg border border-border-base bg-bg-base px-3 py-2", "aria-invalid:border-border-error", "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", fullHeight && "h-full"),
18
+ className: cn("w-full resize-none rounded-xl border border-border-base bg-bg-base px-3 py-2", "aria-invalid:border-border-error", "disabled:cursor-not-allowed disabled:border-border-mute disabled:bg-bg-mute disabled:hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", fullHeight && "h-full"),
19
19
  defaultValue,
20
20
  disabled: isDisabled,
21
21
  id,
@@ -88,7 +88,7 @@ const Tab = ({ id, children }) => {
88
88
  tabIndex: activeIndex === index ? 0 : -1,
89
89
  children: [selectedId === id && /* @__PURE__ */ jsx(motion.div, {
90
90
  className: "absolute right-0 -bottom-0.5 left-0 h-1 bg-primary-border",
91
- layoutId: "underline"
91
+ layoutId: `${rootId}-underline`
92
92
  }), children]
93
93
  });
94
94
  };
@@ -140,7 +140,7 @@ const Modal = ({ ref, type = "center", defaultOpen, isOpen, onClose, children })
140
140
  ]);
141
141
  return /* @__PURE__ */ jsx(motion.dialog, {
142
142
  animate: realDialogOpen ? "open" : "closed",
143
- className: cn("border-border-mute bg-bg-base shadow-md backdrop:bg-back-drop", type === "center" && "m-auto max-h-lg w-5/6 max-w-2xl rounded-lg dark:border", type === "bottom" && "mt-auto w-screen max-w-screen rounded-t-lg dark:border-t", type === "right" && "ml-auto h-svh max-h-none w-screen max-w-sm rounded-l-lg dark:border-l", type === "left" && "mr-auto h-svh max-h-none w-screen max-w-sm rounded-r-lg dark:border-r"),
143
+ className: cn("border-border-mute bg-bg-base text-fg-base shadow-md backdrop:bg-back-drop", type === "center" && "m-auto max-h-lg w-5/6 max-w-2xl rounded-lg dark:border", type === "bottom" && "mt-auto w-screen max-w-screen rounded-t-lg dark:border-t", type === "right" && "ml-auto h-svh max-h-none w-screen max-w-sm rounded-l-lg dark:border-l", type === "left" && "mr-auto h-svh max-h-none w-screen max-w-sm rounded-r-lg dark:border-r"),
144
144
  exit: "closed",
145
145
  initial: "closed",
146
146
  onClick: (e) => {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useClickAway } from "../../../hooks/click-away/index.mjs";
3
- import { createContext, use, useMemo } from "react";
3
+ import { createContext, use, useMemo, useRef } from "react";
4
4
  //#region src/components/overlays/popover/hooks.ts
5
5
  const PopoverContext = createContext(null);
6
6
  const PopoverProvider = PopoverContext;
@@ -33,7 +33,8 @@ const useOpenContext = () => {
33
33
  const usePopoverContent = () => {
34
34
  const popover = usePopoverContext();
35
35
  const isHover = popover.type === "tooltip";
36
- const ref = useClickAway((event) => {
36
+ const ref = useRef(null);
37
+ useClickAway(ref, (event) => {
37
38
  if (!popover.isOpen) return;
38
39
  if (popover.triggerRef.current?.contains(event.target)) return;
39
40
  popover.onClose();
@@ -1,15 +1,16 @@
1
1
  "use client";
2
+ import { useDisclosure } from "../../../hooks/disclosure/index.mjs";
2
3
  import { usePortalRoot } from "../../providers/portal-root.mjs";
3
4
  import { PopoverProvider, useOpenContext, usePopoverContent, usePopoverTrigger } from "./hooks.mjs";
4
5
  import { jsx } from "react/jsx-runtime";
5
- import { useCallback, useEffect, useId, useState } from "react";
6
+ import { useEffect, useId } from "react";
6
7
  import { AnimatePresence } from "motion/react";
7
8
  import * as motion$1 from "motion/react-client";
8
9
  import { FloatingFocusManager, FloatingPortal, autoUpdate, flip, offset, useFloating } from "@floating-ui/react";
9
10
  //#region src/components/overlays/popover/popover.tsx
10
11
  const Root = ({ children, type = "menu", placement = "bottom-start", flipDisabled = false }) => {
11
12
  const id = useId();
12
- const [isOpen, setIsOpen] = useState(false);
13
+ const { isOpen, open, close, toggle } = useDisclosure();
13
14
  const { refs, floatingStyles, context, placement: computedPlacement } = useFloating({
14
15
  strategy: "fixed",
15
16
  placement,
@@ -21,32 +22,23 @@ const Root = ({ children, type = "menu", placement = "bottom-start", flipDisable
21
22
  })],
22
23
  transform: false
23
24
  });
24
- const toggleOpen = useCallback(() => {
25
- setIsOpen((prev) => !prev);
26
- }, []);
27
- const onOpen = useCallback(() => {
28
- setIsOpen(true);
29
- }, []);
30
- const onClose = useCallback(() => {
31
- setIsOpen(false);
32
- }, []);
33
25
  useEffect(() => {
34
26
  const handleKeyDown = (e) => {
35
- if (e.key === "Escape") onClose();
27
+ if (e.key === "Escape") close();
36
28
  };
37
29
  window.addEventListener("keydown", handleKeyDown);
38
30
  return () => {
39
31
  window.removeEventListener("keydown", handleKeyDown);
40
32
  };
41
- }, [onClose]);
33
+ }, [close]);
42
34
  return /* @__PURE__ */ jsx(PopoverProvider, {
43
35
  value: {
44
36
  rootId: id,
45
37
  type,
46
38
  isOpen,
47
- toggleOpen,
48
- onOpen,
49
- onClose,
39
+ toggleOpen: toggle,
40
+ onOpen: open,
41
+ onClose: close,
50
42
  context,
51
43
  placement: computedPlacement,
52
44
  triggerRef: refs.domReference,
@@ -0,0 +1,5 @@
1
+ //#region src/hooks/breakpoint/index.d.ts
2
+ type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl';
3
+ declare const useBreakpoint: (breakpoint: Breakpoint) => boolean;
4
+ //#endregion
5
+ export { useBreakpoint };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { useCallback, useSyncExternalStore } from "react";
3
+ //#region src/hooks/breakpoint/index.ts
4
+ const BREAKPOINTS = {
5
+ sm: "40rem",
6
+ md: "48rem",
7
+ lg: "64rem",
8
+ xl: "80rem",
9
+ "2xl": "96rem"
10
+ };
11
+ const useBreakpoint = (breakpoint) => {
12
+ const query = `(min-width: ${BREAKPOINTS[breakpoint]})`;
13
+ const subscribe = useCallback((cb) => {
14
+ const mediaQueryList = window.matchMedia(query);
15
+ mediaQueryList.addEventListener("change", cb);
16
+ return () => {
17
+ mediaQueryList.removeEventListener("change", cb);
18
+ };
19
+ }, [query]);
20
+ const getSnapshot = () => window.matchMedia(query).matches;
21
+ return useSyncExternalStore(subscribe, getSnapshot, () => false);
22
+ };
23
+ //#endregion
24
+ export { useBreakpoint };
@@ -1,6 +1,6 @@
1
1
  import { RefObject } from "react";
2
2
 
3
3
  //#region src/hooks/click-away/index.d.ts
4
- declare const useClickAway: <T extends Element = HTMLElement>(callback: (e: Event) => void, enabled?: boolean) => RefObject<T | null>;
4
+ declare const useClickAway: <T extends Element = HTMLElement>(ref: RefObject<T | null>, callback: (e: Event) => void, enabled?: boolean) => void;
5
5
  //#endregion
6
6
  export { useClickAway };
@@ -1,8 +1,7 @@
1
1
  "use client";
2
- import { useEffect, useRef } from "react";
2
+ import { useEffect } from "react";
3
3
  //#region src/hooks/click-away/index.ts
4
- const useClickAway = (callback, enabled = true) => {
5
- const ref = useRef(null);
4
+ const useClickAway = (ref, callback, enabled = true) => {
6
5
  useEffect(() => {
7
6
  if (!enabled) return;
8
7
  const handler = (e) => {
@@ -15,8 +14,11 @@ const useClickAway = (callback, enabled = true) => {
15
14
  document.removeEventListener("mousedown", handler);
16
15
  document.removeEventListener("touchstart", handler);
17
16
  };
18
- }, [callback, enabled]);
19
- return ref;
17
+ }, [
18
+ ref,
19
+ callback,
20
+ enabled
21
+ ]);
20
22
  };
21
23
  //#endregion
22
24
  export { useClickAway };
@@ -0,0 +1,13 @@
1
+ //#region src/hooks/controllable-state/index.d.ts
2
+ type UseControllableStateProps<T> = {
3
+ value?: T;
4
+ defaultValue: T;
5
+ onChange?: (value: T) => void;
6
+ };
7
+ declare const useControllableState: <T>({
8
+ value,
9
+ defaultValue,
10
+ onChange
11
+ }: UseControllableStateProps<T>) => [T, (next: T | ((prev: T) => T)) => void];
12
+ //#endregion
13
+ export { useControllableState };
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { useCallback, useRef, useState } from "react";
3
+ //#region src/hooks/controllable-state/index.ts
4
+ const useControllableState = ({ value, defaultValue, onChange }) => {
5
+ const isControlled = value !== void 0;
6
+ const [internalValue, setInternalValue] = useState(defaultValue);
7
+ const currentValue = isControlled ? value : internalValue;
8
+ const currentValueRef = useRef(currentValue);
9
+ currentValueRef.current = currentValue;
10
+ const onChangeRef = useRef(onChange);
11
+ onChangeRef.current = onChange;
12
+ const isControlledRef = useRef(isControlled);
13
+ isControlledRef.current = isControlled;
14
+ return [currentValue, useCallback((next) => {
15
+ const nextValue = typeof next === "function" ? next(currentValueRef.current) : next;
16
+ if (!isControlledRef.current) setInternalValue(nextValue);
17
+ onChangeRef.current?.(nextValue);
18
+ }, [])];
19
+ };
20
+ //#endregion
21
+ export { useControllableState };
@@ -0,0 +1,6 @@
1
+ //#region src/hooks/debounce/index.d.ts
2
+ type AnyFunction = (...args: any[]) => any;
3
+ declare const useDebounce: <T>(value: T, delay: number) => T;
4
+ declare const useDebouncedCallback: <T extends AnyFunction>(callback: T, delay: number) => (...args: Parameters<T>) => void;
5
+ //#endregion
6
+ export { useDebounce, useDebouncedCallback };
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ //#region src/hooks/debounce/index.ts
4
+ const useDebounce = (value, delay) => {
5
+ const [debouncedValue, setDebouncedValue] = useState(value);
6
+ useEffect(() => {
7
+ const timer = setTimeout(() => {
8
+ setDebouncedValue(value);
9
+ }, delay);
10
+ return () => {
11
+ clearTimeout(timer);
12
+ };
13
+ }, [value, delay]);
14
+ return debouncedValue;
15
+ };
16
+ const useDebouncedCallback = (callback, delay) => {
17
+ const timerRef = useRef(void 0);
18
+ const callbackRef = useRef(callback);
19
+ useEffect(() => {
20
+ callbackRef.current = callback;
21
+ });
22
+ useEffect(() => {
23
+ return () => {
24
+ clearTimeout(timerRef.current);
25
+ };
26
+ }, []);
27
+ return useCallback((...args) => {
28
+ clearTimeout(timerRef.current);
29
+ timerRef.current = setTimeout(() => {
30
+ callbackRef.current(...args);
31
+ }, delay);
32
+ }, [delay]);
33
+ };
34
+ //#endregion
35
+ export { useDebounce, useDebouncedCallback };
@@ -0,0 +1,10 @@
1
+ //#region src/hooks/disclosure/index.d.ts
2
+ type UseDisclosureReturn = {
3
+ isOpen: boolean;
4
+ open: () => void;
5
+ close: () => void;
6
+ toggle: () => void;
7
+ };
8
+ declare const useDisclosure: (defaultOpen?: boolean) => UseDisclosureReturn;
9
+ //#endregion
10
+ export { useDisclosure };
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { useCallback, useState } from "react";
3
+ //#region src/hooks/disclosure/index.ts
4
+ const useDisclosure = (defaultOpen = false) => {
5
+ const [isOpen, setIsOpen] = useState(defaultOpen);
6
+ return {
7
+ isOpen,
8
+ open: useCallback(() => setIsOpen(true), []),
9
+ close: useCallback(() => setIsOpen(false), []),
10
+ toggle: useCallback(() => setIsOpen((prev) => !prev), [])
11
+ };
12
+ };
13
+ //#endregion
14
+ export { useDisclosure };
@@ -0,0 +1,12 @@
1
+ //#region src/hooks/hover/index.d.ts
2
+ type HoverProps = {
3
+ onPointerEnter: () => void;
4
+ onPointerLeave: () => void;
5
+ };
6
+ type UseHoverReturn = {
7
+ isHovered: boolean;
8
+ hoverProps: HoverProps;
9
+ };
10
+ declare const useHover: () => UseHoverReturn;
11
+ //#endregion
12
+ export { useHover };
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { useCallback, useState } from "react";
3
+ //#region src/hooks/hover/index.ts
4
+ const useHover = () => {
5
+ const [isHovered, setIsHovered] = useState(false);
6
+ return {
7
+ isHovered,
8
+ hoverProps: {
9
+ onPointerEnter: useCallback(() => setIsHovered(true), []),
10
+ onPointerLeave: useCallback(() => setIsHovered(false), [])
11
+ }
12
+ };
13
+ };
14
+ //#endregion
15
+ export { useHover };