@sigx/lynx-daisyui 0.4.0 → 0.4.2

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 (59) hide show
  1. package/README.md +38 -0
  2. package/dist/buttons/Button.js +53 -0
  3. package/dist/data/Avatar.js +46 -0
  4. package/dist/feedback/Alert.js +13 -0
  5. package/dist/feedback/Badge.js +17 -0
  6. package/dist/feedback/Loading.js +16 -0
  7. package/dist/feedback/Modal.js +23 -0
  8. package/dist/feedback/Progress.js +17 -0
  9. package/dist/feedback/Skeleton.js +18 -0
  10. package/dist/feedback/Steps.js +16 -0
  11. package/dist/forms/Checkbox.js +32 -0
  12. package/dist/forms/FormField.js +5 -0
  13. package/dist/forms/Input.js +25 -0
  14. package/dist/forms/Radio.js +28 -0
  15. package/dist/forms/Select.js +33 -0
  16. package/dist/forms/Textarea.js +31 -0
  17. package/dist/forms/Toggle.js +32 -0
  18. package/dist/index.d.ts +66 -58
  19. package/dist/index.js +41 -678
  20. package/dist/layout/Card.js +39 -0
  21. package/dist/layout/Center.d.ts +2 -1
  22. package/dist/layout/Center.js +24 -0
  23. package/dist/layout/Col.d.ts +2 -2
  24. package/dist/layout/Col.js +33 -0
  25. package/dist/layout/Divider.js +27 -0
  26. package/dist/layout/Row.d.ts +2 -2
  27. package/dist/layout/Row.js +33 -0
  28. package/dist/layout/ScrollView.js +18 -0
  29. package/dist/layout/Spacer.js +11 -0
  30. package/dist/navigation/NavDrawer.d.ts +62 -0
  31. package/dist/navigation/NavDrawer.js +205 -0
  32. package/dist/navigation/NavHeader.d.ts +12 -1
  33. package/dist/navigation/NavHeader.js +74 -0
  34. package/dist/navigation/NavTabBar.js +90 -0
  35. package/dist/navigation/SwiperIndicator.d.ts +59 -0
  36. package/dist/navigation/SwiperIndicator.js +232 -0
  37. package/dist/navigation/Tabs.js +18 -0
  38. package/dist/preset/index.js +66 -40
  39. package/dist/shared/press.d.ts +2 -0
  40. package/dist/shared/press.js +6 -0
  41. package/dist/shared/styles.d.ts +29 -1
  42. package/dist/shared/styles.js +90 -0
  43. package/dist/styles/components/typography.css +36 -2
  44. package/dist/styles/index.css +8 -4
  45. package/dist/styles/themes/shapes.css +2 -1
  46. package/dist/styles/themes/{dark.css → tokens.css} +9 -33
  47. package/dist/theme/StatusBarSync.d.ts +41 -0
  48. package/dist/theme/StatusBarSync.js +85 -0
  49. package/dist/theme/ThemeProvider.d.ts +91 -35
  50. package/dist/theme/ThemeProvider.js +183 -0
  51. package/dist/theme/registry.d.ts +101 -0
  52. package/dist/theme/registry.js +185 -0
  53. package/dist/typography/Heading.js +19 -0
  54. package/dist/typography/Text.d.ts +11 -1
  55. package/dist/typography/Text.js +25 -0
  56. package/package.json +12 -10
  57. package/dist/index.js.map +0 -1
  58. package/dist/preset/index.js.map +0 -1
  59. package/dist/styles/themes/light.css +0 -95
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component, compound } from '@sigx/lynx';
3
+ const _Card = component(({ props, slots }) => {
4
+ const getClasses = () => {
5
+ const c = ['card'];
6
+ if (props.bordered)
7
+ c.push('card-bordered');
8
+ if (props.compact)
9
+ c.push('card-compact');
10
+ if (props.shadow === true)
11
+ c.push('shadow-md');
12
+ else if (props.shadow === 'sm')
13
+ c.push('shadow-sm');
14
+ else if (props.shadow === 'md')
15
+ c.push('shadow-md');
16
+ else if (props.shadow === 'lg')
17
+ c.push('shadow-lg');
18
+ else if (props.shadow === undefined)
19
+ c.push('shadow-md');
20
+ if (props.class)
21
+ c.push(props.class);
22
+ return c.join(' ');
23
+ };
24
+ return () => _jsx("view", { class: getClasses(), children: slots.default?.() });
25
+ });
26
+ const CardBody = component(({ props, slots }) => {
27
+ return () => (_jsx("view", { class: `card-body${props.class ? ' ' + props.class : ''}`, children: slots.default?.() }));
28
+ });
29
+ const CardTitle = component(({ props, slots }) => {
30
+ return () => (_jsx("text", { class: `card-title${props.class ? ' ' + props.class : ''}`, children: slots.default?.() }));
31
+ });
32
+ const CardActions = component(({ props, slots }) => {
33
+ return () => (_jsx("view", { class: `card-actions${props.class ? ' ' + props.class : ''}`, children: slots.default?.() }));
34
+ });
35
+ export const Card = compound(_Card, {
36
+ Body: CardBody,
37
+ Title: CardTitle,
38
+ Actions: CardActions,
39
+ });
@@ -1,5 +1,6 @@
1
1
  import { type Define } from '@sigx/lynx';
