kang-components 0.7.0 → 0.9.1

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.
@@ -0,0 +1,60 @@
1
+ /**
2
+ * BottomSheet — a draggable bottom-sheet modal with gesture support.
3
+ *
4
+ * Ported faithfully from ymy-components (`./bottom-sheet/BottomSheet`). The public
5
+ * API (`BottomSheetProps`) is preserved 1:1 so xunzi's single re-point site
6
+ * (`src/views/bottom-sheet/BottomSheet.tsx`, imported as `LibraryBottomSheet`) is a
7
+ * pure import swap.
8
+ *
9
+ * Behavior: an overlay + a panel that slides up from the bottom. The handle area
10
+ * is draggable (rubber-band physics dragging up, velocity- or distance-based
11
+ * swipe-to-close dragging down). Clicking the overlay or swiping past the close
12
+ * threshold calls `onClose`. The component owns a `shouldRender` flag so it can
13
+ * play the slide-out animation before unmounting, and fires `onExitComplete` once
14
+ * the close actually finishes (an interrupted close — reopened mid-animation —
15
+ * does NOT unmount or fire the callback).
16
+ *
17
+ * ⚠️ onExitComplete / onClose stability (the historically fragile part):
18
+ * ymy listed `onExitComplete` in the deps of the effect driving the open/close
19
+ * spring, so a consumer passing a fresh inline callback every render re-ran that
20
+ * effect on every parent re-render — snapping the sheet off-screen then back
21
+ * (jitter), and, when several landed in a row, leaving it off-screen long enough
22
+ * to look like it self-closed (xunzi worked around this by forcing a stable
23
+ * `useCallback`). Kang removes that footgun: the latest `onExitComplete` and
24
+ * `onClose` are stashed in refs and read from the spring's `onRest` / drag-release
25
+ * handler, so a new callback identity NEVER re-kicks the open/close spring. The
26
+ * open/close effect depends ONLY on `isOpen` (+ `maxHeight`/`api`), which are the
27
+ * things that should actually drive it. See the regression test.
28
+ *
29
+ * Theming: surface / scrim / outline / radius / padding read off styled-components'
30
+ * `props.theme` tokens (assembled by kang's `buildTheme`), with the same literal
31
+ * fallbacks ymy shipped so it renders sensibly with or without a `ThemeProvider`.
32
+ * `styled-components`, `@react-spring/web` and `@use-gesture/react` are optional
33
+ * peer dependencies, used only by this module.
34
+ */
35
+ import { type ReactElement, type ReactNode } from 'react';
36
+ export interface BottomSheetProps {
37
+ /** Whether the sheet is open */
38
+ isOpen: boolean;
39
+ /** Callback when the sheet should close */
40
+ onClose: () => void;
41
+ /** Content to render inside the sheet */
42
+ children: ReactNode;
43
+ /** Accessible title for screen readers */
44
+ title?: string;
45
+ /** Max height of the sheet (default: 300) */
46
+ maxHeight?: number;
47
+ /** Velocity threshold for swipe-to-close in px/ms (default: 0.4) */
48
+ velocityThreshold?: number;
49
+ /** Percentage of height drag required to close (default: 0.25) */
50
+ closeThreshold?: number;
51
+ /** Fires after the close animation finishes (use for cleanup like clearing content) */
52
+ onExitComplete?: () => void;
53
+ }
54
+ /**
55
+ * Draggable bottom sheet modal with gesture support.
56
+ * Features rubber-band physics, velocity-based swipe detection, and smooth animations.
57
+ */
58
+ export declare function BottomSheet({ isOpen, onClose, children, title, maxHeight, velocityThreshold, closeThreshold, onExitComplete, }: BottomSheetProps): ReactElement;
59
+ export default BottomSheet;
60
+ //# sourceMappingURL=bottom-sheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bottom-sheet.d.ts","sourceRoot":"","sources":["../src/bottom-sheet.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACN,KAAK,YAAY,EACjB,KAAK,SAAS,EAKd,MAAM,OAAO,CAAC;AAQf,MAAM,WAAW,gBAAgB;IAChC,gCAAgC;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yCAAyC;IACzC,QAAQ,EAAE,SAAS,CAAC;IACpB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAkED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC3B,MAAM,EACN,OAAO,EACP,QAAQ,EACR,KAAsB,EACtB,SAAe,EACf,iBAAuB,EACvB,cAAqB,EACrB,cAAc,GACd,EAAE,gBAAgB,GAAG,YAAY,CAkJjC;AAED,eAAe,WAAW,CAAC"}
@@ -0,0 +1,201 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * BottomSheet — a draggable bottom-sheet modal with gesture support.
4
+ *
5
+ * Ported faithfully from ymy-components (`./bottom-sheet/BottomSheet`). The public
6
+ * API (`BottomSheetProps`) is preserved 1:1 so xunzi's single re-point site
7
+ * (`src/views/bottom-sheet/BottomSheet.tsx`, imported as `LibraryBottomSheet`) is a
8
+ * pure import swap.
9
+ *
10
+ * Behavior: an overlay + a panel that slides up from the bottom. The handle area
11
+ * is draggable (rubber-band physics dragging up, velocity- or distance-based
12
+ * swipe-to-close dragging down). Clicking the overlay or swiping past the close
13
+ * threshold calls `onClose`. The component owns a `shouldRender` flag so it can
14
+ * play the slide-out animation before unmounting, and fires `onExitComplete` once
15
+ * the close actually finishes (an interrupted close — reopened mid-animation —
16
+ * does NOT unmount or fire the callback).
17
+ *
18
+ * ⚠️ onExitComplete / onClose stability (the historically fragile part):
19
+ * ymy listed `onExitComplete` in the deps of the effect driving the open/close
20
+ * spring, so a consumer passing a fresh inline callback every render re-ran that
21
+ * effect on every parent re-render — snapping the sheet off-screen then back
22
+ * (jitter), and, when several landed in a row, leaving it off-screen long enough
23
+ * to look like it self-closed (xunzi worked around this by forcing a stable
24
+ * `useCallback`). Kang removes that footgun: the latest `onExitComplete` and
25
+ * `onClose` are stashed in refs and read from the spring's `onRest` / drag-release
26
+ * handler, so a new callback identity NEVER re-kicks the open/close spring. The
27
+ * open/close effect depends ONLY on `isOpen` (+ `maxHeight`/`api`), which are the
28
+ * things that should actually drive it. See the regression test.
29
+ *
30
+ * Theming: surface / scrim / outline / radius / padding read off styled-components'
31
+ * `props.theme` tokens (assembled by kang's `buildTheme`), with the same literal
32
+ * fallbacks ymy shipped so it renders sensibly with or without a `ThemeProvider`.
33
+ * `styled-components`, `@react-spring/web` and `@use-gesture/react` are optional
34
+ * peer dependencies, used only by this module.
35
+ */
36
+ import { useRef, useEffect, useCallback, useState, } from 'react';
37
+ import { useSpring, animated } from '@react-spring/web';
38
+ import { useDrag } from '@use-gesture/react';
39
+ // Named import (not the default) so styled-components resolves consistently
40
+ // across bundler and raw ESM/CJS environments — matches the other styled
41
+ // kang primitives (ripple.ts, toggle-switch.tsx).
42
+ import { styled } from 'styled-components';
43
+ const colors = (theme) => theme?.colors ?? {};
44
+ const styling = (theme) => theme?.styling ?? {};
45
+ const spacing = (theme) => theme?.spacing ?? {};
46
+ const SheetOverlay = styled.div `
47
+ position: absolute;
48
+ inset: 0;
49
+ background-color: ${({ theme }) => colors(theme).scrim ?? 'rgba(0, 0, 0, 0.4)'};
50
+ `;
51
+ const SheetContent = styled.div `
52
+ background-color: rgb(${({ theme }) => colors(theme).surfaceRgb ?? '255, 255, 255'});
53
+ border-top-left-radius: ${({ theme }) => styling(theme).borderRadiusPixel ?? 24}px;
54
+ border-top-right-radius: ${({ theme }) => styling(theme).borderRadiusPixel ?? 24}px;
55
+ max-height: 85dvh;
56
+ padding-bottom: max(env(safe-area-inset-bottom), 16px);
57
+ `;
58
+ const SheetHandle = styled.div `
59
+ width: 40px;
60
+ height: 5px;
61
+ background-color: ${({ theme }) => colors(theme).outline ?? 'rgba(0, 0, 0, 0.3)'};
62
+ border-radius: 3px;
63
+ margin: 12px auto 8px;
64
+ `;
65
+ const SheetBody = styled.div `
66
+ padding: 0 ${({ theme }) => spacing(theme).appStandardPadding ?? '16px'};
67
+ `;
68
+ const VisuallyHidden = styled.span `
69
+ position: absolute;
70
+ width: 1px;
71
+ height: 1px;
72
+ padding: 0;
73
+ margin: -1px;
74
+ overflow: hidden;
75
+ clip: rect(0, 0, 0, 0);
76
+ white-space: nowrap;
77
+ border: 0;
78
+ `;
79
+ // Rubber-band damping function (from Vaul)
80
+ function dampenValue(v) {
81
+ return 8 * (Math.log(v + 1) - 2);
82
+ }
83
+ /**
84
+ * Draggable bottom sheet modal with gesture support.
85
+ * Features rubber-band physics, velocity-based swipe detection, and smooth animations.
86
+ */
87
+ export function BottomSheet({ isOpen, onClose, children, title = 'Bottom Sheet', maxHeight = 300, velocityThreshold = 0.4, closeThreshold = 0.25, onExitComplete, }) {
88
+ const modalRef = useRef(null);
89
+ const [shouldRender, setShouldRender] = useState(isOpen);
90
+ // Mirror of shouldRender for the effect to read the latest value without
91
+ // re-subscribing (adding shouldRender to the effect deps would re-run
92
+ // open/close on every toggle and reintroduce the snap).
93
+ const shouldRenderRef = useRef(shouldRender);
94
+ shouldRenderRef.current = shouldRender;
95
+ // Stash the latest callbacks in refs so the open/close spring effect never
96
+ // depends on their identity. A consumer passing a fresh inline onExitComplete
97
+ // (or onClose) every render must NOT re-kick the open/close spring — that was
98
+ // the jitter / apparent-self-close bug. The effect reads .current at the
99
+ // moment it actually needs the callback (on close-finished / on drag-release).
100
+ const onExitCompleteRef = useRef(onExitComplete);
101
+ onExitCompleteRef.current = onExitComplete;
102
+ const onCloseRef = useRef(onClose);
103
+ onCloseRef.current = onClose;
104
+ const [spring, api] = useSpring(() => ({
105
+ y: maxHeight,
106
+ opacity: 0,
107
+ config: { tension: 300, friction: 30 },
108
+ }));
109
+ // Handle open/close. Deps are intentionally ONLY [isOpen, api, maxHeight]:
110
+ // the callbacks are read from refs so their identity can't re-run this effect.
111
+ useEffect(() => {
112
+ if (isOpen) {
113
+ const openDistance = modalRef.current?.offsetHeight || maxHeight;
114
+ // Only hard-snap to the closed position when opening from fully
115
+ // closed. When reopening mid-close (a sheet swap), reverse from the
116
+ // current position instead of jumping to the bottom.
117
+ if (!shouldRenderRef.current) {
118
+ api.set({ y: openDistance, opacity: 0 });
119
+ }
120
+ setShouldRender(true);
121
+ api.start({ y: 0, opacity: 1 });
122
+ }
123
+ else {
124
+ const closeDistance = modalRef.current?.offsetHeight || maxHeight;
125
+ api.start({
126
+ y: closeDistance,
127
+ opacity: 0,
128
+ onRest: (result) => {
129
+ // Only tear down when the close actually finished. An
130
+ // interrupted close (reopened mid-animation) fires onRest
131
+ // with finished:false and must NOT unmount the sheet.
132
+ if (result?.finished) {
133
+ setShouldRender(false);
134
+ onExitCompleteRef.current?.();
135
+ }
136
+ },
137
+ });
138
+ }
139
+ }, [isOpen, api, maxHeight]);
140
+ const handleClose = useCallback(() => {
141
+ onCloseRef.current();
142
+ }, []);
143
+ // Drag handler
144
+ const bind = useDrag(({ active, movement: [, my], velocity: [, vy], direction: [, dy], cancel }) => {
145
+ // Prevent dragging up beyond the top
146
+ if (my < -10) {
147
+ cancel();
148
+ return;
149
+ }
150
+ const dragDistance = Math.max(0, my);
151
+ if (active) {
152
+ // Apply rubber-band effect when dragging up
153
+ const dampened = my < 0 ? dampenValue(-my) * -0.5 : dragDistance;
154
+ api.start({ y: dampened, immediate: true });
155
+ // Fade overlay based on drag progress
156
+ const progress = Math.min(dragDistance / maxHeight, 1);
157
+ api.start({ opacity: 1 - progress * 0.5, immediate: true });
158
+ }
159
+ else {
160
+ // Check if should close based on velocity or distance
161
+ const shouldClose = (vy > velocityThreshold && dy > 0) ||
162
+ dragDistance > maxHeight * closeThreshold;
163
+ if (shouldClose) {
164
+ handleClose();
165
+ }
166
+ else {
167
+ api.start({ y: 0, opacity: 1 });
168
+ }
169
+ }
170
+ }, {
171
+ from: () => [0, spring.y.get()],
172
+ filterTaps: true,
173
+ axis: 'y',
174
+ pointer: { touch: true },
175
+ preventDefault: true,
176
+ eventOptions: { passive: false },
177
+ });
178
+ if (!shouldRender) {
179
+ return _jsx(_Fragment, {});
180
+ }
181
+ return (_jsxs(_Fragment, { children: [_jsx(animated.div, { style: {
182
+ position: 'fixed',
183
+ inset: 0,
184
+ zIndex: 49,
185
+ opacity: spring.opacity,
186
+ pointerEvents: isOpen ? 'auto' : 'none',
187
+ }, onClick: handleClose, children: _jsx(SheetOverlay, {}) }), _jsx(animated.div, { style: {
188
+ position: 'fixed',
189
+ bottom: 0,
190
+ left: 0,
191
+ right: 0,
192
+ zIndex: 50,
193
+ transform: spring.y.to((y) => `translateY(${y}px)`),
194
+ }, children: _jsxs(SheetContent, { ref: modalRef, children: [_jsx(VisuallyHidden, { children: title }), _jsx("div", { ...bind(), style: {
195
+ touchAction: 'none',
196
+ cursor: 'grab',
197
+ padding: '8px 0',
198
+ }, children: _jsx(SheetHandle, {}) }), _jsx(SheetBody, { children: children })] }) })] }));
199
+ }
200
+ export default BottomSheet;
201
+ //# sourceMappingURL=bottom-sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bottom-sheet.js","sourceRoot":"","sources":["../src/bottom-sheet.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * ConfirmDialog — a domain-free confirm/cancel prompt: a centered message, an
3
+ * optional description, and stacked confirm + cancel buttons with Material-style
4
+ * ripple press feedback. The confirm button can be marked `destructive` for a
5
+ * red-tinted danger action.
6
+ *
7
+ * Ported from ymy-components (`./confirm-dialog/ConfirmDialog`), where it lived
8
+ * as a 3-file unit (`ConfirmDialog.tsx` + `.types.ts` + `.styles.ts`) and
9
+ * shipped untested. The kang port collapses it into a single flat-ESM module and
10
+ * preserves the public API exactly (`message`, `description`, `confirmLabel`,
11
+ * `cancelLabel`, `destructive`, `onConfirm`, `onCancel`), so xunzi's two
12
+ * re-point sites (`ClearDataConfirm.tsx`, `LogoutConfirm.tsx`) are pure import
13
+ * swaps.
14
+ *
15
+ * Theming follows kang conventions: the destructive button reads the theme's
16
+ * `error` token (with the ymy literal `#dc2626` / `rgba(220, 38, 38, 0.1)` as
17
+ * fallbacks) so it adapts to a `ThemeProvider` (light/dark) yet still renders
18
+ * sensibly without one. Ripple feedback reuses kang's `useRipple` / `Ripple`
19
+ * primitives. `styled-components` and `react` are the only runtime imports.
20
+ */
21
+ import { type ReactElement, type ReactNode } from 'react';
22
+ export interface ConfirmDialogProps {
23
+ /** Primary message displayed at the top. */
24
+ message: ReactNode;
25
+ /** Optional secondary description below the message. */
26
+ description?: ReactNode;
27
+ /** Label for the confirm button. */
28
+ confirmLabel: ReactNode;
29
+ /** Label for the cancel button. */
30
+ cancelLabel: ReactNode;
31
+ /** Whether the confirm action is destructive (red-tinted button). */
32
+ destructive?: boolean;
33
+ /** Callback when confirm is clicked. */
34
+ onConfirm: () => void;
35
+ /** Callback when cancel is clicked. */
36
+ onCancel: () => void;
37
+ }
38
+ export declare function ConfirmDialog({ message, description, confirmLabel, cancelLabel, destructive, onConfirm, onCancel, }: ConfirmDialogProps): ReactElement;
39
+ export default ConfirmDialog;
40
+ //# sourceMappingURL=confirm-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm-dialog.d.ts","sourceRoot":"","sources":["../src/confirm-dialog.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAmB,MAAM,OAAO,CAAC;AAI3E,MAAM,WAAW,kBAAkB;IAClC,4CAA4C;IAC5C,OAAO,EAAE,SAAS,CAAC;IACnB,wDAAwD;IACxD,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,oCAAoC;IACpC,YAAY,EAAE,SAAS,CAAC;IACxB,mCAAmC;IACnC,WAAW,EAAE,SAAS,CAAC;IACvB,qEAAqE;IACrE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wCAAwC;IACxC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AA8DD,wBAAgB,aAAa,CAAC,EAC7B,OAAO,EACP,WAAW,EACX,YAAY,EACZ,WAAW,EACX,WAAmB,EACnB,SAAS,EACT,QAAQ,GACR,EAAE,kBAAkB,GAAG,YAAY,CA+BnC;AAED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { styled } from 'styled-components';
3
+ import { Ripple, useRipple } from './ripple.js';
4
+ /** Convert a hex color (`#rrggbb`) to an `r, g, b` triple for rgba() usage. */
5
+ function hexToRgbTriple(hex) {
6
+ const h = hex.replace('#', '');
7
+ if (h.length !== 6)
8
+ return null;
9
+ const r = parseInt(h.slice(0, 2), 16);
10
+ const g = parseInt(h.slice(2, 4), 16);
11
+ const b = parseInt(h.slice(4, 6), 16);
12
+ if ([r, g, b].some((n) => Number.isNaN(n)))
13
+ return null;
14
+ return `${r}, ${g}, ${b}`;
15
+ }
16
+ const DialogContainer = styled.div `
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: 0.5rem;
20
+ `;
21
+ const DialogMessage = styled.div `
22
+ text-align: center;
23
+ padding: 0.5rem 1rem;
24
+ font-weight: 300;
25
+ `;
26
+ const DialogDescription = styled.div `
27
+ text-align: center;
28
+ padding: 0 1rem 1rem;
29
+ font-size: 0.875rem;
30
+ opacity: 0.7;
31
+ `;
32
+ const DialogButton = styled.button `
33
+ position: relative;
34
+ z-index: 1;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ padding: 1rem;
39
+ font-family: inherit;
40
+ font-size: 1.125rem;
41
+ font-weight: 300;
42
+ border: none;
43
+ border-radius: 12px;
44
+ background-color: ${({ $destructive, theme }) => {
45
+ if (!$destructive)
46
+ return 'transparent';
47
+ const error = theme?.colors?.error;
48
+ const triple = error ? hexToRgbTriple(error) : null;
49
+ return triple ? `rgba(${triple}, 0.1)` : 'rgba(220, 38, 38, 0.1)';
50
+ }};
51
+ color: ${({ $destructive, theme }) => {
52
+ if (!$destructive)
53
+ return 'inherit';
54
+ return theme?.colors?.error ?? '#dc2626';
55
+ }};
56
+ cursor: pointer;
57
+ outline: none;
58
+ overflow: hidden;
59
+ -webkit-tap-highlight-color: transparent;
60
+ `;
61
+ export function ConfirmDialog({ message, description, confirmLabel, cancelLabel, destructive = false, onConfirm, onCancel, }) {
62
+ const { ripple, trigger, isTarget } = useRipple();
63
+ const handleConfirm = (e) => {
64
+ trigger(e, 'confirm');
65
+ onConfirm();
66
+ };
67
+ const handleCancel = (e) => {
68
+ trigger(e, 'cancel');
69
+ onCancel();
70
+ };
71
+ return (_jsxs(DialogContainer, { children: [_jsx(DialogMessage, { children: message }), description && _jsx(DialogDescription, { children: description }), _jsxs(DialogButton, { "$destructive": destructive, onClick: handleConfirm, children: [isTarget('confirm') && ripple && (_jsx(Ripple, { "$x": ripple.x, "$y": ripple.y }, ripple.key)), confirmLabel] }), _jsxs(DialogButton, { onClick: handleCancel, children: [isTarget('cancel') && ripple && (_jsx(Ripple, { "$x": ripple.x, "$y": ripple.y }, ripple.key)), cancelLabel] })] }));
72
+ }
73
+ export default ConfirmDialog;
74
+ //# sourceMappingURL=confirm-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm-dialog.js","sourceRoot":"","sources":["../src/confirm-dialog.tsx"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { BOUNCE_CURVE, PRESS_SCALE_PRIMARY, PRESS_SCALE_SUBTLE, pressPrimary, pressSubtle, pressPrimaryScale, pressSubtleScale, } from './press.js';
2
2
  export { useAnimatedAction } from './use-animated-action.js';
3
+ export { useViewportSize } from './use-viewport-size.js';
4
+ export type { ViewportSize } from './use-viewport-size.js';
3
5
  export { actionSheetContainer, actionSheetList, actionSheetRow, } from './action-sheet.js';
4
6
  export { Ripple, rippleAnimation, useRipple } from './ripple.js';
5
7
  export type { RippleState } from './ripple.js';
@@ -17,6 +19,10 @@ export { Badge } from './badge.js';
17
19
  export type { BadgeProps, BadgeVariant } from './badge.js';
18
20
  export { default as ToggleSwitch } from './toggle-switch.js';
19
21
  export type { ToggleSwitchProps } from './toggle-switch.js';
22
+ export { ConfirmDialog } from './confirm-dialog.js';
23
+ export type { ConfirmDialogProps } from './confirm-dialog.js';
24
+ export { BottomSheet } from './bottom-sheet.js';
25
+ export type { BottomSheetProps } from './bottom-sheet.js';
20
26
  export { buildTheme, theme, hexToRgb } from './theme.js';
21
27
  export type { ThemeType } from './theme.js';
22
28
  export { lightPalette, darkPalette } from './palettes.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,gBAAgB,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,cAAc,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACN,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,aAAa,GACb,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACtE,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,YAAY,EACX,eAAe,EACf,mBAAmB,EACnB,cAAc,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,QAAQ,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EACX,aAAa,EACb,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,gBAAgB,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,cAAc,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EACN,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,aAAa,GACb,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACzD,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACtE,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,YAAY,EACX,eAAe,EACf,mBAAmB,EACnB,cAAc,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,QAAQ,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EACX,aAAa,EACb,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { BOUNCE_CURVE, PRESS_SCALE_PRIMARY, PRESS_SCALE_SUBTLE, pressPrimary, pressSubtle, pressPrimaryScale, pressSubtleScale, } from './press.js';
2
2
  export { useAnimatedAction } from './use-animated-action.js';
3
+ export { useViewportSize } from './use-viewport-size.js';
3
4
  export { actionSheetContainer, actionSheetList, actionSheetRow, } from './action-sheet.js';
4
5
  export { Ripple, rippleAnimation, useRipple } from './ripple.js';
5
6
  export { SPRING_COMFORTABLE, SPRING_COMFORTABLE_SLOW, SPRING_SNAPPY, SPRING_RESPONSIVE, SPRING_STAGGERED, SPRING_INSTANT, SPRING_GENTLE, SPRING_VERY_SLOW, SPRING_LANDING, SPRING_RISING, } from './spring.js';
@@ -9,6 +10,8 @@ export { default as CircleIconButton } from './circle-icon-button.js';
9
10
  export { default as BannerButton } from './banner-button.js';
10
11
  export { Badge } from './badge.js';
11
12
  export { default as ToggleSwitch } from './toggle-switch.js';
13
+ export { ConfirmDialog } from './confirm-dialog.js';
14
+ export { BottomSheet } from './bottom-sheet.js';
12
15
  export { buildTheme, theme, hexToRgb } from './theme.js';
13
16
  export { lightPalette, darkPalette } from './palettes.js';
14
17
  export { ListItem, UnorderedListItemContainer } from './list-item.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,gBAAgB,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,cAAc,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,EACN,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,aAAa,GACb,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAS1D,OAAO,EAAE,QAAQ,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,gBAAgB,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,OAAO,EACN,oBAAoB,EACpB,eAAe,EACf,cAAc,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,EACN,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,aAAa,GACb,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAS1D,OAAO,EAAE,QAAQ,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface ViewportSize {
2
+ width: number;
3
+ height: number;
4
+ }
5
+ /**
6
+ * Hook to track viewport dimensions with debounced updates.
7
+ * Listens to resize and orientation change events.
8
+ *
9
+ * Ported from ymy-components (`./hooks/useViewportSize`) preserving its public
10
+ * API exactly so xunzi can re-point as a pure import swap.
11
+ *
12
+ * @param debounceMs - Debounce delay in milliseconds (default: 100)
13
+ * @returns ViewportSize - Current viewport width and height
14
+ */
15
+ export declare function useViewportSize(debounceMs?: number): ViewportSize;
16
+ //# sourceMappingURL=use-viewport-size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-viewport-size.d.ts","sourceRoot":"","sources":["../src/use-viewport-size.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,SAAM,GAAG,YAAY,CA8B9D"}
@@ -0,0 +1,38 @@
1
+ import { useState, useEffect } from 'react';
2
+ /**
3
+ * Hook to track viewport dimensions with debounced updates.
4
+ * Listens to resize and orientation change events.
5
+ *
6
+ * Ported from ymy-components (`./hooks/useViewportSize`) preserving its public
7
+ * API exactly so xunzi can re-point as a pure import swap.
8
+ *
9
+ * @param debounceMs - Debounce delay in milliseconds (default: 100)
10
+ * @returns ViewportSize - Current viewport width and height
11
+ */
12
+ export function useViewportSize(debounceMs = 100) {
13
+ const [size, setSize] = useState({
14
+ width: typeof window !== 'undefined' ? window.innerWidth : 0,
15
+ height: typeof window !== 'undefined' ? window.innerHeight : 0,
16
+ });
17
+ useEffect(() => {
18
+ let timeoutId;
19
+ const handleResize = () => {
20
+ clearTimeout(timeoutId);
21
+ timeoutId = setTimeout(() => {
22
+ setSize({
23
+ width: window.innerWidth,
24
+ height: window.innerHeight,
25
+ });
26
+ }, debounceMs);
27
+ };
28
+ window.addEventListener('resize', handleResize);
29
+ window.addEventListener('orientationchange', handleResize);
30
+ return () => {
31
+ clearTimeout(timeoutId);
32
+ window.removeEventListener('resize', handleResize);
33
+ window.removeEventListener('orientationchange', handleResize);
34
+ };
35
+ }, [debounceMs]);
36
+ return size;
37
+ }
38
+ //# sourceMappingURL=use-viewport-size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-viewport-size.js","sourceRoot":"","sources":["../src/use-viewport-size.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAO5C;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,UAAU,GAAG,GAAG;IAC/C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAe;QAC9C,KAAK,EAAE,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,EAAE,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;KAC9D,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,SAAwC,CAAC;QAE7C,MAAM,YAAY,GAAG,GAAG,EAAE;YACzB,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC3B,OAAO,CAAC;oBACP,KAAK,EAAE,MAAM,CAAC,UAAU;oBACxB,MAAM,EAAE,MAAM,CAAC,WAAW;iBAC1B,CAAC,CAAC;YACJ,CAAC,EAAE,UAAU,CAAC,CAAC;QAChB,CAAC,CAAC;QAEF,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAChD,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC;QAE3D,OAAO,GAAG,EAAE;YACX,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;YACnD,MAAM,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC;QAC/D,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,OAAO,IAAI,CAAC;AACb,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kang-components",
3
- "version": "0.7.0",
3
+ "version": "0.9.1",
4
4
  "description": "Generic, domain-free React UI primitives (CSS-first press feedback, delayed-action hook, spring presets, cross-fading text, circle icon button).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,10 +25,12 @@
25
25
  "build": "tsc -p tsconfig.build.json",
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest",
28
+ "test:coverage": "vitest run --coverage",
28
29
  "prepublishOnly": "npm run build"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "@react-spring/web": ">=9",
33
+ "@use-gesture/react": ">=10",
32
34
  "react": ">=18",
33
35
  "styled-components": ">=6"
34
36
  },
@@ -36,6 +38,9 @@
36
38
  "@react-spring/web": {
37
39
  "optional": true
38
40
  },
41
+ "@use-gesture/react": {
42
+ "optional": true
43
+ },
39
44
  "react": {
40
45
  "optional": true
41
46
  },
@@ -49,7 +54,9 @@
49
54
  "@testing-library/react": "^16.1.0",
50
55
  "@types/react": "^18.3.18",
51
56
  "@types/react-dom": "^18.3.5",
57
+ "@use-gesture/react": "^10.3.1",
52
58
  "@vitejs/plugin-react": "^4.3.4",
59
+ "@vitest/coverage-v8": "^2.1.9",
53
60
  "jsdom": "^25.0.1",
54
61
  "react": "^18.3.1",
55
62
  "react-dom": "^18.3.1",