@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.
- package/README.md +38 -0
- package/dist/buttons/Button.js +53 -0
- package/dist/data/Avatar.js +46 -0
- package/dist/feedback/Alert.js +13 -0
- package/dist/feedback/Badge.js +17 -0
- package/dist/feedback/Loading.js +16 -0
- package/dist/feedback/Modal.js +23 -0
- package/dist/feedback/Progress.js +17 -0
- package/dist/feedback/Skeleton.js +18 -0
- package/dist/feedback/Steps.js +16 -0
- package/dist/forms/Checkbox.js +32 -0
- package/dist/forms/FormField.js +5 -0
- package/dist/forms/Input.js +25 -0
- package/dist/forms/Radio.js +28 -0
- package/dist/forms/Select.js +33 -0
- package/dist/forms/Textarea.js +31 -0
- package/dist/forms/Toggle.js +32 -0
- package/dist/index.d.ts +66 -58
- package/dist/index.js +41 -678
- package/dist/layout/Card.js +39 -0
- package/dist/layout/Center.d.ts +2 -1
- package/dist/layout/Center.js +24 -0
- package/dist/layout/Col.d.ts +2 -2
- package/dist/layout/Col.js +33 -0
- package/dist/layout/Divider.js +27 -0
- package/dist/layout/Row.d.ts +2 -2
- package/dist/layout/Row.js +33 -0
- package/dist/layout/ScrollView.js +18 -0
- package/dist/layout/Spacer.js +11 -0
- package/dist/navigation/NavDrawer.d.ts +62 -0
- package/dist/navigation/NavDrawer.js +205 -0
- package/dist/navigation/NavHeader.d.ts +12 -1
- package/dist/navigation/NavHeader.js +74 -0
- package/dist/navigation/NavTabBar.js +90 -0
- package/dist/navigation/SwiperIndicator.d.ts +59 -0
- package/dist/navigation/SwiperIndicator.js +232 -0
- package/dist/navigation/Tabs.js +18 -0
- package/dist/preset/index.js +66 -40
- package/dist/shared/press.d.ts +2 -0
- package/dist/shared/press.js +6 -0
- package/dist/shared/styles.d.ts +29 -1
- package/dist/shared/styles.js +90 -0
- package/dist/styles/components/typography.css +36 -2
- package/dist/styles/index.css +8 -4
- package/dist/styles/themes/shapes.css +2 -1
- package/dist/styles/themes/{dark.css → tokens.css} +9 -33
- package/dist/theme/StatusBarSync.d.ts +41 -0
- package/dist/theme/StatusBarSync.js +85 -0
- package/dist/theme/ThemeProvider.d.ts +91 -35
- package/dist/theme/ThemeProvider.js +183 -0
- package/dist/theme/registry.d.ts +101 -0
- package/dist/theme/registry.js +185 -0
- package/dist/typography/Heading.js +19 -0
- package/dist/typography/Text.d.ts +11 -1
- package/dist/typography/Text.js +25 -0
- package/package.json +12 -10
- package/dist/index.js.map +0 -1
- package/dist/preset/index.js.map +0 -1
- 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
|
+
});
|
package/dist/layout/Center.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Define } from '@sigx/lynx';
|
|
2
|
-
|
|
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
|
+
});
|
package/dist/layout/Col.d.ts
CHANGED
|
@@ -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',
|
|
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
|
+
});
|
package/dist/layout/Row.d.ts
CHANGED
|
@@ -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',
|
|
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
|
-
/**
|
|
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
|
+
});
|