@muja-ui/native 0.3.0 → 0.4.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/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import * as react_native from 'react-native';
4
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';
5
+ import { Theme, IconDefinition, Variant, Size, ColorMode, ResolvedColorMode, ThemeOverride } from '@muja-ui/core';
6
6
  export { ColorMode, IconDefinition, ResolvedColorMode, Size, Status, Theme, ThemeOverride, Variant, createTheme, darkTheme, lightTheme, registerIcons } from '@muja-ui/core';
7
7
  import { SpaceToken, SemanticColorToken, BorderWidthToken, RadiusToken, ShadowToken, ZIndexToken, ShadowValue, FontSizeToken, FontWeightToken, FontFamilyToken, LineHeightToken, LetterSpacingToken } from '@muja-ui/tokens';
8
8
 
@@ -144,7 +144,9 @@ type TextProps = TextOwnProps & StyleProps & Omit<TextProps$1, 'style'> & {
144
144
  /**
145
145
  * Typography primitive. Color always comes from a semantic token, never a raw
146
146
  * value. React Native needs an absolute `lineHeight`, so the unitless token
147
- * multiplier is resolved against the font size here.
147
+ * multiplier is resolved against the font size here, and the theme's CSS font
148
+ * stack is reduced to the one family the platform can load (see
149
+ * `nativeFontFamily`) — the system font unless the theme names a brand face.
148
150
  *
149
151
  * ```tsx
150
152
  * <Text size="sm" color="textSecondary" truncate>…</Text>
@@ -161,7 +163,8 @@ interface HeadingProps extends Omit<TextProps, 'accessibilityRole'> {
161
163
  }
162
164
  /**
163
165
  * A heading with the correct accessibility role. Sizes follow the level unless
164
- * `size` overrides them.
166
+ * `size` overrides them. On iOS the two top levels are bold, matching Large
167
+ * Title / Title 1; the rest are semibold like Headline.
165
168
  *
166
169
  * ```tsx
167
170
  * <Heading level={2}>Upcoming events</Heading>
@@ -213,7 +216,9 @@ interface ButtonProps extends Omit<PressableProps, 'style' | 'children'> {
213
216
  }
214
217
  /**
215
218
  * Pressable button. Same variant/size vocabulary as `@muja-ui/web`'s Button;
216
- * every color resolves from a semantic theme token.
219
+ * every color resolves from a semantic theme token. Heights are minimums so
220
+ * the label can grow with Dynamic Type; a `sm` button widens its touch area
221
+ * to the 44pt floor.
217
222
  *
218
223
  * ```tsx
219
224
  * <Button variant="primary" size="lg" loading onPress={save}>Save</Button>
@@ -257,6 +262,11 @@ interface InputProps extends Omit<TextInputProps, 'style' | 'editable'> {
257
262
  * Single-line text field. Focus and invalid states are drawn on the wrapper so
258
263
  * adornments sit inside the border.
259
264
  *
265
+ * On iOS the field is a filled, borderless rounded rect like a UIKit search
266
+ * field or grouped-list cell; a stroke appears only for focus and errors. The
267
+ * keyboard follows the color mode and shows the native clear button while
268
+ * editing (unless a `rightElement` occupies that spot).
269
+ *
260
270
  * ```tsx
261
271
  * <Input size="md" placeholder="Email" invalid={!!error} />
262
272
  * ```
@@ -270,7 +280,7 @@ interface TextareaProps extends Omit<TextInputProps, 'style' | 'editable' | 'mul
270
280
  rows?: number;
271
281
  style?: StyleProp<TextStyle>;
272
282
  }
273
- /** Multi-line text field. Same states as `Input`. */
283
+ /** Multi-line text field. Same states — and the same iOS filled look — as `Input`. */
274
284
  declare const Textarea: react.ForwardRefExoticComponent<TextareaProps & react.RefAttributes<TextInput>>;
275
285
 
276
286
  interface LabelProps extends TextProps {
@@ -321,7 +331,9 @@ interface CheckboxProps {
321
331
  style?: StyleProp<ViewStyle>;
322
332
  }
323
333
  /**
324
- * Checkbox with an optional inline label.
334
+ * Checkbox with an optional inline label. iOS has no square checkbox — the
335
+ * idiom (Reminders, Mail's edit mode) is a circle that fills with a tick — so
336
+ * the box is round there and square elsewhere.
325
337
  *
326
338
  * ```tsx
327
339
  * <Checkbox checked={agreed} onChange={setAgreed}>I agree</Checkbox>
@@ -339,9 +351,12 @@ interface SwitchProps {
339
351
  style?: StyleProp<ViewStyle>;
340
352
  }
341
353
  /**
342
- * Toggle switch. Built from primitives rather than RN's `Switch` so it follows
343
- * the theme on both platforms (RN's own switch only takes raw colors and
344
- * renders with platform-specific metrics).
354
+ * Toggle switch.
355
+ *
356
+ * On iOS this is the real `UISwitch` (through React Native's `Switch`), tinted
357
+ * with the theme's primary colour — native motion, haptics and VoiceOver
358
+ * semantics for free, as the HIG asks. Elsewhere it is drawn from primitives so
359
+ * it follows the theme's metrics on both platforms.
345
360
  *
346
361
  * ```tsx
347
362
  * <Switch checked={enabled} onChange={setEnabled}>Notifications</Switch>
@@ -570,7 +585,8 @@ interface CardProps {
570
585
  }
571
586
  /**
572
587
  * Surface container. Compose with `CardHeader`, `CardTitle`,
573
- * `CardDescription`, `CardContent` and `CardFooter`.
588
+ * `CardDescription`, `CardContent` and `CardFooter`. On iOS the `outline`
589
+ * stroke is a hairline, the way grouped cells are separated.
574
590
  *
575
591
  * ```tsx
576
592
  * <Card variant="elevated" onPress={open}>
@@ -672,7 +688,8 @@ interface ListRowProps {
672
688
  }
673
689
  /**
674
690
  * Settings/list row: leading slot, two lines of text, trailing slot. Pressable
675
- * rows get a chevron and a button role.
691
+ * rows get a chevron and a button role. On iOS the row follows a table cell:
692
+ * 44pt minimum, body-size title, secondary-label subtitle, bold chevron.
676
693
  *
677
694
  * ```tsx
678
695
  * <ListRow title="Notifications" subtitle="Push and email" onPress={open} />
@@ -720,6 +737,9 @@ interface TabsProps<T extends string = string> {
720
737
  * because native screens usually swap whole lists rather than mounting all
721
738
  * panels at once.
722
739
  *
740
+ * On iOS `segmented` is drawn as a `UISegmentedControl`: a 2pt inset track,
741
+ * the selected segment lifted on a white pill with a soft shadow, 13pt labels.
742
+ *
723
743
  * ```tsx
724
744
  * <Tabs items={tabs} value={tab} onChange={setTab} variant="segmented" />
725
745
  * ```
@@ -877,6 +897,7 @@ interface ModalBodyProps {
877
897
  style?: StyleProp<ViewStyle>;
878
898
  }
879
899
  declare function ModalBody({ children, scrollable, style }: ModalBodyProps): react.JSX.Element;
900
+ /** Action row. On iOS a hairline separates it from the body, as in an alert. */
880
901
  declare function ModalFooter({ children, style, }: {
881
902
  children?: ReactNode;
882
903
  style?: StyleProp<ViewStyle>;
@@ -898,8 +919,9 @@ interface BottomSheetProps {
898
919
  }
899
920
  /**
900
921
  * Sheet that slides up from the bottom edge, with a grab handle and
901
- * drag-to-dismiss. The bottom inset is padded so content clears the home
902
- * indicator.
922
+ * drag-to-dismiss. It arrives on a spring with a fading backdrop, the way a
923
+ * UIKit sheet is presented, and the bottom inset is padded so content clears
924
+ * the home indicator. On iOS the grabber matches the system's 36×5.
903
925
  *
904
926
  * ```tsx
905
927
  * <BottomSheet open={open} onClose={close} title="Pick a room">…</BottomSheet>
@@ -951,6 +973,11 @@ interface ActionSheetProps {
951
973
  * package's `DropdownMenu`. Anchored dropdowns don't belong on a phone, so the
952
974
  * same intent surfaces as a sheet.
953
975
  *
976
+ * On iOS it takes the `UIAlertController` action-sheet form: a floating inset
977
+ * group of centred, tint-coloured rows separated by hairlines, destructive
978
+ * rows in red, and Cancel as its own bold card underneath. Elsewhere it is a
979
+ * list inside `BottomSheet`.
980
+ *
954
981
  * ```tsx
955
982
  * <ActionSheet open={open} onClose={close} actions={[
956
983
  * { label: 'Share ticket', onPress: share },
@@ -958,7 +985,7 @@ interface ActionSheetProps {
958
985
  * ]} />
959
986
  * ```
960
987
  */
961
- declare function ActionSheet({ open, onClose, title, description, actions, cancelLabel, style, }: ActionSheetProps): react.JSX.Element;
988
+ declare function ActionSheet(props: ActionSheetProps): react.JSX.Element;
962
989
 
963
990
  interface ThemeContextValue {
964
991
  /** The active theme for the resolved color mode. */
@@ -984,11 +1011,18 @@ interface ThemeProviderProps {
984
1011
  */
985
1012
  colorMode?: ColorMode;
986
1013
  onColorModeChange?: (mode: ColorMode) => void;
1014
+ /**
1015
+ * Lay the platform's own metrics over the theme — on iOS the HIG type scale
1016
+ * (Body 17, Footnote 13…) and UIKit radii. Colors are never touched. Defaults
1017
+ * to true; turn off to render the shared tokens verbatim on every platform.
1018
+ */
1019
+ platformAdaptive?: boolean;
987
1020
  children: ReactNode;
988
1021
  }
989
1022
  /**
990
1023
  * Provides the resolved theme to every native component. `'system'` follows the
991
- * OS appearance through React Native's `useColorScheme()`.
1024
+ * OS appearance through React Native's `useColorScheme()`. On iOS the theme is
1025
+ * rendered through Apple HIG metrics (see `adaptThemeToPlatform`).
992
1026
  *
993
1027
  * ```tsx
994
1028
  * <ThemeProvider theme={sduLightTheme} darkTheme={sduDarkTheme}>
@@ -996,7 +1030,7 @@ interface ThemeProviderProps {
996
1030
  * </ThemeProvider>
997
1031
  * ```
998
1032
  */
999
- declare function ThemeProvider({ theme, darkTheme, defaultColorMode, colorMode: colorModeProp, onColorModeChange, children, }: ThemeProviderProps): react.JSX.Element;
1033
+ declare function ThemeProvider({ theme, darkTheme, defaultColorMode, colorMode: colorModeProp, onColorModeChange, platformAdaptive, children, }: ThemeProviderProps): react.JSX.Element;
1000
1034
  /** The active theme object (already resolved for the current color mode). */
1001
1035
  declare function useTheme(): Theme;
1002
1036
  /** Color mode state and controls. */
@@ -1018,9 +1052,24 @@ interface VariantColors {
1018
1052
  * Maps a `Variant` onto semantic theme colors. Web does this in CSS via
1019
1053
  * `data-variant` selectors; native resolves it here so both platforms answer
1020
1054
  * `variant="accent"` with the same roles.
1055
+ *
1056
+ * On iOS the two chrome-less variants follow UIKit's button configurations:
1057
+ * `outline` renders as *tinted* (a soft primary fill, no stroke — iOS has no
1058
+ * stroked buttons) and `ghost` as *plain* (tint-coloured label).
1021
1059
  */
1022
1060
  declare function variantColors(variant: Variant, theme: Theme): VariantColors;
1023
- /** Control geometry per size — shared by Button, Input, Select and Textarea. */
1061
+ /**
1062
+ * Background and opacity for a button-like pressable in its current state.
1063
+ * iOS dims the whole control (UIKit's highlight); Android swaps to the pressed
1064
+ * colour. Rows and cells are different — they highlight with a background on
1065
+ * both platforms, so they don't use this.
1066
+ */
1067
+ declare function pressFeedback(pressed: boolean, colors: VariantColors): ViewStyle;
1068
+ /**
1069
+ * Control geometry per size — shared by Button, Input, Select and Textarea.
1070
+ * `height` is a minimum: controls grow with Dynamic Type / font scaling
1071
+ * instead of clipping their label.
1072
+ */
1024
1073
  interface SizeMetrics {
1025
1074
  height: number;
1026
1075
  paddingHorizontal: number;
@@ -1030,6 +1079,49 @@ interface SizeMetrics {
1030
1079
  }
1031
1080
  declare function sizeMetrics(size: Size, theme: Theme): SizeMetrics;
1032
1081
 
1082
+ /**
1083
+ * Read at call time (not module load) so a test can flip the stubbed
1084
+ * `Platform.OS` and exercise both branches of a component.
1085
+ */
1086
+ declare function isIOS(): boolean;
1087
+ /**
1088
+ * Apple HIG metrics layered over the shared tokens on iOS.
1089
+ *
1090
+ * - Type scale follows the Dynamic Type default sizes: `xs` Footnote 13,
1091
+ * `sm` Subheadline 15, `md` Body 17, `lg` Title 3 20, `xl` Title 2 22,
1092
+ * `2xl` Title 1 28, `3xl` Large Title 34.
1093
+ * - Radii follow UIKit: 10 for controls and grouped cells, 12 for large
1094
+ * buttons and cards, 14 for alerts, 20 for sheets.
1095
+ *
1096
+ * Colors are untouched — brand and dark mode stay the theme's business.
1097
+ */
1098
+ declare const iosThemeOverride: ThemeOverride;
1099
+ /**
1100
+ * Returns the theme the current platform should render: on iOS the HIG
1101
+ * metrics above are merged in, elsewhere the theme is returned as-is.
1102
+ * `ThemeProvider` applies this automatically (`platformAdaptive`).
1103
+ */
1104
+ declare function adaptThemeToPlatform(theme: Theme): Theme;
1105
+
1106
+ /**
1107
+ * Turns a CSS font stack from the theme into the single family React Native
1108
+ * wants. The tokens are written as web stacks
1109
+ * (`"'Gilroy', -apple-system, BlinkMacSystemFont, …"`); passing that string
1110
+ * to `fontFamily` on iOS logs "Unrecognized font family" and silently falls
1111
+ * back, so the first usable entry is picked here instead:
1112
+ *
1113
+ * - a system keyword (`-apple-system`, `system-ui`, `sans-serif`, `System`…)
1114
+ * resolves to `undefined` — SF Pro on iOS, Roboto on Android;
1115
+ * - `ui-monospace` / `ui-serif` resolve to the platform's built-in face;
1116
+ * - web-only names (`Segoe UI`, `SF Mono`…) are skipped;
1117
+ * - anything else (`Gilroy`) is returned verbatim for the app to have loaded.
1118
+ *
1119
+ * ```ts
1120
+ * nativeFontFamily(theme.typography.fontFamily.sans) // → undefined on a stock theme
1121
+ * ```
1122
+ */
1123
+ declare function nativeFontFamily(stack: string | undefined): string | undefined;
1124
+
1033
1125
  /** Local-time whole-day helpers. The calendar never leaves local time. */
1034
1126
  declare function startOfDay(date: Date): Date;
1035
1127
  declare function startOfMonth(date: Date): Date;
@@ -1044,4 +1136,4 @@ declare function dayKey(date: Date): string;
1044
1136
  */
1045
1137
  declare function monthGrid(month: Date, weekStartsOn: 0 | 1): Date[];
1046
1138
 
1047
- 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 };
1139
+ 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, adaptThemeToPlatform, addDays, addMonths, dayKey, iosThemeOverride, isIOS, isSameDay, monthGrid, nativeFontFamily, pressFeedback, shadowStyle, sizeMetrics, splitStyleProps, startOfDay, startOfMonth, useColorMode, useColorModeValue, useTheme, useToast, variantColors };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import * as react_native from 'react-native';
4
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';
5
+ import { Theme, IconDefinition, Variant, Size, ColorMode, ResolvedColorMode, ThemeOverride } from '@muja-ui/core';
6
6
  export { ColorMode, IconDefinition, ResolvedColorMode, Size, Status, Theme, ThemeOverride, Variant, createTheme, darkTheme, lightTheme, registerIcons } from '@muja-ui/core';
7
7
  import { SpaceToken, SemanticColorToken, BorderWidthToken, RadiusToken, ShadowToken, ZIndexToken, ShadowValue, FontSizeToken, FontWeightToken, FontFamilyToken, LineHeightToken, LetterSpacingToken } from '@muja-ui/tokens';
8
8
 
@@ -144,7 +144,9 @@ type TextProps = TextOwnProps & StyleProps & Omit<TextProps$1, 'style'> & {
144
144
  /**
145
145
  * Typography primitive. Color always comes from a semantic token, never a raw
146
146
  * value. React Native needs an absolute `lineHeight`, so the unitless token
147
- * multiplier is resolved against the font size here.
147
+ * multiplier is resolved against the font size here, and the theme's CSS font
148
+ * stack is reduced to the one family the platform can load (see
149
+ * `nativeFontFamily`) — the system font unless the theme names a brand face.
148
150
  *
149
151
  * ```tsx
150
152
  * <Text size="sm" color="textSecondary" truncate>…</Text>
@@ -161,7 +163,8 @@ interface HeadingProps extends Omit<TextProps, 'accessibilityRole'> {
161
163
  }
162
164
  /**
163
165
  * A heading with the correct accessibility role. Sizes follow the level unless
164
- * `size` overrides them.
166
+ * `size` overrides them. On iOS the two top levels are bold, matching Large
167
+ * Title / Title 1; the rest are semibold like Headline.
165
168
  *
166
169
  * ```tsx
167
170
  * <Heading level={2}>Upcoming events</Heading>
@@ -213,7 +216,9 @@ interface ButtonProps extends Omit<PressableProps, 'style' | 'children'> {
213
216
  }
214
217
  /**
215
218
  * Pressable button. Same variant/size vocabulary as `@muja-ui/web`'s Button;
216
- * every color resolves from a semantic theme token.
219
+ * every color resolves from a semantic theme token. Heights are minimums so
220
+ * the label can grow with Dynamic Type; a `sm` button widens its touch area
221
+ * to the 44pt floor.
217
222
  *
218
223
  * ```tsx
219
224
  * <Button variant="primary" size="lg" loading onPress={save}>Save</Button>
@@ -257,6 +262,11 @@ interface InputProps extends Omit<TextInputProps, 'style' | 'editable'> {
257
262
  * Single-line text field. Focus and invalid states are drawn on the wrapper so
258
263
  * adornments sit inside the border.
259
264
  *
265
+ * On iOS the field is a filled, borderless rounded rect like a UIKit search
266
+ * field or grouped-list cell; a stroke appears only for focus and errors. The
267
+ * keyboard follows the color mode and shows the native clear button while
268
+ * editing (unless a `rightElement` occupies that spot).
269
+ *
260
270
  * ```tsx
261
271
  * <Input size="md" placeholder="Email" invalid={!!error} />
262
272
  * ```
@@ -270,7 +280,7 @@ interface TextareaProps extends Omit<TextInputProps, 'style' | 'editable' | 'mul
270
280
  rows?: number;
271
281
  style?: StyleProp<TextStyle>;
272
282
  }
273
- /** Multi-line text field. Same states as `Input`. */
283
+ /** Multi-line text field. Same states — and the same iOS filled look — as `Input`. */
274
284
  declare const Textarea: react.ForwardRefExoticComponent<TextareaProps & react.RefAttributes<TextInput>>;
275
285
 
276
286
  interface LabelProps extends TextProps {
@@ -321,7 +331,9 @@ interface CheckboxProps {
321
331
  style?: StyleProp<ViewStyle>;
322
332
  }
323
333
  /**
324
- * Checkbox with an optional inline label.
334
+ * Checkbox with an optional inline label. iOS has no square checkbox — the
335
+ * idiom (Reminders, Mail's edit mode) is a circle that fills with a tick — so
336
+ * the box is round there and square elsewhere.
325
337
  *
326
338
  * ```tsx
327
339
  * <Checkbox checked={agreed} onChange={setAgreed}>I agree</Checkbox>
@@ -339,9 +351,12 @@ interface SwitchProps {
339
351
  style?: StyleProp<ViewStyle>;
340
352
  }
341
353
  /**
342
- * Toggle switch. Built from primitives rather than RN's `Switch` so it follows
343
- * the theme on both platforms (RN's own switch only takes raw colors and
344
- * renders with platform-specific metrics).
354
+ * Toggle switch.
355
+ *
356
+ * On iOS this is the real `UISwitch` (through React Native's `Switch`), tinted
357
+ * with the theme's primary colour — native motion, haptics and VoiceOver
358
+ * semantics for free, as the HIG asks. Elsewhere it is drawn from primitives so
359
+ * it follows the theme's metrics on both platforms.
345
360
  *
346
361
  * ```tsx
347
362
  * <Switch checked={enabled} onChange={setEnabled}>Notifications</Switch>
@@ -570,7 +585,8 @@ interface CardProps {
570
585
  }
571
586
  /**
572
587
  * Surface container. Compose with `CardHeader`, `CardTitle`,
573
- * `CardDescription`, `CardContent` and `CardFooter`.
588
+ * `CardDescription`, `CardContent` and `CardFooter`. On iOS the `outline`
589
+ * stroke is a hairline, the way grouped cells are separated.
574
590
  *
575
591
  * ```tsx
576
592
  * <Card variant="elevated" onPress={open}>
@@ -672,7 +688,8 @@ interface ListRowProps {
672
688
  }
673
689
  /**
674
690
  * Settings/list row: leading slot, two lines of text, trailing slot. Pressable
675
- * rows get a chevron and a button role.
691
+ * rows get a chevron and a button role. On iOS the row follows a table cell:
692
+ * 44pt minimum, body-size title, secondary-label subtitle, bold chevron.
676
693
  *
677
694
  * ```tsx
678
695
  * <ListRow title="Notifications" subtitle="Push and email" onPress={open} />
@@ -720,6 +737,9 @@ interface TabsProps<T extends string = string> {
720
737
  * because native screens usually swap whole lists rather than mounting all
721
738
  * panels at once.
722
739
  *
740
+ * On iOS `segmented` is drawn as a `UISegmentedControl`: a 2pt inset track,
741
+ * the selected segment lifted on a white pill with a soft shadow, 13pt labels.
742
+ *
723
743
  * ```tsx
724
744
  * <Tabs items={tabs} value={tab} onChange={setTab} variant="segmented" />
725
745
  * ```
@@ -877,6 +897,7 @@ interface ModalBodyProps {
877
897
  style?: StyleProp<ViewStyle>;
878
898
  }
879
899
  declare function ModalBody({ children, scrollable, style }: ModalBodyProps): react.JSX.Element;
900
+ /** Action row. On iOS a hairline separates it from the body, as in an alert. */
880
901
  declare function ModalFooter({ children, style, }: {
881
902
  children?: ReactNode;
882
903
  style?: StyleProp<ViewStyle>;
@@ -898,8 +919,9 @@ interface BottomSheetProps {
898
919
  }
899
920
  /**
900
921
  * Sheet that slides up from the bottom edge, with a grab handle and
901
- * drag-to-dismiss. The bottom inset is padded so content clears the home
902
- * indicator.
922
+ * drag-to-dismiss. It arrives on a spring with a fading backdrop, the way a
923
+ * UIKit sheet is presented, and the bottom inset is padded so content clears
924
+ * the home indicator. On iOS the grabber matches the system's 36×5.
903
925
  *
904
926
  * ```tsx
905
927
  * <BottomSheet open={open} onClose={close} title="Pick a room">…</BottomSheet>
@@ -951,6 +973,11 @@ interface ActionSheetProps {
951
973
  * package's `DropdownMenu`. Anchored dropdowns don't belong on a phone, so the
952
974
  * same intent surfaces as a sheet.
953
975
  *
976
+ * On iOS it takes the `UIAlertController` action-sheet form: a floating inset
977
+ * group of centred, tint-coloured rows separated by hairlines, destructive
978
+ * rows in red, and Cancel as its own bold card underneath. Elsewhere it is a
979
+ * list inside `BottomSheet`.
980
+ *
954
981
  * ```tsx
955
982
  * <ActionSheet open={open} onClose={close} actions={[
956
983
  * { label: 'Share ticket', onPress: share },
@@ -958,7 +985,7 @@ interface ActionSheetProps {
958
985
  * ]} />
959
986
  * ```
960
987
  */
961
- declare function ActionSheet({ open, onClose, title, description, actions, cancelLabel, style, }: ActionSheetProps): react.JSX.Element;
988
+ declare function ActionSheet(props: ActionSheetProps): react.JSX.Element;
962
989
 
963
990
  interface ThemeContextValue {
964
991
  /** The active theme for the resolved color mode. */
@@ -984,11 +1011,18 @@ interface ThemeProviderProps {
984
1011
  */
985
1012
  colorMode?: ColorMode;
986
1013
  onColorModeChange?: (mode: ColorMode) => void;
1014
+ /**
1015
+ * Lay the platform's own metrics over the theme — on iOS the HIG type scale
1016
+ * (Body 17, Footnote 13…) and UIKit radii. Colors are never touched. Defaults
1017
+ * to true; turn off to render the shared tokens verbatim on every platform.
1018
+ */
1019
+ platformAdaptive?: boolean;
987
1020
  children: ReactNode;
988
1021
  }
989
1022
  /**
990
1023
  * Provides the resolved theme to every native component. `'system'` follows the
991
- * OS appearance through React Native's `useColorScheme()`.
1024
+ * OS appearance through React Native's `useColorScheme()`. On iOS the theme is
1025
+ * rendered through Apple HIG metrics (see `adaptThemeToPlatform`).
992
1026
  *
993
1027
  * ```tsx
994
1028
  * <ThemeProvider theme={sduLightTheme} darkTheme={sduDarkTheme}>
@@ -996,7 +1030,7 @@ interface ThemeProviderProps {
996
1030
  * </ThemeProvider>
997
1031
  * ```
998
1032
  */
999
- declare function ThemeProvider({ theme, darkTheme, defaultColorMode, colorMode: colorModeProp, onColorModeChange, children, }: ThemeProviderProps): react.JSX.Element;
1033
+ declare function ThemeProvider({ theme, darkTheme, defaultColorMode, colorMode: colorModeProp, onColorModeChange, platformAdaptive, children, }: ThemeProviderProps): react.JSX.Element;
1000
1034
  /** The active theme object (already resolved for the current color mode). */
1001
1035
  declare function useTheme(): Theme;
1002
1036
  /** Color mode state and controls. */
@@ -1018,9 +1052,24 @@ interface VariantColors {
1018
1052
  * Maps a `Variant` onto semantic theme colors. Web does this in CSS via
1019
1053
  * `data-variant` selectors; native resolves it here so both platforms answer
1020
1054
  * `variant="accent"` with the same roles.
1055
+ *
1056
+ * On iOS the two chrome-less variants follow UIKit's button configurations:
1057
+ * `outline` renders as *tinted* (a soft primary fill, no stroke — iOS has no
1058
+ * stroked buttons) and `ghost` as *plain* (tint-coloured label).
1021
1059
  */
1022
1060
  declare function variantColors(variant: Variant, theme: Theme): VariantColors;
1023
- /** Control geometry per size — shared by Button, Input, Select and Textarea. */
1061
+ /**
1062
+ * Background and opacity for a button-like pressable in its current state.
1063
+ * iOS dims the whole control (UIKit's highlight); Android swaps to the pressed
1064
+ * colour. Rows and cells are different — they highlight with a background on
1065
+ * both platforms, so they don't use this.
1066
+ */
1067
+ declare function pressFeedback(pressed: boolean, colors: VariantColors): ViewStyle;
1068
+ /**
1069
+ * Control geometry per size — shared by Button, Input, Select and Textarea.
1070
+ * `height` is a minimum: controls grow with Dynamic Type / font scaling
1071
+ * instead of clipping their label.
1072
+ */
1024
1073
  interface SizeMetrics {
1025
1074
  height: number;
1026
1075
  paddingHorizontal: number;
@@ -1030,6 +1079,49 @@ interface SizeMetrics {
1030
1079
  }
1031
1080
  declare function sizeMetrics(size: Size, theme: Theme): SizeMetrics;
1032
1081
 
1082
+ /**
1083
+ * Read at call time (not module load) so a test can flip the stubbed
1084
+ * `Platform.OS` and exercise both branches of a component.
1085
+ */
1086
+ declare function isIOS(): boolean;
1087
+ /**
1088
+ * Apple HIG metrics layered over the shared tokens on iOS.
1089
+ *
1090
+ * - Type scale follows the Dynamic Type default sizes: `xs` Footnote 13,
1091
+ * `sm` Subheadline 15, `md` Body 17, `lg` Title 3 20, `xl` Title 2 22,
1092
+ * `2xl` Title 1 28, `3xl` Large Title 34.
1093
+ * - Radii follow UIKit: 10 for controls and grouped cells, 12 for large
1094
+ * buttons and cards, 14 for alerts, 20 for sheets.
1095
+ *
1096
+ * Colors are untouched — brand and dark mode stay the theme's business.
1097
+ */
1098
+ declare const iosThemeOverride: ThemeOverride;
1099
+ /**
1100
+ * Returns the theme the current platform should render: on iOS the HIG
1101
+ * metrics above are merged in, elsewhere the theme is returned as-is.
1102
+ * `ThemeProvider` applies this automatically (`platformAdaptive`).
1103
+ */
1104
+ declare function adaptThemeToPlatform(theme: Theme): Theme;
1105
+
1106
+ /**
1107
+ * Turns a CSS font stack from the theme into the single family React Native
1108
+ * wants. The tokens are written as web stacks
1109
+ * (`"'Gilroy', -apple-system, BlinkMacSystemFont, …"`); passing that string
1110
+ * to `fontFamily` on iOS logs "Unrecognized font family" and silently falls
1111
+ * back, so the first usable entry is picked here instead:
1112
+ *
1113
+ * - a system keyword (`-apple-system`, `system-ui`, `sans-serif`, `System`…)
1114
+ * resolves to `undefined` — SF Pro on iOS, Roboto on Android;
1115
+ * - `ui-monospace` / `ui-serif` resolve to the platform's built-in face;
1116
+ * - web-only names (`Segoe UI`, `SF Mono`…) are skipped;
1117
+ * - anything else (`Gilroy`) is returned verbatim for the app to have loaded.
1118
+ *
1119
+ * ```ts
1120
+ * nativeFontFamily(theme.typography.fontFamily.sans) // → undefined on a stock theme
1121
+ * ```
1122
+ */
1123
+ declare function nativeFontFamily(stack: string | undefined): string | undefined;
1124
+
1033
1125
  /** Local-time whole-day helpers. The calendar never leaves local time. */
1034
1126
  declare function startOfDay(date: Date): Date;
1035
1127
  declare function startOfMonth(date: Date): Date;
@@ -1044,4 +1136,4 @@ declare function dayKey(date: Date): string;
1044
1136
  */
1045
1137
  declare function monthGrid(month: Date, weekStartsOn: 0 | 1): Date[];
1046
1138
 
1047
- 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 };
1139
+ 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, adaptThemeToPlatform, addDays, addMonths, dayKey, iosThemeOverride, isIOS, isSameDay, monthGrid, nativeFontFamily, pressFeedback, shadowStyle, sizeMetrics, splitStyleProps, startOfDay, startOfMonth, useColorMode, useColorModeValue, useTheme, useToast, variantColors };