@k8o/arte-odyssey 5.0.4 → 6.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 (38) hide show
  1. package/dist/components/buttons/button/button.d.mts +1 -1
  2. package/dist/components/buttons/button/button.mjs +11 -7
  3. package/dist/components/buttons/icon-button/icon-button.d.mts +1 -1
  4. package/dist/components/buttons/icon-button/icon-button.mjs +1 -1
  5. package/dist/components/buttons/icon-link/icon-link.d.mts +1 -1
  6. package/dist/components/buttons/icon-link/icon-link.mjs +1 -1
  7. package/dist/components/buttons/link-button/link-button.d.mts +1 -1
  8. package/dist/components/buttons/link-button/link-button.mjs +7 -5
  9. package/dist/components/data-display/accordion/accordion-button.mjs +1 -1
  10. package/dist/components/data-display/card/card.mjs +2 -2
  11. package/dist/components/data-display/card/interactive-card.mjs +2 -2
  12. package/dist/components/data-display/card/type.d.mts +0 -1
  13. package/dist/components/form/autocomplete/autocomplete.mjs +2 -2
  14. package/dist/components/form/checkbox-card/checkbox-card.mjs +2 -2
  15. package/dist/components/form/form-control/form-control.mjs +4 -4
  16. package/dist/components/form/number-field/number-field.mjs +12 -6
  17. package/dist/components/form/radio-card/radio-card.mjs +1 -1
  18. package/dist/components/form/switch/switch.mjs +1 -1
  19. package/dist/components/index.d.mts +2 -1
  20. package/dist/components/index.mjs +2 -1
  21. package/dist/components/navigation/pagination/index.d.mts +2 -0
  22. package/dist/components/navigation/pagination/index.mjs +2 -0
  23. package/dist/components/navigation/pagination/pagination.d.mts +15 -0
  24. package/dist/components/navigation/pagination/pagination.mjs +65 -0
  25. package/dist/components/navigation/tabs/tabs.mjs +1 -1
  26. package/dist/components/overlays/dialog/dialog.mjs +1 -1
  27. package/dist/components/overlays/dropdown-menu/dropdown-menu.mjs +2 -2
  28. package/dist/components/overlays/list-box/list-box.mjs +2 -2
  29. package/dist/components/overlays/modal/modal.mjs +1 -1
  30. package/dist/components/overlays/tooltip/tooltip.mjs +1 -1
  31. package/dist/hooks/scroll-direction/index.d.mts +7 -1
  32. package/dist/hooks/scroll-direction/index.mjs +18 -6
  33. package/dist/hooks/scroll-lock/index.d.mts +3 -1
  34. package/dist/hooks/scroll-lock/index.mjs +35 -16
  35. package/dist/index.d.mts +2 -1
  36. package/dist/index.mjs +2 -1
  37. package/dist/styles/index.css +3 -0
  38. package/package.json +2 -1
