@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,76 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useRef } 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 { useOpenState } from "../../overlay/useOpenState.js";
|
|
7
|
+
import { useTheme } from "../../theme/index.js";
|
|
8
|
+
const MenuContext = createContext(null);
|
|
9
|
+
function useMenu(part) {
|
|
10
|
+
const value = useContext(MenuContext);
|
|
11
|
+
if (!value)
|
|
12
|
+
throw new Error(`Menu.${part} must be rendered inside Menu.Root.`);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function Root({ children, ...openProps }) {
|
|
16
|
+
const { open, setOpen } = useOpenState(openProps);
|
|
17
|
+
const { anchor, measure } = useAnchorMeasure();
|
|
18
|
+
return (_jsx(MenuContext.Provider, { value: { open, setOpen, anchor, measure }, children: children }));
|
|
19
|
+
}
|
|
20
|
+
function Trigger({ children, style }) {
|
|
21
|
+
const { setOpen, measure } = useMenu("Trigger");
|
|
22
|
+
const ref = useRef(null);
|
|
23
|
+
const onPress = useCallback(() => {
|
|
24
|
+
measure(ref.current);
|
|
25
|
+
setOpen(true);
|
|
26
|
+
}, [measure, setOpen]);
|
|
27
|
+
return (_jsx(Pressable, { ref: ref, accessibilityRole: "button", onPress: onPress, style: style, children: children }));
|
|
28
|
+
}
|
|
29
|
+
function Portal({ children }) {
|
|
30
|
+
return _jsx(_Fragment, { children: children });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Kept as a passthrough for shape.
|
|
34
|
+
*
|
|
35
|
+
* Base UI splits Positioner from Popup so the positioned box and the styled box
|
|
36
|
+
* are separate elements. Here AnchoredPopup is both, because there is no
|
|
37
|
+
* stacking context to escape and nothing gained by the extra view.
|
|
38
|
+
*/
|
|
39
|
+
function Positioner({ children }) {
|
|
40
|
+
return _jsx(_Fragment, { children: children });
|
|
41
|
+
}
|
|
42
|
+
function Popup({ children, style, ...options }) {
|
|
43
|
+
const { open, setOpen, anchor } = useMenu("Popup");
|
|
44
|
+
const theme = useTheme();
|
|
45
|
+
return (_jsx(AnchoredPopup, { open: open, anchor: anchor, onDismiss: () => setOpen(false), align: "start", style: [{ paddingVertical: theme.space(1), minWidth: 180 }, style], ...options, children: _jsx(View, { accessibilityRole: "menu", children: children }) }));
|
|
46
|
+
}
|
|
47
|
+
function Item({ children, onPress, disabled, destructive, style }) {
|
|
48
|
+
const { setOpen } = useMenu("Item");
|
|
49
|
+
const theme = useTheme();
|
|
50
|
+
return (_jsx(Pressable, { accessibilityRole: "menuitem", accessibilityState: { disabled: !!disabled }, disabled: disabled,
|
|
51
|
+
// Choosing something closes the menu, which is what Base UI does unless
|
|
52
|
+
// an item opts out. Nothing here opts out yet.
|
|
53
|
+
onPress: () => {
|
|
54
|
+
onPress?.();
|
|
55
|
+
setOpen(false);
|
|
56
|
+
}, style: ({ pressed }) => [
|
|
57
|
+
{
|
|
58
|
+
paddingHorizontal: theme.space(4),
|
|
59
|
+
paddingVertical: theme.space(2.5),
|
|
60
|
+
opacity: disabled ? 0.5 : 1,
|
|
61
|
+
backgroundColor: pressed ? theme.color.surfaceHover : "transparent",
|
|
62
|
+
},
|
|
63
|
+
style,
|
|
64
|
+
], children: typeof children === "string" ? (_jsx(Text, { style: {
|
|
65
|
+
color: destructive ? theme.scales.danger[10] : theme.color.text,
|
|
66
|
+
fontSize: 14,
|
|
67
|
+
}, children: children })) : (children) }));
|
|
68
|
+
}
|
|
69
|
+
function Separator({ style }) {
|
|
70
|
+
const theme = useTheme();
|
|
71
|
+
return (_jsx(View, { style: [
|
|
72
|
+
{ height: 1, backgroundColor: theme.color.border, marginVertical: theme.space(1) },
|
|
73
|
+
style,
|
|
74
|
+
] }));
|
|
75
|
+
}
|
|
76
|
+
export const Menu = { Root, Trigger, Portal, Positioner, Popup, Item, Separator };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
export interface MeterProps {
|
|
4
|
+
value: number;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
label?: ReactNode;
|
|
8
|
+
/** Shows the formatted value at the end of the label row. */
|
|
9
|
+
showValue?: boolean;
|
|
10
|
+
format?: (value: number) => string;
|
|
11
|
+
style?: StyleProp<ViewStyle>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A reading, not a progress bar.
|
|
15
|
+
*
|
|
16
|
+
* Progress goes one way and finishes. A meter is a level that moves in both
|
|
17
|
+
* directions: microphone input, disk use, how full a channel is. `@gryt/ui`
|
|
18
|
+
* keeps them separate for that reason and so does this.
|
|
19
|
+
*/
|
|
20
|
+
export declare function Meter({ value, min, max, label, showValue, format, style, }: MeterProps): import("react").JSX.Element;
|
|
21
|
+
//# sourceMappingURL=Meter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Meter.d.ts","sourceRoot":"","sources":["../../../src/components/Meter/Meter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAc,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAI1E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,GAAO,EACP,GAAS,EACT,KAAK,EACL,SAAiB,EACjB,MAAM,EACN,KAAK,GACN,EAAE,UAAU,+BAmDZ"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import { useTheme } from "../../theme/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* A reading, not a progress bar.
|
|
6
|
+
*
|
|
7
|
+
* Progress goes one way and finishes. A meter is a level that moves in both
|
|
8
|
+
* directions: microphone input, disk use, how full a channel is. `@gryt/ui`
|
|
9
|
+
* keeps them separate for that reason and so does this.
|
|
10
|
+
*/
|
|
11
|
+
export function Meter({ value, min = 0, max = 100, label, showValue = false, format, style, }) {
|
|
12
|
+
const theme = useTheme();
|
|
13
|
+
const ratio = Math.max(0, Math.min(1, (value - min) / (max - min || 1)));
|
|
14
|
+
// Green until it is worth noticing, amber, then red. The thresholds match the
|
|
15
|
+
// web's: two thirds and nine tenths.
|
|
16
|
+
const ramp = ratio >= 0.9
|
|
17
|
+
? theme.scales.danger
|
|
18
|
+
: ratio >= 0.66
|
|
19
|
+
? theme.scales.warning
|
|
20
|
+
: theme.scales.success;
|
|
21
|
+
return (_jsxs(View, { style: [{ gap: theme.space(1.5) }, style], children: [label || showValue ? (_jsxs(View, { style: { flexDirection: "row", justifyContent: "space-between" }, children: [typeof label === "string" ? (_jsx(Text, { style: { color: theme.color.muted, fontSize: 13 }, children: label })) : (label), showValue ? (_jsx(Text, { style: { color: theme.color.muted, fontSize: 13 }, children: format ? format(value) : String(Math.round(value)) })) : null] })) : null, _jsx(View, { accessibilityRole: "progressbar", accessibilityValue: { min, max, now: value }, style: {
|
|
22
|
+
height: 6,
|
|
23
|
+
borderRadius: theme.radius.full,
|
|
24
|
+
backgroundColor: theme.scales.neutral[3],
|
|
25
|
+
overflow: "hidden",
|
|
26
|
+
}, children: _jsx(View, { style: {
|
|
27
|
+
width: `${ratio * 100}%`,
|
|
28
|
+
height: "100%",
|
|
29
|
+
borderRadius: theme.radius.full,
|
|
30
|
+
backgroundColor: ramp[8],
|
|
31
|
+
} }) })] }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
export interface NumberFieldProps {
|
|
4
|
+
value?: number;
|
|
5
|
+
defaultValue?: number;
|
|
6
|
+
onValueChange?: (value: number) => void;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
label?: ReactNode;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Typed or stepped, with the keyboard set to numeric.
|
|
16
|
+
*
|
|
17
|
+
* The web version also scrubs: press the label and drag sideways to change the
|
|
18
|
+
* value. That gesture is not reproduced. It competes with scrolling on a phone,
|
|
19
|
+
* and the same interaction already exists here as Slider, which is a better fit
|
|
20
|
+
* for a screen you touch.
|
|
21
|
+
*/
|
|
22
|
+
export declare function NumberField({ value: controlled, defaultValue, onValueChange, min, max, step, label, disabled, style, }: NumberFieldProps): import("react").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=NumberField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NumberField.d.ts","sourceRoot":"","sources":["../../../src/components/NumberField/NumberField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAoC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAuChG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,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,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EAAE,UAAU,EACjB,YAAgB,EAChB,aAAa,EACb,GAA8B,EAC9B,GAA8B,EAC9B,IAAQ,EACR,KAAK,EACL,QAAQ,EACR,KAAK,GACN,EAAE,gBAAgB,+BAgElB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Pressable, Text, TextInput, View } from "react-native";
|
|
4
|
+
import { useTheme } from "../../theme/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Declared here rather than inside NumberField.
|
|
7
|
+
*
|
|
8
|
+
* A component defined in another component's body is a new type on every
|
|
9
|
+
* render, so React unmounts and remounts it each time and any state inside is
|
|
10
|
+
* lost. Harmless for a button with no state, and still the kind of thing that
|
|
11
|
+
* bites later.
|
|
12
|
+
*/
|
|
13
|
+
function StepButton({ glyph, label, disabled, onPress, }) {
|
|
14
|
+
const theme = useTheme();
|
|
15
|
+
return (_jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: label, disabled: disabled, onPress: onPress, style: { paddingHorizontal: theme.space(3), paddingVertical: theme.space(2) }, children: _jsx(Text, { style: { color: theme.color.muted, fontSize: 16, fontWeight: "600" }, children: glyph }) }));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Typed or stepped, with the keyboard set to numeric.
|
|
19
|
+
*
|
|
20
|
+
* The web version also scrubs: press the label and drag sideways to change the
|
|
21
|
+
* value. That gesture is not reproduced. It competes with scrolling on a phone,
|
|
22
|
+
* and the same interaction already exists here as Slider, which is a better fit
|
|
23
|
+
* for a screen you touch.
|
|
24
|
+
*/
|
|
25
|
+
export function NumberField({ value: controlled, defaultValue = 0, onValueChange, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY, step = 1, label, disabled, style, }) {
|
|
26
|
+
const theme = useTheme();
|
|
27
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
28
|
+
const value = controlled ?? uncontrolled;
|
|
29
|
+
// Kept separately so a half-typed "-" or "" does not become 0 mid-edit.
|
|
30
|
+
const [text, setText] = useState(String(defaultValue));
|
|
31
|
+
const commit = (next) => {
|
|
32
|
+
const clamped = Math.max(min, Math.min(max, next));
|
|
33
|
+
if (controlled === undefined)
|
|
34
|
+
setUncontrolled(clamped);
|
|
35
|
+
setText(String(clamped));
|
|
36
|
+
onValueChange?.(clamped);
|
|
37
|
+
};
|
|
38
|
+
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(View, { style: {
|
|
39
|
+
flexDirection: "row",
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
borderWidth: 1,
|
|
42
|
+
borderColor: theme.color.border,
|
|
43
|
+
borderRadius: theme.radius.md,
|
|
44
|
+
backgroundColor: theme.color.surface,
|
|
45
|
+
opacity: disabled ? 0.5 : 1,
|
|
46
|
+
}, children: [_jsx(StepButton, { glyph: "\u2212", label: "Decrease", disabled: disabled || value <= min, onPress: () => commit(value - step) }), _jsx(TextInput, { editable: !disabled, keyboardType: "numeric", value: text, onChangeText: setText, onBlur: () => {
|
|
47
|
+
const parsed = Number(text);
|
|
48
|
+
commit(Number.isFinite(parsed) ? parsed : value);
|
|
49
|
+
}, style: {
|
|
50
|
+
flex: 1,
|
|
51
|
+
textAlign: "center",
|
|
52
|
+
color: theme.color.text,
|
|
53
|
+
fontSize: 14,
|
|
54
|
+
paddingVertical: theme.space(2),
|
|
55
|
+
} }), _jsx(StepButton, { glyph: "+", label: "Increase", disabled: disabled || value >= max, onPress: () => commit(value + step) })] })] }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
export interface OtpFieldProps {
|
|
3
|
+
length?: number;
|
|
4
|
+
value?: string;
|
|
5
|
+
onValueChange?: (value: string) => void;
|
|
6
|
+
onComplete?: (value: string) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
autoFocus?: boolean;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* One hidden input behind a row of boxes.
|
|
13
|
+
*
|
|
14
|
+
* The obvious build is one TextInput per digit, and it is a trap: backspace
|
|
15
|
+
* across an empty box, paste, and autofill all have to be threaded between
|
|
16
|
+
* inputs by hand, and every platform disagrees about the events. A single
|
|
17
|
+
* invisible input holding the whole code gets all three for free, and the boxes
|
|
18
|
+
* become presentation.
|
|
19
|
+
*
|
|
20
|
+
* `textContentType="oneTimeCode"` is what makes iOS offer the code from
|
|
21
|
+
* Messages. Android reads `autoComplete="sms-otp"`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function OtpField({ length, value: controlled, onValueChange, onComplete, disabled, autoFocus, style, }: OtpFieldProps): import("react").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=OtpField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OtpField.d.ts","sourceRoot":"","sources":["../../../src/components/OtpField/OtpField.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIhG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,EACvB,MAAU,EACV,KAAK,EAAE,UAAU,EACjB,aAAa,EACb,UAAU,EACV,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE,aAAa,+BAgEf"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { Pressable, Text, TextInput, View } from "react-native";
|
|
4
|
+
import { useTheme } from "../../theme/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* One hidden input behind a row of boxes.
|
|
7
|
+
*
|
|
8
|
+
* The obvious build is one TextInput per digit, and it is a trap: backspace
|
|
9
|
+
* across an empty box, paste, and autofill all have to be threaded between
|
|
10
|
+
* inputs by hand, and every platform disagrees about the events. A single
|
|
11
|
+
* invisible input holding the whole code gets all three for free, and the boxes
|
|
12
|
+
* become presentation.
|
|
13
|
+
*
|
|
14
|
+
* `textContentType="oneTimeCode"` is what makes iOS offer the code from
|
|
15
|
+
* Messages. Android reads `autoComplete="sms-otp"`.
|
|
16
|
+
*/
|
|
17
|
+
export function OtpField({ length = 6, value: controlled, onValueChange, onComplete, disabled, autoFocus, style, }) {
|
|
18
|
+
const theme = useTheme();
|
|
19
|
+
const [uncontrolled, setUncontrolled] = useState("");
|
|
20
|
+
const value = controlled ?? uncontrolled;
|
|
21
|
+
const [focused, setFocused] = useState(false);
|
|
22
|
+
const input = useRef(null);
|
|
23
|
+
const setValue = (next) => {
|
|
24
|
+
const digits = next.replace(/\D/g, "").slice(0, length);
|
|
25
|
+
if (controlled === undefined)
|
|
26
|
+
setUncontrolled(digits);
|
|
27
|
+
onValueChange?.(digits);
|
|
28
|
+
if (digits.length === length)
|
|
29
|
+
onComplete?.(digits);
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs(Pressable, { accessibilityRole: "none", disabled: disabled, onPress: () => input.current?.focus(), style: [{ flexDirection: "row", gap: theme.space(2) }, style], children: [Array.from({ length }).map((_, i) => {
|
|
32
|
+
const filled = i < value.length;
|
|
33
|
+
const active = focused && i === value.length;
|
|
34
|
+
return (_jsx(View, { style: {
|
|
35
|
+
flex: 1,
|
|
36
|
+
aspectRatio: 0.8,
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
justifyContent: "center",
|
|
39
|
+
borderWidth: 1,
|
|
40
|
+
borderColor: active ? theme.color.accent : theme.color.border,
|
|
41
|
+
borderRadius: theme.radius.md,
|
|
42
|
+
backgroundColor: theme.color.surface,
|
|
43
|
+
opacity: disabled ? 0.5 : 1,
|
|
44
|
+
}, children: _jsx(Text, { style: { color: theme.color.text, fontSize: 20, fontWeight: "600" }, children: filled ? value[i] : "" }) }, i));
|
|
45
|
+
}), _jsx(TextInput, { ref: input, value: value, onChangeText: setValue, onFocus: () => setFocused(true), onBlur: () => setFocused(false), editable: !disabled, autoFocus: autoFocus, keyboardType: "number-pad", textContentType: "oneTimeCode", autoComplete: "sms-otp", maxLength: length,
|
|
46
|
+
// Off-screen rather than hidden: opacity 0 still takes the caret on
|
|
47
|
+
// some Android builds, and display:none stops autofill reaching it.
|
|
48
|
+
style: { position: "absolute", top: -9999, left: -9999, width: 1, height: 1 } })] }));
|
|
49
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
import type { Align, Side } from "../../overlay/placePopup.js";
|
|
4
|
+
import { type OpenStateProps } from "../../overlay/useOpenState.js";
|
|
5
|
+
export interface PopoverRootProps extends OpenStateProps {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
declare function Root({ children, ...openProps }: PopoverRootProps): import("react").JSX.Element;
|
|
9
|
+
declare function Trigger({ children, style }: {
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
style?: StyleProp<ViewStyle>;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
13
|
+
/** A passthrough. React Native's Modal already renders above everything. */
|
|
14
|
+
declare function Portal({ children }: {
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
export interface PopoverPopupProps {
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
side?: Side;
|
|
20
|
+
align?: Align;
|
|
21
|
+
sideOffset?: number;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
}
|
|
24
|
+
declare function Popup({ children, style, ...options }: PopoverPopupProps): import("react").JSX.Element;
|
|
25
|
+
declare function Close({ children, style }: {
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
}): import("react").JSX.Element;
|
|
29
|
+
/**
|
|
30
|
+
* Not drawn.
|
|
31
|
+
*
|
|
32
|
+
* Base UI's Arrow is an element positioned against the popup's edge once
|
|
33
|
+
* Floating UI has settled. Doing it here means a rotated square, a border that
|
|
34
|
+
* only shows on two sides, and knowing which side the popup ended up on. It is
|
|
35
|
+
* doable and it is not free, so it is an exception rather than a silent
|
|
36
|
+
* omission. Kept as a no-op so call sites match.
|
|
37
|
+
*/
|
|
38
|
+
declare function Arrow(): null;
|
|
39
|
+
export declare const Popover: {
|
|
40
|
+
Root: typeof Root;
|
|
41
|
+
Trigger: typeof Trigger;
|
|
42
|
+
Portal: typeof Portal;
|
|
43
|
+
Popup: typeof Popup;
|
|
44
|
+
Close: typeof Close;
|
|
45
|
+
Arrow: typeof Arrow;
|
|
46
|
+
};
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=Popover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../../../src/components/Popover/Popover.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/E,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAE5D,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAkB/E,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,iBAAS,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,EAAE,gBAAgB,+BAQzD;AAED,iBAAS,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAc3F;AAED,4EAA4E;AAC5E,iBAAS,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,+BAErD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,iBAAiB,+BAchE;AAED,iBAAS,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;CAAE,+BAOzF;AAED;;;;;;;;GAQG;AACH,iBAAS,KAAK,SAEb;AAED,eAAO,MAAM,OAAO;;;;;;;CAAiD,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext, useRef } from "react";
|
|
3
|
+
import { Pressable } from "react-native";
|
|
4
|
+
import { AnchoredPopup } from "../../overlay/AnchoredPopup.js";
|
|
5
|
+
import { useAnchorMeasure } from "../../overlay/useAnchoredPosition.js";
|
|
6
|
+
import { useOpenState } from "../../overlay/useOpenState.js";
|
|
7
|
+
import { useTheme } from "../../theme/index.js";
|
|
8
|
+
const PopoverContext = createContext(null);
|
|
9
|
+
function usePopover(part) {
|
|
10
|
+
const value = useContext(PopoverContext);
|
|
11
|
+
if (!value)
|
|
12
|
+
throw new Error(`Popover.${part} must be rendered inside Popover.Root.`);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function Root({ children, ...openProps }) {
|
|
16
|
+
const { open, setOpen } = useOpenState(openProps);
|
|
17
|
+
const { anchor, measure } = useAnchorMeasure();
|
|
18
|
+
return (_jsx(PopoverContext.Provider, { value: { open, setOpen, anchor, measure }, children: children }));
|
|
19
|
+
}
|
|
20
|
+
function Trigger({ children, style }) {
|
|
21
|
+
const { setOpen, measure } = usePopover("Trigger");
|
|
22
|
+
const ref = useRef(null);
|
|
23
|
+
const onPress = useCallback(() => {
|
|
24
|
+
measure(ref.current);
|
|
25
|
+
setOpen(true);
|
|
26
|
+
}, [measure, setOpen]);
|
|
27
|
+
return (_jsx(Pressable, { ref: ref, accessibilityRole: "button", onPress: onPress, style: style, children: children }));
|
|
28
|
+
}
|
|
29
|
+
/** A passthrough. React Native's Modal already renders above everything. */
|
|
30
|
+
function Portal({ children }) {
|
|
31
|
+
return _jsx(_Fragment, { children: children });
|
|
32
|
+
}
|
|
33
|
+
function Popup({ children, style, ...options }) {
|
|
34
|
+
const { open, setOpen, anchor } = usePopover("Popup");
|
|
35
|
+
const theme = useTheme();
|
|
36
|
+
return (_jsx(AnchoredPopup, { open: open, anchor: anchor, onDismiss: () => setOpen(false), style: [{ padding: theme.space(3), minWidth: 160 }, style], ...options, children: children }));
|
|
37
|
+
}
|
|
38
|
+
function Close({ children, style }) {
|
|
39
|
+
const { setOpen } = usePopover("Close");
|
|
40
|
+
return (_jsx(Pressable, { accessibilityRole: "button", onPress: () => setOpen(false), style: style, children: children }));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Not drawn.
|
|
44
|
+
*
|
|
45
|
+
* Base UI's Arrow is an element positioned against the popup's edge once
|
|
46
|
+
* Floating UI has settled. Doing it here means a rotated square, a border that
|
|
47
|
+
* only shows on two sides, and knowing which side the popup ended up on. It is
|
|
48
|
+
* doable and it is not free, so it is an exception rather than a silent
|
|
49
|
+
* omission. Kept as a no-op so call sites match.
|
|
50
|
+
*/
|
|
51
|
+
function Arrow() {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
export const Popover = { Root, Trigger, Portal, Popup, Close, Arrow };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
export interface ProgressProps {
|
|
3
|
+
/** 0 to 100. Leave undefined for an indeterminate bar. */
|
|
4
|
+
value?: number;
|
|
5
|
+
tone?: "accent" | "success" | "danger" | "warning";
|
|
6
|
+
style?: StyleProp<ViewStyle>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Determinate only for now.
|
|
10
|
+
*
|
|
11
|
+
* The web's indeterminate state is a CSS keyframe sweeping a partial bar across
|
|
12
|
+
* the track. Reproducing it here needs an Animated loop and a reduce-motion
|
|
13
|
+
* check, which is worth doing properly rather than as a footnote on this one, so
|
|
14
|
+
* an undefined value currently renders an empty track. Recorded in the parity
|
|
15
|
+
* exceptions table.
|
|
16
|
+
*/
|
|
17
|
+
export declare function Progress({ value, tone, style }: ProgressProps): import("react").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=Progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Progress.d.ts","sourceRoot":"","sources":["../../../src/components/Progress/Progress.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIpE,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAe,EAAE,KAAK,EAAE,EAAE,aAAa,+BA4BxE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useTheme } from "../../theme/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Determinate only for now.
|
|
6
|
+
*
|
|
7
|
+
* The web's indeterminate state is a CSS keyframe sweeping a partial bar across
|
|
8
|
+
* the track. Reproducing it here needs an Animated loop and a reduce-motion
|
|
9
|
+
* check, which is worth doing properly rather than as a footnote on this one, so
|
|
10
|
+
* an undefined value currently renders an empty track. Recorded in the parity
|
|
11
|
+
* exceptions table.
|
|
12
|
+
*/
|
|
13
|
+
export function Progress({ value, tone = "accent", style }) {
|
|
14
|
+
const theme = useTheme();
|
|
15
|
+
const clamped = value === undefined ? 0 : Math.max(0, Math.min(100, value));
|
|
16
|
+
return (_jsx(View, { accessibilityRole: "progressbar", accessibilityValue: value === undefined ? undefined : { now: clamped, min: 0, max: 100 }, style: [
|
|
17
|
+
{
|
|
18
|
+
height: 6,
|
|
19
|
+
borderRadius: theme.radius.full,
|
|
20
|
+
backgroundColor: theme.scales.neutral[3],
|
|
21
|
+
overflow: "hidden",
|
|
22
|
+
},
|
|
23
|
+
style,
|
|
24
|
+
], children: _jsx(View, { style: {
|
|
25
|
+
width: `${clamped}%`,
|
|
26
|
+
height: "100%",
|
|
27
|
+
borderRadius: theme.radius.full,
|
|
28
|
+
backgroundColor: theme.scales[tone][8],
|
|
29
|
+
} }) }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
export type RadioTone = "primary" | "secondary" | "neutral" | "danger";
|
|
4
|
+
export interface RadioGroupProps {
|
|
5
|
+
value?: string | number;
|
|
6
|
+
defaultValue?: string | number;
|
|
7
|
+
onValueChange?: (value: string | number) => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
style?: StyleProp<ViewStyle>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The group holds the value, which is the part a radio cannot do alone.
|
|
14
|
+
*
|
|
15
|
+
* The web gets this from Base UI's RadioGroup, and a lone Radio outside one is
|
|
16
|
+
* a mistake there too. Here it throws, because a radio that silently never
|
|
17
|
+
* checks is worse than one that says why.
|
|
18
|
+
*/
|
|
19
|
+
export declare function RadioGroup({ value: controlled, defaultValue, onValueChange, disabled, children, style, }: RadioGroupProps): import("react").JSX.Element;
|
|
20
|
+
export interface RadioProps {
|
|
21
|
+
value: string | number;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
tone?: RadioTone;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
accessibilityLabel?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function Radio({ value, disabled, tone, style, accessibilityLabel, }: RadioProps): import("react").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=Radio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Radio.d.ts","sourceRoot":"","sources":["../../../src/components/Radio/Radio.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC5E,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAI/E,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAUvE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EAAE,UAAU,EACjB,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,KAAK,GACN,EAAE,eAAe,+BAsBjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,kBAAkB,GACnB,EAAE,UAAU,+BA0CZ"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useState } from "react";
|
|
3
|
+
import { Pressable, View } from "react-native";
|
|
4
|
+
import { toneRamp, useTheme } from "../../theme/index.js";
|
|
5
|
+
const RadioGroupContext = createContext(null);
|
|
6
|
+
/**
|
|
7
|
+
* The group holds the value, which is the part a radio cannot do alone.
|
|
8
|
+
*
|
|
9
|
+
* The web gets this from Base UI's RadioGroup, and a lone Radio outside one is
|
|
10
|
+
* a mistake there too. Here it throws, because a radio that silently never
|
|
11
|
+
* checks is worse than one that says why.
|
|
12
|
+
*/
|
|
13
|
+
export function RadioGroup({ value: controlled, defaultValue, onValueChange, disabled, children, style, }) {
|
|
14
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue ?? null);
|
|
15
|
+
const value = controlled ?? uncontrolled;
|
|
16
|
+
return (_jsx(RadioGroupContext.Provider, { value: {
|
|
17
|
+
value,
|
|
18
|
+
disabled,
|
|
19
|
+
setValue: (next) => {
|
|
20
|
+
if (controlled === undefined)
|
|
21
|
+
setUncontrolled(next);
|
|
22
|
+
onValueChange?.(next);
|
|
23
|
+
},
|
|
24
|
+
}, children: _jsx(View, { accessibilityRole: "radiogroup", style: style, children: children }) }));
|
|
25
|
+
}
|
|
26
|
+
const SIZE = 20;
|
|
27
|
+
export function Radio({ value, disabled, tone = "primary", style, accessibilityLabel, }) {
|
|
28
|
+
const theme = useTheme();
|
|
29
|
+
const group = useContext(RadioGroupContext);
|
|
30
|
+
if (!group)
|
|
31
|
+
throw new Error("Radio must be rendered inside a RadioGroup.");
|
|
32
|
+
const selected = group.value === value;
|
|
33
|
+
const isDisabled = disabled || group.disabled;
|
|
34
|
+
const ramp = toneRamp(theme, tone);
|
|
35
|
+
return (_jsx(Pressable, { accessibilityRole: "radio", accessibilityState: { checked: selected, disabled: !!isDisabled }, accessibilityLabel: accessibilityLabel, disabled: isDisabled, onPress: () => group.setValue(value), style: [
|
|
36
|
+
{
|
|
37
|
+
width: SIZE,
|
|
38
|
+
height: SIZE,
|
|
39
|
+
borderRadius: SIZE / 2,
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
justifyContent: "center",
|
|
42
|
+
borderWidth: 1.5,
|
|
43
|
+
borderColor: selected ? ramp[8] : theme.color.border,
|
|
44
|
+
opacity: isDisabled ? 0.5 : 1,
|
|
45
|
+
},
|
|
46
|
+
style,
|
|
47
|
+
], children: selected ? (_jsx(View, { style: {
|
|
48
|
+
width: SIZE / 2,
|
|
49
|
+
height: SIZE / 2,
|
|
50
|
+
borderRadius: SIZE / 4,
|
|
51
|
+
backgroundColor: ramp[8],
|
|
52
|
+
} })) : null }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type ScrollViewProps, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
export interface ScrollAreaProps extends Omit<ScrollViewProps, "style"> {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
horizontal?: boolean;
|
|
6
|
+
style?: StyleProp<ViewStyle>;
|
|
7
|
+
contentStyle?: StyleProp<ViewStyle>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A thin pass over ScrollView.
|
|
11
|
+
*
|
|
12
|
+
* The web's ScrollArea exists to replace scrollbars the browser draws badly and
|
|
13
|
+
* inconsistently. A phone has no scrollbars to replace: the indicator is drawn
|
|
14
|
+
* by the OS, it fades on its own, and it already matches everything else on the
|
|
15
|
+
* device. So the component stays for call-site parity and does almost nothing,
|
|
16
|
+
* which is the honest version.
|
|
17
|
+
*
|
|
18
|
+
* Its one job is defaulting the indicator off, since the web version hides the
|
|
19
|
+
* native scrollbar too.
|
|
20
|
+
*/
|
|
21
|
+
export declare function ScrollArea({ children, horizontal, style, contentStyle, showsVerticalScrollIndicator, showsHorizontalScrollIndicator, ...rest }: ScrollAreaProps): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=ScrollArea.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollArea.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAc,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhG,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IACrE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACrC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,UAAkB,EAClB,KAAK,EACL,YAAY,EACZ,4BAAoC,EACpC,8BAAsC,EACtC,GAAG,IAAI,EACR,EAAE,eAAe,+BAajB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ScrollView } from "react-native";
|
|
3
|
+
/**
|
|
4
|
+
* A thin pass over ScrollView.
|
|
5
|
+
*
|
|
6
|
+
* The web's ScrollArea exists to replace scrollbars the browser draws badly and
|
|
7
|
+
* inconsistently. A phone has no scrollbars to replace: the indicator is drawn
|
|
8
|
+
* by the OS, it fades on its own, and it already matches everything else on the
|
|
9
|
+
* device. So the component stays for call-site parity and does almost nothing,
|
|
10
|
+
* which is the honest version.
|
|
11
|
+
*
|
|
12
|
+
* Its one job is defaulting the indicator off, since the web version hides the
|
|
13
|
+
* native scrollbar too.
|
|
14
|
+
*/
|
|
15
|
+
export function ScrollArea({ children, horizontal = false, style, contentStyle, showsVerticalScrollIndicator = false, showsHorizontalScrollIndicator = false, ...rest }) {
|
|
16
|
+
return (_jsx(ScrollView, { horizontal: horizontal, style: style, contentContainerStyle: contentStyle, showsVerticalScrollIndicator: showsVerticalScrollIndicator, showsHorizontalScrollIndicator: showsHorizontalScrollIndicator, ...rest, children: children }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
export interface SelectOption {
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
value: string | number;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export type SelectSize = "small" | "medium";
|
|
9
|
+
export interface SelectProps {
|
|
10
|
+
options?: SelectOption[];
|
|
11
|
+
value?: string | number | null;
|
|
12
|
+
defaultValue?: string | number | null;
|
|
13
|
+
onValueChange?: (value: string | number) => void;
|
|
14
|
+
label?: ReactNode;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
size?: SelectSize;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A single component, like the web's, rather than a namespace.
|
|
22
|
+
*
|
|
23
|
+
* `@gryt/ui`'s Select takes an `options` array and renders the trigger, the
|
|
24
|
+
* list, the checkmark and the positioning itself, so this matches that shape
|
|
25
|
+
* instead of exposing Root/Trigger/Popup.
|
|
26
|
+
*
|
|
27
|
+
* Underneath it is the same anchored popup Menu uses. The one thing worth
|
|
28
|
+
* knowing: the platform pickers are not used. iOS would give a wheel and Android
|
|
29
|
+
* a dialog, neither takes the Gryt palette, and the two look nothing like each
|
|
30
|
+
* other. A list matching the rest of the library is more use than two native
|
|
31
|
+
* controls that match neither.
|
|
32
|
+
*/
|
|
33
|
+
export declare function Select({ options, value: controlled, defaultValue, onValueChange, label, placeholder, size, disabled, style, }: SelectProps): import("react").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=Select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACtE,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAMrF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAO5C,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACtC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACjD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,EACrB,OAAY,EACZ,KAAK,EAAE,UAAU,EACjB,YAAmB,EACnB,aAAa,EACb,KAAK,EACL,WAAuB,EACvB,IAAe,EACf,QAAQ,EACR,KAAK,GACN,EAAE,WAAW,+BAgHb"}
|