2
- export type CenterProps = Define.Prop<'width', number | string, false> & Define.Prop<'height', number | string, false> & Define.Prop<'flex', number, false> & Define.Prop<'background', string, false> & Define.Prop<'borderRadius', number, false> & Define.Prop<'class', string, false> & Define.Slot<'default'>;
2
+ import { type BackgroundValue } from '../shared/styles.js';
3
+ export type CenterProps = Define.Prop<'width', number | string, false> & Define.Prop<'height', number | string, false> & Define.Prop<'flex', number, false> & Define.Prop<'background', BackgroundValue, false> & Define.Prop<'borderRadius', number, false> & Define.Prop<'class', string, false> & Define.Slot<'default'>;
3
4
  export declare const Center: import("@sigx/runtime-core").ComponentFactory<CenterProps, void, {
4
5
  default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
5
6
  }>;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component } from '@sigx/lynx';
3
+ import { resolveBoxStyle } from '../shared/styles.js';
4
+ export const Center = component(({ props, slots }) => {
5
+ const getStyle = () => {
6
+ const style = {
7
+ display: 'flex',
8
+ justifyContent: 'center',
9
+ alignItems: 'center',
10
+ };
11
+ const box = resolveBoxStyle({
12
+ width: props.width,
13
+ height: props.height,
14
+ flex: props.flex,
15
+ background: props.background,
16
+ borderRadius: props.borderRadius,
17
+ });
18
+ for (const key in box) {
19
+ style[key] = box[key];
20
+ }
21
+ return style;
22
+ };
23
+ return () => (_jsx("view", { class: props.class, style: getStyle(), children: slots.default?.() }));
24
+ });
@@ -1,6 +1,6 @@
1
1
  import { type Define } from '@sigx/lynx';
2
- import { type SpacingValue } from '../shared/styles';
3
- export type ColProps = Define.Prop<'gap', number, false> & Define.Prop<'align', 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline', false> & Define.Prop<'justify', 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly', false> & Define.Prop<'wrap', boolean, false> & Define.Prop<'padding', SpacingValue, false> & Define.Prop<'margin', SpacingValue, false> & Define.Prop<'width', number | string, false> & Define.Prop<'height', number | string, false> & Define.Prop<'flex', number, false> & Define.Prop<'background', string, false> & Define.Prop<'borderRadius', number, false> & Define.Prop<'class', string, false> & Define.Slot<'default'>;
2
+ import { type SpacingValue, type BackgroundValue } from '../shared/styles.js';
3
+ export type ColProps = Define.Prop<'gap', number, false> & Define.Prop<'align', 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline', false> & Define.Prop<'justify', 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly', false> & Define.Prop<'wrap', boolean, false> & Define.Prop<'padding', SpacingValue, false> & Define.Prop<'margin', SpacingValue, false> & Define.Prop<'width', number | string, false> & Define.Prop<'height', number | string, false> & Define.Prop<'flex', number, false> & Define.Prop<'background', BackgroundValue, false> & Define.Prop<'borderRadius', number, false> & Define.Prop<'class', string, false> & Define.Slot<'default'>;
4
4
  export declare const Col: import("@sigx/runtime-core").ComponentFactory<ColProps, void, {
5
5
  default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
6
6
  }>;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component } from '@sigx/lynx';
