@k8o/arte-odyssey 1.2.0 → 1.4.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.
@@ -13,7 +13,7 @@ const IconButton = ({
13
13
  {
14
14
  "aria-label": props.role ? label : void 0,
15
15
  className: cn(
16
- "inline-flex rounded-full bg-transparent",
16
+ "inline-flex cursor-pointer rounded-full bg-transparent",
17
17
  "hover:bg-bg-subtle",
18
18
  "focus-visible:border-transparent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-border-info active:bg-bg-emphasize",
19
19
  bg === "base" && "bg-bg-base/90",
@@ -1,2 +1,4 @@
1
- import type { FC } from 'react';
2
- export declare const ScrollLinked: FC;
1
+ import type { FC, RefObject } from 'react';
2
+ export declare const ScrollLinked: FC<{
3
+ container?: RefObject<HTMLElement | null>;
4
+ }>;
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { motion, useScroll, useSpring } from "motion/react";
4
- const ScrollLinked = () => {
5
- const { scrollYProgress } = useScroll();
4
+ const ScrollLinked = ({ container }) => {
5
+ const { scrollYProgress } = useScroll({ container });
6
6
  const scaleX = useSpring(scrollYProgress, {
7
7
  stiffness: 100,
8
8
  damping: 30,
@@ -1,4 +1,5 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useRef } from "react";
2
3
  import { ScrollLinked } from "./scroll-linked";
3
4
  const meta = {
4
5
  title: "components/scroll-linked",
@@ -11,8 +12,40 @@ const Scroll = {
11
12
  (Story) => /* @__PURE__ */ jsx("div", { className: "h-lvh overflow-y-scroll", children: /* @__PURE__ */ jsx(Story, {}) })
12
13
  ]
13
14
  };
15
+ const WithContainer = {
16
+ decorators: [
17
+ (Story) => {
18
+ const containerRef = useRef(null);
19
+ return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
20
+ "section",
21
+ {
22
+ "aria-label": "Scrollable container example",
23
+ className: "relative h-96 overflow-y-scroll rounded border border-gray-300",
24
+ ref: containerRef,
25
+ tabIndex: 0,
26
+ children: [
27
+ /* @__PURE__ */ jsx(Story, { args: { container: containerRef } }),
28
+ /* @__PURE__ */ jsxs("div", { className: "h-[200vh] p-4", children: [
29
+ /* @__PURE__ */ jsx("h2", { className: "mb-4 font-bold text-xl", children: "Container Scroll Example" }),
30
+ /* @__PURE__ */ jsx("p", { className: "mb-4", children: "Scroll within this container to see the progress bar at the top." }),
31
+ /* @__PURE__ */ jsx("p", { className: "mb-4", children: "The progress bar tracks this container's scroll position, not the window's scroll." }),
32
+ /* @__PURE__ */ jsx("div", { className: "mt-8 space-y-4", children: Array.from({ length: 20 }, (_, i) => (
33
+ // biome-ignore lint/suspicious/noArrayIndexKey: Static demo content
34
+ /* @__PURE__ */ jsxs("p", { className: "rounded bg-gray-100 p-4", children: [
35
+ "Content block ",
36
+ i + 1
37
+ ] }, `content-${i}`)
38
+ )) })
39
+ ] })
40
+ ]
41
+ }
42
+ ) });
43
+ }
44
+ ]
45
+ };
14
46
  export {
15
47
  NoScroll,
16
48
  Scroll,
49
+ WithContainer,
17
50
  scroll_linked_stories_default as default
18
51
  };
@@ -4,7 +4,9 @@ export * from './clipboard';
4
4
  export * from './hash';
5
5
  export * from './interval';
6
6
  export * from './local-storage';
7
+ export * from './resize';
7
8
  export * from './scroll-direction';
8
9
  export * from './step';
9
10
  export * from './timeout';
11
+ export * from './window-resize';
10
12
  export * from './window-size';
@@ -4,7 +4,9 @@ export * from "./clipboard";
4
4
  export * from "./hash";
5
5
  export * from "./interval";
6
6
  export * from "./local-storage";
7
+ export * from "./resize";
7
8
  export * from "./scroll-direction";
8
9
  export * from "./step";
9
10
  export * from "./timeout";
11
+ export * from "./window-resize";
10
12
  export * from "./window-size";
@@ -0,0 +1,7 @@
1
+ import { type RefObject } from 'react';
2
+ type Options = {
3
+ enabled?: boolean;
4
+ debounceMs?: number;
5
+ };
6
+ export declare const useResize: <T extends Element = HTMLElement>(callback: (entry: ResizeObserverEntry) => void, options?: Options) => RefObject<T | null>;
7
+ export {};
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ const useResize = (callback, options = {}) => {
4
+ const { enabled = true, debounceMs } = options;
5
+ const ref = useRef(null);
6
+ const timeoutRef = useRef(null);
7
+ useEffect(() => {
8
+ if (!enabled) return;
9
+ const element = ref.current;
10
+ if (!element) return;
11
+ const observer = new ResizeObserver((entries) => {
12
+ for (const entry of entries) {
13
+ if (debounceMs !== void 0) {
14
+ if (timeoutRef.current) {
15
+ clearTimeout(timeoutRef.current);
16
+ }
17
+ timeoutRef.current = setTimeout(() => {
18
+ callback(entry);
19
+ }, debounceMs);
20
+ } else {
21
+ callback(entry);
22
+ }
23
+ }
24
+ });
25
+ observer.observe(element);
26
+ return () => {
27
+ if (timeoutRef.current) {
28
+ clearTimeout(timeoutRef.current);
29
+ }
30
+ observer.disconnect();
31
+ };
32
+ }, [callback, enabled, debounceMs]);
33
+ return ref;
34
+ };
35
+ export {
36
+ useResize
37
+ };
@@ -0,0 +1,68 @@
1
+ import { renderHook } from "vitest-browser-react";
2
+ import { useResize } from ".";
3
+ describe("useResize", () => {
4
+ it("\u8981\u7D20\u306E\u30EA\u30B5\u30A4\u30BA\u6642\u306B\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u304C\u547C\u3070\u308C\u308B", async () => {
5
+ const callback = vi.fn();
6
+ const { result } = await renderHook(() => useResize(callback));
7
+ const element = document.createElement("div");
8
+ Object.defineProperty(result.current, "current", {
9
+ writable: true,
10
+ value: element
11
+ });
12
+ const observer = new ResizeObserver(callback);
13
+ observer.observe(element);
14
+ const contentRect = new DOMRectReadOnly(0, 0, 100, 100);
15
+ const entry = {
16
+ target: element,
17
+ contentRect,
18
+ borderBoxSize: [],
19
+ contentBoxSize: [],
20
+ devicePixelContentBoxSize: []
21
+ };
22
+ callback(entry);
23
+ expect(callback).toHaveBeenCalled();
24
+ observer.disconnect();
25
+ });
26
+ it("enabled=false\u306E\u5834\u5408\u306F\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u304C\u547C\u3070\u308C\u306A\u3044", async () => {
27
+ const callback = vi.fn();
28
+ const { result } = await renderHook(
29
+ () => useResize(callback, { enabled: false })
30
+ );
31
+ expect(result.current.current).toBeNull();
32
+ expect(callback).not.toHaveBeenCalled();
33
+ });
34
+ it("debounceMs\u6307\u5B9A\u6642\u306F\u6307\u5B9A\u6642\u9593\u5F8C\u306B\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u304C\u547C\u3070\u308C\u308B", async () => {
35
+ vi.useFakeTimers();
36
+ const callback = vi.fn();
37
+ const { result } = await renderHook(
38
+ () => useResize(callback, { debounceMs: 300 })
39
+ );
40
+ const element = document.createElement("div");
41
+ Object.defineProperty(result.current, "current", {
42
+ writable: true,
43
+ value: element
44
+ });
45
+ const observer = new ResizeObserver((entries) => {
46
+ for (const entry2 of entries) {
47
+ callback(entry2);
48
+ }
49
+ });
50
+ observer.observe(element);
51
+ const contentRect = new DOMRectReadOnly(0, 0, 100, 100);
52
+ const entry = {
53
+ target: element,
54
+ contentRect,
55
+ borderBoxSize: [],
56
+ contentBoxSize: [],
57
+ devicePixelContentBoxSize: []
58
+ };
59
+ callback(entry);
60
+ expect(callback).toHaveBeenCalledTimes(1);
61
+ vi.advanceTimersByTime(299);
62
+ expect(callback).toHaveBeenCalledTimes(1);
63
+ vi.advanceTimersByTime(1);
64
+ expect(callback).toHaveBeenCalledTimes(1);
65
+ observer.disconnect();
66
+ vi.useRealTimers();
67
+ });
68
+ });
@@ -0,0 +1,10 @@
1
+ type Size = {
2
+ width: number;
3
+ height: number;
4
+ };
5
+ type Options = {
6
+ enabled?: boolean;
7
+ debounceMs?: number;
8
+ };
9
+ export declare const useWindowResize: (callback: (size: Size) => void, options?: Options) => void;
10
+ export {};
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ const useWindowResize = (callback, options = {}) => {
4
+ const { enabled = true, debounceMs } = options;
5
+ const timeoutRef = useRef(null);
6
+ useEffect(() => {
7
+ if (!enabled) return;
8
+ const handleResize = () => {
9
+ const size = {
10
+ width: window.innerWidth,
11
+ height: window.innerHeight
12
+ };
13
+ if (debounceMs !== void 0) {
14
+ if (timeoutRef.current) {
15
+ clearTimeout(timeoutRef.current);
16
+ }
17
+ timeoutRef.current = setTimeout(() => {
18
+ callback(size);
19
+ }, debounceMs);
20
+ } else {
21
+ callback(size);
22
+ }
23
+ };
24
+ window.addEventListener("resize", handleResize);
25
+ return () => {
26
+ if (timeoutRef.current) {
27
+ clearTimeout(timeoutRef.current);
28
+ }
29
+ window.removeEventListener("resize", handleResize);
30
+ };
31
+ }, [callback, enabled, debounceMs]);
32
+ };
33
+ export {
34
+ useWindowResize
35
+ };
@@ -0,0 +1,43 @@
1
+ import { renderHook } from "vitest-browser-react";
2
+ import { useWindowResize } from ".";
3
+ describe("useWindowResize", () => {
4
+ it("window\u30EA\u30B5\u30A4\u30BA\u6642\u306B\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u304C\u547C\u3070\u308C\u308B", async () => {
5
+ const resizedWindowSize = { width: 1e3, height: 1e3 };
6
+ const callback = vi.fn();
7
+ const { act } = await renderHook(() => useWindowResize(callback));
8
+ expect(callback).not.toHaveBeenCalled();
9
+ window.innerWidth = resizedWindowSize.width;
10
+ window.innerHeight = resizedWindowSize.height;
11
+ act(() => {
12
+ window.dispatchEvent(new Event("resize"));
13
+ });
14
+ expect(callback).toHaveBeenCalledWith(resizedWindowSize);
15
+ expect(callback).toHaveBeenCalledTimes(1);
16
+ });
17
+ it("enabled=false\u306E\u5834\u5408\u306F\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u304C\u547C\u3070\u308C\u306A\u3044", async () => {
18
+ const callback = vi.fn();
19
+ await renderHook(() => useWindowResize(callback, { enabled: false }));
20
+ expect(callback).not.toHaveBeenCalled();
21
+ });
22
+ it("debounceMs\u6307\u5B9A\u6642\u306F\u6307\u5B9A\u6642\u9593\u5F8C\u306B\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u304C\u547C\u3070\u308C\u308B", async () => {
23
+ vi.useFakeTimers();
24
+ const resizedWindowSize = { width: 1e3, height: 1e3 };
25
+ const callback = vi.fn();
26
+ const { act } = await renderHook(
27
+ () => useWindowResize(callback, { debounceMs: 300 })
28
+ );
29
+ expect(callback).not.toHaveBeenCalled();
30
+ window.innerWidth = resizedWindowSize.width;
31
+ window.innerHeight = resizedWindowSize.height;
32
+ act(() => {
33
+ window.dispatchEvent(new Event("resize"));
34
+ });
35
+ expect(callback).not.toHaveBeenCalled();
36
+ vi.advanceTimersByTime(299);
37
+ expect(callback).not.toHaveBeenCalled();
38
+ vi.advanceTimersByTime(1);
39
+ expect(callback).toHaveBeenCalledWith(resizedWindowSize);
40
+ expect(callback).toHaveBeenCalledTimes(1);
41
+ vi.useRealTimers();
42
+ });
43
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k8o/arte-odyssey",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "k8o's react ui library",
5
5
  "author": "k8o <kosakanoki@gmail.com>",
6
6
  "keywords": [
@@ -63,24 +63,24 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@chromatic-com/storybook": "4.1.2",
66
- "@storybook/addon-a11y": "10.0.2",
67
- "@storybook/addon-docs": "10.0.2",
68
- "@storybook/addon-vitest": "10.0.2",
69
- "@storybook/react-vite": "10.0.2",
66
+ "@storybook/addon-a11y": "10.0.6",
67
+ "@storybook/addon-docs": "10.0.6",
68
+ "@storybook/addon-vitest": "10.0.6",
69
+ "@storybook/react-vite": "10.0.6",
70
70
  "@tailwindcss/postcss": "4.1.16",
71
71
  "@types/react": "19.2.2",
72
72
  "@types/react-dom": "19.2.2",
73
73
  "@vitejs/plugin-react-swc": "4.2.0",
74
- "@vitest/browser-playwright": "4.0.6",
75
- "@vitest/ui": "4.0.6",
74
+ "@vitest/browser-playwright": "4.0.8",
75
+ "@vitest/ui": "4.0.8",
76
76
  "postcss": "8.5.6",
77
77
  "react": "19.2.0",
78
78
  "react-dom": "19.2.0",
79
- "storybook": "10.0.2",
79
+ "storybook": "10.0.6",
80
80
  "storybook-addon-mock-date": "1.0.2",
81
81
  "tailwindcss": "4.1.16",
82
82
  "vite": "7.1.12",
83
- "vitest": "4.0.6",
83
+ "vitest": "4.0.8",
84
84
  "vitest-browser-react": "2.0.2"
85
85
  },
86
86
  "peerDependencies": {