kang-components 0.1.0 → 0.3.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.
package/README.md CHANGED
@@ -74,6 +74,27 @@ const act = useAnimatedAction();
74
74
  const onClick = () => act(() => navigate('/next'));
75
75
  ```
76
76
 
77
+ ## Action-sheet primitives
78
+
79
+ CSS-first layout for a bottom-sheet "actions" panel — a vertical container, a
80
+ list, and tappable action rows with subtle press feedback. Colors stay with the
81
+ consumer: `actionSheetRow` takes the pressed-state background, so Kang carries no
82
+ theme knowledge.
83
+
84
+ ```ts
85
+ import styled from 'styled-components';
86
+ import { actionSheetContainer, actionSheetList, actionSheetRow } from 'kang-components';
87
+
88
+ const Container = styled.div`${actionSheetContainer()}`;
89
+ const List = styled.div`${actionSheetList()}`;
90
+ const Row = styled.button`
91
+ ${({ theme }) => actionSheetRow(theme.colors.surfaceVariant)}
92
+ `;
93
+ ```
94
+
95
+ Per-row content (leading icon, label, destructive tint, ripple) is the
96
+ consumer's to layer on top of the row.
97
+
77
98
  ## Build
78
99
 
79
100
  ```bash
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Action-sheet layout primitives — CSS-first, domain-free.
3
+ *
4
+ * The shared shape of a bottom-sheet "actions" panel: a vertical container, a
5
+ * list, and tappable action rows with subtle press feedback. Each helper
6
+ * returns a plain CSS string to interpolate into your styling layer.
7
+ *
8
+ * const Container = styled.div`${actionSheetContainer()}`;
9
+ * const List = styled.div`${actionSheetList()}`;
10
+ * const Row = styled.button`${actionSheetRow(theme.colors.surfaceVariant)}`;
11
+ *
12
+ * Colors stay with the consumer: `actionSheetRow` takes the pressed-state
13
+ * background so Kang carries no theme knowledge. Per-row content styling (icon
14
+ * tint, destructive text, etc.) is the consumer's to layer on.
15
+ */
16
+ /** Vertical action-sheet wrapper: column flex with standard sheet padding. */
17
+ export declare const actionSheetContainer: () => string;
18
+ /** The list of action rows. */
19
+ export declare const actionSheetList: () => string;
20
+ /**
21
+ * A single tappable action row: leading-icon + label layout with subtle press
22
+ * feedback (`pressSubtle`, with a background-color transition). Pass
23
+ * `activeBackground` to tint the pressed state; omit it to leave the pressed
24
+ * background to the consumer.
25
+ */
26
+ export declare const actionSheetRow: (activeBackground?: string) => string;
27
+ //# sourceMappingURL=action-sheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-sheet.d.ts","sourceRoot":"","sources":["../src/action-sheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,QAAO,MAKvC,CAAC;AAEF,+BAA+B;AAC/B,eAAO,MAAM,eAAe,QAAO,MAGlC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,sBAAuB,MAAM,KAAG,MAiB1D,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Action-sheet layout primitives — CSS-first, domain-free.
3
+ *
4
+ * The shared shape of a bottom-sheet "actions" panel: a vertical container, a
5
+ * list, and tappable action rows with subtle press feedback. Each helper
6
+ * returns a plain CSS string to interpolate into your styling layer.
7
+ *
8
+ * const Container = styled.div`${actionSheetContainer()}`;
9
+ * const List = styled.div`${actionSheetList()}`;
10
+ * const Row = styled.button`${actionSheetRow(theme.colors.surfaceVariant)}`;
11
+ *
12
+ * Colors stay with the consumer: `actionSheetRow` takes the pressed-state
13
+ * background so Kang carries no theme knowledge. Per-row content styling (icon
14
+ * tint, destructive text, etc.) is the consumer's to layer on.
15
+ */
16
+ import { pressSubtle } from './press.js';
17
+ /** Vertical action-sheet wrapper: column flex with standard sheet padding. */
18
+ export const actionSheetContainer = () => `
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 1rem;
22
+ padding: 0.5rem 0;
23
+ `;
24
+ /** The list of action rows. */
25
+ export const actionSheetList = () => `
26
+ display: flex;
27
+ flex-direction: column;
28
+ `;
29
+ /**
30
+ * A single tappable action row: leading-icon + label layout with subtle press
31
+ * feedback (`pressSubtle`, with a background-color transition). Pass
32
+ * `activeBackground` to tint the pressed state; omit it to leave the pressed
33
+ * background to the consumer.
34
+ */
35
+ export const actionSheetRow = (activeBackground) => `
36
+ position: relative;
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 1rem;
40
+ padding: 1rem;
41
+ background: none;
42
+ border: none;
43
+ border-radius: 0.75rem;
44
+ cursor: pointer;
45
+ overflow: hidden;
46
+ ${pressSubtle('background-color 0.15s ease-out')}
47
+ -webkit-tap-highlight-color: transparent;
48
+ ${activeBackground ? `
49
+ &:active {
50
+ background-color: ${activeBackground};
51
+ }` : ''}
52
+ `;
53
+ //# sourceMappingURL=action-sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-sheet.js","sourceRoot":"","sources":["../src/action-sheet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAW,EAAE,CAAC;;;;;CAKjD,CAAC;AAEF,+BAA+B;AAC/B,MAAM,CAAC,MAAM,eAAe,GAAG,GAAW,EAAE,CAAC;;;CAG5C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,gBAAyB,EAAU,EAAE,CAAC;;;;;;;;;;;GAWlE,WAAW,CAAC,iCAAiC,CAAC;;EAE/C,gBAAgB,CAAC,CAAC,CAAC;;sBAEC,gBAAgB;GACnC,CAAC,CAAC,CAAC,EAAE;CACP,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +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 { actionSheetContainer, actionSheetList, actionSheetRow, } from './action-sheet.js';
4
+ export { Ripple, rippleAnimation, useRipple } from './ripple.js';
5
+ export type { RippleState } from './ripple.js';
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
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"}
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
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 { actionSheetContainer, actionSheetList, actionSheetRow, } from './action-sheet.js';
4
+ export { Ripple, rippleAnimation, useRipple } from './ripple.js';
3
5
  //# sourceMappingURL=index.js.map
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"}
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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Material-style ripple — a domain-free press-feedback overlay.
3
+ *
4
+ * Two pieces that work together:
5
+ * - `useRipple<T>()` — a pure React hook that tracks the active ripple's
6
+ * position and which target fired it. `trigger(event, target)` records the
7
+ * click point relative to the element; `isTarget(value)` tells a given
8
+ * element whether it owns the current ripple.
9
+ * - `Ripple` — a styled `<span>` placed inside the (relatively-positioned)
10
+ * pressable element. Give it `key={ripple.key}` so each press restarts the
11
+ * animation, and `$x`/`$y` from the ripple state.
12
+ *
13
+ * The fill reads `theme.colors.ripple` (a generic styling token, not app
14
+ * domain) and falls back to `rgba(0, 0, 0, 0.1)` when absent — so it works with
15
+ * or without a themed `ThemeProvider`.
16
+ *
17
+ * `styled-components` is an optional peer dependency, used only by this module;
18
+ * consumers that import only the press / action-sheet string primitives never
19
+ * pull it in.
20
+ */
21
+ import { type MouseEvent } from 'react';
22
+ export interface RippleState<T> {
23
+ x: number;
24
+ y: number;
25
+ key: number;
26
+ target: T;
27
+ }
28
+ export declare const rippleAnimation: import("styled-components/dist/models/Keyframes").default;
29
+ export declare const Ripple: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "$x" | "$y"> & {
30
+ $x: number;
31
+ $y: number;
32
+ }, never> & Partial<Pick<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "$x" | "$y"> & {
33
+ $x: number;
34
+ $y: number;
35
+ }, never>>> & string;
36
+ export declare function useRipple<T>(): {
37
+ ripple: RippleState<T> | null;
38
+ trigger: (e: MouseEvent<HTMLElement>, target: T) => void;
39
+ isTarget: (value: T) => boolean;
40
+ };
41
+ //# sourceMappingURL=ripple.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ripple.d.ts","sourceRoot":"","sources":["../src/ripple.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAM/D,MAAM,WAAW,WAAW,CAAC,CAAC;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,CAAC,CAAC;CACV;AAED,eAAO,MAAM,eAAe,2DAS3B,CAAC;AAEF,eAAO,MAAM,MAAM;QAAqB,MAAM;QAAM,MAAM;;QAAlB,MAAM;QAAM,MAAM;oBAazD,CAAC;AAEF,wBAAgB,SAAS,CAAC,CAAC;;iBAGM,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC;sBAWzD,CAAC;EAKV"}
package/dist/ripple.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Material-style ripple — a domain-free press-feedback overlay.
3
+ *
4
+ * Two pieces that work together:
5
+ * - `useRipple<T>()` — a pure React hook that tracks the active ripple's
6
+ * position and which target fired it. `trigger(event, target)` records the
7
+ * click point relative to the element; `isTarget(value)` tells a given
8
+ * element whether it owns the current ripple.
9
+ * - `Ripple` — a styled `<span>` placed inside the (relatively-positioned)
10
+ * pressable element. Give it `key={ripple.key}` so each press restarts the
11
+ * animation, and `$x`/`$y` from the ripple state.
12
+ *
13
+ * The fill reads `theme.colors.ripple` (a generic styling token, not app
14
+ * domain) and falls back to `rgba(0, 0, 0, 0.1)` when absent — so it works with
15
+ * or without a themed `ThemeProvider`.
16
+ *
17
+ * `styled-components` is an optional peer dependency, used only by this module;
18
+ * consumers that import only the press / action-sheet string primitives never
19
+ * pull it in.
20
+ */
21
+ import { useCallback, useState } from 'react';
22
+ // Named imports (not the default) so this resolves consistently across bundler
23
+ // and raw ESM/CJS environments — styled-components v6's default export is the
24
+ // CJS namespace under Node's interop, where `styled.span` is undefined.
25
+ import { styled, keyframes } from 'styled-components';
26
+ export const rippleAnimation = keyframes `
27
+ 0% {
28
+ transform: scale(0);
29
+ opacity: 0.6;
30
+ }
31
+ 100% {
32
+ transform: scale(10);
33
+ opacity: 0;
34
+ }
35
+ `;
36
+ export const Ripple = styled.span `
37
+ position: absolute;
38
+ left: ${({ $x }) => $x}px;
39
+ top: ${({ $y }) => $y}px;
40
+ width: 50px;
41
+ height: 50px;
42
+ margin-left: -25px;
43
+ margin-top: -25px;
44
+ border-radius: 50%;
45
+ background-color: ${({ theme }) => theme?.colors?.ripple ?? 'rgba(0, 0, 0, 0.1)'};
46
+ pointer-events: none;
47
+ animation: ${rippleAnimation} 0.4s ease-out forwards;
48
+ `;
49
+ export function useRipple() {
50
+ const [ripple, setRipple] = useState(null);
51
+ const trigger = useCallback((e, target) => {
52
+ const rect = e.currentTarget.getBoundingClientRect();
53
+ setRipple({
54
+ x: e.clientX - rect.left,
55
+ y: e.clientY - rect.top,
56
+ key: Date.now(),
57
+ target,
58
+ });
59
+ }, []);
60
+ const isTarget = useCallback((value) => ripple !== null && ripple.target === value, [ripple]);
61
+ return { ripple, trigger, isTarget };
62
+ }
63
+ //# sourceMappingURL=ripple.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ripple.js","sourceRoot":"","sources":["../src/ripple.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAmB,MAAM,OAAO,CAAC;AAC/D,+EAA+E;AAC/E,8EAA8E;AAC9E,wEAAwE;AACxE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAStD,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAA;;;;;;;;;CASvC,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAA4B;;SAEpD,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;QACf,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;;;;;;qBAMD,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAChC,KAA0C,EAAE,MAAM,EAAE,MAAM,IAAI,oBAAoB;;cAEvE,eAAe;CAC5B,CAAC;AAEF,MAAM,UAAU,SAAS;IACxB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAwB,IAAI,CAAC,CAAC;IAElE,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAA0B,EAAE,MAAS,EAAE,EAAE;QACrE,MAAM,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC,qBAAqB,EAAE,CAAC;QACrD,SAAS,CAAC;YACT,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI;YACxB,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG;YACvB,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;YACf,MAAM;SACN,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,QAAQ,GAAG,WAAW,CAC3B,CAAC,KAAQ,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,EACxD,CAAC,MAAM,CAAC,CACR,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kang-components",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Generic, domain-free React UI primitives (CSS-first press feedback, delayed-action hook).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,16 +26,21 @@
26
26
  "prepublishOnly": "npm run build"
27
27
  },
28
28
  "peerDependencies": {
29
- "react": ">=18"
29
+ "react": ">=18",
30
+ "styled-components": ">=6"
30
31
  },
31
32
  "peerDependenciesMeta": {
32
33
  "react": {
33
34
  "optional": true
35
+ },
36
+ "styled-components": {
37
+ "optional": true
34
38
  }
35
39
  },
36
40
  "devDependencies": {
37
41
  "@types/react": "^18.3.18",
38
42
  "react": "^18.3.1",
43
+ "styled-components": "^6.1.16",
39
44
  "typescript": "~5.6.2"
40
45
  }
41
46
  }