@gryt/ui-native 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/dist/components/Accordion/Accordion.d.ts +35 -0
- package/dist/components/Accordion/Accordion.d.ts.map +1 -0
- package/dist/components/Accordion/Accordion.js +66 -0
- package/dist/components/Alert/Alert.d.ts +21 -0
- package/dist/components/Alert/Alert.d.ts.map +1 -0
- package/dist/components/Alert/Alert.js +35 -0
- package/dist/components/AlertDialog/AlertDialog.d.ts +38 -0
- package/dist/components/AlertDialog/AlertDialog.d.ts.map +1 -0
- package/dist/components/AlertDialog/AlertDialog.js +15 -0
- package/dist/components/Avatar/Avatar.d.ts +10 -0
- package/dist/components/Avatar/Avatar.d.ts.map +1 -0
- package/dist/components/Avatar/Avatar.js +57 -0
- package/dist/components/Badge/Badge.d.ts +13 -0
- package/dist/components/Badge/Badge.d.ts.map +1 -0
- package/dist/components/Badge/Badge.js +28 -0
- package/dist/components/Button/Button.d.ts +24 -0
- package/dist/components/Button/Button.d.ts.map +1 -0
- package/dist/components/Button/Button.js +61 -0
- package/dist/components/Card/Card.d.ts +19 -0
- package/dist/components/Card/Card.d.ts.map +1 -0
- package/dist/components/Card/Card.js +45 -0
- package/dist/components/Checkbox/Checkbox.d.ts +24 -0
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox/Checkbox.js +43 -0
- package/dist/components/Chip/Chip.d.ts +17 -0
- package/dist/components/Chip/Chip.d.ts.map +1 -0
- package/dist/components/Chip/Chip.js +38 -0
- package/dist/components/Collapsible/Collapsible.d.ts +31 -0
- package/dist/components/Collapsible/Collapsible.d.ts.map +1 -0
- package/dist/components/Collapsible/Collapsible.js +34 -0
- package/dist/components/Dialog/Dialog.d.ts +67 -0
- package/dist/components/Dialog/Dialog.d.ts.map +1 -0
- package/dist/components/Dialog/Dialog.js +112 -0
- package/dist/components/Divider/Divider.d.ts +16 -0
- package/dist/components/Divider/Divider.d.ts.map +1 -0
- package/dist/components/Divider/Divider.js +21 -0
- package/dist/components/Drawer/Drawer.d.ts +44 -0
- package/dist/components/Drawer/Drawer.d.ts.map +1 -0
- package/dist/components/Drawer/Drawer.js +83 -0
- package/dist/components/Menu/Menu.d.ts +56 -0
- package/dist/components/Menu/Menu.d.ts.map +1 -0
- package/dist/components/Menu/Menu.js +76 -0
- package/dist/components/Meter/Meter.d.ts +21 -0
- package/dist/components/Meter/Meter.d.ts.map +1 -0
- package/dist/components/Meter/Meter.js +32 -0
- package/dist/components/NumberField/NumberField.d.ts +23 -0
- package/dist/components/NumberField/NumberField.d.ts.map +1 -0
- package/dist/components/NumberField/NumberField.js +56 -0
- package/dist/components/OtpField/OtpField.d.ts +24 -0
- package/dist/components/OtpField/OtpField.d.ts.map +1 -0
- package/dist/components/OtpField/OtpField.js +49 -0
- package/dist/components/Popover/Popover.d.ts +48 -0
- package/dist/components/Popover/Popover.d.ts.map +1 -0
- package/dist/components/Popover/Popover.js +54 -0
- package/dist/components/Progress/Progress.d.ts +18 -0
- package/dist/components/Progress/Progress.d.ts.map +1 -0
- package/dist/components/Progress/Progress.js +30 -0
- package/dist/components/Radio/Radio.d.ts +28 -0
- package/dist/components/Radio/Radio.d.ts.map +1 -0
- package/dist/components/Radio/Radio.js +53 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts +22 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -0
- package/dist/components/ScrollArea/ScrollArea.js +17 -0
- package/dist/components/Select/Select.d.ts +34 -0
- package/dist/components/Select/Select.d.ts.map +1 -0
- package/dist/components/Select/Select.js +71 -0
- package/dist/components/Skeleton/Skeleton.d.ts +18 -0
- package/dist/components/Skeleton/Skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton/Skeleton.js +28 -0
- package/dist/components/Slider/Slider.d.ts +29 -0
- package/dist/components/Slider/Slider.d.ts.map +1 -0
- package/dist/components/Slider/Slider.js +94 -0
- package/dist/components/Spinner/Spinner.d.ts +20 -0
- package/dist/components/Spinner/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner/Spinner.js +18 -0
- package/dist/components/Surface/Surface.d.ts +17 -0
- package/dist/components/Surface/Surface.d.ts.map +1 -0
- package/dist/components/Surface/Surface.js +22 -0
- package/dist/components/Switch/Switch.d.ts +25 -0
- package/dist/components/Switch/Switch.d.ts.map +1 -0
- package/dist/components/Switch/Switch.js +65 -0
- package/dist/components/Tabs/Tabs.d.ts +51 -0
- package/dist/components/Tabs/Tabs.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.js +82 -0
- package/dist/components/TextField/TextField.d.ts +15 -0
- package/dist/components/TextField/TextField.d.ts.map +1 -0
- package/dist/components/TextField/TextField.js +47 -0
- package/dist/components/Toast/Toast.d.ts +31 -0
- package/dist/components/Toast/Toast.d.ts.map +1 -0
- package/dist/components/Toast/Toast.js +86 -0
- package/dist/components/Toggle/Toggle.d.ts +19 -0
- package/dist/components/Toggle/Toggle.d.ts.map +1 -0
- package/dist/components/Toggle/Toggle.js +41 -0
- package/dist/components/Toolbar/Toolbar.d.ts +25 -0
- package/dist/components/Toolbar/Toolbar.d.ts.map +1 -0
- package/dist/components/Toolbar/Toolbar.js +35 -0
- package/dist/components/Tooltip/Tooltip.d.ts +40 -0
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip/Tooltip.js +52 -0
- package/dist/hooks/useReducedMotion.d.ts +10 -0
- package/dist/hooks/useReducedMotion.d.ts.map +1 -0
- package/dist/hooks/useReducedMotion.js +26 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/overlay/AnchoredPopup.d.ts +26 -0
- package/dist/overlay/AnchoredPopup.d.ts.map +1 -0
- package/dist/overlay/AnchoredPopup.js +36 -0
- package/dist/overlay/placePopup.d.ts +51 -0
- package/dist/overlay/placePopup.d.ts.map +1 -0
- package/dist/overlay/placePopup.js +63 -0
- package/dist/overlay/useAnchoredPosition.d.ts +18 -0
- package/dist/overlay/useAnchoredPosition.d.ts.map +1 -0
- package/dist/overlay/useAnchoredPosition.js +25 -0
- package/dist/overlay/useOpenState.d.ts +17 -0
- package/dist/overlay/useOpenState.d.ts.map +1 -0
- package/dist/overlay/useOpenState.js +12 -0
- package/dist/theme/ThemeProvider.d.ts +17 -0
- package/dist/theme/ThemeProvider.d.ts.map +1 -0
- package/dist/theme/ThemeProvider.js +17 -0
- package/dist/theme/createNativeTheme.d.ts +82 -0
- package/dist/theme/createNativeTheme.d.ts.map +1 -0
- package/dist/theme/createNativeTheme.js +65 -0
- package/dist/theme/index.d.ts +6 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +5 -0
- package/dist/theme/themeContext.d.ts +9 -0
- package/dist/theme/themeContext.d.ts.map +1 -0
- package/dist/theme/themeContext.js +9 -0
- package/dist/theme/tones.d.ts +15 -0
- package/dist/theme/tones.d.ts.map +1 -0
- package/dist/theme/tones.js +11 -0
- package/dist/theme/useTheme.d.ts +8 -0
- package/dist/theme/useTheme.d.ts.map +1 -0
- package/dist/theme/useTheme.js +10 -0
- package/package.json +46 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { Pressable, Text, View } from "react-native";
|
|
4
|
+
import { AnchoredPopup } from "../../overlay/AnchoredPopup.js";
|
|
5
|
+
import { useAnchorMeasure } from "../../overlay/useAnchoredPosition.js";
|
|
6
|
+
import { useTheme } from "../../theme/index.js";
|
|
7
|
+
const SIZES = {
|
|
8
|
+
small: { minHeight: 36, fontSize: 14, paddingH: 12 },
|
|
9
|
+
medium: { minHeight: 40, fontSize: 14, paddingH: 14 },
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A single component, like the web's, rather than a namespace.
|
|
13
|
+
*
|
|
14
|
+
* `@gryt/ui`'s Select takes an `options` array and renders the trigger, the
|
|
15
|
+
* list, the checkmark and the positioning itself, so this matches that shape
|
|
16
|
+
* instead of exposing Root/Trigger/Popup.
|
|
17
|
+
*
|
|
18
|
+
* Underneath it is the same anchored popup Menu uses. The one thing worth
|
|
19
|
+
* knowing: the platform pickers are not used. iOS would give a wheel and Android
|
|
20
|
+
* a dialog, neither takes the Gryt palette, and the two look nothing like each
|
|
21
|
+
* other. A list matching the rest of the library is more use than two native
|
|
22
|
+
* controls that match neither.
|
|
23
|
+
*/
|
|
24
|
+
export function Select({ options = [], value: controlled, defaultValue = null, onValueChange, label, placeholder = "Select…", size = "medium", disabled, style, }) {
|
|
25
|
+
const theme = useTheme();
|
|
26
|
+
const metrics = SIZES[size];
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
29
|
+
const value = controlled === undefined ? uncontrolled : controlled;
|
|
30
|
+
const { anchor, measure } = useAnchorMeasure();
|
|
31
|
+
const ref = useRef(null);
|
|
32
|
+
const selected = options.find((o) => o.value === value);
|
|
33
|
+
const openList = useCallback(() => {
|
|
34
|
+
measure(ref.current);
|
|
35
|
+
setOpen(true);
|
|
36
|
+
}, [measure]);
|
|
37
|
+
return (_jsxs(View, { style: [{ gap: theme.space(1.5) }, style], children: [label ? (_jsx(Text, { style: { color: theme.color.muted, fontSize: 13, fontWeight: "500" }, children: label })) : null, _jsxs(Pressable, { ref: ref, accessibilityRole: "button", accessibilityState: { disabled: !!disabled, expanded: open }, disabled: disabled, onPress: openList, style: {
|
|
38
|
+
minHeight: metrics.minHeight,
|
|
39
|
+
paddingHorizontal: metrics.paddingH,
|
|
40
|
+
flexDirection: "row",
|
|
41
|
+
alignItems: "center",
|
|
42
|
+
justifyContent: "space-between",
|
|
43
|
+
gap: theme.space(2),
|
|
44
|
+
borderWidth: 1,
|
|
45
|
+
borderColor: open ? theme.color.accent : theme.color.border,
|
|
46
|
+
borderRadius: theme.radius.md,
|
|
47
|
+
backgroundColor: theme.color.surface,
|
|
48
|
+
opacity: disabled ? 0.5 : 1,
|
|
49
|
+
}, children: [typeof selected?.label === "string" || selected === undefined ? (_jsx(Text, { numberOfLines: 1, style: {
|
|
50
|
+
flex: 1,
|
|
51
|
+
color: selected ? theme.color.text : theme.color.muted,
|
|
52
|
+
fontSize: metrics.fontSize,
|
|
53
|
+
}, children: selected ? selected.label : placeholder })) : (_jsx(View, { style: { flex: 1 }, children: selected.label })), _jsx(Text, { style: { color: theme.color.muted, fontSize: 11 }, children: "\u25BE" })] }), _jsx(AnchoredPopup, { open: open, anchor: anchor, onDismiss: () => setOpen(false), align: "start", style: { paddingVertical: theme.space(1), minWidth: 180 }, children: _jsx(View, { accessibilityRole: "menu", children: options.map((option) => {
|
|
54
|
+
const isSelected = option.value === value;
|
|
55
|
+
return (_jsxs(Pressable, { accessibilityRole: "menuitem", accessibilityState: { selected: isSelected, disabled: !!option.disabled }, disabled: option.disabled, onPress: () => {
|
|
56
|
+
if (controlled === undefined)
|
|
57
|
+
setUncontrolled(option.value);
|
|
58
|
+
onValueChange?.(option.value);
|
|
59
|
+
setOpen(false);
|
|
60
|
+
}, style: ({ pressed }) => ({
|
|
61
|
+
flexDirection: "row",
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
justifyContent: "space-between",
|
|
64
|
+
gap: theme.space(3),
|
|
65
|
+
paddingHorizontal: theme.space(4),
|
|
66
|
+
paddingVertical: theme.space(2.5),
|
|
67
|
+
opacity: option.disabled ? 0.5 : 1,
|
|
68
|
+
backgroundColor: pressed ? theme.color.surfaceHover : "transparent",
|
|
69
|
+
}), children: [typeof option.label === "string" ? (_jsx(Text, { style: { color: theme.color.text, fontSize: 14 }, children: option.label })) : (option.label), isSelected ? (_jsx(Text, { style: { color: theme.color.accent, fontSize: 13, fontWeight: "900" }, children: "\u2713" })) : null] }, String(option.value)));
|
|
70
|
+
}) }) })] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type DimensionValue, type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
export interface SkeletonProps {
|
|
3
|
+
width?: DimensionValue;
|
|
4
|
+
height?: number;
|
|
5
|
+
/** Matches the shape of what is loading. `circle` for avatars. */
|
|
6
|
+
shape?: "rect" | "circle" | "text";
|
|
7
|
+
style?: StyleProp<ViewStyle>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Still, not shimmering.
|
|
11
|
+
*
|
|
12
|
+
* The web version pulses. A looping animation here would run for as long as the
|
|
13
|
+
* request takes, on a device where that costs battery, and it is the kind of
|
|
14
|
+
* motion reduce-motion users turn off first. A flat block reads as "loading"
|
|
15
|
+
* without any of that. Recorded in the parity exceptions table.
|
|
16
|
+
*/
|
|
17
|
+
export declare function Skeleton({ width, height, shape, style, }: SkeletonProps): import("react").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=Skeleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Skeleton.d.ts","sourceRoot":"","sources":["../../../src/components/Skeleton/Skeleton.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAc,EACd,MAAM,EACN,KAAc,EACd,KAAK,GACN,EAAE,aAAa,+BAwBf"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useTheme } from "../../theme/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Still, not shimmering.
|
|
6
|
+
*
|
|
7
|
+
* The web version pulses. A looping animation here would run for as long as the
|
|
8
|
+
* request takes, on a device where that costs battery, and it is the kind of
|
|
9
|
+
* motion reduce-motion users turn off first. A flat block reads as "loading"
|
|
10
|
+
* without any of that. Recorded in the parity exceptions table.
|
|
11
|
+
*/
|
|
12
|
+
export function Skeleton({ width = "100%", height, shape = "rect", style, }) {
|
|
13
|
+
const theme = useTheme();
|
|
14
|
+
const resolved = height ?? (shape === "text" ? 14 : 40);
|
|
15
|
+
return (_jsx(View, { accessibilityRole: "progressbar", accessibilityLabel: "Loading", style: [
|
|
16
|
+
{
|
|
17
|
+
width,
|
|
18
|
+
height: resolved,
|
|
19
|
+
backgroundColor: theme.scales.neutral[3],
|
|
20
|
+
borderRadius: shape === "circle"
|
|
21
|
+
? resolved / 2
|
|
22
|
+
: shape === "text"
|
|
23
|
+
? theme.radius.sm
|
|
24
|
+
: theme.radius.md,
|
|
25
|
+
},
|
|
26
|
+
style,
|
|
27
|
+
] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
import { type ComponentTone } from "../../theme/index.js";
|
|
3
|
+
export type SliderTone = Extract<ComponentTone, "primary" | "secondary" | "neutral" | "danger">;
|
|
4
|
+
export interface SliderProps {
|
|
5
|
+
value?: number;
|
|
6
|
+
defaultValue?: number;
|
|
7
|
+
onValueChange?: (value: number) => void;
|
|
8
|
+
/** Fires once at the end of a drag, for anything expensive. */
|
|
9
|
+
onValueCommit?: (value: number) => void;
|
|
10
|
+
min?: number;
|
|
11
|
+
max?: number;
|
|
12
|
+
step?: number;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
tone?: SliderTone;
|
|
15
|
+
style?: StyleProp<ViewStyle>;
|
|
16
|
+
accessibilityLabel?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Dragging, on a surface where a drag might belong to something else.
|
|
20
|
+
*
|
|
21
|
+
* The web gets pointer capture: once the thumb is grabbed, every move belongs to
|
|
22
|
+
* the slider until release. React Native has no equivalent, so this claims the
|
|
23
|
+
* gesture through PanResponder and holds it, which is what stops a horizontal
|
|
24
|
+
* drag being read as a scroll by whatever list the slider is sitting in.
|
|
25
|
+
*
|
|
26
|
+
* Volume sliders live in scroll views, so this matters more here than it looks.
|
|
27
|
+
*/
|
|
28
|
+
export declare function Slider({ value: controlled, defaultValue, onValueChange, onValueCommit, min, max, step, disabled, tone, style, accessibilityLabel, }: SliderProps): import("react").JSX.Element;
|
|
29
|
+
//# sourceMappingURL=Slider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../../src/components/Slider/Slider.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAErE,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AAEhG,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EAAE,UAAU,EACjB,YAAgB,EAChB,aAAa,EACb,aAAa,EACb,GAAO,EACP,GAAS,EACT,IAAQ,EACR,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,GACnB,EAAE,WAAW,+BA0Gb"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { PanResponder, View, } from "react-native";
|
|
4
|
+
import { toneRamp, useTheme } from "../../theme/index.js";
|
|
5
|
+
const TRACK_HEIGHT = 4;
|
|
6
|
+
const THUMB = 20;
|
|
7
|
+
/**
|
|
8
|
+
* Dragging, on a surface where a drag might belong to something else.
|
|
9
|
+
*
|
|
10
|
+
* The web gets pointer capture: once the thumb is grabbed, every move belongs to
|
|
11
|
+
* the slider until release. React Native has no equivalent, so this claims the
|
|
12
|
+
* gesture through PanResponder and holds it, which is what stops a horizontal
|
|
13
|
+
* drag being read as a scroll by whatever list the slider is sitting in.
|
|
14
|
+
*
|
|
15
|
+
* Volume sliders live in scroll views, so this matters more here than it looks.
|
|
16
|
+
*/
|
|
17
|
+
export function Slider({ value: controlled, defaultValue = 0, onValueChange, onValueCommit, min = 0, max = 100, step = 1, disabled, tone = "primary", style, accessibilityLabel, }) {
|
|
18
|
+
const theme = useTheme();
|
|
19
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
20
|
+
const value = controlled ?? uncontrolled;
|
|
21
|
+
const [width, setWidth] = useState(0);
|
|
22
|
+
const ramp = toneRamp(theme, tone);
|
|
23
|
+
// The responder callbacks are created once and would otherwise close over the
|
|
24
|
+
// first render's values forever, so the current ones live in refs. Written in
|
|
25
|
+
// an effect rather than during render: mutating a ref while rendering is the
|
|
26
|
+
// kind of thing that breaks under concurrent rendering, and the callbacks only
|
|
27
|
+
// fire from touch events, which is always after an effect has run.
|
|
28
|
+
const state = useRef({ width, value, min, max, step, disabled });
|
|
29
|
+
const emit = useRef({ onValueChange, onValueCommit, controlled });
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
state.current = { width, value, min, max, step, disabled };
|
|
32
|
+
}, [width, value, min, max, step, disabled]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
emit.current = { onValueChange, onValueCommit, controlled };
|
|
35
|
+
}, [onValueChange, onValueCommit, controlled]);
|
|
36
|
+
const valueFromX = (x) => {
|
|
37
|
+
const s = state.current;
|
|
38
|
+
if (s.width <= 0)
|
|
39
|
+
return s.value;
|
|
40
|
+
const ratio = Math.max(0, Math.min(1, x / s.width));
|
|
41
|
+
const raw = s.min + ratio * (s.max - s.min);
|
|
42
|
+
const stepped = Math.round(raw / s.step) * s.step;
|
|
43
|
+
return Math.max(s.min, Math.min(s.max, stepped));
|
|
44
|
+
};
|
|
45
|
+
const apply = (next) => {
|
|
46
|
+
if (emit.current.controlled === undefined)
|
|
47
|
+
setUncontrolled(next);
|
|
48
|
+
emit.current.onValueChange?.(next);
|
|
49
|
+
};
|
|
50
|
+
// useState with an initialiser rather than useRef(...).current, which counts
|
|
51
|
+
// as reading a ref during render. Created once either way.
|
|
52
|
+
//
|
|
53
|
+
// The rule is disabled for the block below because it reads what the closures
|
|
54
|
+
// mention, not what runs. Every state.current here executes inside a touch
|
|
55
|
+
// callback, long after render, and none of it decides what is drawn — the
|
|
56
|
+
// rendered position comes from `value` and `width` directly. The thing the
|
|
57
|
+
// rule protects against, a component rendering from a ref and not updating,
|
|
58
|
+
// cannot happen here.
|
|
59
|
+
/* eslint-disable react-hooks/refs */
|
|
60
|
+
const [responder] = useState(() => PanResponder.create({
|
|
61
|
+
onStartShouldSetPanResponder: () => !state.current.disabled,
|
|
62
|
+
// Claimed on move as well, so a drag that begins as a scroll still lands
|
|
63
|
+
// here once it is clearly horizontal.
|
|
64
|
+
onMoveShouldSetPanResponder: () => !state.current.disabled,
|
|
65
|
+
onPanResponderGrant: (e) => apply(valueFromX(e.nativeEvent.locationX)),
|
|
66
|
+
onPanResponderMove: (_e, gesture) => {
|
|
67
|
+
const s = state.current;
|
|
68
|
+
const ratio = (s.value - s.min) / (s.max - s.min || 1);
|
|
69
|
+
apply(valueFromX(ratio * s.width + gesture.dx));
|
|
70
|
+
},
|
|
71
|
+
onPanResponderRelease: () => emit.current.onValueCommit?.(state.current.value),
|
|
72
|
+
}));
|
|
73
|
+
/* eslint-enable react-hooks/refs */
|
|
74
|
+
const ratio = (value - min) / (max - min || 1);
|
|
75
|
+
return (_jsxs(View, { accessibilityRole: "adjustable", accessibilityLabel: accessibilityLabel, accessibilityValue: { min, max, now: value }, onLayout: (e) => setWidth(e.nativeEvent.layout.width), style: [{ height: THUMB, justifyContent: "center", opacity: disabled ? 0.5 : 1 }, style], ...responder.panHandlers, children: [_jsx(View, { style: {
|
|
76
|
+
height: TRACK_HEIGHT,
|
|
77
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
78
|
+
backgroundColor: theme.scales.neutral[4],
|
|
79
|
+
}, children: _jsx(View, { style: {
|
|
80
|
+
width: `${ratio * 100}%`,
|
|
81
|
+
height: "100%",
|
|
82
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
83
|
+
backgroundColor: ramp[8],
|
|
84
|
+
} }) }), _jsx(View, { pointerEvents: "none", style: {
|
|
85
|
+
position: "absolute",
|
|
86
|
+
left: Math.max(0, ratio * width - THUMB / 2),
|
|
87
|
+
width: THUMB,
|
|
88
|
+
height: THUMB,
|
|
89
|
+
borderRadius: THUMB / 2,
|
|
90
|
+
backgroundColor: ramp[8],
|
|
91
|
+
borderWidth: 2,
|
|
92
|
+
borderColor: theme.color.bg,
|
|
93
|
+
} })] }));
|
|
94
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
export interface SpinnerProps {
|
|
3
|
+
size?: "small" | "large";
|
|
4
|
+
/** Defaults to the accent solid step, the same one the web spinner uses. */
|
|
5
|
+
color?: string;
|
|
6
|
+
style?: StyleProp<ViewStyle>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The platform indicator rather than a rotating SVG.
|
|
10
|
+
*
|
|
11
|
+
* `@gryt/ui`'s Spinner is a CSS animation on a stroked circle, and it could be
|
|
12
|
+
* reproduced here with Animated and a loop. It should not be: ActivityIndicator
|
|
13
|
+
* is what the OS draws for "working", it respects reduce-motion for free, and on
|
|
14
|
+
* iOS and Android it looks different from each other on purpose.
|
|
15
|
+
*
|
|
16
|
+
* So this is the first entry on the parity exceptions list — the behaviour
|
|
17
|
+
* matches, the drawing does not, and matching the drawing would be worse.
|
|
18
|
+
*/
|
|
19
|
+
export declare function Spinner({ size, color, style }: SpinnerProps): import("react").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=Spinner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Spinner.d.ts","sourceRoot":"","sources":["../../../src/components/Spinner/Spinner.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIjF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,EAAE,IAAc,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,YAAY,+BASrE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ActivityIndicator } from "react-native";
|
|
3
|
+
import { useTheme } from "../../theme/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* The platform indicator rather than a rotating SVG.
|
|
6
|
+
*
|
|
7
|
+
* `@gryt/ui`'s Spinner is a CSS animation on a stroked circle, and it could be
|
|
8
|
+
* reproduced here with Animated and a loop. It should not be: ActivityIndicator
|
|
9
|
+
* is what the OS draws for "working", it respects reduce-motion for free, and on
|
|
10
|
+
* iOS and Android it looks different from each other on purpose.
|
|
11
|
+
*
|
|
12
|
+
* So this is the first entry on the parity exceptions list — the behaviour
|
|
13
|
+
* matches, the drawing does not, and matching the drawing would be worse.
|
|
14
|
+
*/
|
|
15
|
+
export function Spinner({ size = "small", color, style }) {
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
return (_jsx(ActivityIndicator, { size: size, color: color ?? theme.scales.accent[8], style: style }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
2
|
+
/**
|
|
3
|
+
* A panel. The thing almost everything else sits on.
|
|
4
|
+
*
|
|
5
|
+
* `@gryt/ui`'s Surface is a div with Tailwind classes picking `--gryt-neutral-*`
|
|
6
|
+
* steps. The steps are the same here; only the delivery differs.
|
|
7
|
+
*/
|
|
8
|
+
export type SurfaceLevel = "bg" | "surface" | "raised";
|
|
9
|
+
export interface SurfaceProps extends ViewProps {
|
|
10
|
+
level?: SurfaceLevel;
|
|
11
|
+
bordered?: boolean;
|
|
12
|
+
radius?: "sm" | "md" | "lg" | "xl" | "full" | "none";
|
|
13
|
+
padding?: number;
|
|
14
|
+
style?: StyleProp<ViewStyle>;
|
|
15
|
+
}
|
|
16
|
+
export declare function Surface({ level, bordered, radius, padding, style, ...rest }: SurfaceProps): import("react").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=Surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Surface.d.ts","sourceRoot":"","sources":["../../../src/components/Surface/Surface.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,wBAAgB,OAAO,CAAC,EACtB,KAAiB,EACjB,QAAgB,EAChB,MAAa,EACb,OAAO,EACP,KAAK,EACL,GAAG,IAAI,EACR,EAAE,YAAY,+BA0Bd"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useTheme } from "../../theme/index.js";
|
|
4
|
+
export function Surface({ level = "surface", bordered = false, radius = "md", padding, style, ...rest }) {
|
|
5
|
+
const theme = useTheme();
|
|
6
|
+
const background = level === "bg"
|
|
7
|
+
? theme.color.bg
|
|
8
|
+
: level === "raised"
|
|
9
|
+
? theme.color.surfaceRaised
|
|
10
|
+
: theme.color.surface;
|
|
11
|
+
return (_jsx(View, { style: [
|
|
12
|
+
{
|
|
13
|
+
backgroundColor: background,
|
|
14
|
+
borderRadius: radius === "none" ? 0 : theme.radius[radius],
|
|
15
|
+
...(bordered
|
|
16
|
+
? { borderWidth: 1, borderColor: theme.color.border }
|
|
17
|
+
: null),
|
|
18
|
+
...(padding === undefined ? null : { padding: theme.space(padding) }),
|
|
19
|
+
},
|
|
20
|
+
style,
|
|
21
|
+
], ...rest }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
export type SwitchTone = "primary" | "secondary" | "neutral" | "danger";
|
|
3
|
+
export interface SwitchProps {
|
|
4
|
+
checked?: boolean;
|
|
5
|
+
defaultChecked?: boolean;
|
|
6
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
tone?: SwitchTone;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
accessibilityLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Not React Native's own Switch.
|
|
14
|
+
*
|
|
15
|
+
* `<Switch>` from React Native draws the platform control, which is green on
|
|
16
|
+
* iOS and follows the Material palette on Android. Neither is the Gryt accent,
|
|
17
|
+
* and `thumbColor`/`trackColor` do not reach the whole shape. A design system
|
|
18
|
+
* that cannot colour its own switch is not much of a design system, so this is
|
|
19
|
+
* drawn from the tokens like everything else.
|
|
20
|
+
*
|
|
21
|
+
* The cost is that it does not pick up future platform restyling for free, which
|
|
22
|
+
* is the trade the whole library is already making.
|
|
23
|
+
*/
|
|
24
|
+
export declare function Switch({ checked: controlled, defaultChecked, onCheckedChange, disabled, tone, style, accessibilityLabel, }: SwitchProps): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=Switch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../../src/components/Switch/Switch.tsx"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAKnF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAQD;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAO,EAAE,UAAU,EACnB,cAAsB,EACtB,eAAe,EACf,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,GACnB,EAAE,WAAW,+BA0Db"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Animated, Pressable } from "react-native";
|
|
4
|
+
import { useReducedMotion } from "../../hooks/useReducedMotion.js";
|
|
5
|
+
import { toneRamp, useTheme } from "../../theme/index.js";
|
|
6
|
+
// The web is h-6 w-10 with a thumb translating x-4. Same numbers in points.
|
|
7
|
+
const TRACK_WIDTH = 40;
|
|
8
|
+
const TRACK_HEIGHT = 24;
|
|
9
|
+
const THUMB = 16;
|
|
10
|
+
const TRAVEL = 16;
|
|
11
|
+
/**
|
|
12
|
+
* Not React Native's own Switch.
|
|
13
|
+
*
|
|
14
|
+
* `<Switch>` from React Native draws the platform control, which is green on
|
|
15
|
+
* iOS and follows the Material palette on Android. Neither is the Gryt accent,
|
|
16
|
+
* and `thumbColor`/`trackColor` do not reach the whole shape. A design system
|
|
17
|
+
* that cannot colour its own switch is not much of a design system, so this is
|
|
18
|
+
* drawn from the tokens like everything else.
|
|
19
|
+
*
|
|
20
|
+
* The cost is that it does not pick up future platform restyling for free, which
|
|
21
|
+
* is the trade the whole library is already making.
|
|
22
|
+
*/
|
|
23
|
+
export function Switch({ checked: controlled, defaultChecked = false, onCheckedChange, disabled, tone = "primary", style, accessibilityLabel, }) {
|
|
24
|
+
const theme = useTheme();
|
|
25
|
+
const reducedMotion = useReducedMotion();
|
|
26
|
+
const [uncontrolled, setUncontrolled] = useState(defaultChecked);
|
|
27
|
+
const checked = controlled ?? uncontrolled;
|
|
28
|
+
const [offset] = useState(() => new Animated.Value(checked ? TRAVEL : 0));
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (reducedMotion) {
|
|
31
|
+
offset.setValue(checked ? TRAVEL : 0);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
Animated.spring(offset, {
|
|
35
|
+
toValue: checked ? TRAVEL : 0,
|
|
36
|
+
useNativeDriver: true,
|
|
37
|
+
speed: 40,
|
|
38
|
+
bounciness: 4,
|
|
39
|
+
}).start();
|
|
40
|
+
}, [checked, offset, reducedMotion]);
|
|
41
|
+
const ramp = toneRamp(theme, tone);
|
|
42
|
+
return (_jsx(Pressable, { accessibilityRole: "switch", accessibilityState: { checked, disabled: !!disabled }, accessibilityLabel: accessibilityLabel, disabled: disabled, onPress: () => {
|
|
43
|
+
const next = !checked;
|
|
44
|
+
if (controlled === undefined)
|
|
45
|
+
setUncontrolled(next);
|
|
46
|
+
onCheckedChange?.(next);
|
|
47
|
+
}, style: [
|
|
48
|
+
{
|
|
49
|
+
width: TRACK_WIDTH,
|
|
50
|
+
height: TRACK_HEIGHT,
|
|
51
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
52
|
+
padding: (TRACK_HEIGHT - THUMB) / 2,
|
|
53
|
+
justifyContent: "center",
|
|
54
|
+
backgroundColor: checked ? ramp[8] : theme.scales.neutral[5],
|
|
55
|
+
opacity: disabled ? 0.5 : 1,
|
|
56
|
+
},
|
|
57
|
+
style,
|
|
58
|
+
], children: _jsx(Animated.View, { style: {
|
|
59
|
+
width: THUMB,
|
|
60
|
+
height: THUMB,
|
|
61
|
+
borderRadius: THUMB / 2,
|
|
62
|
+
backgroundColor: checked ? theme.color.bg : theme.color.text,
|
|
63
|
+
transform: [{ translateX: offset }],
|
|
64
|
+
} }) }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
type TabValue = string | number;
|
|
4
|
+
export interface TabsProps {
|
|
5
|
+
value?: TabValue;
|
|
6
|
+
defaultValue?: TabValue;
|
|
7
|
+
onValueChange?: (value: TabValue) => void;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
}
|
|
11
|
+
declare function Root({ value: controlled, defaultValue, onValueChange, children, style, }: TabsProps): import("react").JSX.Element;
|
|
12
|
+
export interface TabsListProps {
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
/** Tabs that do not fit scroll sideways rather than wrapping or shrinking. */
|
|
15
|
+
scrollable?: boolean;
|
|
16
|
+
style?: StyleProp<ViewStyle>;
|
|
17
|
+
}
|
|
18
|
+
declare function List({ children, scrollable, style }: TabsListProps): import("react").JSX.Element;
|
|
19
|
+
export interface TabProps {
|
|
20
|
+
value: TabValue;
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
style?: StyleProp<ViewStyle>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The underline is drawn by the tab, not by a separate indicator.
|
|
27
|
+
*
|
|
28
|
+
* `@gryt/ui` has a `Tabs.Indicator` that Base UI slides between tabs, measured
|
|
29
|
+
* against the active one. Sliding it here means measuring every tab and
|
|
30
|
+
* animating between them, which is real work for a decoration. Each tab draws
|
|
31
|
+
* its own bottom border instead: it appears rather than slides.
|
|
32
|
+
*
|
|
33
|
+
* `Indicator` is still exported, as a no-op, so call sites match.
|
|
34
|
+
*/
|
|
35
|
+
declare function Tab({ value, children, disabled, style }: TabProps): import("react").JSX.Element;
|
|
36
|
+
export interface TabsPanelProps {
|
|
37
|
+
value: TabValue;
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
style?: StyleProp<ViewStyle>;
|
|
40
|
+
}
|
|
41
|
+
declare function Panel({ value, children, style }: TabsPanelProps): import("react").JSX.Element | null;
|
|
42
|
+
/** See Tab: the underline belongs to the tab here. Kept so call sites match. */
|
|
43
|
+
declare function Indicator(): null;
|
|
44
|
+
export declare const Tabs: typeof Root & {
|
|
45
|
+
List: typeof List;
|
|
46
|
+
Tab: typeof Tab;
|
|
47
|
+
Panel: typeof Panel;
|
|
48
|
+
Indicator: typeof Indicator;
|
|
49
|
+
};
|
|
50
|
+
export { Tab };
|
|
51
|
+
//# sourceMappingURL=Tabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/Tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5E,OAAO,EAAqC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIjG,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAehC,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,YAAY,CAAC,EAAE,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,IAAI,CAAC,EACZ,KAAK,EAAE,UAAU,EACjB,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,KAAK,GACN,EAAE,SAAS,+BAgBX;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,UAAiB,EAAE,KAAK,EAAE,EAAE,aAAa,+BAyBlE;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,iBAAS,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,QAAQ,+BAqC1D;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,QAAQ,CAAC;IAChB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,cAAc,sCAOxD;AAED,gFAAgF;AAChF,iBAAS,SAAS,IAAI,IAAI,CAEzB;AAED,eAAO,MAAM,IAAI;;;;;CAAuD,CAAC;AACzE,OAAO,EAAE,GAAG,EAAE,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useState } from "react";
|
|
3
|
+
import { Pressable, ScrollView, Text, View } from "react-native";
|
|
4
|
+
import { useTheme } from "../../theme/index.js";
|
|
5
|
+
const TabsContext = createContext(null);
|
|
6
|
+
function useTabs(part) {
|
|
7
|
+
const value = useContext(TabsContext);
|
|
8
|
+
if (!value)
|
|
9
|
+
throw new Error(`Tabs.${part} must be rendered inside Tabs.`);
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function Root({ value: controlled, defaultValue, onValueChange, children, style, }) {
|
|
13
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue ?? null);
|
|
14
|
+
const value = controlled ?? uncontrolled;
|
|
15
|
+
return (_jsx(TabsContext.Provider, { value: {
|
|
16
|
+
value,
|
|
17
|
+
setValue: (next) => {
|
|
18
|
+
if (controlled === undefined)
|
|
19
|
+
setUncontrolled(next);
|
|
20
|
+
onValueChange?.(next);
|
|
21
|
+
},
|
|
22
|
+
}, children: _jsx(View, { style: style, children: children }) }));
|
|
23
|
+
}
|
|
24
|
+
function List({ children, scrollable = true, style }) {
|
|
25
|
+
const theme = useTheme();
|
|
26
|
+
const row = (_jsx(View, { accessibilityRole: "tablist", style: [
|
|
27
|
+
{
|
|
28
|
+
flexDirection: "row",
|
|
29
|
+
gap: theme.space(1),
|
|
30
|
+
borderBottomWidth: 1,
|
|
31
|
+
borderBottomColor: theme.color.border,
|
|
32
|
+
},
|
|
33
|
+
style,
|
|
34
|
+
], children: children }));
|
|
35
|
+
if (!scrollable)
|
|
36
|
+
return row;
|
|
37
|
+
return (_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: row }));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The underline is drawn by the tab, not by a separate indicator.
|
|
41
|
+
*
|
|
42
|
+
* `@gryt/ui` has a `Tabs.Indicator` that Base UI slides between tabs, measured
|
|
43
|
+
* against the active one. Sliding it here means measuring every tab and
|
|
44
|
+
* animating between them, which is real work for a decoration. Each tab draws
|
|
45
|
+
* its own bottom border instead: it appears rather than slides.
|
|
46
|
+
*
|
|
47
|
+
* `Indicator` is still exported, as a no-op, so call sites match.
|
|
48
|
+
*/
|
|
49
|
+
function Tab({ value, children, disabled, style }) {
|
|
50
|
+
const theme = useTheme();
|
|
51
|
+
const tabs = useTabs("Tab");
|
|
52
|
+
const active = tabs.value === value;
|
|
53
|
+
return (_jsx(Pressable, { accessibilityRole: "tab", accessibilityState: { selected: active, disabled: !!disabled }, disabled: disabled, onPress: () => tabs.setValue(value), style: [
|
|
54
|
+
{
|
|
55
|
+
paddingHorizontal: theme.space(4),
|
|
56
|
+
paddingVertical: theme.space(3),
|
|
57
|
+
borderBottomWidth: 2,
|
|
58
|
+
borderBottomColor: active ? theme.color.accent : "transparent",
|
|
59
|
+
opacity: disabled ? 0.5 : 1,
|
|
60
|
+
},
|
|
61
|
+
style,
|
|
62
|
+
], children: typeof children === "string" ? (_jsx(Text, { style: {
|
|
63
|
+
color: active ? theme.color.text : theme.color.muted,
|
|
64
|
+
fontSize: 14,
|
|
65
|
+
fontWeight: active ? "600" : "500",
|
|
66
|
+
}, children: children })) : (children) }));
|
|
67
|
+
}
|
|
68
|
+
function Panel({ value, children, style }) {
|
|
69
|
+
const tabs = useTabs("Panel");
|
|
70
|
+
if (tabs.value !== value)
|
|
71
|
+
return null;
|
|
72
|
+
// React Native has "tab" and "tablist" but no "tabpanel" role, so there is
|
|
73
|
+
// nothing to put here. The panel is still reachable, it just does not announce
|
|
74
|
+
// itself as the tab's panel. In the exceptions table.
|
|
75
|
+
return _jsx(View, { style: style, children: children });
|
|
76
|
+
}
|
|
77
|
+
/** See Tab: the underline belongs to the tab here. Kept so call sites match. */
|
|
78
|
+
function Indicator() {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
export const Tabs = Object.assign(Root, { List, Tab, Panel, Indicator });
|
|
82
|
+
export { Tab };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type TextInputProps, type ViewStyle } from "react-native";
|
|
3
|
+
export type TextFieldSize = "small" | "medium";
|
|
4
|
+
export interface TextFieldProps extends Omit<TextInputProps, "style"> {
|
|
5
|
+
label?: ReactNode;
|
|
6
|
+
helperText?: ReactNode;
|
|
7
|
+
error?: boolean;
|
|
8
|
+
size?: TextFieldSize;
|
|
9
|
+
multiline?: boolean;
|
|
10
|
+
/** Web calls this minRows. Same meaning: the starting height in lines. */
|
|
11
|
+
minRows?: number;
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
}
|
|
14
|
+
export declare function TextField({ label, helperText, error, size, multiline, minRows, style, editable, ...rest }: TextFieldProps): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=TextField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextField.d.ts","sourceRoot":"","sources":["../../../src/components/TextField/TextField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAItB,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAO/C,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;IACnE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,UAAU,EACV,KAAa,EACb,IAAe,EACf,SAAiB,EACjB,OAAW,EACX,KAAK,EACL,QAAQ,EACR,GAAG,IAAI,EACR,EAAE,cAAc,+BAsEhB"}
|