3
+ import { resolveBoxStyle } from '../shared/styles.js';
4
+ export const Col = component(({ props, slots }) => {
5
+ const getStyle = () => {
6
+ const style = {
7
+ display: 'flex',
8
+ flexDirection: 'column',
9
+ };
10
+ if (props.gap !== undefined)
11
+ style.gap = props.gap;
12
+ if (props.align)
13
+ style.alignItems = props.align;
14
+ if (props.justify)
15
+ style.justifyContent = props.justify;
16
+ if (props.wrap)
17
+ style.flexWrap = 'wrap';
18
+ const box = resolveBoxStyle({
19
+ width: props.width,
20
+ height: props.height,
21
+ flex: props.flex,
22
+ background: props.background,
23
+ borderRadius: props.borderRadius,
24
+ padding: props.padding,
25
+ margin: props.margin,
26
+ });
27
+ for (const key in box) {
28
+ style[key] = box[key];
29
+ }
30
+ return style;
31
+ };
32
+ return () => (_jsx("view", { class: props.class, style: getStyle(), children: slots.default?.() }));
33
+ });
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component } from '@sigx/lynx';
3
+ export const Divider = component(({ props }) => {
4
+ const getClasses = () => {
5
+ const c = [props.vertical ? 'divider-vertical' : 'divider'];
6
+ if (props.class)
7
+ c.push(props.class);
8
+ return c.join(' ');
9
+ };
10
+ const getStyle = () => {
11
+ const style = {};
12
+ if (props.color)
13
+ style.backgroundColor = props.color;
14
+ if (props.margin !== undefined) {
15
+ if (props.vertical) {
16
+ style.marginLeft = props.margin;
17
+ style.marginRight = props.margin;
18
+ }
19
+ else {
20
+ style.marginTop = props.margin;
21
+ style.marginBottom = props.margin;
22
+ }
23
+ }
24
+ return style;
25
+ };
26
+ return () => (_jsx("view", { class: getClasses(), style: getStyle() }));
27
+ });
@@ -1,6 +1,6 @@
1
1
  import { type Define } from '@sigx/lynx';
2
- import { type SpacingValue } from '../shared/styles';
3
- export type RowProps = Define.Prop<'gap', number, false> & Define.Prop<'align', 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline', false> & Define.Prop<'justify', 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly', false> & Define.Prop<'wrap', boolean, false> & Define.Prop<'padding', SpacingValue, false> & Define.Prop<'margin', SpacingValue, false> & Define.Prop<'width', number | string, false> & Define.Prop<'height', number | string, false> & Define.Prop<'flex', number, false> & Define.Prop<'background', string, false> & Define.Prop<'borderRadius', number, false> & Define.Prop<'class', string, false> & Define.Slot<'default'>;
2
+ import { type SpacingValue, type BackgroundValue } from '../shared/styles.js';
3
+ export type RowProps = Define.Prop<'gap', number, false> & Define.Prop<'align', 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline', false> & Define.Prop<'justify', 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly', false> & Define.Prop<'wrap', boolean, false> & Define.Prop<'padding', SpacingValue, false> & Define.Prop<'margin', SpacingValue, false> & Define.Prop<'width', number | string, false> & Define.Prop<'height', number | string, false> & Define.Prop<'flex', number, false> & Define.Prop<'background', BackgroundValue, false> & Define.Prop<'borderRadius', number, false> & Define.Prop<'class', string, false> & Define.Slot<'default'>;
4
4
  export declare const Row: import("@sigx/runtime-core").ComponentFactory<RowProps, void, {
5
5
  default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
6
6
  }>;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component } from '@sigx/lynx';
