@muja-ui/native 0.2.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.
@@ -0,0 +1,1039 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_native from 'react-native';
4
+ import { FlexStyle, ViewStyle, ViewProps, StyleProp, View, TextStyle, TextProps as TextProps$1, Text as Text$1, PressableProps, TextInputProps, TextInput, DimensionValue } from 'react-native';
5
+ import { Theme, IconDefinition, Variant, Size, ColorMode, ResolvedColorMode } from '@muja-ui/core';
6
+ export { ColorMode, IconDefinition, ResolvedColorMode, Size, Status, Theme, ThemeOverride, Variant, createTheme, darkTheme, lightTheme, registerIcons } from '@muja-ui/core';
7
+ import { SpaceToken, SemanticColorToken, BorderWidthToken, RadiusToken, ShadowToken, ZIndexToken, ShadowValue, FontSizeToken, FontWeightToken, FontFamilyToken, LineHeightToken, LetterSpacingToken } from '@muja-ui/tokens';
8
+
9
+ /**
10
+ * Token-bound style props — the same names the web package uses, so a screen
11
+ * reads the same on both platforms. Values resolve against the active theme at
12
+ * render time (React Native has no CSS variables).
13
+ */
14
+ interface StyleProps {
15
+ /** margin */
16
+ m?: SpaceToken;
17
+ mt?: SpaceToken;
18
+ mr?: SpaceToken;
19
+ mb?: SpaceToken;
20
+ ml?: SpaceToken;
21
+ mx?: SpaceToken;
22
+ my?: SpaceToken;
23
+ /** padding */
24
+ p?: SpaceToken;
25
+ pt?: SpaceToken;
26
+ pr?: SpaceToken;
27
+ pb?: SpaceToken;
28
+ pl?: SpaceToken;
29
+ px?: SpaceToken;
30
+ py?: SpaceToken;
31
+ /** colors */
32
+ bg?: SemanticColorToken;
33
+ borderColor?: SemanticColorToken;
34
+ /** borders & effects */
35
+ borderWidth?: BorderWidthToken;
36
+ borderTopWidth?: BorderWidthToken;
37
+ borderBottomWidth?: BorderWidthToken;
38
+ radius?: RadiusToken;
39
+ shadow?: ShadowToken;
40
+ opacity?: number;
41
+ zIndex?: ZIndexToken;
42
+ /** sizing — numbers are density-independent pixels, strings pass through ('100%') */
43
+ w?: FlexStyle['width'];
44
+ h?: FlexStyle['height'];
45
+ minW?: FlexStyle['minWidth'];
46
+ maxW?: FlexStyle['maxWidth'];
47
+ minH?: FlexStyle['minHeight'];
48
+ maxH?: FlexStyle['maxHeight'];
49
+ /** layout */
50
+ flex?: number;
51
+ alignSelf?: FlexStyle['alignSelf'];
52
+ position?: FlexStyle['position'];
53
+ top?: FlexStyle['top'];
54
+ right?: FlexStyle['right'];
55
+ bottom?: FlexStyle['bottom'];
56
+ left?: FlexStyle['left'];
57
+ overflow?: ViewStyle['overflow'];
58
+ }
59
+ /**
60
+ * Translates a token shadow into React Native's two shadow models: iOS reads
61
+ * the offset/opacity/radius quartet, Android only has `elevation`.
62
+ */
63
+ declare function shadowStyle(value: ShadowValue): ViewStyle;
64
+ /**
65
+ * Splits an incoming prop object into a resolved React Native style and the
66
+ * remaining component props.
67
+ */
68
+ declare function splitStyleProps<P extends Record<string, unknown>>(props: P, theme: Theme): {
69
+ style: ViewStyle;
70
+ rest: Omit<P, keyof StyleProps>;
71
+ };
72
+
73
+ type BoxProps = StyleProps & Omit<ViewProps, 'style'> & {
74
+ style?: StyleProp<ViewStyle>;
75
+ };
76
+ /**
77
+ * The base layout primitive — a `View` with token-bound style props. Same prop
78
+ * names as `@muja-ui/web`'s Box, so layout code ports between platforms.
79
+ *
80
+ * ```tsx
81
+ * <Box p={6} bg="surface" radius="lg" shadow="sm" />
82
+ * ```
83
+ */
84
+ declare const Box: react.ForwardRefExoticComponent<StyleProps & Omit<ViewProps, "style"> & {
85
+ style?: StyleProp<ViewStyle>;
86
+ } & react.RefAttributes<View>>;
87
+
88
+ interface FlexOwnProps {
89
+ direction?: FlexStyle['flexDirection'];
90
+ align?: FlexStyle['alignItems'];
91
+ justify?: FlexStyle['justifyContent'];
92
+ wrap?: FlexStyle['flexWrap'];
93
+ gap?: SpaceToken;
94
+ rowGap?: SpaceToken;
95
+ columnGap?: SpaceToken;
96
+ }
97
+ type FlexProps = FlexOwnProps & BoxProps;
98
+ /** Flexbox container with token-bound `gap`. */
99
+ declare const Flex: react.ForwardRefExoticComponent<FlexOwnProps & StyleProps & Omit<react_native.ViewProps, "style"> & {
100
+ style?: react_native.StyleProp<react_native.ViewStyle>;
101
+ } & react.RefAttributes<View>>;
102
+
103
+ type StackProps = FlexProps;
104
+ /** Vertical flex layout with a default gap. Use `direction="row"` for rows. */
105
+ declare const Stack: react.ForwardRefExoticComponent<FlexOwnProps & StyleProps & Omit<react_native.ViewProps, "style"> & {
106
+ style?: react_native.StyleProp<react_native.ViewStyle>;
107
+ } & react.RefAttributes<View>>;
108
+ /** Horizontal convenience wrapper around `Stack`. */
109
+ declare const HStack: react.ForwardRefExoticComponent<FlexOwnProps & StyleProps & Omit<react_native.ViewProps, "style"> & {
110
+ style?: react_native.StyleProp<react_native.ViewStyle>;
111
+ } & react.RefAttributes<View>>;
112
+
113
+ interface SpacerProps {
114
+ /** Fixed size along the parent's main axis. Omit to grow and fill. */
115
+ size?: SpaceToken;
116
+ axis?: 'horizontal' | 'vertical';
117
+ }
118
+ /** Flexible or fixed empty space. Without `size` it expands to fill. */
119
+ declare function Spacer({ size, axis }: SpacerProps): react.JSX.Element;
120
+
121
+ interface DividerProps {
122
+ orientation?: 'horizontal' | 'vertical';
123
+ color?: SemanticColorToken;
124
+ style?: StyleProp<ViewStyle>;
125
+ }
126
+ /** A one-hairline rule. Decorative — hidden from screen readers. */
127
+ declare function Divider({ orientation, color, style }: DividerProps): react.JSX.Element;
128
+
129
+ interface TextOwnProps {
130
+ size?: FontSizeToken;
131
+ weight?: FontWeightToken;
132
+ family?: FontFamilyToken;
133
+ leading?: LineHeightToken;
134
+ tracking?: LetterSpacingToken;
135
+ align?: TextStyle['textAlign'];
136
+ /** Semantic color token. Defaults to the theme's body text color. */
137
+ color?: SemanticColorToken;
138
+ /** Clips overflowing text to a single line with an ellipsis. */
139
+ truncate?: boolean;
140
+ }
141
+ type TextProps = TextOwnProps & StyleProps & Omit<TextProps$1, 'style'> & {
142
+ style?: StyleProp<TextStyle>;
143
+ };
144
+ /**
145
+ * Typography primitive. Color always comes from a semantic token, never a raw
146
+ * value. React Native needs an absolute `lineHeight`, so the unitless token
147
+ * multiplier is resolved against the font size here.
148
+ *
149
+ * ```tsx
150
+ * <Text size="sm" color="textSecondary" truncate>…</Text>
151
+ * ```
152
+ */
153
+ declare const Text: react.ForwardRefExoticComponent<TextOwnProps & StyleProps & Omit<TextProps$1, "style"> & {
154
+ style?: StyleProp<TextStyle>;
155
+ } & react.RefAttributes<Text$1>>;
156
+
157
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
158
+ interface HeadingProps extends Omit<TextProps, 'accessibilityRole'> {
159
+ /** Semantic heading level — sets the screen-reader heading role and default size. */
160
+ level?: HeadingLevel;
161
+ }
162
+ /**
163
+ * A heading with the correct accessibility role. Sizes follow the level unless
164
+ * `size` overrides them.
165
+ *
166
+ * ```tsx
167
+ * <Heading level={2}>Upcoming events</Heading>
168
+ * ```
169
+ */
170
+ declare const Heading: react.ForwardRefExoticComponent<HeadingProps & react.RefAttributes<Text$1>>;
171
+
172
+ interface IconProps {
173
+ /** An icon definition (preferred, tree-shakable) or a registered icon name. */
174
+ icon: IconDefinition | string;
175
+ size?: number;
176
+ /** Semantic color token. Defaults to the current text color. */
177
+ color?: SemanticColorToken;
178
+ /** Accessible label. Without it the icon is treated as decorative. */
179
+ label?: string;
180
+ strokeWidth?: number;
181
+ style?: StyleProp<ViewStyle>;
182
+ }
183
+ /**
184
+ * Renders a stroke-style icon from `@muja-ui/icons` through react-native-svg,
185
+ * from the same platform-neutral path data the web package uses.
186
+ *
187
+ * ```tsx
188
+ * <Icon icon={CheckIcon} color="success" />
189
+ * ```
190
+ */
191
+ declare function Icon({ icon, size, color, label, strokeWidth, style, }: IconProps): react.JSX.Element | null;
192
+
193
+ interface ButtonProps extends Omit<PressableProps, 'style' | 'children'> {
194
+ variant?: Variant;
195
+ size?: Size;
196
+ /** Shows a spinner, marks the control busy and blocks presses. */
197
+ loading?: boolean;
198
+ fullWidth?: boolean;
199
+ /** Decorative icon before the label (replaced by the spinner while loading). */
200
+ leftIcon?: ReactNode;
201
+ /** Decorative icon after the label. */
202
+ rightIcon?: ReactNode;
203
+ children?: ReactNode;
204
+ style?: StyleProp<ViewStyle>;
205
+ }
206
+ /**
207
+ * Pressable button. Same variant/size vocabulary as `@muja-ui/web`'s Button;
208
+ * every color resolves from a semantic theme token.
209
+ *
210
+ * ```tsx
211
+ * <Button variant="primary" size="lg" loading onPress={save}>Save</Button>
212
+ * ```
213
+ */
214
+ declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<View>>;
215
+
216
+ interface IconButtonProps extends Omit<PressableProps, 'style' | 'children'> {
217
+ /** Required: an icon-only control has no visible label. */
218
+ accessibilityLabel: string;
219
+ icon: ReactNode;
220
+ variant?: Variant;
221
+ size?: Size;
222
+ loading?: boolean;
223
+ /** Fully rounded instead of the default `md` radius. */
224
+ round?: boolean;
225
+ style?: StyleProp<ViewStyle>;
226
+ }
227
+ /**
228
+ * A square, icon-only button. `accessibilityLabel` is mandatory — there is no
229
+ * text for a screen reader to fall back on.
230
+ *
231
+ * ```tsx
232
+ * <IconButton icon={<Icon icon={XIcon} />} accessibilityLabel="Close" variant="ghost" />
233
+ * ```
234
+ */
235
+ declare const IconButton: react.ForwardRefExoticComponent<IconButtonProps & react.RefAttributes<View>>;
236
+
237
+ interface InputProps extends Omit<TextInputProps, 'style' | 'editable'> {
238
+ size?: Size;
239
+ /** Marks the field invalid: danger border and `aria-invalid` for a11y. */
240
+ invalid?: boolean;
241
+ disabled?: boolean;
242
+ /** Adornment rendered inside the field, before the text. */
243
+ leftElement?: ReactNode;
244
+ /** Adornment rendered inside the field, after the text. */
245
+ rightElement?: ReactNode;
246
+ style?: StyleProp<ViewStyle>;
247
+ }
248
+ /**
249
+ * Single-line text field. Focus and invalid states are drawn on the wrapper so
250
+ * adornments sit inside the border.
251
+ *
252
+ * ```tsx
253
+ * <Input size="md" placeholder="Email" invalid={!!error} />
254
+ * ```
255
+ */
256
+ declare const Input: react.ForwardRefExoticComponent<InputProps & react.RefAttributes<TextInput>>;
257
+
258
+ interface TextareaProps extends Omit<TextInputProps, 'style' | 'editable' | 'multiline'> {
259
+ invalid?: boolean;
260
+ disabled?: boolean;
261
+ /** Visible rows at rest (the field grows with content beyond this). */
262
+ rows?: number;
263
+ style?: StyleProp<TextStyle>;
264
+ }
265
+ /** Multi-line text field. Same states as `Input`. */
266
+ declare const Textarea: react.ForwardRefExoticComponent<TextareaProps & react.RefAttributes<TextInput>>;
267
+
268
+ interface LabelProps extends TextProps {
269
+ /** Appends a danger-colored asterisk. */
270
+ required?: boolean;
271
+ }
272
+ /** Field label. Pair with `FormField` to also render help and error text. */
273
+ declare const Label: react.ForwardRefExoticComponent<LabelProps & react.RefAttributes<Text$1>>;
274
+
275
+ interface FormFieldProps {
276
+ label?: string;
277
+ required?: boolean;
278
+ /** Hint shown under the control while it is valid. */
279
+ help?: string;
280
+ /** Replaces `help` and marks the row invalid. */
281
+ error?: string;
282
+ children: ReactNode;
283
+ style?: StyleProp<ViewStyle>;
284
+ }
285
+ /**
286
+ * Label + control + help/error row. Keeps every form in the app spacing the
287
+ * three parts identically, and makes the error text a live region so screen
288
+ * readers announce validation failures.
289
+ *
290
+ * ```tsx
291
+ * <FormField label="Email" required error={errors.email?.message}>
292
+ * <Input value={email} onChangeText={setEmail} invalid={!!errors.email} />
293
+ * </FormField>
294
+ * ```
295
+ */
296
+ declare function FormField({ label, required, help, error, children, style, }: FormFieldProps): react.JSX.Element;
297
+
298
+ interface CheckboxProps {
299
+ /** Controlled state. Omit for uncontrolled use with `defaultChecked`. */
300
+ checked?: boolean;
301
+ defaultChecked?: boolean;
302
+ /**
303
+ * Fires with the next state. React Native has no form submission, so unlike
304
+ * the web package this is a value callback rather than a change event.
305
+ */
306
+ onChange?: (checked: boolean) => void;
307
+ indeterminate?: boolean;
308
+ size?: Size;
309
+ invalid?: boolean;
310
+ disabled?: boolean;
311
+ /** Label rendered next to the box; the whole row is the touch target. */
312
+ children?: ReactNode;
313
+ style?: StyleProp<ViewStyle>;
314
+ }
315
+ /**
316
+ * Checkbox with an optional inline label.
317
+ *
318
+ * ```tsx
319
+ * <Checkbox checked={agreed} onChange={setAgreed}>I agree</Checkbox>
320
+ * ```
321
+ */
322
+ declare function Checkbox({ checked: controlledChecked, defaultChecked, onChange, indeterminate, size, invalid, disabled, children, style, }: CheckboxProps): react.JSX.Element;
323
+
324
+ interface SwitchProps {
325
+ checked?: boolean;
326
+ defaultChecked?: boolean;
327
+ onChange?: (checked: boolean) => void;
328
+ size?: Size;
329
+ disabled?: boolean;
330
+ children?: ReactNode;
331
+ style?: StyleProp<ViewStyle>;
332
+ }
333
+ /**
334
+ * Toggle switch. Built from primitives rather than RN's `Switch` so it follows
335
+ * the theme on both platforms (RN's own switch only takes raw colors and
336
+ * renders with platform-specific metrics).
337
+ *
338
+ * ```tsx
339
+ * <Switch checked={enabled} onChange={setEnabled}>Notifications</Switch>
340
+ * ```
341
+ */
342
+ declare function Switch({ checked: controlledChecked, defaultChecked, onChange, size, disabled, children, style, }: SwitchProps): react.JSX.Element;
343
+
344
+ interface RadioGroupProps {
345
+ value?: string;
346
+ defaultValue?: string;
347
+ onChange?: (value: string) => void;
348
+ size?: Size;
349
+ disabled?: boolean;
350
+ orientation?: 'vertical' | 'horizontal';
351
+ children?: ReactNode;
352
+ style?: StyleProp<ViewStyle>;
353
+ accessibilityLabel?: string;
354
+ }
355
+ /**
356
+ * Single-choice group. Wrap `Radio` children; selection state lives here.
357
+ *
358
+ * ```tsx
359
+ * <RadioGroup value={role} onChange={setRole} accessibilityLabel="Role">
360
+ * <Radio value="student">Student</Radio>
361
+ * <Radio value="organizer">Organizer</Radio>
362
+ * </RadioGroup>
363
+ * ```
364
+ */
365
+ declare function RadioGroup({ value: controlledValue, defaultValue, onChange, size, disabled, orientation, children, style, accessibilityLabel, }: RadioGroupProps): react.JSX.Element;
366
+ interface RadioProps {
367
+ value: string;
368
+ disabled?: boolean;
369
+ children?: ReactNode;
370
+ style?: StyleProp<ViewStyle>;
371
+ }
372
+ /** One option inside a `RadioGroup`. */
373
+ declare function Radio({ value, disabled, children, style }: RadioProps): react.JSX.Element | null;
374
+
375
+ interface SelectOption<T extends string = string> {
376
+ value: T;
377
+ label: string;
378
+ /** Secondary line under the label. */
379
+ description?: string;
380
+ disabled?: boolean;
381
+ }
382
+ interface SelectProps<T extends string = string> {
383
+ options: readonly SelectOption<T>[];
384
+ value?: T;
385
+ onChange?: (value: T) => void;
386
+ /** Shown when nothing is selected. */
387
+ placeholder?: string;
388
+ /** Sheet heading. */
389
+ title?: string;
390
+ size?: Size;
391
+ invalid?: boolean;
392
+ disabled?: boolean;
393
+ style?: StyleProp<ViewStyle>;
394
+ accessibilityLabel?: string;
395
+ }
396
+ /**
397
+ * Field that opens a bottom sheet of options — the native counterpart to the
398
+ * web package's `<select>`. Options are data, not `<option>` children, because
399
+ * React Native has no equivalent element.
400
+ *
401
+ * ```tsx
402
+ * <Select options={rooms} value={roomId} onChange={setRoomId} placeholder="Choose a room" />
403
+ * ```
404
+ */
405
+ declare function Select<T extends string = string>({ options, value, onChange, placeholder, title, size, invalid, disabled, style, accessibilityLabel, }: SelectProps<T>): react.JSX.Element;
406
+
407
+ interface SkeletonProps {
408
+ width?: DimensionValue;
409
+ height?: DimensionValue;
410
+ radius?: RadiusToken;
411
+ /** Turns off the pulse (useful in tests and for reduced-motion screens). */
412
+ animated?: boolean;
413
+ style?: StyleProp<ViewStyle>;
414
+ }
415
+ /**
416
+ * Loading placeholder with a pulsing opacity. Hidden from screen readers —
417
+ * announce loading state on the container instead.
418
+ *
419
+ * ```tsx
420
+ * <Skeleton height={20} width="60%" />
421
+ * ```
422
+ */
423
+ declare function Skeleton({ width, height, radius, animated, style, }: SkeletonProps): react.JSX.Element;
424
+
425
+ interface SpinnerProps {
426
+ size?: Size;
427
+ color?: SemanticColorToken;
428
+ /** Accessible label; announced while the spinner is on screen. */
429
+ label?: string;
430
+ style?: StyleProp<ViewStyle>;
431
+ }
432
+ /** Indeterminate activity indicator. */
433
+ declare function Spinner({ size, color, label, style }: SpinnerProps): react.JSX.Element;
434
+
435
+ interface ProgressProps {
436
+ /** Current value, 0–`max`. */
437
+ value: number;
438
+ max?: number;
439
+ /** Track thickness in dp. */
440
+ height?: number;
441
+ color?: SemanticColorToken;
442
+ trackColor?: SemanticColorToken;
443
+ /** Accessible name, e.g. "Semester progress". */
444
+ label?: string;
445
+ style?: StyleProp<ViewStyle>;
446
+ }
447
+ /**
448
+ * Determinate progress bar.
449
+ *
450
+ * ```tsx
451
+ * <Progress value={earned} max={total} label="iGPA points" />
452
+ * ```
453
+ */
454
+ declare function Progress({ value, max, height, color, trackColor, label, style, }: ProgressProps): react.JSX.Element;
455
+
456
+ type ToastTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info';
457
+ type ToastPlacement = 'top' | 'bottom';
458
+ interface ToastOptions {
459
+ title: string;
460
+ description?: string;
461
+ tone?: ToastTone;
462
+ /** Milliseconds before auto-dismiss. `0` keeps it until dismissed. */
463
+ duration?: number;
464
+ /** Trailing action, e.g. Undo. */
465
+ action?: {
466
+ label: string;
467
+ onPress: () => void;
468
+ };
469
+ }
470
+ interface ToastContextValue {
471
+ toast: (options: ToastOptions) => void;
472
+ dismiss: (id: number) => void;
473
+ }
474
+ interface ToastProviderProps {
475
+ children: ReactNode;
476
+ placement?: ToastPlacement;
477
+ /** Most toasts visible at once; older ones are dropped. */
478
+ max?: number;
479
+ }
480
+ /**
481
+ * Mounts the toast host and provides `useToast()`. Put it inside the
482
+ * `ThemeProvider` and `SafeAreaProvider`, above the navigator.
483
+ *
484
+ * ```tsx
485
+ * <ToastProvider>
486
+ * <Stack />
487
+ * </ToastProvider>
488
+ * ```
489
+ */
490
+ declare function ToastProvider({ children, placement, max }: ToastProviderProps): react.JSX.Element;
491
+ /** Queues toasts. Must be called under a `ToastProvider`. */
492
+ declare function useToast(): ToastContextValue;
493
+
494
+ interface EmptyStateProps {
495
+ title: string;
496
+ description?: string;
497
+ /** Illustration or icon above the title. */
498
+ icon?: ReactNode;
499
+ /** Primary action, e.g. a Button. */
500
+ action?: ReactNode;
501
+ style?: StyleProp<ViewStyle>;
502
+ }
503
+ /**
504
+ * Placeholder for an empty list or a failed-but-recoverable state. Consistent
505
+ * empty states are what stop a list screen from looking broken.
506
+ *
507
+ * ```tsx
508
+ * <EmptyState title="No tickets yet" description="Register for an event to get one." />
509
+ * ```
510
+ */
511
+ declare function EmptyState({ title, description, icon, action, style }: EmptyStateProps): react.JSX.Element;
512
+
513
+ type BadgeTone = 'neutral' | 'primary' | 'accent' | 'success' | 'warning' | 'danger' | 'info';
514
+ interface BadgeProps {
515
+ tone?: BadgeTone;
516
+ /** `subtle` = tinted background, `solid` = filled, `outline` = bordered. */
517
+ variant?: 'subtle' | 'solid' | 'outline';
518
+ children?: ReactNode;
519
+ /** Icon rendered before the label. */
520
+ leftIcon?: ReactNode;
521
+ style?: StyleProp<ViewStyle>;
522
+ }
523
+ /**
524
+ * Small status label. Tones map to the semantic status colors, so dark mode and
525
+ * brand themes apply automatically.
526
+ *
527
+ * ```tsx
528
+ * <Badge tone="success">Confirmed</Badge>
529
+ * ```
530
+ */
531
+ declare function Badge({ tone, variant, children, leftIcon, style, }: BadgeProps): react.JSX.Element;
532
+
533
+ interface ChipProps {
534
+ children?: ReactNode;
535
+ /** Filled/primary styling for an active filter. */
536
+ selected?: boolean;
537
+ disabled?: boolean;
538
+ onPress?: () => void;
539
+ /** Renders a trailing × that calls this instead of `onPress`. */
540
+ onRemove?: () => void;
541
+ leftIcon?: ReactNode;
542
+ style?: StyleProp<ViewStyle>;
543
+ }
544
+ /**
545
+ * Compact toggle used for filters and tags. Pressable when `onPress` or
546
+ * `onRemove` is given, static otherwise.
547
+ *
548
+ * ```tsx
549
+ * <Chip selected={filter === 'today'} onPress={() => setFilter('today')}>Today</Chip>
550
+ * ```
551
+ */
552
+ declare function Chip({ children, selected, disabled, onPress, onRemove, leftIcon, style, }: ChipProps): react.JSX.Element;
553
+
554
+ interface CardProps {
555
+ /** `outline` (default) is bordered, `elevated` adds a shadow, `filled` uses a muted background. */
556
+ variant?: 'outline' | 'elevated' | 'filled';
557
+ /** Makes the whole card a button. */
558
+ onPress?: () => void;
559
+ accessibilityLabel?: string;
560
+ children?: ReactNode;
561
+ style?: StyleProp<ViewStyle>;
562
+ }
563
+ /**
564
+ * Surface container. Compose with `CardHeader`, `CardTitle`,
565
+ * `CardDescription`, `CardContent` and `CardFooter`.
566
+ *
567
+ * ```tsx
568
+ * <Card variant="elevated" onPress={open}>
569
+ * <CardHeader>
570
+ * <CardTitle>Room A101</CardTitle>
571
+ * <CardDescription>Available today</CardDescription>
572
+ * </CardHeader>
573
+ * <CardContent>…</CardContent>
574
+ * </Card>
575
+ * ```
576
+ */
577
+ declare const Card: react.ForwardRefExoticComponent<CardProps & react.RefAttributes<View>>;
578
+ interface SectionProps$1 {
579
+ children?: ReactNode;
580
+ style?: StyleProp<ViewStyle>;
581
+ }
582
+ interface TextSectionProps {
583
+ children?: ReactNode;
584
+ style?: StyleProp<TextStyle>;
585
+ }
586
+ declare function CardHeader({ children, style }: SectionProps$1): react.JSX.Element;
587
+ declare function CardTitle({ children, style }: TextSectionProps): react.JSX.Element;
588
+ declare function CardDescription({ children, style }: TextSectionProps): react.JSX.Element;
589
+ declare function CardContent({ children, style }: SectionProps$1): react.JSX.Element;
590
+ declare function CardFooter({ children, style }: SectionProps$1): react.JSX.Element;
591
+
592
+ interface ContainerProps {
593
+ /** Horizontal page gutter. Defaults to 4 (16dp). */
594
+ gutter?: SpaceToken;
595
+ /** Caps the width and centres the block on tablets. */
596
+ maxWidth?: number;
597
+ children?: ReactNode;
598
+ style?: StyleProp<ViewStyle>;
599
+ }
600
+ /** Page-level horizontal gutter, so every screen indents its content equally. */
601
+ declare function Container({ gutter, maxWidth, children, style }: ContainerProps): react.JSX.Element;
602
+ interface SectionProps {
603
+ title?: string;
604
+ description?: string;
605
+ /** Rendered on the title row's trailing edge, e.g. a "See all" link. */
606
+ action?: ReactNode;
607
+ /** Gap between the header and the content. Defaults to 3 (12dp). */
608
+ gap?: SpaceToken;
609
+ children?: ReactNode;
610
+ style?: StyleProp<ViewStyle>;
611
+ }
612
+ /**
613
+ * Titled block of content — the repeating unit of every screen in the app.
614
+ *
615
+ * ```tsx
616
+ * <Section title="Upcoming events" action={<Button variant="link">See all</Button>}>
617
+ * …
618
+ * </Section>
619
+ * ```
620
+ */
621
+ declare function Section({ title, description, action, gap, children, style }: SectionProps): react.JSX.Element;
622
+
623
+ interface ScreenProps {
624
+ children?: ReactNode;
625
+ /** Page background. Defaults to the app background token. */
626
+ bg?: SemanticColorToken;
627
+ /** Wraps content in a ScrollView. Off for screens that own a FlatList. */
628
+ scrollable?: boolean;
629
+ /** Pull-to-refresh; only meaningful when `scrollable`. */
630
+ refreshing?: boolean;
631
+ onRefresh?: () => void;
632
+ /** Which safe-area edges to pad. Defaults to top and bottom. */
633
+ edges?: readonly ('top' | 'bottom')[];
634
+ /** Extra bottom padding so content clears a tab bar or sticky footer. */
635
+ bottomInset?: number;
636
+ contentContainerStyle?: StyleProp<ViewStyle>;
637
+ style?: StyleProp<ViewStyle>;
638
+ }
639
+ /**
640
+ * Screen shell: safe-area padding, themed background and optional scrolling
641
+ * with pull-to-refresh. Every route in an app should start with one.
642
+ *
643
+ * ```tsx
644
+ * <Screen scrollable refreshing={isRefetching} onRefresh={refetch}>
645
+ * <Container>…</Container>
646
+ * </Screen>
647
+ * ```
648
+ */
649
+ declare function Screen({ children, bg, scrollable, refreshing, onRefresh, edges, bottomInset, contentContainerStyle, style, }: ScreenProps): react.JSX.Element;
650
+
651
+ interface ListRowProps {
652
+ title: string;
653
+ /** Secondary line under the title. */
654
+ subtitle?: string;
655
+ /** Leading slot — icon, avatar or a small image. */
656
+ left?: ReactNode;
657
+ /** Trailing slot; replaced by a chevron when `onPress` is set and this is empty. */
658
+ right?: ReactNode;
659
+ onPress?: () => void;
660
+ disabled?: boolean;
661
+ /** Hides the trailing chevron on a pressable row. */
662
+ hideChevron?: boolean;
663
+ style?: StyleProp<ViewStyle>;
664
+ }
665
+ /**
666
+ * Settings/list row: leading slot, two lines of text, trailing slot. Pressable
667
+ * rows get a chevron and a button role.
668
+ *
669
+ * ```tsx
670
+ * <ListRow title="Notifications" subtitle="Push and email" onPress={open} />
671
+ * ```
672
+ */
673
+ declare function ListRow({ title, subtitle, left, right, onPress, disabled, hideChevron, style, }: ListRowProps): react.JSX.Element;
674
+
675
+ interface AvatarProps {
676
+ /** Image URL. Falls back to initials when absent or if loading fails. */
677
+ source?: string | null;
678
+ /** Full name — initials are derived from it, and it names the image for a11y. */
679
+ name?: string;
680
+ size?: Size | number;
681
+ style?: StyleProp<ViewStyle>;
682
+ }
683
+ /**
684
+ * Circular avatar with an initials fallback.
685
+ *
686
+ * ```tsx
687
+ * <Avatar source={user.photoUrl} name={user.fullName} size="lg" />
688
+ * ```
689
+ */
690
+ declare function Avatar({ source, name, size, style }: AvatarProps): react.JSX.Element;
691
+
692
+ interface TabItem<T extends string = string> {
693
+ value: T;
694
+ label: string;
695
+ /** Trailing count, e.g. a number of tickets. */
696
+ badge?: number | string;
697
+ }
698
+ interface TabsProps<T extends string = string> {
699
+ items: readonly TabItem<T>[];
700
+ value?: T;
701
+ defaultValue?: T;
702
+ onChange?: (value: T) => void;
703
+ /** `underline` for page-level tabs, `segmented` for a pill switch. */
704
+ variant?: 'underline' | 'segmented';
705
+ /** Lets tabs overflow horizontally instead of splitting the width. */
706
+ scrollable?: boolean;
707
+ style?: StyleProp<ViewStyle>;
708
+ accessibilityLabel?: string;
709
+ }
710
+ /**
711
+ * Tab bar. Panels are the caller's business — render by the selected value —
712
+ * because native screens usually swap whole lists rather than mounting all
713
+ * panels at once.
714
+ *
715
+ * ```tsx
716
+ * <Tabs items={tabs} value={tab} onChange={setTab} variant="segmented" />
717
+ * ```
718
+ */
719
+ declare function Tabs<T extends string = string>({ items, value: controlledValue, defaultValue, onChange, variant, scrollable, style, accessibilityLabel, }: TabsProps<T>): react.JSX.Element;
720
+
721
+ interface AccordionItemData {
722
+ value: string;
723
+ title: string;
724
+ content: ReactNode;
725
+ }
726
+ interface AccordionProps {
727
+ items: readonly AccordionItemData[];
728
+ /** Controlled open values. */
729
+ value?: string[];
730
+ defaultValue?: string[];
731
+ onChange?: (value: string[]) => void;
732
+ /** Only one section open at a time. */
733
+ single?: boolean;
734
+ style?: StyleProp<ViewStyle>;
735
+ }
736
+ /**
737
+ * Collapsible sections.
738
+ *
739
+ * ```tsx
740
+ * <Accordion single items={[{ value: 'rules', title: 'Rules', content: <Text>…</Text> }]} />
741
+ * ```
742
+ */
743
+ declare function Accordion({ items, value: controlledValue, defaultValue, onChange, single, style, }: AccordionProps): react.JSX.Element;
744
+ interface CollapseProps {
745
+ title: string;
746
+ defaultOpen?: boolean;
747
+ children: ReactNode;
748
+ style?: StyleProp<ViewStyle>;
749
+ }
750
+ /** A single collapsible block, for when there is no list to group. */
751
+ declare function Collapse({ title, defaultOpen, children, style }: CollapseProps): react.JSX.Element;
752
+
753
+ interface CalendarProps {
754
+ /** Controlled selected date. */
755
+ value?: Date;
756
+ defaultValue?: Date;
757
+ onChange?: (date: Date) => void;
758
+ /** First visible month; defaults to the selected date's month, else today's. */
759
+ defaultMonth?: Date;
760
+ minDate?: Date;
761
+ maxDate?: Date;
762
+ /** 0 = Sunday, 1 = Monday. Defaults to Monday. */
763
+ weekStartsOn?: 0 | 1;
764
+ /** BCP 47 locale for month and weekday names. Defaults to the runtime locale. */
765
+ locale?: string;
766
+ /**
767
+ * Days to mark with a dot, keyed `YYYY-MM-DD` — e.g. days that already have
768
+ * bookings. The value is the dot's semantic color role.
769
+ */
770
+ markedDates?: Readonly<Record<string, 'primary' | 'accent' | 'success' | 'danger'>>;
771
+ /** Called when the visible month changes — use it to fetch that month's data. */
772
+ onMonthChange?: (month: Date) => void;
773
+ style?: StyleProp<ViewStyle>;
774
+ }
775
+ /**
776
+ * Single-date month grid. Month and weekday names come from `Intl` — no date
777
+ * library, matching the web package.
778
+ *
779
+ * ```tsx
780
+ * <Calendar value={date} onChange={setDate} minDate={new Date()} />
781
+ * ```
782
+ */
783
+ declare function Calendar({ value: controlledValue, defaultValue, onChange, defaultMonth, minDate, maxDate, weekStartsOn, locale, markedDates, onMonthChange, style, }: CalendarProps): react.JSX.Element;
784
+
785
+ interface CarouselProps {
786
+ /** One element per slide. */
787
+ children: ReactNode[];
788
+ /** Slide width. Defaults to the full screen width minus the gutter. */
789
+ slideWidth?: number;
790
+ /** Horizontal page gutter used to compute the default slide width. */
791
+ gutter?: number;
792
+ /** Shows the page dots. Defaults to true. */
793
+ dots?: boolean;
794
+ onSlideChange?: (index: number) => void;
795
+ style?: StyleProp<ViewStyle>;
796
+ accessibilityLabel?: string;
797
+ }
798
+ /**
799
+ * Snap-scrolling horizontal carousel with page dots. Uses a paging ScrollView
800
+ * rather than a gesture library, so it needs no extra native dependency.
801
+ *
802
+ * ```tsx
803
+ * <Carousel accessibilityLabel="Featured events">
804
+ * {events.map((event) => <EventCard key={event.id} event={event} />)}
805
+ * </Carousel>
806
+ * ```
807
+ */
808
+ declare function Carousel({ children, slideWidth, gutter, dots, onSlideChange, style, accessibilityLabel, }: CarouselProps): react.JSX.Element;
809
+
810
+ interface TooltipProps {
811
+ /** Tooltip copy. Keep it short — there is no room for a paragraph. */
812
+ label: string;
813
+ /** Side the bubble appears on. Defaults to above the trigger. */
814
+ placement?: 'top' | 'bottom';
815
+ children: ReactNode;
816
+ style?: StyleProp<ViewStyle>;
817
+ }
818
+ /**
819
+ * Long-press hint. Phones have no hover, so the trigger reveals the bubble on
820
+ * long press and hides it on release; the label is also the trigger's
821
+ * accessibility hint so screen-reader users get it without the gesture.
822
+ *
823
+ * ```tsx
824
+ * <Tooltip label="Points earned this semester">
825
+ * <Icon icon={InfoIcon} />
826
+ * </Tooltip>
827
+ * ```
828
+ */
829
+ declare function Tooltip({ label, placement, children, style }: TooltipProps): react.JSX.Element;
830
+
831
+ interface ModalProps {
832
+ open: boolean;
833
+ onClose: () => void;
834
+ size?: Size;
835
+ /** Close when the backdrop is pressed. Defaults to true. */
836
+ closeOnOverlayPress?: boolean;
837
+ /** Accessible name for the dialog. */
838
+ accessibilityLabel?: string;
839
+ children?: ReactNode;
840
+ style?: StyleProp<ViewStyle>;
841
+ }
842
+ /**
843
+ * Centered dialog on a dimmed backdrop. Built on React Native's `Modal`, so
844
+ * the Android back button and native stacking are handled for us.
845
+ *
846
+ * ```tsx
847
+ * <Modal open={open} onClose={close} accessibilityLabel="Book room">
848
+ * <ModalHeader onClose={close}><ModalTitle>Book room</ModalTitle></ModalHeader>
849
+ * <ModalBody>…</ModalBody>
850
+ * <ModalFooter><Button onPress={confirm}>Confirm</Button></ModalFooter>
851
+ * </Modal>
852
+ * ```
853
+ */
854
+ declare function Modal({ open, onClose, size, closeOnOverlayPress, accessibilityLabel, children, style, }: ModalProps): react.JSX.Element;
855
+ interface ModalHeaderProps {
856
+ children?: ReactNode;
857
+ /** Renders a close button on the right when provided. */
858
+ onClose?: () => void;
859
+ style?: StyleProp<ViewStyle>;
860
+ }
861
+ declare function ModalHeader({ children, onClose, style }: ModalHeaderProps): react.JSX.Element;
862
+ declare function ModalTitle({ children }: {
863
+ children?: ReactNode;
864
+ }): react.JSX.Element;
865
+ interface ModalBodyProps {
866
+ children?: ReactNode;
867
+ /** Wraps the content in a ScrollView. Defaults to true. */
868
+ scrollable?: boolean;
869
+ style?: StyleProp<ViewStyle>;
870
+ }
871
+ declare function ModalBody({ children, scrollable, style }: ModalBodyProps): react.JSX.Element;
872
+ declare function ModalFooter({ children, style, }: {
873
+ children?: ReactNode;
874
+ style?: StyleProp<ViewStyle>;
875
+ }): react.JSX.Element;
876
+
877
+ interface BottomSheetProps {
878
+ open: boolean;
879
+ onClose: () => void;
880
+ /** Close when the backdrop is pressed. Defaults to true. */
881
+ closeOnOverlayPress?: boolean;
882
+ /** Visible title rendered next to the grab handle. */
883
+ title?: string;
884
+ /** Accessible name when there is no visible `title`. */
885
+ accessibilityLabel?: string;
886
+ /** Cap the sheet height as a fraction of the screen. Defaults to 0.9. */
887
+ maxHeightRatio?: number;
888
+ children?: ReactNode;
889
+ style?: StyleProp<ViewStyle>;
890
+ }
891
+ /**
892
+ * Sheet that slides up from the bottom edge, with a grab handle and
893
+ * drag-to-dismiss. The bottom inset is padded so content clears the home
894
+ * indicator.
895
+ *
896
+ * ```tsx
897
+ * <BottomSheet open={open} onClose={close} title="Pick a room">…</BottomSheet>
898
+ * ```
899
+ */
900
+ declare function BottomSheet({ open, onClose, closeOnOverlayPress, title, accessibilityLabel, maxHeightRatio, children, style, }: BottomSheetProps): react.JSX.Element;
901
+
902
+ interface DrawerProps {
903
+ open: boolean;
904
+ onClose: () => void;
905
+ /** Edge the panel slides in from. Defaults to the right. */
906
+ side?: 'left' | 'right';
907
+ /** Panel width as a fraction of the screen. Defaults to 0.85. */
908
+ widthRatio?: number;
909
+ title?: string;
910
+ accessibilityLabel?: string;
911
+ children?: ReactNode;
912
+ style?: StyleProp<ViewStyle>;
913
+ }
914
+ /**
915
+ * Side sheet — a filter or navigation panel that slides in from an edge.
916
+ *
917
+ * ```tsx
918
+ * <Drawer open={open} onClose={close} title="Filters">…</Drawer>
919
+ * ```
920
+ */
921
+ declare function Drawer({ open, onClose, side, widthRatio, title, accessibilityLabel, children, style, }: DrawerProps): react.JSX.Element;
922
+
923
+ interface ActionSheetAction {
924
+ label: string;
925
+ onPress: () => void;
926
+ /** Danger styling for destructive actions. */
927
+ destructive?: boolean;
928
+ disabled?: boolean;
929
+ icon?: ReactNode;
930
+ }
931
+ interface ActionSheetProps {
932
+ open: boolean;
933
+ onClose: () => void;
934
+ title?: string;
935
+ description?: string;
936
+ actions: readonly ActionSheetAction[];
937
+ /** Label for the trailing cancel row. Pass `null` to omit it. */
938
+ cancelLabel?: string | null;
939
+ style?: StyleProp<ViewStyle>;
940
+ }
941
+ /**
942
+ * Menu of actions in a bottom sheet — the native counterpart to the web
943
+ * package's `DropdownMenu`. Anchored dropdowns don't belong on a phone, so the
944
+ * same intent surfaces as a sheet.
945
+ *
946
+ * ```tsx
947
+ * <ActionSheet open={open} onClose={close} actions={[
948
+ * { label: 'Share ticket', onPress: share },
949
+ * { label: 'Cancel registration', onPress: cancel, destructive: true },
950
+ * ]} />
951
+ * ```
952
+ */
953
+ declare function ActionSheet({ open, onClose, title, description, actions, cancelLabel, style, }: ActionSheetProps): react.JSX.Element;
954
+
955
+ interface ThemeContextValue {
956
+ /** The active theme for the resolved color mode. */
957
+ theme: Theme;
958
+ /** The user preference: 'light' | 'dark' | 'system'. */
959
+ colorMode: ColorMode;
960
+ /** What is actually rendered: 'light' | 'dark'. */
961
+ resolvedColorMode: ResolvedColorMode;
962
+ setColorMode: (mode: ColorMode) => void;
963
+ toggleColorMode: () => void;
964
+ }
965
+ interface ThemeProviderProps {
966
+ /** Theme used in light mode. Defaults to the built-in light theme. */
967
+ theme?: Theme;
968
+ /** Theme used in dark mode. Defaults to the built-in dark theme. */
969
+ darkTheme?: Theme;
970
+ /** Uncontrolled initial preference. */
971
+ defaultColorMode?: ColorMode;
972
+ /**
973
+ * Controlled preference. React Native has no synchronous storage, so
974
+ * persistence belongs to the app (SecureStore, AsyncStorage, MMKV…): pass the
975
+ * stored value here and write it back from `onColorModeChange`.
976
+ */
977
+ colorMode?: ColorMode;
978
+ onColorModeChange?: (mode: ColorMode) => void;
979
+ children: ReactNode;
980
+ }
981
+ /**
982
+ * Provides the resolved theme to every native component. `'system'` follows the
983
+ * OS appearance through React Native's `useColorScheme()`.
984
+ *
985
+ * ```tsx
986
+ * <ThemeProvider theme={sduLightTheme} darkTheme={sduDarkTheme}>
987
+ * <App />
988
+ * </ThemeProvider>
989
+ * ```
990
+ */
991
+ declare function ThemeProvider({ theme, darkTheme, defaultColorMode, colorMode: colorModeProp, onColorModeChange, children, }: ThemeProviderProps): react.JSX.Element;
992
+ /** The active theme object (already resolved for the current color mode). */
993
+ declare function useTheme(): Theme;
994
+ /** Color mode state and controls. */
995
+ declare function useColorMode(): Omit<ThemeContextValue, 'theme'>;
996
+ /** Picks a value based on the resolved color mode. */
997
+ declare function useColorModeValue<T>(lightValue: T, darkValue: T): T;
998
+
999
+ /** Resolved colors for one interactive variant, in its rest and pressed states. */
1000
+ interface VariantColors {
1001
+ background: string;
1002
+ backgroundPressed: string;
1003
+ foreground: SemanticColorToken;
1004
+ borderColor?: string;
1005
+ borderWidth?: number;
1006
+ /** `link` draws its label underlined instead of a container. */
1007
+ underline?: boolean;
1008
+ }
1009
+ /**
1010
+ * Maps a `Variant` onto semantic theme colors. Web does this in CSS via
1011
+ * `data-variant` selectors; native resolves it here so both platforms answer
1012
+ * `variant="accent"` with the same roles.
1013
+ */
1014
+ declare function variantColors(variant: Variant, theme: Theme): VariantColors;
1015
+ /** Control geometry per size — shared by Button, Input, Select and Textarea. */
1016
+ interface SizeMetrics {
1017
+ height: number;
1018
+ paddingHorizontal: number;
1019
+ fontSize: 'sm' | 'md' | 'lg';
1020
+ gap: number;
1021
+ iconSize: number;
1022
+ }
1023
+ declare function sizeMetrics(size: Size, theme: Theme): SizeMetrics;
1024
+
1025
+ /** Local-time whole-day helpers. The calendar never leaves local time. */
1026
+ declare function startOfDay(date: Date): Date;
1027
+ declare function startOfMonth(date: Date): Date;
1028
+ declare function addDays(date: Date, amount: number): Date;
1029
+ declare function addMonths(date: Date, amount: number): Date;
1030
+ declare function isSameDay(a: Date, b: Date): boolean;
1031
+ /** `YYYY-MM-DD` in local time — safe as a React key or a lookup key. */
1032
+ declare function dayKey(date: Date): string;
1033
+ /**
1034
+ * The 6×7 grid of days covering `month`, starting on `weekStartsOn`.
1035
+ * Six rows always, so the grid height never jumps between months.
1036
+ */
1037
+ declare function monthGrid(month: Date, weekStartsOn: 0 | 1): Date[];
1038
+
1039
+ export { Accordion, type AccordionItemData, type AccordionProps, ActionSheet, type ActionSheetAction, type ActionSheetProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, BottomSheet, type BottomSheetProps, Box, type BoxProps, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, type CardProps, CardTitle, Carousel, type CarouselProps, Checkbox, type CheckboxProps, Chip, type ChipProps, Collapse, type CollapseProps, Container, type ContainerProps, Divider, type DividerProps, Drawer, type DrawerProps, EmptyState, type EmptyStateProps, Flex, type FlexOwnProps, type FlexProps, FormField, type FormFieldProps, HStack, Heading, type HeadingLevel, type HeadingProps, Icon, IconButton, type IconButtonProps, type IconProps, Input, type InputProps, Label, type LabelProps, ListRow, type ListRowProps, Modal, ModalBody, type ModalBodyProps, ModalFooter, ModalHeader, type ModalHeaderProps, type ModalProps, ModalTitle, Progress, type ProgressProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, Screen, type ScreenProps, Section, type SectionProps, Select, type SelectOption, type SelectProps, type SizeMetrics, Skeleton, type SkeletonProps, Spacer, type SpacerProps, Spinner, type SpinnerProps, Stack, type StackProps, type StyleProps, Switch, type SwitchProps, type TabItem, Tabs, type TabsProps, Text, type TextOwnProps, type TextProps, Textarea, type TextareaProps, type ThemeContextValue, ThemeProvider, type ThemeProviderProps, type ToastContextValue, type ToastOptions, type ToastPlacement, ToastProvider, type ToastProviderProps, type ToastTone, Tooltip, type TooltipProps, type VariantColors, addDays, addMonths, dayKey, isSameDay, monthGrid, shadowStyle, sizeMetrics, splitStyleProps, startOfDay, startOfMonth, useColorMode, useColorModeValue, useTheme, useToast, variantColors };