@@ -4,7 +4,7 @@ import { FC, HTMLProps, ReactNode } from "react";
4
4
  declare const Button: FC<{
5
5
  type?: 'button' | 'submit';
6
6
  size?: 'sm' | 'md' | 'lg';
7
- color?: 'primary' | 'gray';
7
+ color?: 'primary' | 'secondary' | 'gray';
8
8
  variant?: 'contained' | 'outlined' | 'skeleton';
9
9
  fullWidth?: boolean;
10
10
  startIcon?: ReactNode;
@@ -4,14 +4,18 @@ import { jsxs } from "react/jsx-runtime";
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
6
  className: cn("cursor-pointer rounded-full border-2 text-center font-bold transition-colors", {
7
- "border-transparent bg-primary-bg text-fg hover:bg-primary-bg/90 active:bg-primary-bg/80": variant === "contained" && color === "primary",
8
- "border-transparent bg-bg-subtle text-fg-base hover:bg-bg-mute active:bg-bg-emphasize": variant === "contained" && color === "gray",
9
- "cursor-not-allowed opacity-35 hover:bg-primary-bg active:bg-primary-bg": disabled && variant === "contained",
10
- "border-primary-border bg-bg-base text-primary-fg hover:bg-bg-subtle active:bg-bg-emphasize": variant === "outlined" && color === "primary",
11
- "border-border-base bg-bg-base text-fg-base hover:bg-bg-subtle active:bg-bg-emphasize": variant === "outlined" && color === "gray",
7
+ "border-transparent bg-primary-bg text-fg hover:bg-primary-bg-emphasize/80 active:bg-primary-bg-emphasize": variant === "contained" && color === "primary",
8
+ "border-transparent bg-secondary-bg text-fg hover:bg-secondary-bg-emphasize/80 active:bg-secondary-bg-emphasize": variant === "contained" && color === "secondary",
9
+ "border-transparent bg-bg-subtle text-fg-base hover:bg-bg-mute/80 active:bg-bg-mute": variant === "contained" && color === "gray",
10
+ "cursor-not-allowed opacity-35 hover:bg-primary-bg active:bg-primary-bg": disabled && variant === "contained" && color === "primary",
11
+ "cursor-not-allowed opacity-35 hover:bg-secondary-bg active:bg-secondary-bg": disabled && variant === "contained" && color === "secondary",
12
+ "cursor-not-allowed opacity-35 hover:bg-bg-subtle active:bg-bg-subtle": disabled && variant === "contained" && color === "gray",
13
+ "border-primary-border bg-bg-base text-primary-fg hover:bg-bg-subtle active:bg-bg-mute": variant === "outlined" && color === "primary",
14
+ "border-secondary-border bg-bg-base text-secondary-fg hover:bg-bg-subtle active:bg-bg-mute": variant === "outlined" && color === "secondary",
15
+ "border-border-base bg-bg-base text-fg-base hover:bg-bg-subtle active:bg-bg-mute": variant === "outlined" && color === "gray",
12
16
  "cursor-not-allowed bg-bg-base opacity-35 hover:bg-bg-base active:bg-bg-base": disabled && variant === "outlined",
13
- "border-transparent bg-transparent text-fg-mute hover:text-fg-base active:text-fg-base": variant === "skeleton",
14
- "cursor-not-allowed bg-transparent text-fg-mute opacity-35 hover:text-fg-mute active:text-fg-mute": disabled && variant === "skeleton"
17
+ "border-transparent bg-transparent text-fg-mute hover:bg-bg-subtle hover:text-fg-base active:bg-bg-mute active:text-fg-base": variant === "skeleton",
18
+ "cursor-not-allowed bg-transparent text-fg-mute opacity-35 hover:bg-transparent hover:text-fg-mute active:bg-transparent active:text-fg-mute": disabled && variant === "skeleton"
15
19
  }, "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", size === "sm" && "px-3 py-1 text-sm", size === "md" && "px-4 py-2 text-md", size === "lg" && "px-6 py-3 text-lg", fullWidth && "w-full", Boolean(startIcon ?? endIcon) && "flex items-center gap-2", startIcon && endIcon ? "justify-between" : startIcon && variant !== "skeleton" ? "justify-center" : endIcon && "justify-between"),
16
20
  disabled,
17
21
  onClick,
@@ -3,7 +3,7 @@ import { FC, HTMLProps } from "react";
3
3
  //#region src/components/buttons/icon-button/icon-button.d.ts
4
4
  type Props = {
5
5
  size?: 'sm' | 'md' | 'lg';
6
- bg?: 'transparent' | 'base' | 'primary';
6
+ bg?: 'transparent' | 'base' | 'primary' | 'secondary';
7
7
  label: string;
8
8
  } & Omit<HTMLProps<HTMLButtonElement>, 'size' | 'type'>;
9
9
  declare const IconButton: FC<Props>;
@@ -4,7 +4,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
4
4
  const IconButton = ({ ref, size = "md", bg = "transparent", label, children, ...props }) => {
5
5
  return /* @__PURE__ */ jsxs("button", {
6
6
  "aria-label": props.role ? label : void 0,
7
- className: cn("inline-flex cursor-pointer rounded-full bg-transparent transition-colors", "hover:bg-bg-subtle", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info active:bg-bg-emphasize", bg === "base" && "bg-bg-base", bg === "transparent" && "bg-transparent", bg === "primary" && "bg-primary-bg hover:bg-primary-bg/90 active:bg-primary-bg-emphasize", size === "sm" && "p-1", size === "md" && "p-2", size === "lg" && "p-3", props.disabled && "cursor-not-allowed opacity-50 hover:bg-transparent active:bg-transparent"),
7
+ className: cn("inline-flex cursor-pointer rounded-full transition-colors", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", (bg === "transparent" || bg === "base") && "hover:bg-bg-subtle active:bg-bg-mute", bg === "base" && "bg-bg-base", bg === "transparent" && "bg-transparent", bg === "primary" && "bg-primary-bg hover:bg-primary-bg-emphasize/80 active:bg-primary-bg-emphasize", bg === "secondary" && "bg-secondary-bg hover:bg-secondary-bg-emphasize/80 active:bg-secondary-bg-emphasize", size === "sm" && "p-1", size === "md" && "p-2", size === "lg" && "p-3", props.disabled && "cursor-not-allowed opacity-50 hover:bg-transparent active:bg-transparent"),
8
8
  ref,
9
9
  ...props,
10
10
  children: [!props.role && /* @__PURE__ */ jsx("span", {
@@ -11,7 +11,7 @@ declare const IconLink: <T extends string>({
11
11
  renderAnchor
12
12
  }: PropsWithChildren<{
13
13
  size?: "sm" | "md" | "lg";
14
- bg?: "transparent" | "base" | "primary";
14
+ bg?: "transparent" | "base" | "primary" | "secondary";
15
15
  label?: string;
16
16
  href: T;
17
17
  openInNewTab?: boolean;
@@ -12,7 +12,7 @@ const IconLink = ({ size = "md", bg = "transparent", label, href, children, open
12
12
  };
13
13
  return renderAnchor({
14
14
  href,
15
- className: cn("inline-flex rounded-full transition-colors hover:bg-bg-subtle active:bg-bg-emphasize focus-visible:ring-2 focus-visible:ring-border-info", bg === "base" && "bg-bg-base", bg === "transparent" && "bg-transparent", bg === "primary" && "bg-primary-bg hover:bg-primary-bg/90 active:bg-primary-bg-emphasize", size === "sm" && "p-1", size === "md" && "p-2", size === "lg" && "p-3"),
15
+ className: cn("inline-flex rounded-full transition-colors focus-visible:ring-2 focus-visible:ring-border-info", (bg === "transparent" || bg === "base") && "hover:bg-bg-subtle active:bg-bg-mute", bg === "base" && "bg-bg-base", bg === "transparent" && "bg-transparent", bg === "primary" && "bg-primary-bg hover:bg-primary-bg-emphasize/80 active:bg-primary-bg-emphasize", bg === "secondary" && "bg-secondary-bg hover:bg-secondary-bg-emphasize/80 active:bg-secondary-bg-emphasize", size === "sm" && "p-1", size === "md" && "p-2", size === "lg" && "p-3"),
16
16
  ...props,
17
17
  children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
18
18
  className: "sr-only",
@@ -15,7 +15,7 @@ declare const LinkButton: <T extends string>({
15
15
  }: {
16
16
  variant?: "contained" | "outlined" | "skeleton";
17
17
  size?: "sm" | "md" | "lg";
18
- color?: "primary" | "gray";
18
+ color?: "primary" | "secondary" | "gray";
19
19
  href: T;
20
20
  startIcon?: ReactNode;
21
21
  endIcon?: ReactNode;
@@ -13,11 +13,13 @@ const LinkButton = ({ children, size = "md", color = "primary", variant = "conta
13
13
  return renderAnchor({
14
14
  href,
15
15
  className: cn("rounded-full border-2 text-center font-bold transition-colors", {
16
- "border-transparent bg-primary-bg text-fg hover:bg-primary-bg/90 active:bg-primary-bg/80": variant === "contained" && color === "primary",
17
- "border-transparent bg-bg-subtle text-fg-base hover:bg-bg-mute active:bg-bg-emphasize": variant === "contained" && color === "gray",
18
- "border-primary-border bg-bg-base text-primary-fg hover:bg-bg-subtle active:bg-bg-emphasize": variant === "outlined" && color === "primary",
19
- "border-border-base bg-bg-base text-fg-base hover:bg-bg-subtle active:bg-bg-emphasize": variant === "outlined" && color === "gray",
20
- "border-transparent bg-transparent text-fg-mute hover:text-fg-base active:text-fg-base": variant === "skeleton"
16
+ "border-transparent bg-primary-bg text-fg hover:bg-primary-bg-emphasize/80 active:bg-primary-bg-emphasize": variant === "contained" && color === "primary",
17
+ "border-transparent bg-secondary-bg text-fg hover:bg-secondary-bg-emphasize/80 active:bg-secondary-bg-emphasize": variant === "contained" && color === "secondary",
18
+ "border-transparent bg-bg-subtle text-fg-base hover:bg-bg-mute/80 active:bg-bg-mute": variant === "contained" && color === "gray",
19
+ "border-primary-border bg-bg-base text-primary-fg hover:bg-bg-subtle active:bg-bg-mute": variant === "outlined" && color === "primary",
20
+ "border-secondary-border bg-bg-base text-secondary-fg hover:bg-bg-subtle active:bg-bg-mute": variant === "outlined" && color === "secondary",
21
+ "border-border-base bg-bg-base text-fg-base hover:bg-bg-subtle active:bg-bg-mute": variant === "outlined" && color === "gray",
22
+ "border-transparent bg-transparent text-fg-mute hover:bg-bg-subtle hover:text-fg-base active:bg-bg-mute active:text-fg-base": variant === "skeleton"
21
23
  }, "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info", size === "sm" && "px-3 py-1 text-sm", size === "md" && "px-4 py-2 text-md", size === "lg" && "px-6 py-3 text-lg", Boolean(startIcon ?? endIcon) && "flex items-center gap-2", Boolean(endIcon) && "justify-between", active && "text-fg-info hover:text-fg-info active:text-fg-info"),
22
24
  "aria-label": children,
23
25
  ...props,
@@ -11,7 +11,7 @@ const AccordionButton = ({ children }) => {
11
11
  return /* @__PURE__ */ jsxs("button", {
12
12
  "aria-controls": `${id}-panel`,
13
13
  "aria-expanded": open,
14
- className: cn("flex w-full cursor-pointer items-center justify-between rounded-md p-4 text-fg-base", "hover:text-primary-fg", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-info"),
14
+ className: cn("flex w-full cursor-pointer items-center justify-between rounded-md p-4 text-fg-base transition-colors", "hover:bg-primary-bg-subtle hover:text-primary-fg", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-info"),
15
15
  id: `${id}-button`,
16
16
  onClick: toggleOpen,
17
17
  type: "button",
@@ -1,8 +1,8 @@
1
1
  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
- const Card = ({ children, variant = "primary", width = "full", appearance = "shadow" }) => /* @__PURE__ */ jsx("div", {
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"),
4
+ const Card = ({ children, width = "full", appearance = "shadow" }) => /* @__PURE__ */ jsx("div", {
5
+ className: cn("rounded-xl", appearance === "shadow" && "shadow-sm dark:border dark:border-border-mute", appearance === "bordered" && "border border-border-mute", width === "full" && "w-full", width === "fit" && "w-fit", "bg-bg-raised"),
6
6
  children
7
7
  });
8
8
  //#endregion
@@ -1,8 +1,8 @@
1
1
  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
- const InteractiveCard = ({ children, variant = "primary", width = "full", appearance = "shadow" }) => /* @__PURE__ */ jsx("div", {
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"),
4
+ const InteractiveCard = ({ children, width = "full", appearance = "shadow" }) => /* @__PURE__ */ jsx("div", {
5
+ className: cn("rounded-xl transition-transform hover:scale-[1.02] active:scale-[0.98]", appearance === "shadow" && "shadow-sm dark:border dark:border-border-mute", appearance === "bordered" && "border border-border-mute", width === "full" && "w-full", width === "fit" && "w-fit", "bg-bg-raised"),
6
6
  children
7
7
  });
8
8
  //#endregion
@@ -2,7 +2,6 @@ import { PropsWithChildren } from "react";
2
2
 
3
3
  //#region src/components/data-display/card/type.d.ts
4
4
  type CardProps = PropsWithChildren<{
5
- variant?: 'primary' | 'secondary';
6
5
  width?: 'full' | 'fit';
7
6
  appearance?: 'shadow' | 'bordered';
8
7
  }>;
@@ -142,7 +142,7 @@ const Autocomplete = ({ id, name, describedbyId, isInvalid, isDisabled, isRequir
142
142
  /* @__PURE__ */ jsx("div", {
143
143
  className: "relative w-full",
144
144
  children: open && /* @__PURE__ */ jsx("div", {
145
- className: "absolute top-1 z-10 w-full rounded-xl border border-border-mute bg-bg-base shadow-md",
145
+ className: "absolute top-1 z-10 w-full rounded-xl bg-bg-raised shadow-md",
146
146
  role: "presentation",
147
147
  children: /* @__PURE__ */ jsxs("ul", {
148
148
  className: "max-h-96 py-2",
@@ -153,7 +153,7 @@ const Autocomplete = ({ id, name, describedbyId, isInvalid, isDisabled, isRequir
153
153
  }), filteredOptions.map((option, idx) => {
154
154
  const selected = currentValue.includes(option.value);
155
155
  return /* @__PURE__ */ jsx("li", {
156
- className: cn("cursor-pointer px-3 py-2", selected && "bg-bg-mute", selectIndex === idx && !selected && "bg-bg-emphasize", selectIndex === idx && selected && "bg-bg-mute"),
156
+ 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"),
157
157
  id: `${id}_option_${option.value}`,
158
158
  onClick: (e) => {
159
159
  e.stopPropagation();
@@ -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-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"),
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-primary-border bg-primary-bg-subtle hover:bg-primary-bg-mute", isInvalid ? "border-border-error" : !checked && "border-border-mute hover:bg-bg-subtle", 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", {
@@ -55,7 +55,7 @@ const CheckboxCard = ({ labelId, name, isDisabled, isInvalid = false, options, v
55
55
  }),
56
56
  /* @__PURE__ */ jsx("span", {
57
57
  "aria-hidden": true,
58
- className: cn("mt-0.5 ml-4 inline-flex size-5 shrink-0 items-center justify-center rounded-md border", checked ? "border-border-info bg-primary-bg text-fg-base" : "border-border-mute bg-bg-base text-transparent"),
58
+ className: cn("mt-0.5 ml-4 inline-flex size-5 shrink-0 items-center justify-center rounded-md border", checked ? "border-border-base bg-primary-bg text-fg-base" : "border-border-mute bg-bg-base text-transparent"),
59
59
  children: /* @__PURE__ */ jsx(CheckIcon, { size: "sm" })
60
60
  })
61
61
  ]
@@ -10,7 +10,7 @@ const FormControl = ({ isDisabled = false, isInvalid = false, isRequired = false
10
10
  className: "flex w-full flex-col",
11
11
  children: [
12
12
  labelAs === "label" ? /* @__PURE__ */ jsxs("label", {
13
- className: "mb-1 flex gap-2 font-bold text-fg-base text-md",
13
+ className: "mb-1 flex gap-2 pl-0.5 font-bold text-fg-base text-md",
14
14
  htmlFor: id,
15
15
  id: labelId,
16
16
  children: [label, isRequired && /* @__PURE__ */ jsx("span", {
@@ -18,7 +18,7 @@ const FormControl = ({ isDisabled = false, isInvalid = false, isRequired = false
18
18
  children: "必須"
19
19
  })]
20
20
  }) : /* @__PURE__ */ jsxs("legend", {
21
- className: "mb-1 flex gap-2 font-bold text-fg-base text-md",
21
+ className: "mb-1 flex gap-2 pl-0.5 font-bold text-fg-base text-md",
22
22
  children: [label, isRequired && /* @__PURE__ */ jsx("span", {
23
23
  className: "font-medium text-fg-error",
24
24
  children: "必須"
@@ -34,11 +34,11 @@ const FormControl = ({ isDisabled = false, isInvalid = false, isRequired = false
34
34
  }),
35
35
  isInvalid && errorText ? /* @__PURE__ */ jsx("p", {
36
36
  "aria-live": "polite",
37
- className: "text-fg-error text-sm",
37
+ className: "mt-1 pl-0.5 text-fg-error text-sm",
38
38
  id: `${id}-feedback`,
39
39
  children: errorText
40
40
  }) : helpText && /* @__PURE__ */ jsx("p", {
41
- className: "text-fg-mute text-sm",
41
+ className: "mt-1 pl-0.5 text-fg-mute text-sm",
42
42
  id: `${id}-helptext`,
43
43
  children: helpText
44
44
  })
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../../../helpers/cn.mjs";
3
- import { MinusIcon, PlusIcon } from "../../icons/lucide.mjs";
3
+ import { ChevronIcon } from "../../icons/lucide.mjs";
4
4
  import { between } from "../../../helpers/number/between.mjs";
5
5
  import { toPrecision } from "../../../helpers/number/to-precision.mjs";
6
6
  import { cast } from "../../../helpers/number/cast.mjs";
@@ -66,9 +66,9 @@ const NumberField = ({ id, name, describedbyId, isInvalid, isDisabled, isRequire
66
66
  value: displayValue
67
67
  }), /* @__PURE__ */ jsxs("div", {
68
68
  "aria-hidden": "true",
69
- className: "absolute right-0 flex h-full flex-col",
69
+ className: "absolute right-1 flex h-full flex-col py-1",
70
70
  children: [/* @__PURE__ */ jsxs("button", {
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"),
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 disabled:hover:bg-transparent"),
72
72
  disabled: isDisabled,
73
73
  onClick: () => {
74
74
  const newValue = between(toPrecision(cast(displayValue, precision) + step, precision), min, max);
@@ -80,9 +80,12 @@ const NumberField = ({ id, name, describedbyId, isInvalid, isDisabled, isRequire
80
80
  children: [/* @__PURE__ */ jsx("span", {
81
81
  className: "sr-only",
82
82
  children: "増やす"
83
- }), /* @__PURE__ */ jsx(PlusIcon, { size: "sm" })]
83
+ }), /* @__PURE__ */ jsx(ChevronIcon, {
84
+ direction: "up",
85
+ size: "sm"
86
+ })]
84
87
  }), /* @__PURE__ */ jsxs("button", {
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"),
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 disabled:hover:bg-transparent"),
86
89
  disabled: isDisabled,
87
90
  onClick: () => {
88
91
  const newValue = between(toPrecision(cast(displayValue, precision) - step, precision), min, max);
@@ -94,7 +97,10 @@ const NumberField = ({ id, name, describedbyId, isInvalid, isDisabled, isRequire
94
97
  children: [/* @__PURE__ */ jsx("span", {
95
98
  className: "sr-only",
96
99
  children: "減らす"
97
- }), /* @__PURE__ */ jsx(MinusIcon, { size: "sm" })]
100
+ }), /* @__PURE__ */ jsx(ChevronIcon, {
101
+ direction: "down",
102
+ size: "sm"
103
+ })]
98
104
  })]
99
105
  })]
100
106
  });
@@ -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-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"),
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-primary-border bg-primary-bg-subtle hover:bg-primary-bg-mute", isInvalid ? "border-border-error" : !checked && "border-border-mute hover:bg-bg-subtle", disabled && "cursor-not-allowed border-border-mute bg-bg-subtle text-fg-mute"),
40
40
  disabled,
41
41
  id: optionId,
42
42
  onClick: () => {
@@ -34,7 +34,7 @@ const Switch = ({ value, defaultChecked, describedbyId, id, isDisabled, isInvali
34
34
  type: "checkbox"
35
35
  }), /* @__PURE__ */ jsx("span", {
36
36
  "aria-hidden": true,
37
- className: cn("inline-flex h-7 w-12 items-center rounded-full border transition-colors", isInvalid && "border-border-error", isSelected ? "border-border-base bg-primary-bg" : "border-border-mute bg-bg-mute", isDisabled && "border-border-mute bg-bg-subtle", "peer-focus-visible:outline-hidden peer-focus-visible:ring-2 peer-focus-visible:ring-border-info peer-focus-visible:ring-offset-2"),
37
+ className: cn("inline-flex h-7 w-12 items-center rounded-full transition-colors", isInvalid && "ring-2 ring-border-error", isSelected ? "bg-primary-bg" : "bg-bg-mute", isDisabled && "bg-bg-subtle", "peer-focus-visible:outline-hidden peer-focus-visible:ring-2 peer-focus-visible:ring-border-info peer-focus-visible:ring-offset-2"),
38
38
  children: /* @__PURE__ */ jsx("span", { className: cn("ml-0.5 size-5 rounded-full bg-bg-base shadow-xs transition-transform", isSelected && "translate-x-5", isDisabled && "bg-bg-emphasize") })
39
39
  })]
40
40
  }), label ? /* @__PURE__ */ jsx("span", { children: label }) : null]
@@ -41,6 +41,7 @@ import { ScrollLinked } from "./layout/scroll-linked/scroll-linked.mjs";
41
41
  import { Separator } from "./layout/separator/separator.mjs";
42
42
  import { Anchor } from "./navigation/anchor/anchor.mjs";
43
43
  import { Breadcrumb } from "./navigation/breadcrumb/breadcrumb.mjs";
44
+ import { Pagination } from "./navigation/pagination/pagination.mjs";
44
45
  import { Tabs } from "./navigation/tabs/tabs.mjs";
45
46
  import { Dialog } from "./overlays/dialog/dialog.mjs";
46
47
  import { Drawer } from "./overlays/drawer/drawer.mjs";
@@ -52,4 +53,4 @@ import { Popover } from "./overlays/popover/popover.mjs";
52
53
  import { Tooltip } from "./overlays/tooltip/tooltip.mjs";
53
54
  import { ArteOdysseyProvider } from "./providers/arte-odyssey-provider.mjs";
54
55
  import { PortalRootProvider, usePortalRoot } from "./providers/portal-root.mjs";
55
- export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, useOpenContext, usePortalRoot, useToast };
56
+ export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, Pagination, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, useOpenContext, usePortalRoot, useToast };
@@ -41,6 +41,7 @@ import { ScrollLinked } from "./layout/scroll-linked/scroll-linked.mjs";
41
41
  import { Separator } from "./layout/separator/separator.mjs";
42
42
  import { Anchor } from "./navigation/anchor/anchor.mjs";
43
43
  import { Breadcrumb } from "./navigation/breadcrumb/breadcrumb.mjs";
44
+ import { Pagination } from "./navigation/pagination/pagination.mjs";
44
45
  import { Tabs } from "./navigation/tabs/tabs.mjs";
45
46
  import { Dialog } from "./overlays/dialog/dialog.mjs";
46
47
  import { Modal } from "./overlays/modal/modal.mjs";
@@ -52,4 +53,4 @@ import { Popover } from "./overlays/popover/popover.mjs";
52
53
  import { DropdownMenu } from "./overlays/dropdown-menu/dropdown-menu.mjs";
53
54
  import { ListBox } from "./overlays/list-box/list-box.mjs";
54
55
  import { Tooltip } from "./overlays/tooltip/tooltip.mjs";
55
- export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, useOpenContext, usePortalRoot, useToast };
56
+ export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, Pagination, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, useOpenContext, usePortalRoot, useToast };
@@ -0,0 +1,2 @@
1
+ import { Pagination } from "./pagination.mjs";
2
+ export { Pagination };
@@ -0,0 +1,2 @@
1
+ import { Pagination } from "./pagination.mjs";
2
+ export { Pagination };
@@ -0,0 +1,15 @@
1
+ import { FC } from "react";
2
+
3
+ //#region src/components/navigation/pagination/pagination.d.ts
4
+ type Props = {
5
+ totalPages: number;
6
+ currentPage: number;
7
+ onPageChange: (page: number) => void;
8
+ isDisabled?: boolean;
9
+ prevLabel?: string;
10
+ nextLabel?: string;
11
+ 'aria-label'?: string;
12
+ };
13
+ declare const Pagination: FC<Props>;
14
+ //#endregion
15
+ export { Pagination };
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { Button } from "../../buttons/button/button.mjs";
3
+ import { ChevronIcon } from "../../icons/lucide.mjs";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/components/navigation/pagination/pagination.tsx
6
+ const Pagination = ({ totalPages, currentPage, onPageChange, isDisabled = false, prevLabel = "前へ", nextLabel = "次へ", "aria-label": ariaLabel = "ページネーション" }) => {
7
+ const safeTotal = Math.max(1, totalPages);
8
+ const safeCurrent = Math.min(Math.max(1, currentPage), safeTotal);
9
+ const isFirst = safeCurrent <= 1;
10
+ const isLast = safeCurrent >= safeTotal;
11
+ return /* @__PURE__ */ jsx("nav", {
12
+ "aria-label": ariaLabel,
13
+ children: /* @__PURE__ */ jsxs("div", {
14
+ className: "flex items-center justify-center gap-2",
15
+ children: [
16
+ /* @__PURE__ */ jsx(Button, {
17
+ color: "gray",
18
+ disabled: isDisabled || isFirst,
19
+ onClick: () => {
20
+ onPageChange(safeCurrent - 1);
21
+ },
22
+ size: "sm",
23
+ startIcon: /* @__PURE__ */ jsx(ChevronIcon, {
24
+ direction: "left",
25
+ size: "sm"
26
+ }),
27
+ variant: "skeleton",
28
+ children: prevLabel
29
+ }),
30
+ /* @__PURE__ */ jsxs("p", {
31
+ "aria-current": "page",
32
+ "aria-live": "polite",
33
+ className: "px-3 text-fg-mute text-sm tabular-nums",
34
+ children: [
35
+ /* @__PURE__ */ jsx("span", {
36
+ className: "text-fg-base",
37
+ children: safeCurrent
38
+ }),
39
+ /* @__PURE__ */ jsx("span", {
40
+ className: "mx-1",
41
+ children: "/"
42
+ }),
43
+ /* @__PURE__ */ jsx("span", { children: safeTotal })
44
+ ]
45
+ }),
46
+ /* @__PURE__ */ jsx(Button, {
47
+ color: "gray",
48
+ disabled: isDisabled || isLast,
49
+ endIcon: /* @__PURE__ */ jsx(ChevronIcon, {
50
+ direction: "right",
51
+ size: "sm"
52
+ }),
53
+ onClick: () => {
54
+ onPageChange(safeCurrent + 1);
55
+ },
56
+ size: "sm",
57
+ variant: "skeleton",
58
+ children: nextLabel
59
+ })
60
+ ]
61
+ })
62
+ });
63
+ };
64
+ //#endregion
65
+ export { Pagination };
@@ -66,7 +66,7 @@ const Tab = ({ id, children }) => {
66
66
  return /* @__PURE__ */ jsxs("div", {
67
67
  "aria-controls": selectedId === id ? `${rootId}-panel-${id}` : void 0,
68
68
  "aria-selected": selectedId === id,
69
- className: cn("relative cursor-pointer rounded-lg p-2 transition-colors", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
69
+ className: cn("relative cursor-pointer rounded-lg p-2 transition-colors", selectedId !== id && "hover:bg-primary-bg-subtle hover:text-primary-fg", "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info"),
70
70
  id: `${rootId}-tab-${id}`,
71
71
  onClick: () => {
72
72
  setSelectedId(id);
@@ -17,7 +17,7 @@ const Root = ({ ref, id, children, tabIndex, role = "dialog" }) => {
17
17
  return /* @__PURE__ */ jsx("section", {
18
18
  "aria-describedby": `${rootId}-content`,
19
19
  "aria-labelledby": `${rootId}-header`,
20
- className: "relative w-full rounded-lg border border-border-mute bg-bg-base shadow-md",
20
+ className: "relative w-full rounded-lg bg-bg-raised shadow-md",
21
21
  id,
22
22
  ref,
23
23
  role,
@@ -44,7 +44,7 @@ const Content = ({ children }) => {
44
44
  children: /* @__PURE__ */ jsx(Popover.Content, { renderItem: (props) => /* @__PURE__ */ jsx("div", {
45
45
  ...props,
46
46
  ...contentProps,
47
- className: "flex min-w-40 flex-col rounded-lg border border-border-mute bg-bg-base py-2 shadow-md",
47
+ className: "flex min-w-40 flex-col rounded-lg bg-bg-raised py-2 shadow-md",
48
48
  children
49
49
  }) })
50
50
  });
@@ -52,7 +52,7 @@ const Content = ({ children }) => {
52
52
  const Item = ({ label, onClick }) => {
53
53
  const props = useMenuItem({ onClick });
54
54
  return /* @__PURE__ */ jsx("button", {
55
- className: cn("w-full px-2 py-1 text-left transition-colors", "hover:bg-bg-mute", "focus-visible:bg-bg-mute focus-visible:outline-none"),
55
+ className: cn("w-full px-2 py-1 text-left transition-colors", "hover:bg-bg-subtle", "focus-visible:bg-bg-subtle focus-visible:outline-none"),
56
56
  ...props,
57
57
  children: label
58
58
  });
@@ -59,7 +59,7 @@ const Content = ({ helpContent }) => {
59
59
  children: /* @__PURE__ */ jsx(Popover.Content, { renderItem: (props) => /* @__PURE__ */ jsxs("div", {
60
60
  ...props,
61
61
  ...contentProps,
62
- className: "flex max-h-48 min-w-40 flex-col overflow-y-auto rounded-lg border border-border-mute bg-bg-base py-2 shadow-md",
62
+ className: "flex max-h-48 min-w-40 flex-col overflow-y-auto rounded-lg bg-bg-raised py-2 shadow-md",
63
63
  children: [helpContent, options.map(({ key, label }, idx) => /* @__PURE__ */ jsx(Item, {
64
64
  index: idx,
65
65
  label
@@ -70,7 +70,7 @@ const Content = ({ helpContent }) => {
70
70
  const Item = ({ label, index }) => {
71
71
  const { props, selected } = useMenuItem(index);
72
72
  return /* @__PURE__ */ jsxs("button", {
73
- className: cn("flex w-full items-center justify-between px-3 py-2 text-left transition-colors", "hover:bg-bg-mute", "focus-visible:border-transparent focus-visible:bg-bg-mute focus-visible:outline-hidden"),
73
+ className: cn("flex w-full items-center justify-between px-3 py-2 text-left transition-colors", "hover:bg-bg-subtle", "focus-visible:border-transparent focus-visible:bg-bg-subtle focus-visible:outline-hidden"),
74
74
  ...props,
75
75
  children: [label, selected && /* @__PURE__ */ jsx("span", {
76
76
  className: "text-fg-success",
@@ -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 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"),
143
+ className: cn("bg-bg-raised text-fg-base shadow-md backdrop:bg-back-drop", type === "center" && "m-auto max-h-lg w-5/6 max-w-2xl rounded-lg", type === "bottom" && "mt-auto w-screen max-w-screen rounded-t-lg", type === "right" && "ml-auto h-svh max-h-none w-screen max-w-sm rounded-l-lg", type === "left" && "mr-auto h-svh max-h-none w-screen max-w-sm rounded-r-lg"),
144
144
  exit: "closed",
145
145
  initial: "closed",
146
146
  onClick: (e) => {
@@ -39,7 +39,7 @@ const Content = ({ children }) => {
39
39
  },
40
40
  renderItem: (props) => /* @__PURE__ */ jsx("div", {
41
41
  ...props,
42
- className: "rounded-lg border border-border-mute bg-bg-base px-4 py-2 shadow-md",
42
+ className: "rounded-lg bg-bg-inverse px-4 py-2 text-fg-inverse shadow-md",
43
43
  children
44
44
  })
45
45
  });
@@ -1,8 +1,14 @@
1
+ import { RefObject } from "react";
2
+
1
3
  //#region src/hooks/scroll-direction/index.d.ts
2
4
  type ScrollDirection = {
3
5
  x: 'left' | 'right';
4
6
  y: 'up' | 'down';
5
7
  };
6
- declare const useScrollDirection: (threshold?: number) => ScrollDirection;
8
+ type UseScrollDirectionOptions = {
9
+ threshold?: number;
10
+ target?: RefObject<HTMLElement | null>;
11
+ };
12
+ declare const useScrollDirection: (options?: UseScrollDirectionOptions) => ScrollDirection;
7
13
  //#endregion
8
14
  export { useScrollDirection };
@@ -6,7 +6,8 @@ const SERVER_SNAPSHOT = {
6
6
  y: "up"
7
7
  };
8
8
  const getServerSnapshot = () => SERVER_SNAPSHOT;
9
- const useScrollDirection = (threshold = 50) => {
9
+ const useScrollDirection = (options = {}) => {
10
+ const { threshold = 50, target } = options;
10
11
  const stateRef = useRef({
11
12
  direction: {
12
13
  x: "right",
@@ -16,9 +17,20 @@ const useScrollDirection = (threshold = 50) => {
16
17
  prevScrollY: 0
17
18
  });
18
19
  const subscribe = useCallback((callback) => {
20
+ const element = target?.current ?? null;
21
+ const eventTarget = element ?? window;
22
+ const getScroll = () => {
23
+ if (element) return {
24
+ x: element.scrollLeft,
25
+ y: element.scrollTop
26
+ };
27
+ return {
28
+ x: window.scrollX,
29
+ y: window.scrollY
30
+ };
31
+ };
19
32
  const handleScroll = () => {
20
- const currentScrollY = window.scrollY;
21
- const currentScrollX = window.scrollX;
33
+ const { x: currentScrollX, y: currentScrollY } = getScroll();
22
34
  const state = stateRef.current;
23
35
  let changed = false;
24
36
  const newDirection = { ...state.direction };
@@ -51,11 +63,11 @@ const useScrollDirection = (threshold = 50) => {
51
63
  };
52
64
  if (changed) callback();
53
65
  };
54
- window.addEventListener("scroll", handleScroll, { passive: true });
66
+ eventTarget.addEventListener("scroll", handleScroll, { passive: true });
55
67
  return () => {
56
- window.removeEventListener("scroll", handleScroll);
68
+ eventTarget.removeEventListener("scroll", handleScroll);
57
69
  };
58
- }, [threshold]);
70
+ }, [threshold, target]);
59
71
  const getSnapshot = () => stateRef.current.direction;
60
72
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
61
73
  };
@@ -1,8 +1,10 @@
1
+ import { RefObject } from "react";
2
+
1
3
  //#region src/hooks/scroll-lock/index.d.ts
2
4
  type UseScrollLockReturn = {
3
5
  lock: () => void;
4
6
  unlock: () => void;
5
7
  };
6
- declare const useScrollLock: () => UseScrollLockReturn;
8
+ declare const useScrollLock: (target?: RefObject<HTMLElement | null>) => UseScrollLockReturn;
7
9
  //#endregion
8
10
  export { useScrollLock };
@@ -1,36 +1,55 @@
1
1
  "use client";
2
2
  import { useCallback, useEffect, useRef } from "react";
3
3
  //#region src/hooks/scroll-lock/index.ts
4
- let lockCount = 0;
5
- let originalOverflow = null;
6
- const useScrollLock = () => {
4
+ const lockRegistry = /* @__PURE__ */ new WeakMap();
5
+ const resolveTarget = (target) => {
6
+ return target?.current ?? document.body;
7
+ };
8
+ const useScrollLock = (target) => {
7
9
  const isLockedRef = useRef(false);
10
+ const lockedElementRef = useRef(null);
8
11
  const lock = useCallback(() => {
9
12
  if (isLockedRef.current) return;
13
+ const element = resolveTarget(target);
10
14
  isLockedRef.current = true;
11
- if (lockCount === 0) {
12
- originalOverflow = document.body.style.overflow;
13
- document.body.style.overflow = "hidden";
15
+ lockedElementRef.current = element;
16
+ const entry = lockRegistry.get(element);
17
+ if (entry) entry.count++;
18
+ else {
19
+ lockRegistry.set(element, {
20
+ count: 1,
21
+ originalOverflow: element.style.overflow
22
+ });
23
+ element.style.overflow = "hidden";
14
24
  }
15
- lockCount++;
16
- }, []);
25
+ }, [target]);
17
26
  const unlock = useCallback(() => {
18
27
  if (!isLockedRef.current) return;
28
+ const element = lockedElementRef.current;
29
+ if (!element) return;
19
30
  isLockedRef.current = false;
20
- lockCount--;
21
- if (lockCount === 0 && originalOverflow !== null) {
22
- document.body.style.overflow = originalOverflow;
23
- originalOverflow = null;
31
+ lockedElementRef.current = null;
32
+ const entry = lockRegistry.get(element);
33
+ if (!entry) return;
34
+ entry.count--;
35
+ if (entry.count === 0) {
36
+ element.style.overflow = entry.originalOverflow;
37
+ lockRegistry.delete(element);
24
38
  }
25
39
  }, []);
26
40
  useEffect(() => {
27
41
  return () => {
28
42
  if (isLockedRef.current) {
43
+ const element = lockedElementRef.current;
29
44
  isLockedRef.current = false;
30
- lockCount--;
31
- if (lockCount === 0 && originalOverflow !== null) {
32
- document.body.style.overflow = originalOverflow;
33
- originalOverflow = null;
45
+ lockedElementRef.current = null;
46
+ if (!element) return;
47
+ const entry = lockRegistry.get(element);
48
+ if (!entry) return;
49
+ entry.count--;
50
+ if (entry.count === 0) {
51
+ element.style.overflow = entry.originalOverflow;
52
+ lockRegistry.delete(element);
34
53
  }
35
54
  }
36
55
  };
package/dist/index.d.mts CHANGED
@@ -42,6 +42,7 @@ import { ScrollLinked } from "./components/layout/scroll-linked/scroll-linked.mj
42
42
  import { Separator } from "./components/layout/separator/separator.mjs";
43
43
  import { Anchor } from "./components/navigation/anchor/anchor.mjs";
44
44
  import { Breadcrumb } from "./components/navigation/breadcrumb/breadcrumb.mjs";
45
+ import { Pagination } from "./components/navigation/pagination/pagination.mjs";
45
46
  import { Tabs } from "./components/navigation/tabs/tabs.mjs";
46
47
  import { Dialog } from "./components/overlays/dialog/dialog.mjs";
47
48
  import { Drawer } from "./components/overlays/drawer/drawer.mjs";
@@ -83,4 +84,4 @@ import { useThrottle, useThrottledCallback } from "./hooks/throttle/index.mjs";
83
84
  import { useTimeout } from "./hooks/timeout/index.mjs";
84
85
  import { useWindowResize } from "./hooks/window-resize/index.mjs";
85
86
  import { useWindowSize } from "./hooks/window-size/index.mjs";
86
- export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Direction, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, Option, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, Status, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, between, cast, cn, commalize, findAllColors, isInternalRoute, toPrecision, useBreakpoint, useClickAway, useClient, useClipboard, useControllableState, useDebounce, useDebouncedCallback, useDisclosure, useHash, useHover, useInView, useIntersectionObserver, useInterval, useLocalStorage, useOpenContext, usePortalRoot, useResize, useScrollDirection, useScrollLock, useSessionStorage, useStep, useThrottle, useThrottledCallback, useTimeout, useToast, useWindowResize, useWindowSize, uuidV4 };
87
+ export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Direction, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, Option, Pagination, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, Status, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, between, cast, cn, commalize, findAllColors, isInternalRoute, toPrecision, useBreakpoint, useClickAway, useClient, useClipboard, useControllableState, useDebounce, useDebouncedCallback, useDisclosure, useHash, useHover, useInView, useIntersectionObserver, useInterval, useLocalStorage, useOpenContext, usePortalRoot, useResize, useScrollDirection, useScrollLock, useSessionStorage, useStep, useThrottle, useThrottledCallback, useTimeout, useToast, useWindowResize, useWindowSize, uuidV4 };
package/dist/index.mjs CHANGED
@@ -52,6 +52,7 @@ import { ScrollLinked } from "./components/layout/scroll-linked/scroll-linked.mj
52
52
  import { Separator } from "./components/layout/separator/separator.mjs";
53
53
  import { Anchor } from "./components/navigation/anchor/anchor.mjs";
54
54
  import { Breadcrumb } from "./components/navigation/breadcrumb/breadcrumb.mjs";
55
+ import { Pagination } from "./components/navigation/pagination/pagination.mjs";
55
56
  import { Tabs } from "./components/navigation/tabs/tabs.mjs";
56
57
  import { Dialog } from "./components/overlays/dialog/dialog.mjs";
57
58
  import { Modal } from "./components/overlays/modal/modal.mjs";
@@ -82,4 +83,4 @@ import { useStep } from "./hooks/step/index.mjs";
82
83
  import { useThrottle, useThrottledCallback } from "./hooks/throttle/index.mjs";
83
84
  import { useWindowResize } from "./hooks/window-resize/index.mjs";
84
85
  import { useWindowSize } from "./hooks/window-size/index.mjs";
85
- export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, between, cast, cn, commalize, findAllColors, isInternalRoute, toPrecision, useBreakpoint, useClickAway, useClient, useClipboard, useControllableState, useDebounce, useDebouncedCallback, useDisclosure, useHash, useHover, useInView, useIntersectionObserver, useInterval, useLocalStorage, useOpenContext, usePortalRoot, useResize, useScrollDirection, useScrollLock, useSessionStorage, useStep, useThrottle, useThrottledCallback, useTimeout, useToast, useWindowResize, useWindowSize, uuidV4 };
86
+ export { AIIcon, AccessibilityIcon, Accordion, Alert, AlertIcon, Anchor, ArteOdyssey, ArteOdysseyProvider, AtomIcon, Autocomplete, Avatar, BadIcon, Badge, BaselineStatus, BlogIcon, BoringIcon, Breadcrumb, Button, Card, CheckIcon, Checkbox, CheckboxCard, CheckboxGroup, ChevronIcon, CloseIcon, Code, ColorContrastIcon, ColorInfoIcon, CopyIcon, DarkModeIcon, Dialog, DifficultIcon, Drawer, DropdownMenu, EasyIcon, ExternalLinkIcon, FileField, FormControl, FormIcon, GitHubIcon, GoodIcon, Heading, HistoryIcon, IconButton, IconLink, InformativeIcon, InteractiveCard, InterestingIcon, LightModeIcon, LinkButton, LinkIcon, ListBox, ListIcon, LocationIcon, Logo, LogoIcon, MailIcon, MinusIcon, MixedColorIcon, Modal, NavigationMenuIcon, NewsIcon, NumberField, Pagination, PaletteIcon, PasswordInput, PlusIcon, Popover, PortalRootProvider, PrepareIcon, Progress, PublishDateIcon, QiitaIcon, RSSIcon, Radio, RadioCard, ScrollLinked, Select, SendIcon, Separator, ShallowIcon, ShieldCheckIcon, Skeleton, SlideIcon, Slider, SparklesIcon, Spinner, SubscribeIcon, Switch, Table, TableIcon, Tabs, TagIcon, TextField, Textarea, ToastProvider, Tooltip, TwitterIcon, UpdateDateIcon, ViewIcon, ViewOffIcon, between, cast, cn, commalize, findAllColors, isInternalRoute, toPrecision, useBreakpoint, useClickAway, useClient, useClipboard, useControllableState, useDebounce, useDebouncedCallback, useDisclosure, useHash, useHover, useInView, useIntersectionObserver, useInterval, useLocalStorage, useOpenContext, usePortalRoot, useResize, useScrollDirection, useScrollLock, useSessionStorage, useStep, useThrottle, useThrottledCallback, useTimeout, useToast, useWindowResize, useWindowSize, uuidV4 };
@@ -222,6 +222,7 @@
222
222
  --fg-error: var(--red-800);
223
223
 
224
224
  --bg-base: var(--white);
225
+ --bg-raised: var(--white);
225
226
  --bg-surface: var(--gray-50);
226
227
  --bg-subtle: var(--gray-100);
227
228
  --bg-mute: var(--gray-200);
@@ -275,6 +276,7 @@
275
276
  --fg-error: var(--red-200);
276
277
 
277
278
  --bg-base: var(--gray-900);
279
+ --bg-raised: var(--gray-800);
278
280
  --bg-surface: var(--gray-950);
279
281
  --bg-subtle: var(--gray-800);
280
282
  --bg-mute: var(--gray-700);
@@ -328,6 +330,7 @@
328
330
  --color-fg-error: var(--fg-error);
329
331
 
330
332
  --color-bg-base: var(--bg-base);
333
+ --color-bg-raised: var(--bg-raised);
331
334
  --color-bg-surface: var(--bg-surface);
332
335
  --color-bg-subtle: var(--bg-subtle);
333
336
  --color-bg-mute: var(--bg-mute);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k8o/arte-odyssey",
3
- "version": "5.0.4",
3
+ "version": "6.0.0",
4
4
  "description": "k8o's react ui library",
5
5
  "keywords": [
6
6
  "components",
@@ -52,6 +52,7 @@
52
52
  "@chromatic-com/storybook": "5.1.1",
53
53
  "@storybook/addon-a11y": "10.3.4",
54
54
  "@storybook/addon-docs": "10.3.4",
55
+ "@storybook/addon-mcp": "0.5.0",
55
56
  "@storybook/addon-vitest": "10.3.4",
56
57
  "@storybook/react-vite": "10.3.4",
57
58
  "@tailwindcss/postcss": "4.2.2",