3
+ import { resolveBoxStyle } from '../shared/styles.js';
4
+ export const Row = component(({ props, slots }) => {
5
+ const getStyle = () => {
6
+ const style = {
7
+ display: 'flex',
8
+ flexDirection: 'row',
9
+ };
10
+ if (props.gap !== undefined)
11
+ style.gap = props.gap;
12
+ if (props.align)
13
+ style.alignItems = props.align;
14
+ if (props.justify)
15
+ style.justifyContent = props.justify;
16
+ if (props.wrap)
17
+ style.flexWrap = 'wrap';
18
+ const box = resolveBoxStyle({
19
+ width: props.width,
20
+ height: props.height,
21
+ flex: props.flex,
22
+ background: props.background,
23
+ borderRadius: props.borderRadius,
24
+ padding: props.padding,
25
+ margin: props.margin,
26
+ });
27
+ for (const key in box) {
28
+ style[key] = box[key];
29
+ }
30
+ return style;
31
+ };
32
+ return () => (_jsx("view", { class: props.class, style: getStyle(), children: slots.default?.() }));
33
+ });
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component } from '@sigx/lynx';
3
+ export const ScrollView = component(({ props, slots }) => {
4
+ const getStyle = () => {
5
+ const style = {};
6
+ if (props.height !== undefined)
7
+ style.height = props.height;
8
+ if (props.width !== undefined)
9
+ style.width = props.width;
10
+ if (props.flex !== undefined)
11
+ style.flex = props.flex;
12
+ return style;
13
+ };
14
+ return () => {
15
+ const dir = props.direction ?? 'vertical';
16
+ return (_jsx("scroll-view", { class: props.class, style: getStyle(), "scroll-orientation": dir, "scroll-y": dir === 'vertical' ? true : undefined, "scroll-x": dir === 'horizontal' ? true : undefined, "show-scrollbar": props.showScrollbar, bounces: props.bounces, children: slots.default?.() }));
17
+ };
18
+ });
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component } from '@sigx/lynx';
3
+ export const Spacer = component(({ props }) => {
4
+ const getStyle = () => {
5
+ if (props.size !== undefined) {
6
+ return { width: props.size, height: props.size };
7
+ }
8
+ return { flex: 1 };
9
+ };
10
+ return () => (_jsx("view", { class: props.class, style: getStyle() }));
11
+ });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `<NavDrawer>` — daisy-themed off-canvas drawer for `@sigx/lynx-navigation`.
3
+ *
4
+ * Composes the primitive `<Drawer>` purely as the state provider (so
5
+ * `useDrawer()` resolves for descendants) and drives its own
6
+ * `SharedValue`-backed slide + fade transition via `@sigx/lynx-motion`.
7
+ *
8
+ * Behavior:
9
+ * - Panel translates from off-screen on the configured `side` to `0`
10
+ * on open (and back on close). Default side is `'left'`.
11
+ * - Backdrop fades 0 → 0.3 in tandem.
12
+ * - Chrome mounts on open and unmounts after the exit animation completes,
13
+ * so the closed-state drawer doesn't intercept taps to underlying tabs.
14
+ * - Backdrop is a plain `<view bindtap>` — no Pressable scale/opacity
15
+ * feedback (which flickers an opaque scrim).
16
+ *
17
+ * Usage:
18
+ *
19
+ * ```tsx
20
+ * <NavigationRoot routes={routes}>
21
+ * <NavDrawer slots={{ sidebar: () => <MyMenu /> }}>
22
+ * <Stack />
23
+ * </NavDrawer>
24
+ * </NavigationRoot>
25
+ * ```
26
+ *
27
+ * Inside descendants, `useDrawer()` from `@sigx/lynx-navigation` returns
28
+ * `{ isOpen, open, close, toggle }`.
29
+ *
30
+ * The primitive's own `<Drawer />` is intentionally minimal (state +
31
+ * `display: none` overlay only); this component is the
32
+ * batteries-included variant for daisyui consumers.
33
+ */
34
+ import { type Define, type JSXElement } from '@sigx/lynx';
35
+ import { type BackgroundValue } from '../shared/styles.js';
36
+ export type NavDrawerSide = 'left' | 'right';
37
+ export type NavDrawerProps =
38
+ /** Which edge the panel slides in from. Default 'left'. */
39
+ Define.Prop<'side', NavDrawerSide, false>
40
+ /** Panel surface color. Accepts daisy tokens ('base-100', 'primary', …)
41
+ * — applied as a `bg-<token>` Tailwind class so the daisy preset's
42
+ * CSS-pipeline rule resolves the `var(--color-<token>)`. Also accepts
43
+ * raw CSS color strings ('#facc15', 'rgb(...)') — applied as inline
44
+ * `backgroundColor`. Default 'base-100'. */
45
+ & Define.Prop<'background', BackgroundValue, false>
46
+ /** Show a separator line on the panel's inner edge. Default true. */
47
+ & Define.Prop<'bordered', boolean, false>
48
+ /** Render a dismiss-on-tap scrim over the main content when open. Default true. */
49
+ & Define.Prop<'backdrop', boolean, false>
50
+ /** Panel width in pixels. Default 280. */
51
+ & Define.Prop<'width', number, false>
52
+ /** Open the drawer at mount. Default false. Passthrough to primitive `<Drawer>`. */
53
+ & Define.Prop<'initialOpen', boolean, false>
54
+ /** Drawer panel contents — your menu UI. */
55
+ & Define.Slot<'sidebar'>
56
+ /** Main content — usually a `<Stack>` or `<Tabs>`. */
57
+ & Define.Slot<'default'>;
58
+ export declare const NavDrawer: import("@sigx/runtime-core").ComponentFactory<NavDrawerProps, void, {
59
+ sidebar: () => JSXElement | JSXElement[] | null;
60
+ } & {
61
+ default: () => JSXElement | JSXElement[] | null;
62
+ }>;
@@ -0,0 +1,205 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@sigx/lynx/jsx-runtime";
2
+ /**
3
+ * `<NavDrawer>` — daisy-themed off-canvas drawer for `@sigx/lynx-navigation`.
4
+ *
5
+ * Composes the primitive `<Drawer>` purely as the state provider (so
6
+ * `useDrawer()` resolves for descendants) and drives its own
7
+ * `SharedValue`-backed slide + fade transition via `@sigx/lynx-motion`.
8
+ *
9
+ * Behavior:
10
+ * - Panel translates from off-screen on the configured `side` to `0`
11
+ * on open (and back on close). Default side is `'left'`.
12
+ * - Backdrop fades 0 → 0.3 in tandem.
13
+ * - Chrome mounts on open and unmounts after the exit animation completes,
14
+ * so the closed-state drawer doesn't intercept taps to underlying tabs.
15
+ * - Backdrop is a plain `<view bindtap>` — no Pressable scale/opacity
16
+ * feedback (which flickers an opaque scrim).
17
+ *
18
+ * Usage:
19
+ *
20
+ * ```tsx
21
+ * <NavigationRoot routes={routes}>
22
+ * <NavDrawer slots={{ sidebar: () => <MyMenu /> }}>
23
+ * <Stack />
24
+ * </NavDrawer>
25
+ * </NavigationRoot>
26
+ * ```
27
+ *
28
+ * Inside descendants, `useDrawer()` from `@sigx/lynx-navigation` returns
29
+ * `{ isOpen, open, close, toggle }`.
30
+ *
31
+ * The primitive's own `<Drawer />` is intentionally minimal (state +
32
+ * `display: none` overlay only); this component is the
33
+ * batteries-included variant for daisyui consumers.
34
+ */
35
+ import { component, effect, onUnmounted, runOnMainThread, signal, untrack, useAnimatedStyle, useMainThreadRef, useSharedValue, } from '@sigx/lynx';
36
+ import { withTiming } from '@sigx/lynx-motion';
37
+ import { Drawer, useDrawer } from '@sigx/lynx-navigation';
38
+ import { resolveDaisyColor } from '../shared/styles.js';
39
+ /**
40
+ * Slide-in / fade-in timing. Slightly longer than the slide-out so the
41
+ * drawer feels deliberate on open and snappy on dismiss — matches the
42
+ * convention used by Stack's push/pop transitions in `lynx-navigation`.
43
+ */
44
+ const ENTER_DURATION_SEC = 0.28;
45
+ const EXIT_DURATION_SEC = 0.22;
46
+ const EXIT_DURATION_MS = Math.round(EXIT_DURATION_SEC * 1000);
47
+ const BACKDROP_OPACITY = 0.3;
48
+ export const NavDrawer = component(({ props, slots }) => {
49
+ return () => (_jsx(Drawer, { initialOpen: props.initialOpen, children: _jsx(NavDrawerShell, { side: props.side ?? 'left', background: props.background ?? 'base-100', bordered: props.bordered ?? true, backdrop: props.backdrop ?? true, width: props.width ?? 280, renderSidebar: slots.sidebar, children: slots.default?.() }) }));
50
+ });
51
+ const NavDrawerShell = component(({ props, slots }) => {
52
+ const drawer = useDrawer();
53
+ // Seed progress from current open state so `initialOpen=true` mounts
54
+ // already-open without a slide-in flash.
55
+ const progress = useSharedValue(drawer.isOpen ? 1 : 0);
56
+ const shouldRender = signal(drawer.isOpen);
57
+ // Track whether the chrome is currently mounted (or animating out) so the
58
+ // initial effect tick on a closed drawer doesn't kick a no-op close
59
+ // animation + unmount timer.
60
+ let chromeMounted = drawer.isOpen;
61
+ let exitTimer = null;
62
+ // Pre-register the worklets at setup so the SWC main-thread transform
63
+ // captures `progress` once. Re-registering on every effect tick would
64
+ // re-ship the worklet body across the bridge unnecessarily.
65
+ const openAnim = runOnMainThread(() => {
66
+ 'main thread';
67
+ withTiming(progress, 1, { duration: ENTER_DURATION_SEC });
68
+ });
69
+ const closeAnim = runOnMainThread(() => {
70
+ 'main thread';
71
+ withTiming(progress, 0, { duration: EXIT_DURATION_SEC });
72
+ });
73
+ const animRunner = effect(() => {
74
+ const open = drawer.isOpen;
75
+ if (open) {
76
+ if (exitTimer != null) {
77
+ clearTimeout(exitTimer);
78
+ exitTimer = null;
79
+ }
80
+ chromeMounted = true;
81
+ untrack(() => {
82
+ shouldRender.value = true;
83
+ });
84
+ openAnim();
85
+ }
86
+ else if (chromeMounted) {
87
+ chromeMounted = false;
88
+ closeAnim();
89
+ // Wait for the exit animation to finish before unmounting the
90
+ // chrome — otherwise the panel pops out instead of sliding,
91
+ // and the backdrop's bindtap area disappears mid-fade.
92
+ exitTimer = setTimeout(() => {
93
+ untrack(() => {
94
+ shouldRender.value = false;
95
+ });
96
+ exitTimer = null;
97
+ }, EXIT_DURATION_MS);
98
+ }
99
+ // else: drawer is closed and the chrome was never mounted (the
100
+ // common initial-mount case) — nothing to animate or schedule.
101
+ });
102
+ onUnmounted(() => {
103
+ animRunner.stop();
104
+ if (exitTimer != null)
105
+ clearTimeout(exitTimer);
106
+ });
107
+ return () => {
108
+ return (_jsxs("view", { style: {
109
+ display: 'flex',
110
+ flexDirection: 'column',
111
+ position: 'relative',
112
+ width: '100%',
113
+ height: '100%',
114
+ }, children: [slots.default?.(), shouldRender.value
115
+ ? (_jsx(DrawerChrome
116
+ // Key by side+width — `useAnimatedStyle`
117
+ // snapshots `outputRange` at setup, so a
118
+ // runtime change to either (panel slide
119
+ // distance is signed by side, magnitude by
120
+ // width) needs a remount + rebind. Width
121
+ // changes mid-open are vanishingly rare;
122
+ // toggling `side` likewise. The explicit
123
+ // remount keeps the binding consistent if
124
+ // a consumer wires either to a reactive
125
+ // value.
126
+ , { side: props.side, progress: progress, width: props.width, background: props.background, bordered: props.bordered, backdrop: props.backdrop, renderSidebar: props.renderSidebar, onBackdropPress: () => drawer.close() }, `drawer-chrome-${props.side}-${props.width}`))
127
+ : null] }));
128
+ };
129
+ });
130
+ const DrawerChrome = component(({ props }) => {
131
+ const panelRef = useMainThreadRef(null);
132
+ const backdropRef = useMainThreadRef(null);
133
+ // Slide range mirrors `side`: left-side starts at `-width` (off-screen
134
+ // left) and lands at `0`; right-side starts at `+width` and lands at `0`.
135
+ // Capture once — NavDrawerShell remounts on side/width change to rebind.
136
+ const closedTx = props.side === 'right' ? props.width : -props.width;
137
+ // Bind once at setup. `useAnimatedStyle` snapshots its mapper/range
138
+ // params at registration time; NavDrawerShell keys DrawerChrome by
139
+ // side+width so a change to either forces a remount + rebind here.
140
+ useAnimatedStyle(panelRef, props.progress, 'translateX', {
141
+ inputRange: [0, 1],
142
+ outputRange: [closedTx, 0],
143
+ });
144
+ // Register unconditionally so a runtime `backdrop` toggle works
145
+ // both directions. `useAnimatedStyle` only binds once at setup; if
146
+ // this lived inside `if (props.backdrop)` a false→true toggle would
147
+ // mount a backdrop view with no opacity binding, leaving it stuck
148
+ // at the inline `opacity: 0` seed. When the backdrop view isn't
149
+ // rendered, `backdropRef.current` is null and the MT bridge's
150
+ // `setStyleProperties` apply silently skips — no harm.
151
+ useAnimatedStyle(backdropRef, props.progress, 'opacity', {
152
+ inputRange: [0, 1],
153
+ outputRange: [0, BACKDROP_OPACITY],
154
+ });
155
+ return () => {
156
+ const isRight = props.side === 'right';
157
+ // Lynx resolves `var(--color-*)` inside CSS-pipeline rules (Tailwind
158
+ // classes, stylesheet imports) but NOT inside inline `style.backgroundColor`
159
+ // — an inline `'var(--color-base-100)'` paints transparent. So for known
160
+ // daisy tokens we apply the surface via the Tailwind class `bg-<token>`
161
+ // (which the daisy preset compiles to a `var()` rule that DOES resolve);
162
+ // raw CSS strings ('#facc15', 'rgb(...)', 'var(--my-custom)') fall through
163
+ // to inline because there's no compiled class to use for them.
164
+ const resolved = resolveDaisyColor(props.background);
165
+ const isDaisyToken = resolved !== props.background;
166
+ const bgClass = isDaisyToken ? `bg-${props.background}` : '';
167
+ // Border lives on the panel's *inner* edge (the one facing the
168
+ // main content). Daisy class names are still the cleanest way to
169
+ // pick up `--color-base-300` for the separator hairline.
170
+ const borderClass = props.bordered
171
+ ? (isRight ? 'border-l border-base-300' : 'border-r border-base-300')
172
+ : '';
173
+ const panelClass = [bgClass, borderClass].filter(Boolean).join(' ');
174
+ const panelStyle = {
175
+ position: 'absolute',
176
+ top: 0,
177
+ bottom: 0,
178
+ width: props.width,
179
+ };
180
+ if (!isDaisyToken)
181
+ panelStyle.backgroundColor = props.background;
182
+ // Only the side-relevant inset is set; omitting the other lets
183
+ // the panel size to `width` rather than stretching edge-to-edge.
184
+ if (isRight)
185
+ panelStyle.right = 0;
186
+ else
187
+ panelStyle.left = 0;
188
+ return (_jsxs("view", { style: {
189
+ position: 'absolute',
190
+ top: 0,
191
+ left: 0,
192
+ right: 0,
193
+ bottom: 0,
194
+ }, children: [props.backdrop
195
+ ? (_jsx("view", { "main-thread:ref": backdropRef, bindtap: () => props.onBackdropPress(), class: "bg-base-content", style: {
196
+ position: 'absolute',
197
+ top: 0,
198
+ left: 0,
199
+ right: 0,
200
+ bottom: 0,
201
+ opacity: 0,
202
+ }, "accessibility-element": true, "accessibility-label": "Close drawer", "accessibility-trait": "button" }))
203
+ : null, _jsx("view", { "main-thread:ref": panelRef, class: panelClass, style: panelStyle, children: props.renderSidebar?.() })] }));
204
+ };
205
+ });
@@ -19,13 +19,24 @@
19
19
  * for daisyui consumers.
