@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,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@sigx/lynx/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `<NavTabBar>` — daisy-themed tab bar for `@sigx/lynx-navigation`.
|
|
4
|
+
*
|
|
5
|
+
* Pairs with `<Tabs>` + `<Tabs.Screen>` from `@sigx/lynx-navigation`:
|
|
6
|
+
* subscribes to `useTabs()` for the active tab + tab list, dispatches
|
|
7
|
+
* tab changes via `setActive`. Pure UI / styling; the navigation
|
|
8
|
+
* package owns state.
|
|
9
|
+
*
|
|
10
|
+
* Default visual treatment: bottom navigation bar — base-200 background,
|
|
11
|
+
* top separator line, active label in primary color.
|
|
12
|
+
*
|
|
13
|
+
* Use the standalone daisy `<Tabs>` / `<Tab>` (also exported from this
|
|
14
|
+
* package) instead when you want generic tab UI not driven by navigation
|
|
15
|
+
* state (e.g. segmented controls inside a settings panel).
|
|
16
|
+
*/
|
|
17
|
+
import { component } from '@sigx/lynx';
|
|
18
|
+
import { Pressable } from '@sigx/lynx-gestures';
|
|
19
|
+
import { Icon } from '@sigx/lynx-icons';
|
|
20
|
+
import { useTabs } from '@sigx/lynx-navigation';
|
|
21
|
+
import { PRESSED_SCALE, PRESSED_OPACITY } from '../shared/press.js';
|
|
22
|
+
/** Narrow `TabInfo.icon` to its `IconSpec` variant — the bar renders `<Icon>` for these. */
|
|
23
|
+
const isIconSpec = (v) => typeof v === 'object' && v !== null && 'set' in v && 'name' in v
|
|
24
|
+
&& typeof v.set === 'string'
|
|
25
|
+
&& typeof v.name === 'string';
|
|
26
|
+
const backgroundClass = {
|
|
27
|
+
'base-100': 'bg-base-100',
|
|
28
|
+
'base-200': 'bg-base-200',
|
|
29
|
+
'base-300': 'bg-base-300',
|
|
30
|
+
'transparent': '',
|
|
31
|
+
};
|
|
32
|
+
export const NavTabBar = component(({ props }) => {
|
|
33
|
+
const nav = useTabs();
|
|
34
|
+
return () => {
|
|
35
|
+
const tabs = nav.tabs;
|
|
36
|
+
const active = nav.active;
|
|
37
|
+
const renderer = props.renderTab;
|
|
38
|
+
const position = props.position ?? 'bottom';
|
|
39
|
+
const bg = backgroundClass[props.background ?? 'base-200'];
|
|
40
|
+
const bordered = props.bordered ?? true;
|
|
41
|
+
// Bottom tab bar → top border. Top tab bar → bottom border.
|
|
42
|
+
const borderClass = bordered
|
|
43
|
+
? (position === 'bottom' ? 'border-t border-base-300' : 'border-b border-base-300')
|
|
44
|
+
: '';
|
|
45
|
+
const containerClass = ['flex flex-row', bg, borderClass].filter(Boolean).join(' ');
|
|
46
|
+
return (_jsx("view", { "accessibility-element": false, class: containerClass, children: tabs.map((info) => {
|
|
47
|
+
const isActive = info.name === active;
|
|
48
|
+
const onPress = () => nav.setActive(info.name);
|
|
49
|
+
if (renderer) {
|
|
50
|
+
return renderer(info, { active: isActive, onPress });
|
|
51
|
+
}
|
|
52
|
+
return (_jsx(DefaultNavTab, { info: info, active: isActive, onPress: onPress }));
|
|
53
|
+
}) }));
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Pixel size the bar uses when rendering `<Icon>` from an `IconSpec`.
|
|
58
|
+
* Matches the default tab-row height visually.
|
|
59
|
+
*/
|
|
60
|
+
const TAB_ICON_SIZE = 22;
|
|
61
|
+
const DefaultNavTab = component(({ props }) => {
|
|
62
|
+
return () => {
|
|
63
|
+
const label = props.info.label ?? props.info.name;
|
|
64
|
+
const a11y = props.info.accessibilityLabel ?? label;
|
|
65
|
+
// Label uses native CSS color via daisy `text-*` classes — Lynx's
|
|
66
|
+
// `<text>` honors color inheritance normally. The icon path below
|
|
67
|
+
// can't rely on the same trick (see comment there for why).
|
|
68
|
+
const labelTone = props.active ? 'text-primary' : 'text-base-content opacity-60';
|
|
69
|
+
const weight = props.active ? 'font-semibold' : '';
|
|
70
|
+
const icon = props.info.icon;
|
|
71
|
+
// For an `IconSpec`, render `<Icon>` with the matching daisy
|
|
72
|
+
// variant. `<ThemeProvider>`'s color resolver maps `'primary'` /
|
|
73
|
+
// `'base-content'` (etc.) to the current theme's hex value, which
|
|
74
|
+
// `<Icon>` substitutes directly into the SVG `fill=` attribute —
|
|
75
|
+
// Lynx's `<svg content=…>` parses inline SVG in isolation and
|
|
76
|
+
// doesn't inherit host `color`, so class-based theming doesn't
|
|
77
|
+
// reach the SVG content. Inactive layers `opacity-60` as a class
|
|
78
|
+
// on the outer element (opacity does propagate to the raster).
|
|
79
|
+
//
|
|
80
|
+
// For a `JSXElement`, the consumer is in charge of styling — we
|
|
81
|
+
// leave it untouched. They can opt into the same theming by
|
|
82
|
+
// passing `variant="primary"` themselves.
|
|
83
|
+
const iconVariant = props.active ? 'primary' : 'base-content';
|
|
84
|
+
const iconClass = props.active ? undefined : 'opacity-60';
|
|
85
|
+
const renderedIcon = isIconSpec(icon)
|
|
86
|
+
? _jsx(Icon, { set: icon.set, name: icon.name, size: TAB_ICON_SIZE, variant: iconVariant, class: iconClass })
|
|
87
|
+
: (icon ?? null);
|
|
88
|
+
return (_jsxs(Pressable, { class: "flex-1 items-center justify-center py-3", pressedScale: PRESSED_SCALE, pressedOpacity: PRESSED_OPACITY, longPressDuration: 0, "accessibility-element": true, "accessibility-label": a11y, "accessibility-trait": "button", "accessibility-status": props.active ? 'selected' : undefined, onPress: () => props.onPress(), children: [renderedIcon, _jsx("text", { class: `text-sm ${labelTone} ${weight}`, children: label })] }));
|
|
89
|
+
};
|
|
90
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type Define, type PrimitiveSignal, type SharedValue } from '@sigx/lynx';
|
|
2
|
+
import { type DaisyColor } from '../shared/styles.js';
|
|
3
|
+
/**
|
|
4
|
+
* Visual style for the swiper page indicator.
|
|
5
|
+
*
|
|
6
|
+
* - `dots` — equally-spaced circles, the active one fades in via opacity.
|
|
7
|
+
* Today's default. Cheap (opacity-only MT mapper, no layout each frame).
|
|
8
|
+
* - `bar` — fixed track with a single sliding thumb. Single MT binding
|
|
9
|
+
* regardless of page count, so cheapest for very long carousels.
|
|
10
|
+
* - `pill` — the active dot stretches horizontally into a pill while
|
|
11
|
+
* neighbours stay circular. Uses `scaleX` so siblings don't reflow.
|
|
12
|
+
* - `numbered` — text counter like `2 / 5`. Pure BG-thread, no animation.
|
|
13
|
+
* - `scale-pulse` — circles where the active one scales up. No colour
|
|
14
|
+
* crossfade — pairs well with monochrome palettes.
|
|
15
|
+
*/
|
|
16
|
+
export type SwiperIndicatorVariant = 'dots' | 'bar' | 'pill' | 'numbered' | 'scale-pulse';
|
|
17
|
+
export type SwiperIndicatorSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
18
|
+
export type SwiperIndicatorProps = Define.Prop<'variant', SwiperIndicatorVariant, false>
|
|
19
|
+
/** Live MT pixel offset from the parent `<Swiper>`. Required for all animated variants. */
|
|
20
|
+
& Define.Prop<'offset', SharedValue<number>, false>
|
|
21
|
+
/** Page width in CSS px. Must match the Swiper's effective page width. */
|
|
22
|
+
& Define.Prop<'pageWidth', number, false>
|
|
23
|
+
/** Total page count. */
|
|
24
|
+
& Define.Prop<'count', number, true>
|
|
25
|
+
/**
|
|
26
|
+
* Current page (whole-units). Required for `numbered`, used by `bar`
|
|
27
|
+
* as fallback when `offset` isn't wired, and consumed by all variants
|
|
28
|
+
* for tap-to-jump.
|
|
29
|
+
*/
|
|
30
|
+
& Define.Prop<'index', PrimitiveSignal<number>, false> & Define.Prop<'color', DaisyColor, false> & Define.Prop<'inactiveColor', DaisyColor, false> & Define.Prop<'size', SwiperIndicatorSize, false>
|
|
31
|
+
/**
|
|
32
|
+
* Tap-to-jump handler. The receiver should typically write
|
|
33
|
+
* `index.value = i` to glide the swiper to that page.
|
|
34
|
+
*/
|
|
35
|
+
& Define.Prop<'onDotPress', (index: number) => void, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false>;
|
|
36
|
+
/**
|
|
37
|
+
* Themed swiper page indicator with five preset variants. Each variant
|
|
38
|
+
* is a thin shell over a headless hook from `@sigx/lynx-gestures` (see
|
|
39
|
+
* `useSwiperDotProgress`, `useSwiperDotScale`, `useSwiperDotGrowX`,
|
|
40
|
+
* `useSwiperDotTranslate`). For a fully custom indicator, compose the
|
|
41
|
+
* hooks yourself rather than forking this file.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* const offset = useSharedValue(0);
|
|
46
|
+
* const idx = signal({ value: 0 });
|
|
47
|
+
* <Swiper offset={offset} index={idx} width={W}>…</Swiper>
|
|
48
|
+
* <SwiperIndicator
|
|
49
|
+
* variant="pill"
|
|
50
|
+
* offset={offset}
|
|
51
|
+
* pageWidth={W}
|
|
52
|
+
* count={photos.length}
|
|
53
|
+
* index={idx}
|
|
54
|
+
* color="primary"
|
|
55
|
+
* onDotPress={(i) => { idx.value = i; }}
|
|
56
|
+
* />
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare const SwiperIndicator: import("@sigx/runtime-core").ComponentFactory<SwiperIndicatorProps, void, {}>;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@sigx/lynx/jsx-runtime";
|
|
2
|
+
import { component, effect, signal, } from '@sigx/lynx';
|
|
3
|
+
import { useSwiperDotProgress, useSwiperDotScale, useSwiperDotGrowX, useSwiperDotTranslate, } from '@sigx/lynx-gestures';
|
|
4
|
+
import { resolveDaisyColor } from '../shared/styles.js';
|
|
5
|
+
const SIZE_TABLE = {
|
|
6
|
+
xs: { dot: 4, gap: 4, barHeight: 3, fontSize: 11 },
|
|
7
|
+
sm: { dot: 6, gap: 6, barHeight: 4, fontSize: 12 },
|
|
8
|
+
md: { dot: 8, gap: 8, barHeight: 5, fontSize: 14 },
|
|
9
|
+
lg: { dot: 12, gap: 10, barHeight: 6, fontSize: 16 },
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Themed swiper page indicator with five preset variants. Each variant
|
|
13
|
+
* is a thin shell over a headless hook from `@sigx/lynx-gestures` (see
|
|
14
|
+
* `useSwiperDotProgress`, `useSwiperDotScale`, `useSwiperDotGrowX`,
|
|
15
|
+
* `useSwiperDotTranslate`). For a fully custom indicator, compose the
|
|
16
|
+
* hooks yourself rather than forking this file.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const offset = useSharedValue(0);
|
|
21
|
+
* const idx = signal({ value: 0 });
|
|
22
|
+
* <Swiper offset={offset} index={idx} width={W}>…</Swiper>
|
|
23
|
+
* <SwiperIndicator
|
|
24
|
+
* variant="pill"
|
|
25
|
+
* offset={offset}
|
|
26
|
+
* pageWidth={W}
|
|
27
|
+
* count={photos.length}
|
|
28
|
+
* index={idx}
|
|
29
|
+
* color="primary"
|
|
30
|
+
* onDotPress={(i) => { idx.value = i; }}
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const SwiperIndicator = component(({ props }) => {
|
|
35
|
+
return () => {
|
|
36
|
+
const variant = props.variant ?? 'dots';
|
|
37
|
+
const size = SIZE_TABLE[props.size ?? 'md'];
|
|
38
|
+
const activeColor = resolveDaisyColor(props.color ?? 'primary');
|
|
39
|
+
const inactiveColor = resolveDaisyColor(props.inactiveColor ?? 'base-content');
|
|
40
|
+
if (variant === 'numbered') {
|
|
41
|
+
return (_jsx(NumberedIndicator, { count: props.count, index: props.index ?? FALLBACK_INDEX, color: activeColor, fontSize: size.fontSize, class: props.class, style: props.style }));
|
|
42
|
+
}
|
|
43
|
+
if (variant === 'bar') {
|
|
44
|
+
if (props.offset == null || props.pageWidth == null)
|
|
45
|
+
return null;
|
|
46
|
+
return (_jsx(BarIndicator, { offset: props.offset, pageWidth: props.pageWidth, count: props.count, activeColor: activeColor, inactiveColor: inactiveColor, barHeight: size.barHeight, dotSize: size.dot, gap: size.gap, onDotPress: props.onDotPress, class: props.class, style: props.style }));
|
|
47
|
+
}
|
|
48
|
+
if (props.offset == null || props.pageWidth == null)
|
|
49
|
+
return null;
|
|
50
|
+
return (_jsx("view", { class: props.class, style: {
|
|
51
|
+
display: 'flex',
|
|
52
|
+
flexDirection: 'row',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
justifyContent: 'center',
|
|
55
|
+
gap: size.gap + 'px',
|
|
56
|
+
...(props.style || {}),
|
|
57
|
+
}, children: Array.from({ length: props.count }, (_, i) => (_jsx(Dot, { index: i, offset: props.offset, pageWidth: props.pageWidth, variant: variant, size: size, activeColor: activeColor, inactiveColor: inactiveColor, onPress: props.onDotPress }, i))) }));
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
61
|
+
// Per-variant pieces. Each owns a single `useAnimatedStyle` call-site
|
|
62
|
+
// (per-iteration call inside `.map()` is fine — call-sites are stable).
|
|
63
|
+
const FALLBACK_INDEX = signal({ value: 0 });
|
|
64
|
+
const Dot = component(({ props }) => {
|
|
65
|
+
// Each branch picks a different headless hook. Variants that need
|
|
66
|
+
// *two* simultaneous channels (opacity AND scale, or scale AND scaleX)
|
|
67
|
+
// need two refs — one per element — because `useAnimatedStyle` is
|
|
68
|
+
// one-binding-per-element.
|
|
69
|
+
if (props.variant === 'dots') {
|
|
70
|
+
return DotsBody(props);
|
|
71
|
+
}
|
|
72
|
+
if (props.variant === 'pill') {
|
|
73
|
+
return PillBody(props);
|
|
74
|
+
}
|
|
75
|
+
// scale-pulse
|
|
76
|
+
return ScalePulseBody(props);
|
|
77
|
+
});
|
|
78
|
+
function DotsBody(props) {
|
|
79
|
+
const overlayRef = useSwiperDotProgress({
|
|
80
|
+
offset: props.offset,
|
|
81
|
+
pageWidth: props.pageWidth,
|
|
82
|
+
index: props.index,
|
|
83
|
+
});
|
|
84
|
+
return () => (_jsx("view", { catchtap: props.onPress ? () => props.onPress?.(props.index) : undefined, style: {
|
|
85
|
+
width: props.size.dot + 'px',
|
|
86
|
+
height: props.size.dot + 'px',
|
|
87
|
+
borderRadius: (props.size.dot / 2) + 'px',
|
|
88
|
+
backgroundColor: withAlpha(props.inactiveColor, 0.4),
|
|
89
|
+
position: 'relative',
|
|
90
|
+
overflow: 'hidden',
|
|
91
|
+
}, children: _jsx("view", { "main-thread:ref": overlayRef, style: {
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
left: '0',
|
|
94
|
+
top: '0',
|
|
95
|
+
right: '0',
|
|
96
|
+
bottom: '0',
|
|
97
|
+
backgroundColor: props.activeColor,
|
|
98
|
+
opacity: '0',
|
|
99
|
+
} }) }));
|
|
100
|
+
}
|
|
101
|
+
function PillBody(props) {
|
|
102
|
+
// Pill stretches horizontally via scaleX (no layout cost) and brightens
|
|
103
|
+
// via opacity on the active-colour overlay. Both channels target the
|
|
104
|
+
// same dot — but each needs its own bound element, so we wrap the
|
|
105
|
+
// overlay inside a scaling shell.
|
|
106
|
+
const shellRef = useSwiperDotGrowX({
|
|
107
|
+
offset: props.offset,
|
|
108
|
+
pageWidth: props.pageWidth,
|
|
109
|
+
index: props.index,
|
|
110
|
+
inactive: 1,
|
|
111
|
+
active: 3,
|
|
112
|
+
});
|
|
113
|
+
const overlayRef = useSwiperDotProgress({
|
|
114
|
+
offset: props.offset,
|
|
115
|
+
pageWidth: props.pageWidth,
|
|
116
|
+
index: props.index,
|
|
117
|
+
});
|
|
118
|
+
return () => (_jsx("view", { catchtap: props.onPress ? () => props.onPress?.(props.index) : undefined, "main-thread:ref": shellRef, style: {
|
|
119
|
+
width: props.size.dot + 'px',
|
|
120
|
+
height: props.size.dot + 'px',
|
|
121
|
+
borderRadius: (props.size.dot / 2) + 'px',
|
|
122
|
+
backgroundColor: withAlpha(props.inactiveColor, 0.4),
|
|
123
|
+
position: 'relative',
|
|
124
|
+
overflow: 'hidden',
|
|
125
|
+
transformOrigin: 'center center',
|
|
126
|
+
}, children: _jsx("view", { "main-thread:ref": overlayRef, style: {
|
|
127
|
+
position: 'absolute',
|
|
128
|
+
left: '0',
|
|
129
|
+
top: '0',
|
|
130
|
+
right: '0',
|
|
131
|
+
bottom: '0',
|
|
132
|
+
backgroundColor: props.activeColor,
|
|
133
|
+
opacity: '0',
|
|
134
|
+
} }) }));
|
|
135
|
+
}
|
|
136
|
+
function ScalePulseBody(props) {
|
|
137
|
+
// No colour crossfade — pure scale. Active dot uses `activeColor`,
|
|
138
|
+
// inactive uses `inactiveColor` at low alpha. Visual is monochrome
|
|
139
|
+
// friendly.
|
|
140
|
+
const scaleRef = useSwiperDotScale({
|
|
141
|
+
offset: props.offset,
|
|
142
|
+
pageWidth: props.pageWidth,
|
|
143
|
+
index: props.index,
|
|
144
|
+
inactive: 1,
|
|
145
|
+
active: 1.6,
|
|
146
|
+
});
|
|
147
|
+
const opacityRef = useSwiperDotProgress({
|
|
148
|
+
offset: props.offset,
|
|
149
|
+
pageWidth: props.pageWidth,
|
|
150
|
+
index: props.index,
|
|
151
|
+
});
|
|
152
|
+
return () => (_jsx("view", { catchtap: props.onPress ? () => props.onPress?.(props.index) : undefined, "main-thread:ref": scaleRef, style: {
|
|
153
|
+
width: props.size.dot + 'px',
|
|
154
|
+
height: props.size.dot + 'px',
|
|
155
|
+
borderRadius: (props.size.dot / 2) + 'px',
|
|
156
|
+
backgroundColor: withAlpha(props.inactiveColor, 0.4),
|
|
157
|
+
position: 'relative',
|
|
158
|
+
overflow: 'hidden',
|
|
159
|
+
}, children: _jsx("view", { "main-thread:ref": opacityRef, style: {
|
|
160
|
+
position: 'absolute',
|
|
161
|
+
left: '0',
|
|
162
|
+
top: '0',
|
|
163
|
+
right: '0',
|
|
164
|
+
bottom: '0',
|
|
165
|
+
backgroundColor: props.activeColor,
|
|
166
|
+
opacity: '0',
|
|
167
|
+
} }) }));
|
|
168
|
+
}
|
|
169
|
+
const BarIndicator = component(({ props }) => {
|
|
170
|
+
// The thumb advances by (dot + gap) per page. We use the headless
|
|
171
|
+
// translate hook — a single MT binding regardless of page count.
|
|
172
|
+
const step = props.dotSize + props.gap;
|
|
173
|
+
const thumbRef = useSwiperDotTranslate({
|
|
174
|
+
offset: props.offset,
|
|
175
|
+
pageWidth: props.pageWidth,
|
|
176
|
+
step,
|
|
177
|
+
});
|
|
178
|
+
return () => {
|
|
179
|
+
const trackWidth = props.count * props.dotSize + Math.max(0, props.count - 1) * props.gap;
|
|
180
|
+
return (_jsxs("view", { class: props.class, style: {
|
|
181
|
+
position: 'relative',
|
|
182
|
+
width: trackWidth + 'px',
|
|
183
|
+
height: props.barHeight + 'px',
|
|
184
|
+
borderRadius: (props.barHeight / 2) + 'px',
|
|
185
|
+
backgroundColor: withAlpha(props.inactiveColor, 0.25),
|
|
186
|
+
overflow: 'visible',
|
|
187
|
+
...(props.style || {}),
|
|
188
|
+
}, children: [props.onDotPress
|
|
189
|
+
? (_jsx("view", { style: {
|
|
190
|
+
position: 'absolute',
|
|
191
|
+
inset: '0',
|
|
192
|
+
display: 'flex',
|
|
193
|
+
flexDirection: 'row',
|
|
194
|
+
alignItems: 'center',
|
|
195
|
+
}, children: Array.from({ length: props.count }, (_, i) => (_jsx("view", { catchtap: () => props.onDotPress?.(i), style: {
|
|
196
|
+
width: (props.dotSize + props.gap) + 'px',
|
|
197
|
+
height: '100%',
|
|
198
|
+
} }, i))) }))
|
|
199
|
+
: null, _jsx("view", { "main-thread:ref": thumbRef, style: {
|
|
200
|
+
position: 'absolute',
|
|
201
|
+
left: '0',
|
|
202
|
+
top: '0',
|
|
203
|
+
width: props.dotSize + 'px',
|
|
204
|
+
height: '100%',
|
|
205
|
+
borderRadius: (props.barHeight / 2) + 'px',
|
|
206
|
+
backgroundColor: props.activeColor,
|
|
207
|
+
} })] }));
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
const NumberedIndicator = component(({ props }) => {
|
|
211
|
+
const label = signal({ value: '' });
|
|
212
|
+
effect(() => {
|
|
213
|
+
label.value = `${(props.index.value | 0) + 1} / ${props.count}`;
|
|
214
|
+
});
|
|
215
|
+
return () => (_jsx("text", { class: props.class, style: {
|
|
216
|
+
color: props.color,
|
|
217
|
+
fontSize: props.fontSize + 'px',
|
|
218
|
+
fontWeight: '600',
|
|
219
|
+
...(props.style || {}),
|
|
220
|
+
}, children: label.value }));
|
|
221
|
+
});
|
|
222
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
223
|
+
// Helpers
|
|
224
|
+
/**
|
|
225
|
+
* Apply an alpha to a CSS colour value. Works for `var(--color-*)`
|
|
226
|
+
* (uses `color-mix`) and for raw rgb/hex strings (uses `color-mix`
|
|
227
|
+
* too — broadly supported on the platforms Lynx targets).
|
|
228
|
+
*/
|
|
229
|
+
function withAlpha(color, alpha) {
|
|
230
|
+
const pct = Math.round(Math.max(0, Math.min(1, alpha)) * 100);
|
|
231
|
+
return `color-mix(in srgb, ${color} ${pct}%, transparent)`;
|
|
232
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@sigx/lynx/jsx-runtime";
|
|
2
|
+
import { component, compound } from '@sigx/lynx';
|
|
3
|
+
import { Pressable } from '@sigx/lynx-gestures';
|
|
4
|
+
import { PRESSED_SCALE, PRESSED_OPACITY } from '../shared/press.js';
|
|
5
|
+
const _Tabs = component(({ props, slots }) => {
|
|
6
|
+
return () => (_jsx("view", { class: `tabs${props.class ? ' ' + props.class : ''}`, children: slots.default?.() }));
|
|
7
|
+
});
|
|
8
|
+
const Tab = component(({ props, slots }) => {
|
|
9
|
+
return () => {
|
|
10
|
+
const isActive = props.active ?? false;
|
|
11
|
+
return (_jsxs(Pressable, { class: `tab${isActive ? ' tab-active' : ''}${props.class ? ' ' + props.class : ''}`, pressedScale: PRESSED_SCALE, pressedOpacity: PRESSED_OPACITY, longPressDuration: 0, onPress: () => {
|
|
12
|
+
props.onPress?.();
|
|
13
|
+
}, children: [slots.default?.(), props.label ? _jsx("text", { children: props.label }) : null] }));
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
export const Tabs = compound(_Tabs, {
|
|
17
|
+
Tab,
|
|
18
|
+
});
|
package/dist/preset/index.js
CHANGED
|
@@ -1,40 +1,66 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
import plugin from 'tailwindcss/plugin';
|
|
2
|
+
/**
|
|
3
|
+
* DaisyUI Lynx Tailwind Preset
|
|
4
|
+
*
|
|
5
|
+
* Maps DaisyUI semantic color tokens to CSS custom properties
|
|
6
|
+
* defined in @sigx/lynx-daisyui/styles. Consumers add this
|
|
7
|
+
* preset to their tailwind.config.ts so utilities like
|
|
8
|
+
* `bg-primary` and `text-base-content` resolve to our tokens.
|
|
9
|
+
*
|
|
10
|
+
* Also ships a `flex-fill` utility — the Lynx-correct "fill remaining
|
|
11
|
+
* space" class. Why this is in our preset rather than baked into Lynx's
|
|
12
|
+
* own tailwind preset: in Lynx (like React Native) `flex: 1` shorthand
|
|
13
|
+
* expands to `flex: 1 1 auto`, where `flexBasis: 'auto'` sizes the box
|
|
14
|
+
* to its content first, collapsing the layout chain. The browser-CSS
|
|
15
|
+
* intuition that `flex-1` = "fill remaining space" is wrong here, and
|
|
16
|
+
* Tailwind's own `flex-1` class expands to the same broken shorthand.
|
|
17
|
+
* `flex-fill` writes the long-form properties directly so the result
|
|
18
|
+
* actually fills.
|
|
19
|
+
*/
|
|
20
|
+
const daisyColors = {
|
|
21
|
+
'primary': 'var(--color-primary)',
|
|
22
|
+
'primary-content': 'var(--color-primary-content)',
|
|
23
|
+
'secondary': 'var(--color-secondary)',
|
|
24
|
+
'secondary-content': 'var(--color-secondary-content)',
|
|
25
|
+
'accent': 'var(--color-accent)',
|
|
26
|
+
'accent-content': 'var(--color-accent-content)',
|
|
27
|
+
'neutral': 'var(--color-neutral)',
|
|
28
|
+
'neutral-content': 'var(--color-neutral-content)',
|
|
29
|
+
'base-100': 'var(--color-base-100)',
|
|
30
|
+
'base-200': 'var(--color-base-200)',
|
|
31
|
+
'base-300': 'var(--color-base-300)',
|
|
32
|
+
'base-content': 'var(--color-base-content)',
|
|
33
|
+
'info': 'var(--color-info)',
|
|
34
|
+
'info-content': 'var(--color-info-content)',
|
|
35
|
+
'success': 'var(--color-success)',
|
|
36
|
+
'success-content': 'var(--color-success-content)',
|
|
37
|
+
'warning': 'var(--color-warning)',
|
|
38
|
+
'warning-content': 'var(--color-warning-content)',
|
|
39
|
+
'error': 'var(--color-error)',
|
|
40
|
+
'error-content': 'var(--color-error-content)',
|
|
41
|
+
};
|
|
42
|
+
const lynxLayoutPlugin = plugin(({ addUtilities }) => {
|
|
43
|
+
addUtilities({
|
|
44
|
+
// Long-form flex-fill — the Lynx-correct "take remaining space along
|
|
45
|
+
// the main axis" utility. Default flex direction column; consumers
|
|
46
|
+
// who want a horizontal fill compose with `flex-row` on the parent.
|
|
47
|
+
'.flex-fill': {
|
|
48
|
+
flexGrow: '1',
|
|
49
|
+
flexShrink: '1',
|
|
50
|
+
flexBasis: '0',
|
|
51
|
+
minHeight: '0',
|
|
52
|
+
display: 'flex',
|
|
53
|
+
flexDirection: 'column',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
export const DaisyLynxPreset = {
|
|
58
|
+
theme: {
|
|
59
|
+
extend: {
|
|
60
|
+
colors: daisyColors,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
plugins: [lynxLayoutPlugin],
|
|
64
|
+
};
|
|
65
|
+
/** Alias — preferred consumer name. */
|
|
66
|
+
export const daisyuiPreset = DaisyLynxPreset;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Shared press-feedback defaults applied across all interactive daisyui
|
|
2
|
+
// components. Lynx has no CSS `:active` support, so press feedback comes from
|
|
3
|
+
// `<Pressable>` (in `@sigx/lynx-gestures`) flipping inline opacity/transform
|
|
4
|
+
// on the main thread.
|
|
5
|
+
export const PRESSED_SCALE = 0.97;
|
|
6
|
+
export const PRESSED_OPACITY = 0.85;
|
package/dist/shared/styles.d.ts
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DaisyUI color tokens — the set of semantic colors exposed by the
|
|
3
|
+
* built-in themes (`daisy-light` / `daisy-dark`) as `--color-<token>`
|
|
4
|
+
* CSS custom properties.
|
|
5
|
+
*
|
|
6
|
+
* Used by layout components' `background` prop so consumers can write
|
|
7
|
+
* `<Col background="base-100">` instead of `<Col class="bg-base-100">`
|
|
8
|
+
* and still get autocomplete + type safety.
|
|
9
|
+
*
|
|
10
|
+
* Single source of truth: both the `DaisyColor` union and the runtime
|
|
11
|
+
* `DAISY_COLOR_TOKENS` Set are derived from this tuple, so adding /
|
|
12
|
+
* removing a token in one place is impossible.
|
|
13
|
+
*/
|
|
14
|
+
declare const DAISY_COLOR_TOKEN_LIST: readonly ['primary', 'primary-content', 'secondary', 'secondary-content', 'accent', 'accent-content', 'neutral', 'neutral-content', 'base-100', 'base-200', 'base-300', 'base-content', 'info', 'info-content', 'success', 'success-content', 'warning', 'warning-content', 'error', 'error-content'];
|
|
15
|
+
export type DaisyColor = typeof DAISY_COLOR_TOKEN_LIST[number];
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a `background` prop value to a CSS color string.
|
|
18
|
+
*
|
|
19
|
+
* - Known daisyUI tokens (e.g. `'base-100'`) → `var(--color-base-100)`.
|
|
20
|
+
* - Anything else (`'#ffaa00'`, `'rgb(…)'`, `'var(--my-custom)'`) is passed through unchanged.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveDaisyColor(value: string): string;
|
|
1
23
|
export type SpacingValue = number | {
|
|
2
24
|
x?: number;
|
|
3
25
|
y?: number;
|
|
@@ -6,14 +28,20 @@ export type SpacingValue = number | {
|
|
|
6
28
|
bottom?: number;
|
|
7
29
|
left?: number;
|
|
8
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Accepts a daisyUI color token (autocompleted) OR any raw CSS color
|
|
33
|
+
* string (`'#fff'`, `'rgb(…)'`, `'var(--foo)'`).
|
|
34
|
+
*/
|
|
35
|
+
export type BackgroundValue = DaisyColor | (string & {});
|
|
9
36
|
export interface BoxProps {
|
|
10
37
|
width?: number | string;
|
|
11
38
|
height?: number | string;
|
|
12
39
|
flex?: number;
|
|
13
|
-
background?:
|
|
40
|
+
background?: BackgroundValue;
|
|
14
41
|
borderRadius?: number;
|
|
15
42
|
padding?: SpacingValue;
|
|
16
43
|
margin?: SpacingValue;
|
|
17
44
|
}
|
|
18
45
|
export declare function resolveSpacing(value: SpacingValue | undefined, prefix: 'padding' | 'margin'): Record<string, number>;
|
|
19
46
|
export declare function resolveBoxStyle(props: BoxProps): Record<string, unknown>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DaisyUI color tokens — the set of semantic colors exposed by the
|
|
3
|
+
* built-in themes (`daisy-light` / `daisy-dark`) as `--color-<token>`
|
|
4
|
+
* CSS custom properties.
|
|
5
|
+
*
|
|
6
|
+
* Used by layout components' `background` prop so consumers can write
|
|
7
|
+
* `<Col background="base-100">` instead of `<Col class="bg-base-100">`
|
|
8
|
+
* and still get autocomplete + type safety.
|
|
9
|
+
*
|
|
10
|
+
* Single source of truth: both the `DaisyColor` union and the runtime
|
|
11
|
+
* `DAISY_COLOR_TOKENS` Set are derived from this tuple, so adding /
|
|
12
|
+
* removing a token in one place is impossible.
|
|
13
|
+
*/
|
|
14
|
+
const DAISY_COLOR_TOKEN_LIST = [
|
|
15
|
+
'primary', 'primary-content',
|
|
16
|
+
'secondary', 'secondary-content',
|
|
17
|
+
'accent', 'accent-content',
|
|
18
|
+
'neutral', 'neutral-content',
|
|
19
|
+
'base-100', 'base-200', 'base-300', 'base-content',
|
|
20
|
+
'info', 'info-content',
|
|
21
|
+
'success', 'success-content',
|
|
22
|
+
'warning', 'warning-content',
|
|
23
|
+
'error', 'error-content',
|
|
24
|
+
];
|
|
25
|
+
const DAISY_COLOR_TOKENS = new Set(DAISY_COLOR_TOKEN_LIST);
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a `background` prop value to a CSS color string.
|
|
28
|
+
*
|
|
29
|
+
* - Known daisyUI tokens (e.g. `'base-100'`) → `var(--color-base-100)`.
|
|
30
|
+
* - Anything else (`'#ffaa00'`, `'rgb(…)'`, `'var(--my-custom)'`) is passed through unchanged.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveDaisyColor(value) {
|
|
33
|
+
return DAISY_COLOR_TOKENS.has(value)
|
|
34
|
+
? `var(--color-${value})`
|
|
35
|
+
: value;
|
|
36
|
+
}
|
|
37
|
+
export function resolveSpacing(value, prefix) {
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return {};
|
|
40
|
+
if (typeof value === 'number') {
|
|
41
|
+
return {
|
|
42
|
+
[`${prefix}Top`]: value,
|
|
43
|
+
[`${prefix}Right`]: value,
|
|
44
|
+
[`${prefix}Bottom`]: value,
|
|
45
|
+
[`${prefix}Left`]: value,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const style = {};
|
|
49
|
+
if (value.top !== undefined)
|
|
50
|
+
style[`${prefix}Top`] = value.top;
|
|
51
|
+
else if (value.y !== undefined)
|
|
52
|
+
style[`${prefix}Top`] = value.y;
|
|
53
|
+
if (value.bottom !== undefined)
|
|
54
|
+
style[`${prefix}Bottom`] = value.bottom;
|
|
55
|
+
else if (value.y !== undefined)
|
|
56
|
+
style[`${prefix}Bottom`] = value.y;
|
|
57
|
+
if (value.right !== undefined)
|
|
58
|
+
style[`${prefix}Right`] = value.right;
|
|
59
|
+
else if (value.x !== undefined)
|
|
60
|
+
style[`${prefix}Right`] = value.x;
|
|
61
|
+
if (value.left !== undefined)
|
|
62
|
+
style[`${prefix}Left`] = value.left;
|
|
63
|
+
else if (value.x !== undefined)
|
|
64
|
+
style[`${prefix}Left`] = value.x;
|
|
65
|
+
return style;
|
|
66
|
+
}
|
|
67
|
+
export function resolveBoxStyle(props) {
|
|
68
|
+
const style = {};
|
|
69
|
+
if (props.width !== undefined)
|
|
70
|
+
style.width = props.width;
|
|
71
|
+
if (props.height !== undefined)
|
|
72
|
+
style.height = props.height;
|
|
73
|
+
if (props.flex !== undefined) {
|
|
74
|
+
// Lynx (like React Native) expands `flex: n` shorthand to
|
|
75
|
+
// `flex: n n auto`, where `flexBasis: 'auto'` means "size to content
|
|
76
|
+
// first" — which collapses the layout chain. Write the long-form so
|
|
77
|
+
// `<Center flex={1}>` etc. actually fill remaining space.
|
|
78
|
+
style.flexGrow = props.flex;
|
|
79
|
+
style.flexShrink = 1;
|
|
80
|
+
style.flexBasis = 0;
|
|
81
|
+
style.minHeight = 0;
|
|
82
|
+
}
|
|
83
|
+
if (props.background !== undefined)
|
|
84
|
+
style.backgroundColor = resolveDaisyColor(props.background);
|
|
85
|
+
if (props.borderRadius !== undefined)
|
|
86
|
+
style.borderRadius = props.borderRadius;
|
|
87
|
+
Object.assign(style, resolveSpacing(props.padding, 'padding'));
|
|
88
|
+
Object.assign(style, resolveSpacing(props.margin, 'margin'));
|
|
89
|
+
return style;
|
|
90
|
+
}
|