@cyberkaidev/ui 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/LICENSE +20 -0
- package/README.md +721 -0
- package/lib/module/ThemeProvider.js +84 -0
- package/lib/module/ThemeProvider.js.map +1 -0
- package/lib/module/components/AppBar.js +185 -0
- package/lib/module/components/AppBar.js.map +1 -0
- package/lib/module/components/BounceEffect.js +50 -0
- package/lib/module/components/BounceEffect.js.map +1 -0
- package/lib/module/components/Button.js +48 -0
- package/lib/module/components/Button.js.map +1 -0
- package/lib/module/components/Chips.js +94 -0
- package/lib/module/components/Chips.js.map +1 -0
- package/lib/module/components/IconButton.js +71 -0
- package/lib/module/components/IconButton.js.map +1 -0
- package/lib/module/components/InforList.js +92 -0
- package/lib/module/components/InforList.js.map +1 -0
- package/lib/module/components/LoadingApp.js +58 -0
- package/lib/module/components/LoadingApp.js.map +1 -0
- package/lib/module/components/Overlays.js +20 -0
- package/lib/module/components/Overlays.js.map +1 -0
- package/lib/module/components/RippleEffect.js +178 -0
- package/lib/module/components/RippleEffect.js.map +1 -0
- package/lib/module/components/Scaffold.js +172 -0
- package/lib/module/components/Scaffold.js.map +1 -0
- package/lib/module/components/SearchBar.js +161 -0
- package/lib/module/components/SearchBar.js.map +1 -0
- package/lib/module/components/SegmentedButton.js +151 -0
- package/lib/module/components/SegmentedButton.js.map +1 -0
- package/lib/module/components/ShapedButton.js +58 -0
- package/lib/module/components/ShapedButton.js.map +1 -0
- package/lib/module/components/SlideModal.js +196 -0
- package/lib/module/components/SlideModal.js.map +1 -0
- package/lib/module/components/TabBar.js +107 -0
- package/lib/module/components/TabBar.js.map +1 -0
- package/lib/module/components/Text.js +47 -0
- package/lib/module/components/Text.js.map +1 -0
- package/lib/module/components/TextButton.js +53 -0
- package/lib/module/components/TextButton.js.map +1 -0
- package/lib/module/components/TextInput.js +83 -0
- package/lib/module/components/TextInput.js.map +1 -0
- package/lib/module/components/Toast.js +101 -0
- package/lib/module/components/Toast.js.map +1 -0
- package/lib/module/hooks/useAppBarScroll.js +20 -0
- package/lib/module/hooks/useAppBarScroll.js.map +1 -0
- package/lib/module/hooks/usePressAndHoldRepeat.js +71 -0
- package/lib/module/hooks/usePressAndHoldRepeat.js.map +1 -0
- package/lib/module/icons/Icon.js +85 -0
- package/lib/module/icons/Icon.js.map +1 -0
- package/lib/module/index.js +42 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/responsive.js +28 -0
- package/lib/module/responsive.js.map +1 -0
- package/lib/module/stores/useLoadingApp.js +14 -0
- package/lib/module/stores/useLoadingApp.js.map +1 -0
- package/lib/module/stores/useToast.js +24 -0
- package/lib/module/stores/useToast.js.map +1 -0
- package/lib/module/theme.js +86 -0
- package/lib/module/theme.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/jest.setup.d.ts +2 -0
- package/lib/typescript/jest.setup.d.ts.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/ThemeProvider.d.ts +39 -0
- package/lib/typescript/src/ThemeProvider.d.ts.map +1 -0
- package/lib/typescript/src/components/AppBar.d.ts +33 -0
- package/lib/typescript/src/components/AppBar.d.ts.map +1 -0
- package/lib/typescript/src/components/BounceEffect.d.ts +12 -0
- package/lib/typescript/src/components/BounceEffect.d.ts.map +1 -0
- package/lib/typescript/src/components/Button.d.ts +24 -0
- package/lib/typescript/src/components/Button.d.ts.map +1 -0
- package/lib/typescript/src/components/Chips.d.ts +23 -0
- package/lib/typescript/src/components/Chips.d.ts.map +1 -0
- package/lib/typescript/src/components/IconButton.d.ts +14 -0
- package/lib/typescript/src/components/IconButton.d.ts.map +1 -0
- package/lib/typescript/src/components/InforList.d.ts +28 -0
- package/lib/typescript/src/components/InforList.d.ts.map +1 -0
- package/lib/typescript/src/components/LoadingApp.d.ts +8 -0
- package/lib/typescript/src/components/LoadingApp.d.ts.map +1 -0
- package/lib/typescript/src/components/Overlays.d.ts +12 -0
- package/lib/typescript/src/components/Overlays.d.ts.map +1 -0
- package/lib/typescript/src/components/RippleEffect.d.ts +18 -0
- package/lib/typescript/src/components/RippleEffect.d.ts.map +1 -0
- package/lib/typescript/src/components/Scaffold.d.ts +62 -0
- package/lib/typescript/src/components/Scaffold.d.ts.map +1 -0
- package/lib/typescript/src/components/SearchBar.d.ts +20 -0
- package/lib/typescript/src/components/SearchBar.d.ts.map +1 -0
- package/lib/typescript/src/components/SegmentedButton.d.ts +37 -0
- package/lib/typescript/src/components/SegmentedButton.d.ts.map +1 -0
- package/lib/typescript/src/components/ShapedButton.d.ts +18 -0
- package/lib/typescript/src/components/ShapedButton.d.ts.map +1 -0
- package/lib/typescript/src/components/SlideModal.d.ts +15 -0
- package/lib/typescript/src/components/SlideModal.d.ts.map +1 -0
- package/lib/typescript/src/components/TabBar.d.ts +20 -0
- package/lib/typescript/src/components/TabBar.d.ts.map +1 -0
- package/lib/typescript/src/components/Text.d.ts +26 -0
- package/lib/typescript/src/components/Text.d.ts.map +1 -0
- package/lib/typescript/src/components/TextButton.d.ts +15 -0
- package/lib/typescript/src/components/TextButton.d.ts.map +1 -0
- package/lib/typescript/src/components/TextInput.d.ts +22 -0
- package/lib/typescript/src/components/TextInput.d.ts.map +1 -0
- package/lib/typescript/src/components/Toast.d.ts +4 -0
- package/lib/typescript/src/components/Toast.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useAppBarScroll.d.ts +10 -0
- package/lib/typescript/src/hooks/useAppBarScroll.d.ts.map +1 -0
- package/lib/typescript/src/hooks/usePressAndHoldRepeat.d.ts +21 -0
- package/lib/typescript/src/hooks/usePressAndHoldRepeat.d.ts.map +1 -0
- package/lib/typescript/src/icons/Icon.d.ts +64 -0
- package/lib/typescript/src/icons/Icon.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +30 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/responsive.d.ts +15 -0
- package/lib/typescript/src/responsive.d.ts.map +1 -0
- package/lib/typescript/src/stores/useLoadingApp.d.ts +14 -0
- package/lib/typescript/src/stores/useLoadingApp.d.ts.map +1 -0
- package/lib/typescript/src/stores/useToast.d.ts +25 -0
- package/lib/typescript/src/stores/useToast.d.ts.map +1 -0
- package/lib/typescript/src/theme.d.ts +51 -0
- package/lib/typescript/src/theme.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +26 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +189 -0
- package/src/ThemeProvider.tsx +108 -0
- package/src/components/AppBar.tsx +226 -0
- package/src/components/BounceEffect.tsx +53 -0
- package/src/components/Button.tsx +70 -0
- package/src/components/Chips.tsx +115 -0
- package/src/components/IconButton.tsx +84 -0
- package/src/components/InforList.tsx +110 -0
- package/src/components/LoadingApp.tsx +55 -0
- package/src/components/Overlays.tsx +21 -0
- package/src/components/RippleEffect.tsx +244 -0
- package/src/components/Scaffold.tsx +219 -0
- package/src/components/SearchBar.tsx +166 -0
- package/src/components/SegmentedButton.tsx +206 -0
- package/src/components/ShapedButton.tsx +74 -0
- package/src/components/SlideModal.tsx +232 -0
- package/src/components/TabBar.tsx +122 -0
- package/src/components/Text.tsx +70 -0
- package/src/components/TextButton.tsx +57 -0
- package/src/components/TextInput.tsx +108 -0
- package/src/components/Toast.tsx +95 -0
- package/src/hooks/useAppBarScroll.ts +19 -0
- package/src/hooks/usePressAndHoldRepeat.ts +85 -0
- package/src/icons/Icon.tsx +51 -0
- package/src/index.tsx +64 -0
- package/src/responsive.ts +32 -0
- package/src/stores/useLoadingApp.ts +19 -0
- package/src/stores/useToast.ts +36 -0
- package/src/theme.ts +125 -0
- package/src/types.ts +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
# @cyberkaidev/ui
|
|
2
|
+
|
|
3
|
+
A themeable React Native component library. Expo-first, TypeScript-first.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Setup](#setup)
|
|
9
|
+
- [Theming](#theming)
|
|
10
|
+
- [Components](#components)
|
|
11
|
+
- [Text & buttons](#text--buttons)
|
|
12
|
+
- [Inputs](#inputs)
|
|
13
|
+
- [Layout — `Scaffold`](#layout--scaffold)
|
|
14
|
+
- [Fixed bars — `AppBar`, `TabBar`, `SearchBar`](#fixed-bars--appbar-tabbar-searchbar)
|
|
15
|
+
- [Lists — `InforList`](#lists--inforlist)
|
|
16
|
+
- [Overlays — `Overlays`, `SlideModal`, `Toast`, `LoadingApp`](#overlays--overlays-slidemodal-toast-loadingapp)
|
|
17
|
+
- [Touch effects — `BounceEffect`, `RippleEffect`](#touch-effects--bounceeffect-rippleeffect)
|
|
18
|
+
- [Icons](#icons)
|
|
19
|
+
- [Hooks](#hooks)
|
|
20
|
+
- [Global state](#global-state)
|
|
21
|
+
- [Responsive helpers](#responsive-helpers)
|
|
22
|
+
- [Exported types](#exported-types)
|
|
23
|
+
- [TypeScript & config notes](#typescript--config-notes)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
yarn add @cyberkaidev/ui
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Peer dependencies
|
|
34
|
+
|
|
35
|
+
Install these in the consuming app:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
yarn expo install react-native-reanimated react-native-worklets react-native-safe-area-context \
|
|
39
|
+
@shopify/flash-list expo-symbols expo-blur expo-constants
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `react-native-reanimated` needs its Babel plugin — `babel-preset-expo` adds it automatically on
|
|
43
|
+
Expo SDK 54+. On bare React Native, add `react-native-worklets/plugin` (last) to `babel.config.js`.
|
|
44
|
+
- `zustand` ships as a normal dependency — you don't install it.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
Wrap the app **once** in `SafeAreaProvider` and `ThemeProvider`, and mount `Overlays` at the
|
|
51
|
+
root.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
55
|
+
import { ThemeProvider, Overlays, colors } from '@cyberkaidev/ui';
|
|
56
|
+
|
|
57
|
+
export default function App() {
|
|
58
|
+
return (
|
|
59
|
+
<SafeAreaProvider>
|
|
60
|
+
<ThemeProvider colors={colors}>
|
|
61
|
+
<RootNavigator />
|
|
62
|
+
<Overlays loadingLabel="Loading…" />
|
|
63
|
+
</ThemeProvider>
|
|
64
|
+
</SafeAreaProvider>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- **`ThemeProvider` is required, and its `colors` prop is required** — there is no default
|
|
70
|
+
palette. Pass the exported `colors` (a ready-made palette), spread from it, or provide your own
|
|
71
|
+
`ColorsType`. Any component rendered outside a `ThemeProvider` throws.
|
|
72
|
+
- **`SafeAreaProvider` is required.** `Scaffold.ScrollView` / `Scaffold.FlashList`, `TabBar` and
|
|
73
|
+
`SlideModal` read safe-area insets and throw without it.
|
|
74
|
+
- **`Overlays`** renders the app-wide `Toast` + `LoadingApp` (see [`Overlays`](#overlays)).
|
|
75
|
+
Mount it once at the root, inside `ThemeProvider`; trigger them from anywhere with `useToast` /
|
|
76
|
+
`useLoadingApp`. You can also mount `<Toast />` / `<LoadingApp />` individually if you need to.
|
|
77
|
+
- **Fonts.** Components reference the `Inter-Regular` / `Inter-Medium` / `Inter-Bold` families.
|
|
78
|
+
Load them in the app (e.g. `expo-font`); without them, text falls back to the system font.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Theming
|
|
83
|
+
|
|
84
|
+
The theme has six token groups: `colors` (**required**, full palette) plus `fontSizes`,
|
|
85
|
+
`iconSize`, `spaces`, `borderRadius`, `pageSpacing` (**optional**, partial — a missing key is
|
|
86
|
+
inherited from the nearest parent provider, or from the built-in defaults). Providers nest.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { colors } from '@cyberkaidev/ui';
|
|
90
|
+
|
|
91
|
+
<ThemeProvider
|
|
92
|
+
colors={{ ...colors, primary: '#ff8a00', background: '#0b0b0f' }} // full ColorsType
|
|
93
|
+
fontSizes={{ m: 20 }} // partial
|
|
94
|
+
spaces={{ vertical: { m: 24 } }} // deep-merged one level
|
|
95
|
+
borderRadius={{ radius_20: 8 }}
|
|
96
|
+
>
|
|
97
|
+
<Screen />
|
|
98
|
+
</ThemeProvider>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A **nested** `ThemeProvider` also needs a full `colors` (it replaces the palette); the other
|
|
102
|
+
groups still merge over the parent.
|
|
103
|
+
|
|
104
|
+
### Reading the theme
|
|
105
|
+
|
|
106
|
+
| API | Returns | Use for |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `useColors()` | `ColorsType` | color values inside a component (shortcut for `useTheme().colors`) |
|
|
109
|
+
| `useTheme()` | `ThemeType` | any token group inside a component |
|
|
110
|
+
| `useThemedStyles(makeStyles)` | `T` | a `StyleSheet` built from the resolved theme, memoized against it |
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { StyleSheet } from 'react-native';
|
|
114
|
+
import { useTheme, useThemedStyles, type ThemeType } from '@cyberkaidev/ui';
|
|
115
|
+
|
|
116
|
+
function Card() {
|
|
117
|
+
const { colors } = useTheme();
|
|
118
|
+
const styles = useThemedStyles(makeStyles);
|
|
119
|
+
return <View style={[styles.card, { borderColor: colors.border }]} />;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// module-level factory — keep it out of the component so the memo stays stable
|
|
123
|
+
function makeStyles(t: ThemeType) {
|
|
124
|
+
return StyleSheet.create({
|
|
125
|
+
card: {
|
|
126
|
+
padding: t.spaces.horizontal.m,
|
|
127
|
+
borderRadius: t.borderRadius.radius_15,
|
|
128
|
+
backgroundColor: t.colors.surface,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Exported token objects
|
|
135
|
+
|
|
136
|
+
- **`colors`** — a ready-made `ColorsType` palette. Not a default: you must still pass a palette
|
|
137
|
+
to `ThemeProvider`, but this is a fine one to pass.
|
|
138
|
+
- **`fontSizes`, `iconSize`, `spaces`, `borderRadius`, `pageSpacing`** — the built-in defaults for
|
|
139
|
+
those groups (`ThemeProvider` merges your partials on top). `defaultTokens` bundles all five.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
export const colors = {
|
|
143
|
+
background: 'black',
|
|
144
|
+
background_60: 'rgba(0,0,0,0.6)',
|
|
145
|
+
on_surface: '#1A191E',
|
|
146
|
+
surface: '#100e12',
|
|
147
|
+
surface_90: 'rgba(16, 14, 18, 0.9)',
|
|
148
|
+
border: '#3D3B40',
|
|
149
|
+
text: '#f6f1ff',
|
|
150
|
+
primary: '#4284ff',
|
|
151
|
+
red: '#ff0004',
|
|
152
|
+
green: '#00ff2a',
|
|
153
|
+
amber: '#FFC107',
|
|
154
|
+
transparent: 'transparent',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// fontSizes / iconSize: keys are xxxl…xs (fontSizes) and xl…s (iconSize), values in dp
|
|
158
|
+
// spaces: { horizontal: { xs…xxl }, vertical: { xs…xxl } } in dp
|
|
159
|
+
// borderRadius: { radius_5, radius_10, radius_15, radius_20, radius_100 }
|
|
160
|
+
// pageSpacing: { horizontal, top, bottom }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
> A **dark/light** theme is the app's job: keep two `ColorsType` palettes and swap which one you
|
|
164
|
+
> pass to `ThemeProvider`.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Components
|
|
169
|
+
|
|
170
|
+
Every component takes an optional `testID`. Every value shown as a token (`FontSizeType`,
|
|
171
|
+
`ColorsType` keys, …) resolves against the active theme.
|
|
172
|
+
|
|
173
|
+
### Text & buttons
|
|
174
|
+
|
|
175
|
+
#### `Text`
|
|
176
|
+
|
|
177
|
+
Themed text primitive. All copy comes from `children`.
|
|
178
|
+
|
|
179
|
+
| Prop | Type | Default | Notes |
|
|
180
|
+
|---|---|---|---|
|
|
181
|
+
| `children` | `ReactNode` | — | |
|
|
182
|
+
| `weight` | `'regular' \| 'medium' \| 'bold'` | `'regular'` | → `Inter-*` family |
|
|
183
|
+
| `size` | `FontSizeType` (`xs`…`xxxl`) | `'s'` | |
|
|
184
|
+
| `color` | `string` | theme `text` | |
|
|
185
|
+
| `numberOfLines` | `number` | — | truncation |
|
|
186
|
+
| `textAlign` | `'left' \| 'center' \| 'right'` | — | |
|
|
187
|
+
| `marginT` / `marginB` / `marginL` / `marginR` | `number` | `0` | dp |
|
|
188
|
+
| `opacity` | `number` | — | |
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
<Text weight="bold" size="l">Title</Text>
|
|
192
|
+
<Text color="#8a8a8a" numberOfLines={2}>Subtitle that may wrap…</Text>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### `Button`
|
|
196
|
+
|
|
197
|
+
Base pressable. iOS gets opacity feedback; Android gets a touch ripple. Style it yourself — the
|
|
198
|
+
other buttons wrap this.
|
|
199
|
+
|
|
200
|
+
| Prop | Type | Notes |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `children` | `ReactNode` | |
|
|
203
|
+
| `onPress` / `onPressIn` / `onPressOut` | `() => void` | |
|
|
204
|
+
| `disabled` | `boolean` | |
|
|
205
|
+
| `style` | `StyleProp<ViewStyle>` | |
|
|
206
|
+
| `hitSlop` | `Insets` | |
|
|
207
|
+
| `rippleColor` / `rippleContainerBorderRadius` | `string` / `number` | Android only |
|
|
208
|
+
|
|
209
|
+
#### `ShapedButton`
|
|
210
|
+
|
|
211
|
+
Filled call-to-action with a loading state.
|
|
212
|
+
|
|
213
|
+
| Prop | Type | Default |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `children` | `ReactNode` | — |
|
|
216
|
+
| `onPress` | `() => void` | — (required) |
|
|
217
|
+
| `type` | `'primary' \| 'secondary'` | `'primary'` |
|
|
218
|
+
| `size` | `FontSizeType` | — |
|
|
219
|
+
| `disabled` | `boolean` | — |
|
|
220
|
+
| `isLoading` | `boolean` | `false` |
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
<ShapedButton isLoading={saving} onPress={save}>Save</ShapedButton>
|
|
224
|
+
<ShapedButton type="secondary" onPress={cancel}>Cancel</ShapedButton>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### `TextButton`
|
|
228
|
+
|
|
229
|
+
Text-only button, no fill.
|
|
230
|
+
|
|
231
|
+
| Prop | Type | Default |
|
|
232
|
+
|---|---|---|
|
|
233
|
+
| `children` | `ReactNode` | — |
|
|
234
|
+
| `onPress` | `() => void` | — (required) |
|
|
235
|
+
| `color` | `string` | theme `primary` |
|
|
236
|
+
| `size` | `FontSizeType` | — |
|
|
237
|
+
| `disabled` | `boolean` | `false` |
|
|
238
|
+
|
|
239
|
+
#### `IconButton`
|
|
240
|
+
|
|
241
|
+
Circular icon-only button with an optional "active" dot.
|
|
242
|
+
|
|
243
|
+
| Prop | Type | Default |
|
|
244
|
+
|---|---|---|
|
|
245
|
+
| `icon` | `IconNameType` | — (required) |
|
|
246
|
+
| `onPress` | `() => void` | — (required) |
|
|
247
|
+
| `active` | `boolean` | `false` |
|
|
248
|
+
| `disabled` | `boolean` | `false` |
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
<IconButton icon="filter" active={hasFilters} onPress={openFilters} />
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Inputs
|
|
255
|
+
|
|
256
|
+
#### `Chips`
|
|
257
|
+
|
|
258
|
+
Horizontally scrolling row of selectable chips. Generic over the item key type.
|
|
259
|
+
|
|
260
|
+
| Prop | Type | Default |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| `data` | `{ key: Item; label: string; disabled?: boolean; prefix?: ReactNode; scheme?: 'primary' \| 'surface' \| 'on_surface' }[]` | — (`scheme` defaults to `'surface'`) |
|
|
263
|
+
| `onPress` | `(key: Item) => void` | — (omit → non-interactive) |
|
|
264
|
+
| `marginT` / `marginB` | `number` | `0` |
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<Chips
|
|
268
|
+
data={[
|
|
269
|
+
{ key: 'all', label: 'All' },
|
|
270
|
+
{ key: 'new', label: 'New', scheme: 'primary' },
|
|
271
|
+
{ key: 'used', label: 'Used', disabled: true },
|
|
272
|
+
]}
|
|
273
|
+
onPress={setFilter}
|
|
274
|
+
/>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### `TextInput`
|
|
278
|
+
|
|
279
|
+
Single-line field with a supporting message and error state.
|
|
280
|
+
|
|
281
|
+
| Prop | Type | Default |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `value` | `string` | — (required) |
|
|
284
|
+
| `placeholder` | `string` | — (required) |
|
|
285
|
+
| `onChangeText` | `(text: string) => void` | — (required) |
|
|
286
|
+
| `supportingText` | `string` | — (shown in the error color) |
|
|
287
|
+
| `isError` | `boolean` | `false` |
|
|
288
|
+
| `onEnter` | `() => void` | fired on submit when the value is non-empty |
|
|
289
|
+
| `onBlur` | `TextInputProps['onBlur']` | — |
|
|
290
|
+
| `autoFocus` / `autoCapitalize` / `autoCorrect` / `autoComplete` / `inputMode` | passthrough to RN `TextInput` | — |
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
<TextInput
|
|
294
|
+
value={name}
|
|
295
|
+
placeholder="Your name"
|
|
296
|
+
supportingText={name.length < 2 ? 'At least 2 characters' : undefined}
|
|
297
|
+
isError={touched && name.length < 2}
|
|
298
|
+
onChangeText={setName}
|
|
299
|
+
onEnter={submit}
|
|
300
|
+
/>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### `SegmentedButton`
|
|
304
|
+
|
|
305
|
+
Single-select segmented button in the Material 3 **Expressive** style — a rounded track holding
|
|
306
|
+
equal-width segments. The selected segment morphs into a filled pill with a springy pop, and every
|
|
307
|
+
segment squishes on press. Controlled, generic over the segment `value` type. Best with 2–5 segments.
|
|
308
|
+
|
|
309
|
+
| Prop | Type | Default |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `segments` | `{ value: Value; label: string; icon?: IconNameType; disabled?: boolean }[]` | — (required) |
|
|
312
|
+
| `value` | `Value` | — (required) the selected segment |
|
|
313
|
+
| `onChange` | `(value: Value) => void` | — (required) |
|
|
314
|
+
| `size` | `'s' \| 'm' \| 'l'` | `'m'` (track height scale) |
|
|
315
|
+
| `disabled` | `boolean` | `false` (disables the whole control) |
|
|
316
|
+
| `showSelectedCheck` | `boolean` | `true` (leading check on the selected segment) |
|
|
317
|
+
|
|
318
|
+
Export: `SEGMENTED_BUTTON_HEIGHT` (the `'m'` track height).
|
|
319
|
+
|
|
320
|
+
> The classic outlined-with-dividers Material 3 version is archived at
|
|
321
|
+
> [`docs/checkpoints/segmented-button-m3.md`](docs/checkpoints/segmented-button-m3.md).
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
const [range, setRange] = useState<'day' | 'week' | 'month'>('week');
|
|
325
|
+
|
|
326
|
+
<SegmentedButton
|
|
327
|
+
value={range}
|
|
328
|
+
onChange={setRange}
|
|
329
|
+
segments={[
|
|
330
|
+
{ value: 'day', label: 'Day' },
|
|
331
|
+
{ value: 'week', label: 'Week' },
|
|
332
|
+
{ value: 'month', label: 'Month' },
|
|
333
|
+
]}
|
|
334
|
+
/>;
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Layout — `Scaffold`
|
|
338
|
+
|
|
339
|
+
Compound component for page structure.
|
|
340
|
+
|
|
341
|
+
| Piece | Props | Purpose |
|
|
342
|
+
|---|---|---|
|
|
343
|
+
| `Scaffold.Body` | `children`, `surface?: boolean` | full-screen container; `surface` uses the elevated background |
|
|
344
|
+
| `Scaffold.View` | `children`, `paddingHorizontal?`, `paddingTop?` | non-scrolling region with page insets |
|
|
345
|
+
| `Scaffold.ScrollView` | `spaceTopBar`, `spaceBottomBar`, `children`, `paddingHorizontal?`, `paddingTop?`, `paddingBottom?`, `onScroll?` | scrolling region (reanimated) |
|
|
346
|
+
| `Scaffold.FlashList<T>` | `spaceTopBar`, `spaceBottomBar`, all `@shopify/flash-list` props + `paddingHorizontal?`, `paddingTop?`, `paddingBottom?`, `onScroll?`, `drawDistance?` | virtualized list |
|
|
347
|
+
|
|
348
|
+
`spaceTopBar` (`'large' | 'small' | 'none'`) and `spaceBottomBar` (`boolean`) are **required** on
|
|
349
|
+
`Scaffold.ScrollView` and `Scaffold.FlashList` — they reserve the top/bottom insets for the
|
|
350
|
+
screen's fixed bars so you never pass `APP_BAR_HEIGHT` / `useTabBarHeight()` by hand:
|
|
351
|
+
|
|
352
|
+
| `spaceTopBar` | top inset |
|
|
353
|
+
|---|---|
|
|
354
|
+
| `'large'` | `APP_BAR_LARGE_HEIGHT` — for `<AppBar large />` |
|
|
355
|
+
| `'small'` | `APP_BAR_HEIGHT` — for a fixed `<AppBar />` / `<SearchBar />` |
|
|
356
|
+
| `'none'` | `pageSpacing.top` |
|
|
357
|
+
|
|
358
|
+
`spaceBottomBar={true}` adds the `TabBar` height (incl. bottom safe-area) to the bottom inset;
|
|
359
|
+
`false` keeps just `pageSpacing.bottom` + safe-area. The numeric `paddingTop` / `paddingBottom` props
|
|
360
|
+
still win when passed. `onScroll` takes a reanimated scroll handler (see [`useAppBarScroll`](#hooks)).
|
|
361
|
+
|
|
362
|
+
`Scaffold.View` is non-scrolling — its content never slides under the fixed bars — so it takes
|
|
363
|
+
neither prop; pass an explicit `paddingTop` if a bar covers it.
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
<Scaffold.Body>
|
|
367
|
+
<AppBar title="Collection" large scrollY={scrollY} />
|
|
368
|
+
<Scaffold.FlashList
|
|
369
|
+
spaceTopBar="large"
|
|
370
|
+
spaceBottomBar
|
|
371
|
+
data={cards}
|
|
372
|
+
keyExtractor={(c) => c.id}
|
|
373
|
+
renderItem={({ item }) => <Row card={item} />}
|
|
374
|
+
onScroll={scrollHandler}
|
|
375
|
+
/>
|
|
376
|
+
</Scaffold.Body>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Fixed bars — `AppBar`, `TabBar`, `SearchBar`
|
|
380
|
+
|
|
381
|
+
All three are `position: absolute` — render them as siblings of your scrollable content inside a
|
|
382
|
+
`Scaffold.Body`. The content region reserves the matching inset via `spaceTopBar` / `spaceBottomBar`
|
|
383
|
+
(see above); the height constants below are exported for manual layouts.
|
|
384
|
+
|
|
385
|
+
#### `AppBar`
|
|
386
|
+
|
|
387
|
+
| Prop | Type | Notes |
|
|
388
|
+
|---|---|---|
|
|
389
|
+
| `title` | `string` | — |
|
|
390
|
+
| `enabledBack` | `boolean` | shows the back control |
|
|
391
|
+
| `onBack` | `() => void` | called by the back control |
|
|
392
|
+
| `large` | `boolean` | large title that collapses on scroll |
|
|
393
|
+
| `trailing` | `AppBarTrailingType` | right-side action |
|
|
394
|
+
| `scrollY` | `SharedValue<number>` | drives the large-title collapse |
|
|
395
|
+
|
|
396
|
+
`AppBarTrailingType = { label?: string; icon?: IconNameType; tintColor?: ColorValue; hidden?: boolean; onPress: () => void }`
|
|
397
|
+
|
|
398
|
+
Exports: `APP_BAR_HEIGHT`, `APP_BAR_LARGE_HEIGHT` (computed from the **default** `pageSpacing`).
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
const { scrollY, scrollHandler } = useAppBarScroll();
|
|
402
|
+
|
|
403
|
+
<AppBar
|
|
404
|
+
title="Settings"
|
|
405
|
+
enabledBack
|
|
406
|
+
onBack={router.back}
|
|
407
|
+
trailing={{ icon: 'check', onPress: save }}
|
|
408
|
+
scrollY={scrollY}
|
|
409
|
+
/>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### `TabBar`
|
|
413
|
+
|
|
414
|
+
Data-driven — it knows nothing about routing.
|
|
415
|
+
|
|
416
|
+
| Prop | Type | Default |
|
|
417
|
+
|---|---|---|
|
|
418
|
+
| `items` | `TabItemType[]` | — |
|
|
419
|
+
| `visible` | `boolean` | `true` (fades out + disables touches when `false`) |
|
|
420
|
+
|
|
421
|
+
`TabItemType = { key: string; icon: IconNameType; label: string; active: boolean; onPress: () => void }`
|
|
422
|
+
|
|
423
|
+
Export: `useTabBarHeight()` — the height a screen should reserve at the bottom.
|
|
424
|
+
|
|
425
|
+
```tsx
|
|
426
|
+
<TabBar
|
|
427
|
+
items={[
|
|
428
|
+
{ key: 'home', icon: 'folder', label: 'Home', active: route === 'home', onPress: () => go('home') },
|
|
429
|
+
{ key: 'search', icon: 'search', label: 'Search', active: route === 'search', onPress: () => go('search') },
|
|
430
|
+
]}
|
|
431
|
+
visible={!keyboardOpen}
|
|
432
|
+
/>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### `SearchBar`
|
|
436
|
+
|
|
437
|
+
| Prop | Type | Notes |
|
|
438
|
+
|---|---|---|
|
|
439
|
+
| `placeholder` | `string` | — |
|
|
440
|
+
| `onEnter` | `(query: string) => void` | fired with the trimmed query on submit |
|
|
441
|
+
| `onBack` | `() => void` | back control |
|
|
442
|
+
| `editable` | `boolean` | — |
|
|
443
|
+
| `autoFocus` | `boolean` | — |
|
|
444
|
+
|
|
445
|
+
Export: `SEARCH_BAR_HEIGHT`.
|
|
446
|
+
|
|
447
|
+
### Lists — `InforList`
|
|
448
|
+
|
|
449
|
+
#### `InforList`
|
|
450
|
+
|
|
451
|
+
Grouped list with dividers between rows. Compound.
|
|
452
|
+
|
|
453
|
+
| Piece | Props |
|
|
454
|
+
|---|---|
|
|
455
|
+
| `InforList.Container` | `children`, `marginT?`, `marginB?` |
|
|
456
|
+
| `InforList.Content` | `children`, `onPress?` (pressable when provided) |
|
|
457
|
+
| `InforList.Title` | `children`, `color?` |
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
<InforList.Container>
|
|
461
|
+
<InforList.Content onPress={openProfile}>
|
|
462
|
+
<InforList.Title>Name</InforList.Title>
|
|
463
|
+
<InforList.Title color={colors.primary}>Ada</InforList.Title>
|
|
464
|
+
</InforList.Content>
|
|
465
|
+
<InforList.Content>
|
|
466
|
+
<InforList.Title>Role</InforList.Title>
|
|
467
|
+
<InforList.Title>Engineer</InforList.Title>
|
|
468
|
+
</InforList.Content>
|
|
469
|
+
</InforList.Container>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Overlays — `Overlays`, `SlideModal`, `Toast`, `LoadingApp`
|
|
473
|
+
|
|
474
|
+
#### `Overlays`
|
|
475
|
+
|
|
476
|
+
The app-wide overlays (`Toast` + `LoadingApp`) in one component. **Mount once at the root**,
|
|
477
|
+
inside `ThemeProvider`, as a sibling of your navigator — they render above everything and are
|
|
478
|
+
driven from anywhere via `useToast` / `useLoadingApp`.
|
|
479
|
+
|
|
480
|
+
| Prop | Type | Notes |
|
|
481
|
+
|---|---|---|
|
|
482
|
+
| `loadingLabel` | `string` | forwarded to `LoadingApp`'s `label` |
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
<ThemeProvider colors={colors}>
|
|
486
|
+
<RootNavigator />
|
|
487
|
+
<Overlays loadingLabel="Loading…" />
|
|
488
|
+
</ThemeProvider>
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Use `<Toast />` / `<LoadingApp />` directly only if you need them mounted separately.
|
|
492
|
+
|
|
493
|
+
#### `SlideModal`
|
|
494
|
+
|
|
495
|
+
Bottom sheet, controlled through a `ref`.
|
|
496
|
+
|
|
497
|
+
| Prop | Type | Default |
|
|
498
|
+
|---|---|---|
|
|
499
|
+
| `children` | `ReactNode` | — |
|
|
500
|
+
| `title` | `string` | — (required) |
|
|
501
|
+
| `isLoading` | `boolean` | `false` (swaps content for a spinner, blocks drag-to-dismiss) |
|
|
502
|
+
|
|
503
|
+
`ref` exposes `SlideModalRef = { show: () => void; hide: () => void }`.
|
|
504
|
+
|
|
505
|
+
```tsx
|
|
506
|
+
const modal = useRef<SlideModalRef>(null);
|
|
507
|
+
|
|
508
|
+
<ShapedButton onPress={() => modal.current?.show()}>Open</ShapedButton>
|
|
509
|
+
<SlideModal ref={modal} title="Edit">
|
|
510
|
+
<EditForm onDone={() => modal.current?.hide()} />
|
|
511
|
+
</SlideModal>
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
#### `Toast`
|
|
515
|
+
|
|
516
|
+
Transient centered notice, auto-hides after ~3s. Mounted via [`Overlays`](#overlays) (or on its
|
|
517
|
+
own once at the root); triggered from anywhere via [`useToast`](#global-state).
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
useToast().showToast({ message: 'Saved', variant: 'success' });
|
|
521
|
+
// variant: 'error' | 'success' | 'info' (default 'info')
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
#### `LoadingApp`
|
|
525
|
+
|
|
526
|
+
Blocking full-screen loading overlay. Mounted via [`Overlays`](#overlays) (or on its own once at
|
|
527
|
+
the root); driven from anywhere via [`useLoadingApp`](#global-state).
|
|
528
|
+
|
|
529
|
+
| Prop | Type | Notes |
|
|
530
|
+
|---|---|---|
|
|
531
|
+
| `label` | `string` | optional caption under the spinner |
|
|
532
|
+
|
|
533
|
+
```tsx
|
|
534
|
+
useLoadingApp().setLoadingVisible(true);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Touch effects — `BounceEffect`, `RippleEffect`
|
|
538
|
+
|
|
539
|
+
#### `BounceEffect`
|
|
540
|
+
|
|
541
|
+
Wraps a child and plays a scale "bounce" on press, firing the callback on release.
|
|
542
|
+
|
|
543
|
+
| Prop | Type |
|
|
544
|
+
|---|---|
|
|
545
|
+
| `onActionTrigger` | `() => void` |
|
|
546
|
+
| `children` | `ReactNode` |
|
|
547
|
+
| `style` | `PressableProps['style']` |
|
|
548
|
+
|
|
549
|
+
#### `RippleEffect`
|
|
550
|
+
|
|
551
|
+
Wraps a child and paints a touch ripple at the press point. Accepts all `Pressable` props plus:
|
|
552
|
+
|
|
553
|
+
| Prop | Type | Default |
|
|
554
|
+
|---|---|---|
|
|
555
|
+
| `rippleColor` | `string` | theme `text` |
|
|
556
|
+
| `rippleOpacity` | `number` | `0.3` |
|
|
557
|
+
| `rippleDuration` | `number` | `400` |
|
|
558
|
+
| `rippleSize` | `number` | `0` (auto) |
|
|
559
|
+
| `rippleContainerBorderRadius` | `number` | `0` |
|
|
560
|
+
| `rippleCentered` | `boolean` | `false` |
|
|
561
|
+
| `rippleSequential` | `boolean` | `false` |
|
|
562
|
+
| `rippleFades` | `boolean` | `true` |
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## Icons
|
|
567
|
+
|
|
568
|
+
`Icon` renders a curated set of UI glyphs via `expo-symbols` (`SymbolView`).
|
|
569
|
+
|
|
570
|
+
| Prop | Type | Default |
|
|
571
|
+
|---|---|---|
|
|
572
|
+
| `name` | `IconNameType` | — (required) |
|
|
573
|
+
| `color` | `string` | theme `primary` |
|
|
574
|
+
| `size` | `number` (dp) | `iconSize.m` |
|
|
575
|
+
|
|
576
|
+
```tsx
|
|
577
|
+
import { Icon, iconSize } from '@cyberkaidev/ui';
|
|
578
|
+
|
|
579
|
+
<Icon name="search" />
|
|
580
|
+
<Icon name="check" color="#00ff2a" size={iconSize.l} />
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**Available names:** `arrow_back`, `search`, `close`, `add`, `remove`, `edit`, `check`, `error`,
|
|
584
|
+
`filter`, `settings`, `folder`, `question`.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Hooks
|
|
589
|
+
|
|
590
|
+
### `useAppBarScroll()`
|
|
591
|
+
|
|
592
|
+
Wires a collapsing `AppBar` to a scroll region.
|
|
593
|
+
|
|
594
|
+
```tsx
|
|
595
|
+
const { scrollY, scrollHandler } = useAppBarScroll();
|
|
596
|
+
|
|
597
|
+
<Scaffold.Body>
|
|
598
|
+
<AppBar title="Feed" large scrollY={scrollY} />
|
|
599
|
+
<Scaffold.ScrollView spaceTopBar="large" spaceBottomBar onScroll={scrollHandler}>
|
|
600
|
+
{/* … */}
|
|
601
|
+
</Scaffold.ScrollView>
|
|
602
|
+
</Scaffold.Body>;
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Returns `{ scrollY: SharedValue<number>, scrollHandler }`.
|
|
606
|
+
|
|
607
|
+
### `usePressAndHoldRepeat({ onTrigger, disabled? })`
|
|
608
|
+
|
|
609
|
+
Press-and-hold with acceleration. Fires `onTrigger` once on press, then repeatedly (speeding up)
|
|
610
|
+
while held. Clamping is the caller's job.
|
|
611
|
+
|
|
612
|
+
```tsx
|
|
613
|
+
const { onPressIn, onPressOut } = usePressAndHoldRepeat({
|
|
614
|
+
onTrigger: () => setCount((c) => Math.min(c + 1, max)),
|
|
615
|
+
disabled: count >= max,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
<Button onPressIn={onPressIn} onPressOut={onPressOut}>
|
|
619
|
+
<Icon name="add" />
|
|
620
|
+
</Button>;
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Global state
|
|
626
|
+
|
|
627
|
+
Two [zustand](https://zustand.docs.pmnd.rs/) stores. Use the hook inside components, or
|
|
628
|
+
`.getState()` anywhere.
|
|
629
|
+
|
|
630
|
+
### `useToast`
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
useToast(): {
|
|
634
|
+
visible: boolean;
|
|
635
|
+
variant: 'error' | 'success' | 'info';
|
|
636
|
+
message: string;
|
|
637
|
+
showToast: (args: { message: string; variant?: 'error' | 'success' | 'info' }) => void;
|
|
638
|
+
hideToast: () => void;
|
|
639
|
+
};
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### `useLoadingApp`
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
useLoadingApp(): {
|
|
646
|
+
visible: boolean;
|
|
647
|
+
setLoadingVisible: (visible: boolean) => void;
|
|
648
|
+
};
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
```ts
|
|
652
|
+
// outside React (e.g. an API layer)
|
|
653
|
+
import { useLoadingApp } from '@cyberkaidev/ui';
|
|
654
|
+
|
|
655
|
+
async function withLoading<T>(fn: () => Promise<T>) {
|
|
656
|
+
useLoadingApp.getState().setLoadingVisible(true);
|
|
657
|
+
try {
|
|
658
|
+
return await fn();
|
|
659
|
+
} finally {
|
|
660
|
+
useLoadingApp.getState().setLoadingVisible(false);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## Responsive helpers
|
|
668
|
+
|
|
669
|
+
```ts
|
|
670
|
+
import { widthDP, heightDP } from '@cyberkaidev/ui';
|
|
671
|
+
|
|
672
|
+
widthDP(50); // 50% of screen width, in dp, rounded to the nearest pixel
|
|
673
|
+
heightDP(4.3); // 4.3% of screen height
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
The theme's spacing / font-size / icon-size scales are built from these. Screen size is read once
|
|
677
|
+
at module load — values don't react to rotation.
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## Exported types
|
|
682
|
+
|
|
683
|
+
| Type | Shape |
|
|
684
|
+
|---|---|
|
|
685
|
+
| `FontSizeType` | `'xs' \| 's' \| 'm' \| 'l' \| 'xl' \| 'xxl' \| 'xxxl'` |
|
|
686
|
+
| `IconSizeType` | `'s' \| 'm' \| 'l' \| 'xl'` |
|
|
687
|
+
| `FontWeightType` | `'regular' \| 'medium' \| 'bold'` |
|
|
688
|
+
| `TextAlignType` | `'left' \| 'center' \| 'right'` |
|
|
689
|
+
| `ToastVariantType` | `'error' \| 'success' \| 'info'` |
|
|
690
|
+
| `ColorsType` | the 12-key palette shape |
|
|
691
|
+
| `ThemeType` | `{ colors, fontSizes, iconSize, spaces, borderRadius, pageSpacing }` |
|
|
692
|
+
| `FontSizesType` / `IconSizesType` / `SpacesType` / `SpaceScaleType` / `BorderRadiusType` / `PageSpacingType` | individual token-group shapes |
|
|
693
|
+
| `ThemeTokensType` | `ThemeType` without `colors` (the shape of `defaultTokens`) |
|
|
694
|
+
| `IconNameType` | union of the icon names above |
|
|
695
|
+
| `SlideModalRef` | `{ show(): void; hide(): void }` |
|
|
696
|
+
| `TabItemType` | tab-bar item shape |
|
|
697
|
+
| `AppBarTrailingType` | app-bar trailing action shape |
|
|
698
|
+
| `SpaceTopBarType` | `'large' \| 'small' \| 'none'` — `Scaffold` `spaceTopBar` |
|
|
699
|
+
| `SegmentedButtonItemType<Value>` | segmented-button segment shape |
|
|
700
|
+
| `SegmentedButtonSizeType` | `'s' \| 'm' \| 'l'` — `SegmentedButton` `size` |
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## TypeScript & config notes
|
|
705
|
+
|
|
706
|
+
- Ships ESM + `.d.ts` (built with `react-native-builder-bob`); single entry point
|
|
707
|
+
`@cyberkaidev/ui`.
|
|
708
|
+
- No `moduleSuffixes` needed in the consumer's `tsconfig` — `Button` is a single file with a
|
|
709
|
+
`Platform.OS` branch.
|
|
710
|
+
- `Text` referencing `Inter-*` is a soft dependency: unregistered fonts fall back to the system
|
|
711
|
+
font, no crash.
|
|
712
|
+
|
|
713
|
+
## Contributing
|
|
714
|
+
|
|
715
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
716
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
717
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
718
|
+
|
|
719
|
+
## License
|
|
720
|
+
|
|
721
|
+
MIT
|