20
20
  */
21
21
  import { type Define, type JSXElement } from '@sigx/lynx';
22
+ import { type IconSpec } from '@sigx/lynx-icons';
22
23
  export type NavHeaderBackground = 'base-100' | 'base-200' | 'base-300' | 'transparent';
23
24
  export type NavHeaderProps =
24
25
  /** Surface color token. Default 'base-200'. */
25
26
  Define.Prop<'background', NavHeaderBackground, false>
26
27
  /** Show a separator line at the bottom. Default true. */
27
28
  & Define.Prop<'bordered', boolean, false>
28
- /** Replace the back button entirely. Receives `pop` so the custom node can wire its own tap handler. */
29
+ /**
30
+ * Render the back chevron from an `IconSpec` (e.g. `{ set: 'lucide',
31
+ * name: 'chevron-left' }`). The icon is rendered with
32
+ * `variant="primary"`, which `<ThemeProvider>`'s color resolver maps
33
+ * to the daisy primary hex and substitutes into the SVG `fill=`.
34
+ * Wrapped in a Pressable wired to the stack's pop. Falls back to the
35
+ * default "‹ Back" text when not provided. Ignored when `renderBack`
36
+ * or `<Screen.HeaderLeft>` is also supplied — those win.
37
+ */
38
+ & Define.Prop<'backIcon', IconSpec, false>
39
+ /** Full override: render any JSX for the back button. Takes priority over `backIcon`. */
29
40
  & Define.Prop<'renderBack', (ctx: {
30
41
  pop: () => void;
31
42
  }) => JSXElement, false>;
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@sigx/lynx/jsx-runtime";
2
+ /**
3
+ * `<NavHeader>` — daisy-themed header bar for `@sigx/lynx-navigation`.
4
+ *
5
+ * Pairs with `<Stack>` from `@sigx/lynx-navigation`:
6
+ *
7
+ * ```tsx
8
+ * <Stack initialRoute="tripsHome">
9
+ * <NavHeader />
10
+ * </Stack>
11
+ * ```
12
+ *
13
+ * Reads the focused screen's options + slot fills via
14
+ * `useScreenChrome()`, applies daisy theming (base-200 surface, bottom
15
+ * separator, native-ish horizontal layout with centred title).
16
+ *
17
+ * The navigation package's own `<Header />` is intentionally headless —
18
+ * no flex-row, no padding, no theme — for consumers who want to do all
19
+ * styling themselves. This component is the batteries-included variant
20
+ * for daisyui consumers.
21
+ */
22
+ import { component } from '@sigx/lynx';
23
+ import { Pressable } from '@sigx/lynx-gestures';
24
+ import { Icon } from '@sigx/lynx-icons';
25
+ import { useScreenChrome } from '@sigx/lynx-navigation';
26
+ import { PRESSED_SCALE, PRESSED_OPACITY } from '../shared/press.js';
27
+ /** Pixel size used when rendering the back-button icon from an `IconSpec`. */
28
+ const NAV_HEADER_ICON_SIZE = 22;
29
+ const backgroundClass = {
30
+ 'base-100': 'bg-base-100',
31
+ 'base-200': 'bg-base-200',
32
+ 'base-300': 'bg-base-300',
33
+ 'transparent': '',
34
+ };
35
+ export const NavHeader = component(({ props }) => {
36
+ const chrome = useScreenChrome();
37
+ return () => {
38
+ if (!chrome.headerShown)
39
+ return null;
40
+ // Full override: <Screen.Header> rendered.
41
+ const override = chrome.header;
42
+ if (override)
43
+ return override();
44
+ const bg = backgroundClass[props.background ?? 'base-200'];
45
+ const bordered = props.bordered ?? true;
46
+ const borderClass = bordered ? 'border-b border-base-300' : '';
47
+ const containerClass = [
48
+ 'flex flex-row items-center px-3',
49
+ 'h-12', // ~48dp / standard nav bar height
50
+ bg,
51
+ borderClass,
52
+ ].filter(Boolean).join(' ');
53
+ // Resolution order: <Screen.HeaderLeft> slot fill → custom renderBack
54
+ // → backIcon spec → default text. The spec path renders `<Icon>`
55
+ // with `variant="primary"`, which the daisy resolver maps to the
56
+ // primary hex and substitutes into the SVG `fill=`.
57
+ const left = chrome.headerLeft?.()
58
+ ?? (chrome.canGoBack
59
+ ? (props.renderBack
60
+ ? props.renderBack({ pop: chrome.pop })
61
+ : (props.backIcon
62
+ ? _jsx(BackIconButton, { spec: props.backIcon, onPress: chrome.pop })
63
+ : _jsx(DefaultBackButton, { onPress: chrome.pop })))
64
+ : null);
65
+ const right = chrome.headerRight?.() ?? null;
66
+ return (_jsxs("view", { class: containerClass, children: [_jsx("view", { class: "flex flex-row items-center", style: { minWidth: 56 }, children: left }), _jsx("view", { class: "flex-1 items-center justify-center", children: _jsx("text", { class: "text-base-content text-base font-semibold", children: chrome.title }) }), _jsx("view", { class: "flex flex-row items-center justify-end", style: { minWidth: 56 }, children: right })] }));
67
+ };
68
+ });
69
+ const DefaultBackButton = component(({ props }) => {
70
+ return () => (_jsx(Pressable, { class: "px-2 py-2", pressedScale: PRESSED_SCALE, pressedOpacity: PRESSED_OPACITY, longPressDuration: 0, "accessibility-element": true, "accessibility-label": "Back", "accessibility-trait": "button", onPress: () => props.onPress(), children: _jsx("text", { class: "text-primary text-base", children: "\u2039 Back" }) }));
71
+ });
72
+ const BackIconButton = component(({ props }) => {
73
+ return () => (_jsx(Pressable, { class: "px-2 py-2", pressedScale: PRESSED_SCALE, pressedOpacity: PRESSED_OPACITY, longPressDuration: 0, "accessibility-element": true, "accessibility-label": "Back", "accessibility-trait": "button", onPress: () => props.onPress(), children: _jsx(Icon, { set: props.spec.set, name: props.spec.name, size: NAV_HEADER_ICON_SIZE, variant: "primary" }) }));
74
+ });