@castui/cast-ui 4.1.1 → 4.2.1
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 +129 -19
- package/dist/components/Alert/Alert.d.ts +40 -0
- package/dist/components/Alert/Alert.js +71 -0
- package/dist/components/Alert/index.d.ts +1 -0
- package/dist/components/Alert/index.js +5 -0
- package/dist/components/Avatar/Avatar.d.ts +31 -0
- package/dist/components/Avatar/Avatar.js +55 -0
- package/dist/components/Avatar/index.d.ts +1 -0
- package/dist/components/Avatar/index.js +5 -0
- package/dist/components/Badge/Badge.d.ts +41 -0
- package/dist/components/Badge/Badge.js +71 -0
- package/dist/components/Badge/index.d.ts +1 -0
- package/dist/components/Badge/index.js +5 -0
- package/dist/components/Button/Button.d.ts +0 -1
- package/dist/components/Button/Button.js +21 -19
- package/dist/components/Button/index.d.ts +0 -1
- package/dist/components/Button/index.js +5 -2
- package/dist/components/Card/Card.d.ts +41 -0
- package/dist/components/Card/Card.js +91 -0
- package/dist/components/Card/index.d.ts +1 -0
- package/dist/components/Card/index.js +5 -0
- package/dist/components/Checkbox/Checkbox.d.ts +32 -0
- package/dist/components/Checkbox/Checkbox.js +107 -0
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.js +5 -0
- package/dist/components/Chip/Chip.d.ts +46 -0
- package/dist/components/Chip/Chip.js +82 -0
- package/dist/components/Chip/index.d.ts +1 -0
- package/dist/components/Chip/index.js +5 -0
- package/dist/components/Dialog/Dialog.d.ts +0 -1
- package/dist/components/Dialog/Dialog.js +33 -26
- package/dist/components/Dialog/index.d.ts +0 -1
- package/dist/components/Dialog/index.js +6 -2
- package/dist/components/Divider/Divider.d.ts +22 -0
- package/dist/components/Divider/Divider.js +19 -0
- package/dist/components/Divider/index.d.ts +1 -0
- package/dist/components/Divider/index.js +5 -0
- package/dist/components/Icon/Icon.d.ts +26 -9
- package/dist/components/Icon/Icon.js +16 -6
- package/dist/components/Icon/index.d.ts +0 -1
- package/dist/components/Icon/index.js +5 -2
- package/dist/components/Input/Input.d.ts +62 -0
- package/dist/components/Input/Input.js +141 -0
- package/dist/components/Input/index.d.ts +1 -0
- package/dist/components/Input/index.js +5 -0
- package/dist/components/List/List.d.ts +58 -0
- package/dist/components/List/List.js +116 -0
- package/dist/components/List/index.d.ts +1 -0
- package/dist/components/List/index.js +8 -0
- package/dist/components/Popover/Popover.d.ts +34 -0
- package/dist/components/Popover/Popover.js +62 -0
- package/dist/components/Popover/index.d.ts +1 -0
- package/dist/components/Popover/index.js +5 -0
- package/dist/components/Radio/Radio.d.ts +52 -0
- package/dist/components/Radio/Radio.js +127 -0
- package/dist/components/Radio/index.d.ts +1 -0
- package/dist/components/Radio/index.js +6 -0
- package/dist/components/Select/Select.d.ts +0 -1
- package/dist/components/Select/Select.js +114 -96
- package/dist/components/Select/index.d.ts +0 -1
- package/dist/components/Select/index.js +10 -2
- package/dist/components/Skeleton/Skeleton.d.ts +33 -0
- package/dist/components/Skeleton/Skeleton.js +66 -0
- package/dist/components/Skeleton/index.d.ts +1 -0
- package/dist/components/Skeleton/index.js +5 -0
- package/dist/components/Toast/Toast.d.ts +35 -0
- package/dist/components/Toast/Toast.js +79 -0
- package/dist/components/Toast/index.d.ts +1 -0
- package/dist/components/Toast/index.js +5 -0
- package/dist/components/Toggle/Toggle.d.ts +31 -0
- package/dist/components/Toggle/Toggle.js +91 -0
- package/dist/components/Toggle/index.d.ts +1 -0
- package/dist/components/Toggle/index.js +5 -0
- package/dist/components/Tooltip/Tooltip.d.ts +34 -0
- package/dist/components/Tooltip/Tooltip.js +67 -0
- package/dist/components/Tooltip/index.d.ts +1 -0
- package/dist/components/Tooltip/index.js +5 -0
- package/dist/index.d.ts +17 -3
- package/dist/index.js +81 -7
- package/dist/theme/ThemeContext.d.ts +24 -8
- package/dist/theme/ThemeContext.js +41 -22
- package/dist/theme/index.d.ts +1 -2
- package/dist/theme/index.js +8 -3
- package/dist/theme/themes.d.ts +0 -1
- package/dist/theme/themes.js +214 -2
- package/dist/theme/types.d.ts +183 -1
- package/dist/theme/types.js +2 -2
- package/dist/tokens/colors.d.ts +294 -26
- package/dist/tokens/colors.js +324 -99
- package/dist/tokens/index.d.ts +1 -2
- package/dist/tokens/index.js +29 -3
- package/dist/tokens/typography.d.ts +0 -1
- package/dist/tokens/typography.js +13 -11
- package/package.json +13 -2
- package/dist/components/Button/Button.d.ts.map +0 -1
- package/dist/components/Button/Button.js.map +0 -1
- package/dist/components/Button/index.d.ts.map +0 -1
- package/dist/components/Button/index.js.map +0 -1
- package/dist/components/Dialog/Dialog.d.ts.map +0 -1
- package/dist/components/Dialog/Dialog.js.map +0 -1
- package/dist/components/Dialog/index.d.ts.map +0 -1
- package/dist/components/Dialog/index.js.map +0 -1
- package/dist/components/Icon/Icon.d.ts.map +0 -1
- package/dist/components/Icon/Icon.js.map +0 -1
- package/dist/components/Icon/index.d.ts.map +0 -1
- package/dist/components/Icon/index.js.map +0 -1
- package/dist/components/Select/Select.d.ts.map +0 -1
- package/dist/components/Select/Select.js.map +0 -1
- package/dist/components/Select/index.d.ts.map +0 -1
- package/dist/components/Select/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/theme/ThemeContext.d.ts.map +0 -1
- package/dist/theme/ThemeContext.js.map +0 -1
- package/dist/theme/index.d.ts.map +0 -1
- package/dist/theme/index.js.map +0 -1
- package/dist/theme/themes.d.ts.map +0 -1
- package/dist/theme/themes.js.map +0 -1
- package/dist/theme/types.d.ts.map +0 -1
- package/dist/theme/types.js.map +0 -1
- package/dist/tokens/colors.d.ts.map +0 -1
- package/dist/tokens/colors.js.map +0 -1
- package/dist/tokens/index.d.ts.map +0 -1
- package/dist/tokens/index.js.map +0 -1
- package/dist/tokens/typography.d.ts.map +0 -1
- package/dist/tokens/typography.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Cast UI
|
|
2
2
|
|
|
3
|
-
A cross-platform component library for React Native
|
|
3
|
+
A cross-platform component library for React Native. One set of components
|
|
4
|
+
that works on iOS, Android, and the web.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Every colour, size, and spacing value in Cast UI comes from design tokens
|
|
7
|
+
kept in sync with the
|
|
8
|
+
[cast-ui-kit Figma file](https://www.figma.com/design/JGtlpxLPJMZcwvQ3UZ9ZUl/cast-ui-kit),
|
|
9
|
+
so what designers see in Figma is what ships in the app. Every component
|
|
10
|
+
supports light and dark mode, three spacing densities, and your own brand
|
|
11
|
+
colours — all switchable while the app is running, with no rebuild.
|
|
6
12
|
|
|
7
13
|
## Installation
|
|
8
14
|
|
|
@@ -10,36 +16,140 @@ A cross-platform component library for React Native (iOS, Android, Web).
|
|
|
10
16
|
npm install @castui/cast-ui
|
|
11
17
|
```
|
|
12
18
|
|
|
13
|
-
**Peer dependencies:** `react` (>=18)
|
|
19
|
+
**Peer dependencies:** `react` (>=18) and `react-native` (>=0.72). Nothing
|
|
20
|
+
else is installed alongside the package.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { ThemeProvider, Button } from '@castui/cast-ui';
|
|
24
|
+
|
|
25
|
+
export function App() {
|
|
26
|
+
return (
|
|
27
|
+
<ThemeProvider>
|
|
28
|
+
<Button intent="brand" prominence="bold" onPress={save}>
|
|
29
|
+
Save changes
|
|
30
|
+
</Button>
|
|
31
|
+
</ThemeProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
14
35
|
|
|
15
|
-
##
|
|
36
|
+
## Components
|
|
37
|
+
|
|
38
|
+
| Component | Description |
|
|
39
|
+
|-----------|-------------|
|
|
40
|
+
| **Alert** | Inline message with neutral, brand, and danger intents, an intent-matched icon, and optional close button |
|
|
41
|
+
| **Avatar** | User representation as an image, initials, or icon |
|
|
42
|
+
| **Badge** | Compact pill for labels, counts, and status, with optional status dot |
|
|
43
|
+
| **Button** | Action button with three intents, three prominences, and icon support |
|
|
44
|
+
| **Card** | Content container with optional image, icon, and actions |
|
|
45
|
+
| **Checkbox** | Form control with checked, unchecked, and indeterminate states |
|
|
46
|
+
| **Chip** | Compact element for filters, selections, and tags |
|
|
47
|
+
| **Dialog** | Modal overlay for confirmations, alerts, and focused tasks |
|
|
48
|
+
| **Divider** | Horizontal or vertical separator line |
|
|
49
|
+
| **Icon** | Material Symbols Outlined icon, rendered via font ligatures |
|
|
50
|
+
| **Input** | Single-line text field with label, helper text, and error state |
|
|
51
|
+
| **List** | Item list with selection, icons, subheaders, and dividers |
|
|
52
|
+
| **Popover** | Anchored floating panel for contextual content |
|
|
53
|
+
| **Radio** | Single-choice control, with `RadioGroup` for managing a set |
|
|
54
|
+
| **Select** | Dropdown with single, multi (tag pills), and combobox (search) modes |
|
|
55
|
+
| **Skeleton** | Loading placeholder in text, circle, and rectangle shapes |
|
|
56
|
+
| **Toast** | Brief notification with icon and optional close button |
|
|
57
|
+
| **Toggle** | On/off switch with label |
|
|
58
|
+
| **Tooltip** | Short hint shown on hover or focus |
|
|
59
|
+
|
|
60
|
+
Components share a common prop vocabulary, mirrored from the Figma kit:
|
|
61
|
+
|
|
62
|
+
- `intent` — what it means: `neutral`, `brand`, or `danger`
|
|
63
|
+
- `prominence` — how visually heavy it is: `default` (outlined), `bold`
|
|
64
|
+
(filled), or `subtle` (ghost)
|
|
65
|
+
- `size` — `small`, `default`, or `large`
|
|
66
|
+
|
|
67
|
+
## Customising
|
|
68
|
+
|
|
69
|
+
Wrap your app in `ThemeProvider`. It controls three independent settings:
|
|
70
|
+
|
|
71
|
+
**Density** — how tight or roomy spacing feels: `compact`, `default`, or
|
|
72
|
+
`comfortable`. Changing it scales padding and spacing across every component
|
|
73
|
+
at once. Colours and typography never change with density, so your brand
|
|
74
|
+
looks the same at any setting.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<ThemeProvider density="compact">
|
|
78
|
+
```
|
|
16
79
|
|
|
17
|
-
|
|
80
|
+
**Colour mode** — `light` or `dark`. Switches every colour in the library:
|
|
81
|
+
buttons, surfaces, text, and form controls.
|
|
18
82
|
|
|
19
|
-
|
|
20
|
-
-
|
|
83
|
+
```tsx
|
|
84
|
+
import { useColorScheme } from 'react-native';
|
|
21
85
|
|
|
22
|
-
|
|
86
|
+
const scheme = useColorScheme();
|
|
87
|
+
<ThemeProvider colorMode={scheme === 'dark' ? 'dark' : 'light'}>
|
|
88
|
+
```
|
|
23
89
|
|
|
24
|
-
|
|
25
|
-
|
|
90
|
+
**Colour overrides** — pass a `colors` prop to use your own brand colours.
|
|
91
|
+
You only write the colours you want to change; everything else keeps its
|
|
92
|
+
default:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<ThemeProvider
|
|
96
|
+
colors={{
|
|
97
|
+
brand: {
|
|
98
|
+
bold: {
|
|
99
|
+
default: { bg: '#7C3AED', fg: '#FFFFFF', border: '#7C3AED' },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
26
104
|
```
|
|
27
105
|
|
|
28
|
-
|
|
106
|
+
ThemeProviders can be nested — for example, a compact data table inside a
|
|
107
|
+
comfortable app. Your own components can read the active theme with the
|
|
108
|
+
`useTheme` hook, and all the underlying values (colours, typography scales,
|
|
109
|
+
density spacing) are exported for direct use.
|
|
29
110
|
|
|
30
|
-
|
|
31
|
-
|
|
111
|
+
The full guide lives in Storybook under **Guides → Customisation**.
|
|
112
|
+
|
|
113
|
+
## Theming from Figma — the cast-sync plugin
|
|
114
|
+
|
|
115
|
+
[`cast-sync/`](./cast-sync) is a Figma plugin that turns the Figma file's
|
|
116
|
+
colour variables into a theme file for this package. Run it inside the Cast
|
|
117
|
+
Design System file and it shows a preview, then downloads a
|
|
118
|
+
`cast-theme.json` you pass straight to `ThemeProvider`:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import theme from './cast-theme.json';
|
|
122
|
+
|
|
123
|
+
<ThemeProvider colorMode="light" colors={theme.colors.light}>
|
|
32
124
|
```
|
|
33
125
|
|
|
34
|
-
|
|
126
|
+
The workflow: recolour the variables in Figma → run the plugin → swap in the
|
|
127
|
+
new file. No code changes. Setup instructions are in
|
|
128
|
+
[cast-sync/README.md](./cast-sync/README.md).
|
|
35
129
|
|
|
36
|
-
|
|
130
|
+
## How the tokens work
|
|
37
131
|
|
|
38
|
-
|
|
39
|
-
|
|
132
|
+
Cast UI's values come from three layers of design tokens:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Component tokens button.default.paddingX = 14 → changes with density
|
|
136
|
+
Semantic tokens intent/brand/bold/default/bg → changes with colour mode
|
|
137
|
+
Primitive tokens blue/600 = #2563EB → the raw palette
|
|
40
138
|
```
|
|
41
139
|
|
|
42
|
-
|
|
140
|
+
The raw token JSON exported from Figma lives in
|
|
141
|
+
[`design-tokens/`](./design-tokens). The library ships a lean TypeScript
|
|
142
|
+
version of the same values, so nothing is parsed at runtime.
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
Requires Node.js >= 18.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm install
|
|
150
|
+
npm run storybook # component workshop at http://localhost:6006
|
|
151
|
+
npm run build # compile to dist/
|
|
152
|
+
```
|
|
43
153
|
|
|
44
154
|
| Script | Description |
|
|
45
155
|
|--------|-------------|
|
|
@@ -53,7 +163,7 @@ npm run build
|
|
|
53
163
|
|----------|---------|---------|
|
|
54
164
|
| Chromatic | Every push | Visual regression testing via Storybook snapshots |
|
|
55
165
|
| Adoption Tracking | Push to `main` | Registers package version with Zeroheight |
|
|
56
|
-
| Publish to npm | Push to `main` | Builds and publishes
|
|
166
|
+
| Publish to npm | Push to `main` | Builds and publishes to npm (only when the version changes) |
|
|
57
167
|
|
|
58
168
|
## License
|
|
59
169
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert — inline message banner for notes, info, and errors.
|
|
3
|
+
*
|
|
4
|
+
* Maps to the Figma <Alert> component (node 307:3283):
|
|
5
|
+
* intent → neutral | brand | danger (drives colour)
|
|
6
|
+
* size → small | default | large (drives padding, gap, type scale)
|
|
7
|
+
* variant → subtle (no fill/border) | outline (white fill + intent border)
|
|
8
|
+
*
|
|
9
|
+
* Padding + gap come from the density theme's `alert` tokens (size × density).
|
|
10
|
+
* Icon/close sizes track the `size` prop. Colours reuse the intent system.
|
|
11
|
+
*/
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { type ViewStyle, type StyleProp } from 'react-native';
|
|
14
|
+
import type { IntentName } from '../../tokens';
|
|
15
|
+
export type AlertSize = 'small' | 'default' | 'large';
|
|
16
|
+
export type AlertVariant = 'subtle' | 'outline';
|
|
17
|
+
export type AlertProps = {
|
|
18
|
+
/** Semantic intent — drives colour scheme. */
|
|
19
|
+
intent?: IntentName;
|
|
20
|
+
/** Size variant — controls padding, gap, and typography scale. */
|
|
21
|
+
size?: AlertSize;
|
|
22
|
+
/** Visual style — subtle (no fill) or outline (white fill + border). */
|
|
23
|
+
variant?: AlertVariant;
|
|
24
|
+
/** Bold title line. */
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Supporting description text. */
|
|
27
|
+
description?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Leading icon — Material Symbols name string or a ReactNode.
|
|
30
|
+
* Pass `null` to hide. Defaults to an intent-appropriate symbol.
|
|
31
|
+
*/
|
|
32
|
+
icon?: string | React.ReactNode | null;
|
|
33
|
+
/** When provided, renders a close button that calls this handler. */
|
|
34
|
+
onClose?: () => void;
|
|
35
|
+
/** Style override for the outer container. */
|
|
36
|
+
style?: StyleProp<ViewStyle>;
|
|
37
|
+
/** Accessibility label — falls back to the title text. */
|
|
38
|
+
accessibilityLabel?: string;
|
|
39
|
+
};
|
|
40
|
+
export declare function Alert({ intent, size, variant, title: titleText, description, icon, onClose, style, accessibilityLabel, }: AlertProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Alert = Alert;
|
|
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 Icon_1 = require("../Icon");
|
|
8
|
+
const tokens_1 = require("../../tokens");
|
|
9
|
+
/** Maps alert size → title typography scale */
|
|
10
|
+
const TITLE_SCALE = {
|
|
11
|
+
small: 'sm',
|
|
12
|
+
default: 'md',
|
|
13
|
+
large: 'lg',
|
|
14
|
+
};
|
|
15
|
+
/** Maps alert size → body (description) typography scale */
|
|
16
|
+
const BODY_SCALE = {
|
|
17
|
+
small: 'sm',
|
|
18
|
+
default: 'md',
|
|
19
|
+
large: 'lg',
|
|
20
|
+
};
|
|
21
|
+
/** Default leading icon per intent */
|
|
22
|
+
const DEFAULT_ICON = {
|
|
23
|
+
neutral: 'info',
|
|
24
|
+
brand: 'info',
|
|
25
|
+
danger: 'error',
|
|
26
|
+
};
|
|
27
|
+
function Alert({ intent = 'neutral', size = 'default', variant = 'subtle', title: titleText, description, icon, onClose, style, accessibilityLabel, }) {
|
|
28
|
+
const { components, scheme } = (0, theme_1.useTheme)();
|
|
29
|
+
const intentColors = scheme.intents;
|
|
30
|
+
const tokens = components.alert;
|
|
31
|
+
const sizeTokens = tokens[size];
|
|
32
|
+
const titleTokens = tokens_1.title[TITLE_SCALE[size]];
|
|
33
|
+
const bodyTokens = tokens_1.body[BODY_SCALE[size]];
|
|
34
|
+
// Colours derive from the intent system. Outline uses the white fill +
|
|
35
|
+
// intent border; subtle is transparent. Foreground is the intent fg.
|
|
36
|
+
const intentClr = intentColors[intent].default.default;
|
|
37
|
+
const bg = variant === 'outline' ? intentClr.bg : 'transparent';
|
|
38
|
+
const borderColor = variant === 'outline' ? intentClr.border : 'transparent';
|
|
39
|
+
const fg = intentClr.fg;
|
|
40
|
+
// Resolve leading icon — undefined uses the intent default, null hides it.
|
|
41
|
+
const iconNode = icon === null
|
|
42
|
+
? null
|
|
43
|
+
: icon === undefined ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: DEFAULT_ICON[intent], size: sizeTokens.iconSize, color: fg })) : typeof icon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: icon, size: sizeTokens.iconSize, color: fg })) : (icon);
|
|
44
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { accessibilityRole: "alert", accessibilityLabel: accessibilityLabel || titleText, style: [
|
|
45
|
+
{
|
|
46
|
+
flexDirection: 'row',
|
|
47
|
+
alignItems: 'flex-start',
|
|
48
|
+
gap: sizeTokens.gap,
|
|
49
|
+
padding: sizeTokens.padding,
|
|
50
|
+
borderRadius: tokens.borderRadius,
|
|
51
|
+
borderWidth: tokens_1.controlTokens.borderWidth,
|
|
52
|
+
borderColor,
|
|
53
|
+
backgroundColor: bg,
|
|
54
|
+
},
|
|
55
|
+
style,
|
|
56
|
+
], children: [iconNode ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: sizeTokens.iconSize, height: sizeTokens.iconSize }, children: iconNode })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1 }, children: [titleText ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
57
|
+
fontFamily: tokens_1.fontFamily.sans,
|
|
58
|
+
fontWeight: tokens_1.fontWeight.medium,
|
|
59
|
+
fontSize: titleTokens.fontSize,
|
|
60
|
+
lineHeight: titleTokens.lineHeight,
|
|
61
|
+
letterSpacing: titleTokens.letterSpacing,
|
|
62
|
+
color: fg,
|
|
63
|
+
}, selectable: false, children: titleText })) : null, description ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
64
|
+
fontFamily: tokens_1.fontFamily.sans,
|
|
65
|
+
fontWeight: tokens_1.fontWeight.regular,
|
|
66
|
+
fontSize: bodyTokens.fontSize,
|
|
67
|
+
lineHeight: bodyTokens.lineHeight,
|
|
68
|
+
letterSpacing: bodyTokens.letterSpacing,
|
|
69
|
+
color: fg,
|
|
70
|
+
}, selectable: false, children: description })) : null] }), onClose ? ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onClose, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Dismiss", style: { width: sizeTokens.closeSize, height: sizeTokens.closeSize }, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "close", size: sizeTokens.closeSize, color: fg }) })) : null] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Alert, type AlertProps, type AlertSize, type AlertVariant, } from './Alert';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatar — a circular representation of a user or entity.
|
|
3
|
+
*
|
|
4
|
+
* Maps to the Figma <Avatar> component (node 307:4153):
|
|
5
|
+
* size → small | default | large (drives diameter, glyph + initials scale)
|
|
6
|
+
* type → image | initials | icon (inferred from the props you pass)
|
|
7
|
+
*
|
|
8
|
+
* The frame diameter comes from the `avatar` density theme (size × density);
|
|
9
|
+
* the circle radius is constant (full pill). Initials use the label scale
|
|
10
|
+
* matched to the size; the icon glyph and initials colour are the neutral
|
|
11
|
+
* foreground. Image avatars fill the frame and have no background.
|
|
12
|
+
*/
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import { type ImageSourcePropType, type ViewStyle, type StyleProp } from 'react-native';
|
|
15
|
+
export type AvatarSize = 'small' | 'default' | 'large';
|
|
16
|
+
export type AvatarType = 'image' | 'initials' | 'icon';
|
|
17
|
+
export type AvatarProps = {
|
|
18
|
+
/** Size variant — controls diameter, initials, and glyph scale. */
|
|
19
|
+
size?: AvatarSize;
|
|
20
|
+
/** Image source — when set, renders an image avatar filling the frame. */
|
|
21
|
+
source?: ImageSourcePropType;
|
|
22
|
+
/** Initials to display (e.g. "AB"). Used when no `source` is provided. */
|
|
23
|
+
initials?: string;
|
|
24
|
+
/** Fallback icon — Material Symbols name string or a ReactNode. */
|
|
25
|
+
icon?: string | React.ReactNode;
|
|
26
|
+
/** Outer style — use for positioning (margin, flex, alignSelf). */
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
/** Accessibility label — falls back to initials, else "Avatar". */
|
|
29
|
+
accessibilityLabel?: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function Avatar({ size, source, initials, icon, style, accessibilityLabel, }: AvatarProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Avatar = Avatar;
|
|
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 Icon_1 = require("../Icon");
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Constants
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/** Maps avatar size → initials typography scale */
|
|
13
|
+
const LABEL_SCALE = {
|
|
14
|
+
small: 'sm',
|
|
15
|
+
default: 'md',
|
|
16
|
+
large: 'lg',
|
|
17
|
+
};
|
|
18
|
+
/** Default fallback glyph when no source/initials/icon is supplied. */
|
|
19
|
+
const FALLBACK_ICON = 'person';
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Component
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function resolveType(props) {
|
|
24
|
+
if (props.source)
|
|
25
|
+
return 'image';
|
|
26
|
+
if (props.initials)
|
|
27
|
+
return 'initials';
|
|
28
|
+
return 'icon';
|
|
29
|
+
}
|
|
30
|
+
function Avatar({ size = 'default', source, initials, icon, style, accessibilityLabel, }) {
|
|
31
|
+
const { components, scheme } = (0, theme_1.useTheme)();
|
|
32
|
+
const avatarColors = scheme.avatar;
|
|
33
|
+
const tokens = components.avatar;
|
|
34
|
+
const sizeTokens = tokens[size];
|
|
35
|
+
const labelTokens = tokens_1.label[LABEL_SCALE[size]];
|
|
36
|
+
const type = resolveType({ source, initials, icon });
|
|
37
|
+
const frameStyle = {
|
|
38
|
+
width: sizeTokens.size,
|
|
39
|
+
height: sizeTokens.size,
|
|
40
|
+
borderRadius: tokens.borderRadius,
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
justifyContent: 'center',
|
|
43
|
+
overflow: 'hidden',
|
|
44
|
+
backgroundColor: type === 'image' ? undefined : avatarColors.bg,
|
|
45
|
+
};
|
|
46
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityRole: "image", accessibilityLabel: accessibilityLabel || initials || 'Avatar', style: [frameStyle, style], children: type === 'image' ? ((0, jsx_runtime_1.jsx)(react_native_1.Image, { source: source, resizeMode: "cover", style: { width: '100%', height: '100%' } })) : type === 'initials' ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { selectable: false, style: {
|
|
47
|
+
fontFamily: tokens_1.fontFamily.sans,
|
|
48
|
+
fontWeight: tokens_1.fontWeight.medium,
|
|
49
|
+
fontSize: labelTokens.fontSize,
|
|
50
|
+
lineHeight: labelTokens.lineHeight,
|
|
51
|
+
letterSpacing: labelTokens.letterSpacing,
|
|
52
|
+
color: avatarColors.fg,
|
|
53
|
+
textAlign: 'center',
|
|
54
|
+
}, children: initials })) : ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", children: typeof icon === 'string' || icon == null ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: typeof icon === 'string' ? icon : FALLBACK_ICON, size: sizeTokens.iconSize, color: avatarColors.fg })) : (icon) })) }));
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Avatar, type AvatarProps, type AvatarSize, type AvatarType } from './Avatar';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Badge — a compact pill for labels, counts, and status indicators.
|
|
3
|
+
*
|
|
4
|
+
* Maps to the Figma <Badge> component (node 307:3354):
|
|
5
|
+
* intent → neutral | brand | danger (Figma "default" === neutral)
|
|
6
|
+
* variant → solid | subtle | outline (Figma surface treatment)
|
|
7
|
+
* size → small | default | large (Figma "neutral" === default)
|
|
8
|
+
*
|
|
9
|
+
* Spacing/sizing comes from the `badge` theme tokens (keyed by size; constant
|
|
10
|
+
* across density). Colours map directly onto the semantic intent system:
|
|
11
|
+
* solid → intent bold (filled, white text)
|
|
12
|
+
* subtle → intent subtle (transparent fill, coloured text)
|
|
13
|
+
* outline → intent bold border + intent default text (transparent fill)
|
|
14
|
+
* Typography uses the label scale (sm/md/lg) matched to the badge size.
|
|
15
|
+
*/
|
|
16
|
+
import React from 'react';
|
|
17
|
+
import { type ViewStyle, type StyleProp } from 'react-native';
|
|
18
|
+
import type { IntentName } from '../../tokens';
|
|
19
|
+
export type BadgeSize = 'small' | 'default' | 'large';
|
|
20
|
+
export type BadgeVariant = 'solid' | 'subtle' | 'outline';
|
|
21
|
+
export type BadgeProps = {
|
|
22
|
+
/** The badge label text. */
|
|
23
|
+
children: string;
|
|
24
|
+
/** Semantic intent — drives the colour scheme. */
|
|
25
|
+
intent?: IntentName;
|
|
26
|
+
/** Surface treatment — solid (filled), subtle (tinted), or outline (bordered). */
|
|
27
|
+
variant?: BadgeVariant;
|
|
28
|
+
/** Size variant — controls padding, gap, and typography scale. */
|
|
29
|
+
size?: BadgeSize;
|
|
30
|
+
/** Shows a leading status dot in the foreground colour. */
|
|
31
|
+
dot?: boolean;
|
|
32
|
+
/** Icon before the label — Material Symbols name string or a ReactNode. */
|
|
33
|
+
leadingIcon?: string | React.ReactNode;
|
|
34
|
+
/** Icon after the label — Material Symbols name string or a ReactNode. */
|
|
35
|
+
trailingIcon?: string | React.ReactNode;
|
|
36
|
+
/** Outer style — use for positioning (margin, flex, alignSelf). */
|
|
37
|
+
style?: StyleProp<ViewStyle>;
|
|
38
|
+
/** Accessibility label — falls back to children text if not provided. */
|
|
39
|
+
accessibilityLabel?: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function Badge({ children, intent, variant, size, dot, leadingIcon, trailingIcon, style, accessibilityLabel, }: BadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Badge = Badge;
|
|
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 Icon_1 = require("../Icon");
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Constants
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/** Maps badge size → label typography scale */
|
|
13
|
+
const LABEL_SCALE = {
|
|
14
|
+
small: 'sm',
|
|
15
|
+
default: 'md',
|
|
16
|
+
large: 'lg',
|
|
17
|
+
};
|
|
18
|
+
/** Icon size — fixed at 16px, matched to the label baseline. */
|
|
19
|
+
const ICON_SIZE = 16;
|
|
20
|
+
function resolveColors(intent, variant, intentColors) {
|
|
21
|
+
const clrs = intentColors[intent];
|
|
22
|
+
switch (variant) {
|
|
23
|
+
case 'subtle':
|
|
24
|
+
return clrs.subtle.default;
|
|
25
|
+
case 'outline':
|
|
26
|
+
return {
|
|
27
|
+
bg: 'transparent',
|
|
28
|
+
fg: clrs.default.default.fg,
|
|
29
|
+
border: clrs.bold.default.border,
|
|
30
|
+
};
|
|
31
|
+
case 'solid':
|
|
32
|
+
default:
|
|
33
|
+
return clrs.bold.default;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function Badge({ children, intent = 'neutral', variant = 'solid', size = 'default', dot = false, leadingIcon, trailingIcon, style, accessibilityLabel, }) {
|
|
37
|
+
const { components, scheme } = (0, theme_1.useTheme)();
|
|
38
|
+
const sizeTokens = components.badge[size];
|
|
39
|
+
const labelTokens = tokens_1.label[LABEL_SCALE[size]];
|
|
40
|
+
const colors = resolveColors(intent, variant, scheme.intents);
|
|
41
|
+
const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: ICON_SIZE, color: colors.fg })) : (leadingIcon);
|
|
42
|
+
const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: ICON_SIZE, color: colors.fg })) : (trailingIcon);
|
|
43
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { accessibilityRole: "text", accessibilityLabel: accessibilityLabel || children, style: [
|
|
44
|
+
{
|
|
45
|
+
flexDirection: 'row',
|
|
46
|
+
alignItems: 'center',
|
|
47
|
+
justifyContent: 'center',
|
|
48
|
+
alignSelf: 'flex-start',
|
|
49
|
+
gap: sizeTokens.gap,
|
|
50
|
+
paddingHorizontal: sizeTokens.paddingX,
|
|
51
|
+
paddingVertical: sizeTokens.paddingY,
|
|
52
|
+
borderRadius: components.badge.borderRadius,
|
|
53
|
+
borderWidth: tokens_1.controlTokens.borderWidth,
|
|
54
|
+
borderColor: colors.border,
|
|
55
|
+
backgroundColor: colors.bg,
|
|
56
|
+
},
|
|
57
|
+
style,
|
|
58
|
+
], children: [dot ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: {
|
|
59
|
+
width: sizeTokens.dotSize,
|
|
60
|
+
height: sizeTokens.dotSize,
|
|
61
|
+
borderRadius: sizeTokens.dotSize / 2,
|
|
62
|
+
backgroundColor: colors.fg,
|
|
63
|
+
} })) : null, resolvedLeading ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedLeading })) : null, (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
64
|
+
fontFamily: tokens_1.fontFamily.sans,
|
|
65
|
+
fontWeight: tokens_1.fontWeight.medium,
|
|
66
|
+
fontSize: labelTokens.fontSize,
|
|
67
|
+
lineHeight: labelTokens.lineHeight,
|
|
68
|
+
letterSpacing: labelTokens.letterSpacing,
|
|
69
|
+
color: colors.fg,
|
|
70
|
+
}, selectable: false, children: children }), resolvedTrailing ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedTrailing })) : null] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Badge, type BadgeProps, type BadgeSize, type BadgeVariant } from './Badge';
|
|
@@ -37,4 +37,3 @@ export type ButtonProps = {
|
|
|
37
37
|
accessibilityLabel?: string;
|
|
38
38
|
};
|
|
39
39
|
export declare function Button({ children, intent, prominence, size, disabled, leadingIcon, trailingIcon, onPress, style, accessibilityLabel, }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
-
//# sourceMappingURL=Button.d.ts.map
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Button = Button;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
2
5
|
/**
|
|
3
6
|
* Button — the foundational interactive component of Cast UI.
|
|
4
7
|
*
|
|
@@ -11,11 +14,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
11
14
|
* Colours come from the semantic intent system (constant across densities).
|
|
12
15
|
* Typography uses the label scale (sm/md/lg) matched to the button size.
|
|
13
16
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const react_1 = require("react");
|
|
18
|
+
const react_native_1 = require("react-native");
|
|
19
|
+
const theme_1 = require("../../theme");
|
|
20
|
+
const tokens_1 = require("../../tokens");
|
|
21
|
+
const Icon_1 = require("../Icon");
|
|
19
22
|
// ---------------------------------------------------------------------------
|
|
20
23
|
// Constants
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
@@ -30,15 +33,15 @@ const ICON_SIZE = 16;
|
|
|
30
33
|
// ---------------------------------------------------------------------------
|
|
31
34
|
// Component
|
|
32
35
|
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
const { components, colors } = useTheme();
|
|
35
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
36
|
+
function Button({ children, intent = 'neutral', prominence = 'default', size = 'default', disabled = false, leadingIcon, trailingIcon, onPress, style, accessibilityLabel, }) {
|
|
37
|
+
const { components, colors } = (0, theme_1.useTheme)();
|
|
38
|
+
const [isHovered, setIsHovered] = (0, react_1.useState)(false);
|
|
36
39
|
// Resolve tokens for current size + density
|
|
37
40
|
const sizeTokens = components.button[size];
|
|
38
|
-
const labelTokens = label[LABEL_SCALE[size]];
|
|
41
|
+
const labelTokens = tokens_1.label[LABEL_SCALE[size]];
|
|
39
42
|
const intentClrs = colors[intent];
|
|
40
43
|
// Resolve colours based on interaction state
|
|
41
|
-
const getStateColors = useCallback((pressed, hovered) => {
|
|
44
|
+
const getStateColors = (0, react_1.useCallback)((pressed, hovered) => {
|
|
42
45
|
if (disabled) {
|
|
43
46
|
return {
|
|
44
47
|
bg: '#F3F4F6',
|
|
@@ -52,7 +55,7 @@ export function Button({ children, intent = 'neutral', prominence = 'default', s
|
|
|
52
55
|
return intentClrs[prominence].hover;
|
|
53
56
|
return intentClrs[prominence].default;
|
|
54
57
|
}, [disabled, intentClrs, prominence]);
|
|
55
|
-
return (
|
|
58
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: onPress, disabled: disabled, onHoverIn: () => setIsHovered(true), onHoverOut: () => setIsHovered(false), accessibilityRole: "button", accessibilityLabel: accessibilityLabel || children, accessibilityState: { disabled }, style: style, children: ({ pressed }) => {
|
|
56
59
|
const stateColors = getStateColors(pressed, isHovered);
|
|
57
60
|
const containerStyle = {
|
|
58
61
|
flexDirection: 'row',
|
|
@@ -63,16 +66,16 @@ export function Button({ children, intent = 'neutral', prominence = 'default', s
|
|
|
63
66
|
paddingHorizontal: sizeTokens.paddingX,
|
|
64
67
|
paddingVertical: sizeTokens.paddingY,
|
|
65
68
|
borderRadius: sizeTokens.borderRadius,
|
|
66
|
-
borderWidth: controlTokens.borderWidth,
|
|
69
|
+
borderWidth: tokens_1.controlTokens.borderWidth,
|
|
67
70
|
borderColor: stateColors.border,
|
|
68
71
|
backgroundColor: stateColors.bg,
|
|
69
72
|
};
|
|
70
73
|
// Resolve icon props — strings become <Icon> with auto-matched colour
|
|
71
|
-
const resolvedLeading = typeof leadingIcon === 'string' ? (
|
|
72
|
-
const resolvedTrailing = typeof trailingIcon === 'string' ? (
|
|
73
|
-
return (
|
|
74
|
-
fontFamily: fontFamily.sans,
|
|
75
|
-
fontWeight: fontWeight.medium,
|
|
74
|
+
const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: ICON_SIZE, color: stateColors.fg })) : (leadingIcon);
|
|
75
|
+
const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: ICON_SIZE, color: stateColors.fg })) : (trailingIcon);
|
|
76
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: containerStyle, children: [resolvedLeading, (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
|
|
77
|
+
fontFamily: tokens_1.fontFamily.sans,
|
|
78
|
+
fontWeight: tokens_1.fontWeight.medium,
|
|
76
79
|
fontSize: labelTokens.fontSize,
|
|
77
80
|
lineHeight: labelTokens.lineHeight,
|
|
78
81
|
letterSpacing: labelTokens.letterSpacing,
|
|
@@ -80,4 +83,3 @@ export function Button({ children, intent = 'neutral', prominence = 'default', s
|
|
|
80
83
|
}, selectable: false, children: children }), resolvedTrailing] }));
|
|
81
84
|
} }));
|
|
82
85
|
}
|
|
83
|
-
//# sourceMappingURL=Button.js.map
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Button = void 0;
|
|
4
|
+
var Button_1 = require("./Button");
|
|
5
|
+
Object.defineProperty(exports, "Button", { enumerable: true, get: function () { return Button_1.Button; } });
|