@castui/cast-ui 4.7.0 → 4.9.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 +61 -0
- package/dist/components/Accordion/Accordion.d.ts +80 -0
- package/dist/components/Accordion/Accordion.js +157 -0
- package/dist/components/Accordion/index.d.ts +1 -0
- package/dist/components/Accordion/index.js +6 -0
- package/dist/components/AppBar/AppBar.d.ts +47 -0
- package/dist/components/AppBar/AppBar.js +47 -0
- package/dist/components/AppBar/index.d.ts +1 -0
- package/dist/components/AppBar/index.js +5 -0
- package/dist/components/Autocomplete/Autocomplete.d.ts +70 -0
- package/dist/components/Autocomplete/Autocomplete.js +249 -0
- package/dist/components/Autocomplete/index.d.ts +1 -0
- package/dist/components/Autocomplete/index.js +5 -0
- package/dist/components/Backdrop/Backdrop.d.ts +32 -0
- package/dist/components/Backdrop/Backdrop.js +74 -0
- package/dist/components/Backdrop/index.d.ts +1 -0
- package/dist/components/Backdrop/index.js +5 -0
- package/dist/components/BottomSheet/BottomSheet.d.ts +50 -0
- package/dist/components/BottomSheet/BottomSheet.js +159 -0
- package/dist/components/BottomSheet/index.d.ts +1 -0
- package/dist/components/BottomSheet/index.js +6 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +63 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.js +143 -0
- package/dist/components/Breadcrumbs/index.d.ts +1 -0
- package/dist/components/Breadcrumbs/index.js +6 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +42 -0
- package/dist/components/CodeBlock/CodeBlock.js +110 -0
- package/dist/components/CodeBlock/index.d.ts +1 -0
- package/dist/components/CodeBlock/index.js +5 -0
- package/dist/components/Drawer/Drawer.d.ts +51 -0
- package/dist/components/Drawer/Drawer.js +168 -0
- package/dist/components/Drawer/index.d.ts +1 -0
- package/dist/components/Drawer/index.js +6 -0
- package/dist/components/Link/Link.d.ts +51 -0
- package/dist/components/Link/Link.js +73 -0
- package/dist/components/Link/index.d.ts +1 -0
- package/dist/components/Link/index.js +5 -0
- package/dist/components/Menu/Menu.d.ts +91 -0
- package/dist/components/Menu/Menu.js +211 -0
- package/dist/components/Menu/index.d.ts +1 -0
- package/dist/components/Menu/index.js +9 -0
- package/dist/components/Slider/Slider.d.ts +47 -0
- package/dist/components/Slider/Slider.js +132 -0
- package/dist/components/Slider/index.d.ts +1 -0
- package/dist/components/Slider/index.js +5 -0
- package/dist/components/SpeedDial/SpeedDial.d.ts +72 -0
- package/dist/components/SpeedDial/SpeedDial.js +189 -0
- package/dist/components/SpeedDial/index.d.ts +1 -0
- package/dist/components/SpeedDial/index.js +6 -0
- package/dist/components/Table/Table.d.ts +74 -0
- package/dist/components/Table/Table.js +176 -0
- package/dist/components/Table/index.d.ts +1 -0
- package/dist/components/Table/index.js +9 -0
- package/dist/components/ToggleButtonGroup/ToggleButtonGroup.d.ts +69 -0
- package/dist/components/ToggleButtonGroup/ToggleButtonGroup.js +158 -0
- package/dist/components/ToggleButtonGroup/index.d.ts +1 -0
- package/dist/components/ToggleButtonGroup/index.js +6 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/useBreakpoint.d.ts +22 -0
- package/dist/hooks/useBreakpoint.js +45 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +59 -2
- package/dist/theme/ThemeContext.d.ts +8 -1
- package/dist/theme/ThemeContext.js +7 -4
- package/dist/theme/applyCastTheme.d.ts +75 -0
- package/dist/theme/applyCastTheme.js +95 -0
- package/dist/theme/index.d.ts +2 -1
- package/dist/theme/index.js +3 -1
- package/dist/theme/themes.js +177 -0
- package/dist/theme/types.d.ts +177 -0
- package/dist/tokens/breakpoints.d.ts +57 -0
- package/dist/tokens/breakpoints.js +92 -0
- package/dist/tokens/colors.d.ts +44 -0
- package/dist/tokens/colors.js +47 -1
- package/dist/tokens/index.d.ts +2 -1
- package/dist/tokens/index.js +9 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
<img src="https://raw.githubusercontent.com/Connagh/cast-ui/main/logo.png" alt="Cast UI" width="300" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@castui/cast-ui"><img src="https://img.shields.io/npm/v/@castui/cast-ui.svg" alt="npm version" /></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/@castui/cast-ui"><img src="https://img.shields.io/npm/dm/@castui/cast-ui.svg" alt="npm downloads" /></a>
|
|
8
|
+
<a href="https://github.com/Connagh/cast-ui/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@castui/cast-ui.svg" alt="license" /></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
5
11
|
A cross-platform component library for React Native. One set of components
|
|
6
12
|
that works on iOS, Android, and the web.
|
|
7
13
|
|
|
@@ -157,6 +163,61 @@ The full guide lives in the
|
|
|
157
163
|
[hosted Storybook](https://main--6990f00d7b8682c18d2ed5f3.chromatic.com)
|
|
158
164
|
under **Guides → Customisation**.
|
|
159
165
|
|
|
166
|
+
## Responsive layout
|
|
167
|
+
|
|
168
|
+
Cast UI ships a set of breakpoints and hooks for building layouts that adapt
|
|
169
|
+
across phones, tablets, and desktops. The values follow the Material 3 window
|
|
170
|
+
size classes, so the same numbers hold up across watches, phones, foldables,
|
|
171
|
+
tablets, and large screens.
|
|
172
|
+
|
|
173
|
+
| Tier | Range (dp) | Typical devices |
|
|
174
|
+
|------|-----------|-----------------|
|
|
175
|
+
| `base` | `< 600` | Watches and every phone in portrait. Your default layout. |
|
|
176
|
+
| `sm` | `>= 600` | Large phones in landscape, foldables unfolded, small tablets |
|
|
177
|
+
| `md` | `>= 840` | Tablets |
|
|
178
|
+
| `lg` | `>= 1200` | Laptops and desktops |
|
|
179
|
+
| `xl` | `>= 1600` | Large desktops and TVs |
|
|
180
|
+
|
|
181
|
+
The hooks read the live window width, so they update on resize, rotation, and
|
|
182
|
+
foldables, and they behave the same on the web. The most common one picks a
|
|
183
|
+
value per tier:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { useResponsiveValue, useBreakpoint, useMinWidth } from '@castui/cast-ui';
|
|
187
|
+
|
|
188
|
+
function Gallery() {
|
|
189
|
+
const columns = useResponsiveValue({ base: 1, md: 2, xl: 4 });
|
|
190
|
+
// 1 column on phones, 2 on tablets, 4 on large desktops
|
|
191
|
+
...
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`useResponsiveValue` is mobile-first: a tier with no value falls back to the
|
|
196
|
+
nearest one below it, so `{ base: 1, md: 2 }` gives 1 up to `md` and 2 from
|
|
197
|
+
`md` on. The other two hooks cover the rest:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
const tier = useBreakpoint(); // 'base' | 'sm' | 'md' | 'lg' | 'xl'
|
|
201
|
+
const isWide = useMinWidth('lg'); // true from 1200dp up
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The raw thresholds are also exported as `breakpoints` if you need a number
|
|
205
|
+
directly.
|
|
206
|
+
|
|
207
|
+
A few things worth knowing. React Native has no CSS media queries, so a
|
|
208
|
+
breakpoint here is a width threshold you compare against, not automatic
|
|
209
|
+
restyling. `base` is the mobile-first default: you write the phone layout with
|
|
210
|
+
no breakpoint, then add overrides for larger screens. The gap between a small
|
|
211
|
+
and a large phone is better handled with flexible layout (flex, percentages,
|
|
212
|
+
`maxWidth`) than with a breakpoint.
|
|
213
|
+
|
|
214
|
+
Breakpoints are a fixed foundation, which sets them apart from the rest of the
|
|
215
|
+
theme. They are not part of `ThemeProvider`: they do not change with density,
|
|
216
|
+
they are not touched by brand colour overrides, and they are not carried in
|
|
217
|
+
`cast-theme.json`. The scale stays identical in every app, so layouts stay
|
|
218
|
+
predictable. The same values live as the `breakpoint/*` primitive variables in
|
|
219
|
+
the [cast-ui-kit Figma file](https://www.figma.com/design/JGtlpxLPJMZcwvQ3UZ9ZUl/cast-ui-kit).
|
|
220
|
+
|
|
160
221
|
## Theming from Figma — the cast-sync plugin
|
|
161
222
|
|
|
162
223
|
[`cast-sync/`](./cast-sync) is a Figma plugin that turns the Figma file's
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accordion — a stack of expandable sections, flush/divided style.
|
|
3
|
+
*
|
|
4
|
+
* Compound component. `<Accordion>` owns which items are open and how they
|
|
5
|
+
* coordinate; `<AccordionItem>` is one section with a header and collapsible
|
|
6
|
+
* content. They communicate through context, so a consumer writes:
|
|
7
|
+
*
|
|
8
|
+
* <Accordion type="single" defaultValue="shipping">
|
|
9
|
+
* <AccordionItem value="shipping" title="Shipping">
|
|
10
|
+
* Free delivery on orders over £50.
|
|
11
|
+
* </AccordionItem>
|
|
12
|
+
* <AccordionItem value="returns" title="Returns" leadingIcon="undo">
|
|
13
|
+
* Return any item within 30 days.
|
|
14
|
+
* </AccordionItem>
|
|
15
|
+
* <AccordionItem value="legacy" title="Archived" disabled>
|
|
16
|
+
* Hidden content.
|
|
17
|
+
* </AccordionItem>
|
|
18
|
+
* </Accordion>
|
|
19
|
+
*
|
|
20
|
+
* Maps 1:1 to the Figma <Accordion> component:
|
|
21
|
+
* type → single | multiple (single opens one section at a time)
|
|
22
|
+
* size → small | default | large (header padding, gap, typography)
|
|
23
|
+
* AccordionItem state=open → expanded; state=disabled → disabled;
|
|
24
|
+
* state=hover → runtime onHoverIn (not a prop)
|
|
25
|
+
*
|
|
26
|
+
* Accordion is neutral only. The header label uses scheme.text.primary (disabled
|
|
27
|
+
* uses scheme.disabled.fg). The chevron (chevron_right, rotated 90deg when open)
|
|
28
|
+
* and the optional leadingIcon render through <Icon> at the named size keyed by
|
|
29
|
+
* the `size` prop and take the same colour as the label. The header hover and
|
|
30
|
+
* press backgrounds reuse the neutral subtle intent. The divider between items is
|
|
31
|
+
* the overlay border colour (like <Divider>) at the control border width.
|
|
32
|
+
*
|
|
33
|
+
* Tokens: `gap` / `paddingX` / `paddingY` are density spacing from
|
|
34
|
+
* `components.accordion[size]`. No new colour tokens.
|
|
35
|
+
*/
|
|
36
|
+
import React from 'react';
|
|
37
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
38
|
+
export type AccordionSize = 'small' | 'default' | 'large';
|
|
39
|
+
export type AccordionType = 'single' | 'multiple';
|
|
40
|
+
export type AccordionProps = {
|
|
41
|
+
/** 'single' opens one section at a time; 'multiple' opens any number. */
|
|
42
|
+
type?: AccordionType;
|
|
43
|
+
/**
|
|
44
|
+
* Controlled open value(s). A string for `type="single"`, a string array for
|
|
45
|
+
* `type="multiple"`. Provide with `onValueChange` for a controlled accordion.
|
|
46
|
+
*/
|
|
47
|
+
value?: string | string[];
|
|
48
|
+
/** Uncontrolled initial open value(s). Same shape rules as `value`. */
|
|
49
|
+
defaultValue?: string | string[];
|
|
50
|
+
/** Called with the next open value(s) when a section toggles. */
|
|
51
|
+
onValueChange?: (value: string | string[]) => void;
|
|
52
|
+
/** Size variant — header padding, gap, and typography. */
|
|
53
|
+
size?: AccordionSize;
|
|
54
|
+
/** For `type="single"`, allow closing the open section. Defaults to true. */
|
|
55
|
+
collapsible?: boolean;
|
|
56
|
+
/** `<AccordionItem>` children. */
|
|
57
|
+
children: React.ReactNode;
|
|
58
|
+
/** Outer style — use for positioning (margin, width, alignSelf). */
|
|
59
|
+
style?: StyleProp<ViewStyle>;
|
|
60
|
+
/** Accessibility label for the group. */
|
|
61
|
+
accessibilityLabel?: string;
|
|
62
|
+
};
|
|
63
|
+
export type AccordionItemProps = {
|
|
64
|
+
/** Unique value identifying this section. */
|
|
65
|
+
value: string;
|
|
66
|
+
/** The header label text. */
|
|
67
|
+
title: string;
|
|
68
|
+
/** Icon before the title — Material Symbols name string or a ReactNode. */
|
|
69
|
+
leadingIcon?: string | React.ReactNode;
|
|
70
|
+
/** Disables interaction and applies muted styling. */
|
|
71
|
+
disabled?: boolean;
|
|
72
|
+
/** The collapsible content. A string is wrapped in <Text> automatically. */
|
|
73
|
+
children: React.ReactNode;
|
|
74
|
+
/** Outer style — applied to the item container. */
|
|
75
|
+
style?: StyleProp<ViewStyle>;
|
|
76
|
+
/** Accessibility label — falls back to the title text. */
|
|
77
|
+
accessibilityLabel?: string;
|
|
78
|
+
};
|
|
79
|
+
export declare function AccordionItem({ value, title, leadingIcon, disabled, children, style, accessibilityLabel, }: AccordionItemProps): import("react/jsx-runtime").JSX.Element;
|
|
80
|
+
export declare function Accordion({ type, value, defaultValue, onValueChange, size, collapsible, children, style, accessibilityLabel, }: AccordionProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccordionItem = AccordionItem;
|
|
4
|
+
exports.Accordion = Accordion;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
/**
|
|
7
|
+
* Accordion — a stack of expandable sections, flush/divided style.
|
|
8
|
+
*
|
|
9
|
+
* Compound component. `<Accordion>` owns which items are open and how they
|
|
10
|
+
* coordinate; `<AccordionItem>` is one section with a header and collapsible
|
|
11
|
+
* content. They communicate through context, so a consumer writes:
|
|
12
|
+
*
|
|
13
|
+
* <Accordion type="single" defaultValue="shipping">
|
|
14
|
+
* <AccordionItem value="shipping" title="Shipping">
|
|
15
|
+
* Free delivery on orders over £50.
|
|
16
|
+
* </AccordionItem>
|
|
17
|
+
* <AccordionItem value="returns" title="Returns" leadingIcon="undo">
|
|
18
|
+
* Return any item within 30 days.
|
|
19
|
+
* </AccordionItem>
|
|
20
|
+
* <AccordionItem value="legacy" title="Archived" disabled>
|
|
21
|
+
* Hidden content.
|
|
22
|
+
* </AccordionItem>
|
|
23
|
+
* </Accordion>
|
|
24
|
+
*
|
|
25
|
+
* Maps 1:1 to the Figma <Accordion> component:
|
|
26
|
+
* type → single | multiple (single opens one section at a time)
|
|
27
|
+
* size → small | default | large (header padding, gap, typography)
|
|
28
|
+
* AccordionItem state=open → expanded; state=disabled → disabled;
|
|
29
|
+
* state=hover → runtime onHoverIn (not a prop)
|
|
30
|
+
*
|
|
31
|
+
* Accordion is neutral only. The header label uses scheme.text.primary (disabled
|
|
32
|
+
* uses scheme.disabled.fg). The chevron (chevron_right, rotated 90deg when open)
|
|
33
|
+
* and the optional leadingIcon render through <Icon> at the named size keyed by
|
|
34
|
+
* the `size` prop and take the same colour as the label. The header hover and
|
|
35
|
+
* press backgrounds reuse the neutral subtle intent. The divider between items is
|
|
36
|
+
* the overlay border colour (like <Divider>) at the control border width.
|
|
37
|
+
*
|
|
38
|
+
* Tokens: `gap` / `paddingX` / `paddingY` are density spacing from
|
|
39
|
+
* `components.accordion[size]`. No new colour tokens.
|
|
40
|
+
*/
|
|
41
|
+
const react_1 = require("react");
|
|
42
|
+
const react_native_1 = require("react-native");
|
|
43
|
+
const theme_1 = require("../../theme");
|
|
44
|
+
const tokens_1 = require("../../tokens");
|
|
45
|
+
const Text_1 = require("../Text");
|
|
46
|
+
const Icon_1 = require("../Icon");
|
|
47
|
+
const AccordionCtx = (0, react_1.createContext)(null);
|
|
48
|
+
function useAccordionContext(component) {
|
|
49
|
+
const ctx = (0, react_1.useContext)(AccordionCtx);
|
|
50
|
+
if (!ctx) {
|
|
51
|
+
throw new Error(`<${component}> must be used within <Accordion>`);
|
|
52
|
+
}
|
|
53
|
+
return ctx;
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Constants
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/** Header label typography scale (medium weight title). */
|
|
59
|
+
const HEADER_TYPE = {
|
|
60
|
+
small: 'title-sm',
|
|
61
|
+
default: 'title-md',
|
|
62
|
+
large: 'title-lg',
|
|
63
|
+
};
|
|
64
|
+
/** Content body typography scale. */
|
|
65
|
+
const CONTENT_TYPE = {
|
|
66
|
+
small: 'body-sm',
|
|
67
|
+
default: 'body-md',
|
|
68
|
+
large: 'body-lg',
|
|
69
|
+
};
|
|
70
|
+
/** Normalise a value/defaultValue prop into an array of open values. */
|
|
71
|
+
function toOpenArray(v, type) {
|
|
72
|
+
if (v === undefined || v === null)
|
|
73
|
+
return [];
|
|
74
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
75
|
+
const cleaned = arr.filter((x) => x !== '');
|
|
76
|
+
return type === 'single' ? cleaned.slice(0, 1) : cleaned;
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// AccordionItem
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
function AccordionItem({ value, title, leadingIcon, disabled = false, children, style, accessibilityLabel, }) {
|
|
82
|
+
const { openValues, toggle, size } = useAccordionContext('AccordionItem');
|
|
83
|
+
const { components, colors, scheme } = (0, theme_1.useTheme)();
|
|
84
|
+
const [isHovered, setIsHovered] = (0, react_1.useState)(false);
|
|
85
|
+
const sizeTokens = components.accordion[size];
|
|
86
|
+
const isOpen = openValues.includes(value);
|
|
87
|
+
// Chevron rotation: chevron_right (0deg) rotates to point down (90deg) when open.
|
|
88
|
+
const spin = (0, react_1.useRef)(new react_native_1.Animated.Value(isOpen ? 1 : 0)).current;
|
|
89
|
+
(0, react_1.useEffect)(() => {
|
|
90
|
+
react_native_1.Animated.timing(spin, {
|
|
91
|
+
toValue: isOpen ? 1 : 0,
|
|
92
|
+
duration: 160,
|
|
93
|
+
easing: react_native_1.Easing.inOut(react_native_1.Easing.ease),
|
|
94
|
+
useNativeDriver: true,
|
|
95
|
+
}).start();
|
|
96
|
+
}, [isOpen, spin]);
|
|
97
|
+
const rotate = spin.interpolate({
|
|
98
|
+
inputRange: [0, 1],
|
|
99
|
+
outputRange: ['0deg', '90deg'],
|
|
100
|
+
});
|
|
101
|
+
const labelFg = disabled ? scheme.disabled.fg : scheme.text.primary;
|
|
102
|
+
const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: size, color: labelFg })) : (leadingIcon ?? null);
|
|
103
|
+
const content = typeof children === 'string' ? ((0, jsx_runtime_1.jsx)(Text_1.Text, { type: CONTENT_TYPE[size], color: scheme.text.description, children: children })) : (children);
|
|
104
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
105
|
+
{
|
|
106
|
+
borderBottomWidth: tokens_1.controlTokens.borderWidth,
|
|
107
|
+
borderBottomColor: scheme.surface.overlay.border,
|
|
108
|
+
},
|
|
109
|
+
style,
|
|
110
|
+
], children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => {
|
|
111
|
+
if (!disabled)
|
|
112
|
+
toggle(value);
|
|
113
|
+
}, disabled: disabled, onHoverIn: () => setIsHovered(true), onHoverOut: () => setIsHovered(false), accessibilityRole: "button", accessibilityLabel: accessibilityLabel || title, accessibilityState: { expanded: isOpen, disabled }, children: ({ pressed }) => {
|
|
114
|
+
const headerBg = disabled
|
|
115
|
+
? 'transparent'
|
|
116
|
+
: pressed
|
|
117
|
+
? colors.neutral.subtle.active.bg
|
|
118
|
+
: isHovered
|
|
119
|
+
? colors.neutral.subtle.hover.bg
|
|
120
|
+
: 'transparent';
|
|
121
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
|
|
122
|
+
flexDirection: 'row',
|
|
123
|
+
alignItems: 'center',
|
|
124
|
+
gap: sizeTokens.gap,
|
|
125
|
+
paddingHorizontal: sizeTokens.paddingX,
|
|
126
|
+
paddingVertical: sizeTokens.paddingY,
|
|
127
|
+
backgroundColor: headerBg,
|
|
128
|
+
}, children: [resolvedLeading, (0, jsx_runtime_1.jsx)(Text_1.Text, { type: HEADER_TYPE[size], color: labelFg, selectable: false, style: { flex: 1 }, children: title }), (0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { pointerEvents: "none", style: { transform: [{ rotate }] }, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "chevron_right", size: size, color: labelFg }) })] }));
|
|
129
|
+
} }), isOpen ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
|
|
130
|
+
paddingHorizontal: sizeTokens.paddingX,
|
|
131
|
+
paddingBottom: sizeTokens.paddingY,
|
|
132
|
+
}, children: content })) : null] }));
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Accordion
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
function Accordion({ type = 'single', value, defaultValue, onValueChange, size = 'default', collapsible = true, children, style, accessibilityLabel, }) {
|
|
138
|
+
const isControlled = value !== undefined;
|
|
139
|
+
const [internal, setInternal] = (0, react_1.useState)(() => toOpenArray(defaultValue, type));
|
|
140
|
+
const openValues = toOpenArray(isControlled ? value : internal, type);
|
|
141
|
+
const toggle = (item) => {
|
|
142
|
+
let next;
|
|
143
|
+
if (type === 'multiple') {
|
|
144
|
+
next = openValues.includes(item)
|
|
145
|
+
? openValues.filter((x) => x !== item)
|
|
146
|
+
: [...openValues, item];
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const isOpen = openValues.includes(item);
|
|
150
|
+
next = isOpen ? (collapsible ? [] : openValues) : [item];
|
|
151
|
+
}
|
|
152
|
+
if (!isControlled)
|
|
153
|
+
setInternal(next);
|
|
154
|
+
onValueChange?.(type === 'multiple' ? next : (next[0] ?? ''));
|
|
155
|
+
};
|
|
156
|
+
return ((0, jsx_runtime_1.jsx)(AccordionCtx.Provider, { value: { openValues, toggle, size }, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityRole: "none", accessibilityLabel: accessibilityLabel, style: [{ alignSelf: 'stretch' }, style], children: children }) }));
|
|
157
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Accordion, AccordionItem, type AccordionProps, type AccordionItemProps, type AccordionSize, type AccordionType, } from './Accordion';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccordionItem = exports.Accordion = void 0;
|
|
4
|
+
var Accordion_1 = require("./Accordion");
|
|
5
|
+
Object.defineProperty(exports, "Accordion", { enumerable: true, get: function () { return Accordion_1.Accordion; } });
|
|
6
|
+
Object.defineProperty(exports, "AccordionItem", { enumerable: true, get: function () { return Accordion_1.AccordionItem; } });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppBar — a top bar with a leading control, a title, and trailing actions.
|
|
3
|
+
*
|
|
4
|
+
* Maps 1:1 to the Figma <App Bar> component:
|
|
5
|
+
* intent → neutral | brand | danger
|
|
6
|
+
* prominence → default | bold | subtle
|
|
7
|
+
* size → small | default | large (padding, gap, title scale)
|
|
8
|
+
* align → start | center (title alignment)
|
|
9
|
+
*
|
|
10
|
+
* Colour comes from the intent system, like Button. prominence picks the
|
|
11
|
+
* surface: bold is a filled bar (intent bg, white text), default is a plain bar
|
|
12
|
+
* with a bottom divider, subtle is transparent. The title and the leadingIcon
|
|
13
|
+
* inherit the bar foreground; trailing actions are a free slot, so colour them
|
|
14
|
+
* to match on a bold bar. The bar hugs its content height from the padding, so
|
|
15
|
+
* there is no fixed height token. Spacing varies by density; the title scale by
|
|
16
|
+
* size. Fonts are consumer-loaded.
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { type StyleProp, type ViewStyle, type GestureResponderEvent } from 'react-native';
|
|
20
|
+
import type { IntentName, ProminenceName } from '../../tokens';
|
|
21
|
+
export type AppBarSize = 'small' | 'default' | 'large';
|
|
22
|
+
export type AppBarAlign = 'start' | 'center';
|
|
23
|
+
export type AppBarProps = {
|
|
24
|
+
/** The bar title. */
|
|
25
|
+
title: string;
|
|
26
|
+
/** Standard leading control icon (menu / back). Auto-coloured + pressable. */
|
|
27
|
+
leadingIcon?: string;
|
|
28
|
+
/** Press handler for the leading icon. */
|
|
29
|
+
onLeadingPress?: (e: GestureResponderEvent) => void;
|
|
30
|
+
/** Custom leading slot. Wins over leadingIcon. */
|
|
31
|
+
leading?: React.ReactNode;
|
|
32
|
+
/** Trailing actions slot. */
|
|
33
|
+
trailing?: React.ReactNode;
|
|
34
|
+
/** Semantic intent. */
|
|
35
|
+
intent?: IntentName;
|
|
36
|
+
/** Visual weight — bold (filled), default (divider), subtle (transparent). */
|
|
37
|
+
prominence?: ProminenceName;
|
|
38
|
+
/** Size variant — padding, gap, and title scale. */
|
|
39
|
+
size?: AppBarSize;
|
|
40
|
+
/** Title alignment. Defaults to start. */
|
|
41
|
+
align?: AppBarAlign;
|
|
42
|
+
/** Outer style. */
|
|
43
|
+
style?: StyleProp<ViewStyle>;
|
|
44
|
+
/** Accessibility label — falls back to the title. */
|
|
45
|
+
accessibilityLabel?: string;
|
|
46
|
+
};
|
|
47
|
+
export declare function AppBar({ title, leadingIcon, onLeadingPress, leading, trailing, intent, prominence, size, align, style, accessibilityLabel, }: AppBarProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppBar = AppBar;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_native_1 = require("react-native");
|
|
6
|
+
const theme_1 = require("../../theme");
|
|
7
|
+
const tokens_1 = require("../../tokens");
|
|
8
|
+
const Text_1 = require("../Text");
|
|
9
|
+
const Icon_1 = require("../Icon");
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Constants
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
/** Maps size → title typography scale (Text component `type`). */
|
|
14
|
+
const TITLE_TYPE = {
|
|
15
|
+
small: 'title-sm',
|
|
16
|
+
default: 'title-md',
|
|
17
|
+
large: 'title-lg',
|
|
18
|
+
};
|
|
19
|
+
/** Maps size → named Icon scale for the leading control. */
|
|
20
|
+
const ICON_SIZE = {
|
|
21
|
+
small: 'default',
|
|
22
|
+
default: 'large',
|
|
23
|
+
large: 'large',
|
|
24
|
+
};
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Component
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function AppBar({ title, leadingIcon, onLeadingPress, leading, trailing, intent = 'neutral', prominence = 'default', size = 'default', align = 'start', style, accessibilityLabel, }) {
|
|
29
|
+
const { components, colors } = (0, theme_1.useTheme)();
|
|
30
|
+
const tokens = components.appBar[size];
|
|
31
|
+
const barColors = colors[intent][prominence].default;
|
|
32
|
+
const resolvedLeading = leading ??
|
|
33
|
+
(leadingIcon ? ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onLeadingPress, accessibilityRole: "button", accessibilityLabel: "Navigation", hitSlop: 8, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: ICON_SIZE[size], color: barColors.fg }) })) : null);
|
|
34
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { accessibilityRole: "header", accessibilityLabel: accessibilityLabel || title, style: [
|
|
35
|
+
{
|
|
36
|
+
flexDirection: 'row',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
gap: tokens.gap,
|
|
39
|
+
paddingHorizontal: tokens.paddingX,
|
|
40
|
+
paddingVertical: tokens.paddingY,
|
|
41
|
+
backgroundColor: barColors.bg,
|
|
42
|
+
borderBottomWidth: tokens_1.controlTokens.borderWidth,
|
|
43
|
+
borderBottomColor: barColors.border,
|
|
44
|
+
},
|
|
45
|
+
style,
|
|
46
|
+
], children: [resolvedLeading, (0, jsx_runtime_1.jsx)(Text_1.Text, { type: TITLE_TYPE[size], color: barColors.fg, numberOfLines: 1, selectable: false, style: { flex: 1, textAlign: align === 'center' ? 'center' : 'left' }, children: title }), trailing ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { flexDirection: 'row', alignItems: 'center', gap: tokens.gap }, children: trailing })) : null] }));
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AppBar, type AppBarProps, type AppBarSize, type AppBarAlign } from './AppBar';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete — a text field that filters a list of options as you type.
|
|
3
|
+
*
|
|
4
|
+
* Maps 1:1 to the Figma <Autocomplete> component:
|
|
5
|
+
* size → small | default | large
|
|
6
|
+
* state → default | hover | focus | error | disabled
|
|
7
|
+
* option state → default | hover | selected | disabled
|
|
8
|
+
*
|
|
9
|
+
* Autocomplete is the Select combobox specialised for client-side filtering. Its
|
|
10
|
+
* field is an Input, so it reuses the input tokens; its options are
|
|
11
|
+
* value-selection rows, so it reuses the select tokens and the
|
|
12
|
+
* scheme.select.option colours. It introduces no new tokens. Type to filter the
|
|
13
|
+
* options by label, press one to select it, or clear the field.
|
|
14
|
+
*
|
|
15
|
+
* value is controlled with value/onValueChange (null = nothing selected) or
|
|
16
|
+
* uncontrolled with defaultValue. Pass a filterOptions function to change how
|
|
17
|
+
* matching works. Fonts are consumer-loaded.
|
|
18
|
+
*/
|
|
19
|
+
import React from 'react';
|
|
20
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
21
|
+
export type AutocompleteSize = 'small' | 'default' | 'large';
|
|
22
|
+
export type AutocompleteOption = {
|
|
23
|
+
/** Unique value. */
|
|
24
|
+
value: string;
|
|
25
|
+
/** Display label — also what the filter matches against. */
|
|
26
|
+
label: string;
|
|
27
|
+
/** Supporting text below the label. */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Leading icon — Material Symbols name or a ReactNode. */
|
|
30
|
+
icon?: string | React.ReactNode;
|
|
31
|
+
/** Disables this option. */
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
};
|
|
34
|
+
export type AutocompleteProps = {
|
|
35
|
+
/** The options to choose from. */
|
|
36
|
+
options: AutocompleteOption[];
|
|
37
|
+
/** Selected value (controlled). null = nothing selected. */
|
|
38
|
+
value?: string | null;
|
|
39
|
+
/** Initial value (uncontrolled). */
|
|
40
|
+
defaultValue?: string | null;
|
|
41
|
+
/** Selection change handler. */
|
|
42
|
+
onValueChange?: (value: string | null) => void;
|
|
43
|
+
/** Called with the input text as it changes. */
|
|
44
|
+
onInputChange?: (text: string) => void;
|
|
45
|
+
/** Field label above the input. */
|
|
46
|
+
label?: string;
|
|
47
|
+
/** Helper or error text below the input. */
|
|
48
|
+
helperText?: string;
|
|
49
|
+
/** Placeholder when empty. */
|
|
50
|
+
placeholder?: string;
|
|
51
|
+
/** Leading icon in the field. */
|
|
52
|
+
leadingIcon?: string | React.ReactNode;
|
|
53
|
+
/** Size variant. */
|
|
54
|
+
size?: AutocompleteSize;
|
|
55
|
+
/** Disables the field. */
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
/** Error state — danger border + red helper text. */
|
|
58
|
+
error?: boolean;
|
|
59
|
+
/** Show a clear button when there is input. Defaults to true. */
|
|
60
|
+
clearable?: boolean;
|
|
61
|
+
/** Text shown when no option matches. Defaults to "No options". */
|
|
62
|
+
noOptionsText?: string;
|
|
63
|
+
/** Override the default case-insensitive label filter. */
|
|
64
|
+
filterOptions?: (options: AutocompleteOption[], query: string) => AutocompleteOption[];
|
|
65
|
+
/** Style override for the outer container. */
|
|
66
|
+
style?: StyleProp<ViewStyle>;
|
|
67
|
+
/** Accessibility label — falls back to the label prop. */
|
|
68
|
+
accessibilityLabel?: string;
|
|
69
|
+
};
|
|
70
|
+
export declare function Autocomplete({ options, value: controlledValue, defaultValue, onValueChange, onInputChange, label: fieldLabel, helperText, placeholder, leadingIcon, size, disabled, error, clearable, noOptionsText, filterOptions, style, accessibilityLabel, }: AutocompleteProps): import("react/jsx-runtime").JSX.Element;
|