react-native-terra-ui 0.1.1 → 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.
- package/CHANGELOG.md +90 -0
- package/lib/module/components/avatar/Avatar.js +118 -35
- package/lib/module/components/avatar/Avatar.js.map +1 -1
- package/lib/module/components/box/Box.js +15 -2
- package/lib/module/components/box/Box.js.map +1 -1
- package/lib/module/components/box/utils.js +4 -1
- package/lib/module/components/box/utils.js.map +1 -1
- package/lib/module/components/button/Button.js +7 -14
- package/lib/module/components/button/Button.js.map +1 -1
- package/lib/module/components/chip/Chip.js +372 -0
- package/lib/module/components/chip/Chip.js.map +1 -0
- package/lib/module/components/chip/index.js +4 -0
- package/lib/module/components/chip/index.js.map +1 -0
- package/lib/module/components/chip/types.js +2 -0
- package/lib/module/components/chip/types.js.map +1 -0
- package/lib/module/components/chip/utils.js +36 -0
- package/lib/module/components/chip/utils.js.map +1 -0
- package/lib/module/components/empty-state/EmptyState.js +200 -0
- package/lib/module/components/empty-state/EmptyState.js.map +1 -0
- package/lib/module/components/empty-state/index.js +4 -0
- package/lib/module/components/empty-state/index.js.map +1 -0
- package/lib/module/components/empty-state/types.js +2 -0
- package/lib/module/components/empty-state/types.js.map +1 -0
- package/lib/module/components/header/header-buttons.js +31 -36
- package/lib/module/components/header/header-buttons.js.map +1 -1
- package/lib/module/components/header/index.js +1 -1
- package/lib/module/components/header/index.js.map +1 -1
- package/lib/module/components/header/parts/HeaderGradientOverlay.js +38 -0
- package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -0
- package/lib/module/components/header/variants/LargeTitleHeader/index.js +69 -99
- package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
- package/lib/module/components/header/variants/TitleHeader/index.js +32 -63
- package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
- package/lib/module/components/icon/Icon.js +20 -8
- package/lib/module/components/icon/Icon.js.map +1 -1
- package/lib/module/components/image/Image.js +54 -0
- package/lib/module/components/image/Image.js.map +1 -0
- package/lib/module/components/image/index.js +4 -0
- package/lib/module/components/image/index.js.map +1 -0
- package/lib/module/components/image/types.js +4 -0
- package/lib/module/components/image/types.js.map +1 -0
- package/lib/module/components/index.js +4 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/portal/Portal.js +42 -0
- package/lib/module/components/portal/Portal.js.map +1 -0
- package/lib/module/components/portal/PortalContext.js +1 -16
- package/lib/module/components/portal/PortalContext.js.map +1 -1
- package/lib/module/components/portal/PortalHost.js +16 -14
- package/lib/module/components/portal/PortalHost.js.map +1 -1
- package/lib/module/components/portal/PortalProvider.js +51 -19
- package/lib/module/components/portal/PortalProvider.js.map +1 -1
- package/lib/module/components/portal/index.js +1 -1
- package/lib/module/components/portal/index.js.map +1 -1
- package/lib/module/components/screen/Screen.js +25 -28
- package/lib/module/components/screen/Screen.js.map +1 -1
- package/lib/module/components/screen/ScreenContext.js +7 -5
- package/lib/module/components/screen/ScreenContext.js.map +1 -1
- package/lib/module/components/screen/ScreenFlashList.js +144 -0
- package/lib/module/components/screen/ScreenFlashList.js.map +1 -0
- package/lib/module/components/screen/ScreenFlatList.js +88 -27
- package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
- package/lib/module/components/screen/ScreenScrollView.js +101 -34
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/module/components/screen/index.js +1 -0
- package/lib/module/components/screen/index.js.map +1 -1
- package/lib/module/components/screen/parts/BottomSafeArea.js +62 -0
- package/lib/module/components/screen/parts/BottomSafeArea.js.map +1 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js +50 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -0
- package/lib/module/components/screen/types.js +4 -0
- package/lib/module/components/screen/types.js.map +1 -0
- package/lib/module/components/screen/utils.js +12 -9
- package/lib/module/components/screen/utils.js.map +1 -1
- package/lib/module/components/surface/Surface.js +16 -9
- package/lib/module/components/surface/Surface.js.map +1 -1
- package/lib/module/components/text/Text.js +23 -5
- package/lib/module/components/text/Text.js.map +1 -1
- package/lib/module/components/toast/Toast.js +8 -8
- package/lib/module/components/toast/Toast.js.map +1 -1
- package/lib/module/components/toolbar/Toolbar.js +268 -0
- package/lib/module/components/toolbar/Toolbar.js.map +1 -0
- package/lib/module/components/toolbar/index.js +4 -0
- package/lib/module/components/toolbar/index.js.map +1 -0
- package/lib/module/context/ThemeProvider.js +5 -2
- package/lib/module/context/ThemeProvider.js.map +1 -1
- package/lib/module/{components/icon/utils.js → theme/defaults/icons.js} +1 -1
- package/lib/module/theme/defaults/icons.js.map +1 -0
- package/lib/module/theme/defaults/image.js +30 -0
- package/lib/module/theme/defaults/image.js.map +1 -0
- package/lib/module/theme/index.js +1 -1
- package/lib/module/theme/{configure.js → registry.js} +21 -28
- package/lib/module/theme/registry.js.map +1 -0
- package/lib/module/theme/runtime.js +1 -1
- package/lib/module/theme/theme.js +91 -10
- package/lib/module/theme/theme.js.map +1 -1
- package/lib/module/theme/tokens/dark.js +59 -68
- package/lib/module/theme/tokens/dark.js.map +1 -1
- package/lib/module/theme/tokens/light.js +49 -67
- package/lib/module/theme/tokens/light.js.map +1 -1
- package/lib/module/theme/tokens/primitives.js +241 -20
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/module/utils/accent-utils.js +4 -4
- package/lib/module/utils/accent-utils.js.map +1 -1
- package/lib/typescript/src/components/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/avatar/types.d.ts +4 -4
- package/lib/typescript/src/components/avatar/types.d.ts.map +1 -1
- package/lib/typescript/src/components/box/Box.d.ts.map +1 -1
- package/lib/typescript/src/components/box/utils.d.ts +6 -1
- package/lib/typescript/src/components/box/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/button/Button.d.ts +1 -1
- package/lib/typescript/src/components/button/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/chip/Chip.d.ts +24 -0
- package/lib/typescript/src/components/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/index.d.ts +4 -0
- package/lib/typescript/src/components/chip/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/types.d.ts +8 -0
- package/lib/typescript/src/components/chip/types.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/utils.d.ts +8 -0
- package/lib/typescript/src/components/chip/utils.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts +45 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/index.d.ts +4 -0
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/types.d.ts +6 -0
- package/lib/typescript/src/components/empty-state/types.d.ts.map +1 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts +27 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts.map +1 -1
- package/lib/typescript/src/components/header/index.d.ts +1 -1
- package/lib/typescript/src/components/header/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts +7 -0
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts.map +1 -0
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +1 -1
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +18 -7
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/icon/Icon.d.ts.map +1 -1
- package/lib/typescript/src/components/image/Image.d.ts +16 -0
- package/lib/typescript/src/components/image/Image.d.ts.map +1 -0
- package/lib/typescript/src/components/image/index.d.ts +3 -0
- package/lib/typescript/src/components/image/index.d.ts.map +1 -0
- package/lib/typescript/src/components/image/types.d.ts +7 -0
- package/lib/typescript/src/components/image/types.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +4 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/Portal.d.ts +26 -0
- package/lib/typescript/src/components/portal/Portal.d.ts.map +1 -0
- package/lib/typescript/src/components/portal/PortalContext.d.ts +11 -14
- package/lib/typescript/src/components/portal/PortalContext.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalHost.d.ts +9 -4
- package/lib/typescript/src/components/portal/PortalHost.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalProvider.d.ts +3 -3
- package/lib/typescript/src/components/portal/PortalProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/index.d.ts +4 -2
- package/lib/typescript/src/components/portal/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/Screen.d.ts +18 -13
- package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenContext.d.ts +12 -9
- package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts +43 -0
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +17 -7
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +51 -16
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/index.d.ts +3 -0
- package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +30 -0
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +22 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/types.d.ts +42 -0
- package/lib/typescript/src/components/screen/types.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/utils.d.ts +9 -9
- package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/Surface.d.ts +7 -0
- package/lib/typescript/src/components/surface/Surface.d.ts.map +1 -1
- package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts +115 -0
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts.map +1 -0
- package/lib/typescript/src/components/toolbar/index.d.ts +3 -0
- package/lib/typescript/src/components/toolbar/index.d.ts.map +1 -0
- package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/{components/icon/utils.d.ts → theme/defaults/icons.d.ts} +2 -2
- package/lib/typescript/src/theme/defaults/icons.d.ts.map +1 -0
- package/lib/typescript/src/theme/defaults/image.d.ts +4 -0
- package/lib/typescript/src/theme/defaults/image.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +1 -1
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/{configure.d.ts → registry.d.ts} +1 -1
- package/lib/typescript/src/theme/registry.d.ts.map +1 -0
- package/lib/typescript/src/theme/theme.d.ts +21 -1
- package/lib/typescript/src/theme/theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/dark.d.ts +41 -64
- package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/light.d.ts +44 -65
- package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/primitives.d.ts +228 -17
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +65 -35
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/package.json +23 -9
- package/skills/terra-ui/SKILL.md +193 -0
- package/skills/terra-ui/references/recipes.md +198 -0
- package/skills/terra-ui/references/troubleshooting.md +112 -0
- package/src/components/avatar/Avatar.tsx +128 -38
- package/src/components/avatar/types.ts +5 -5
- package/src/components/box/Box.tsx +13 -1
- package/src/components/box/utils.ts +10 -0
- package/src/components/button/Button.tsx +7 -15
- package/src/components/chip/Chip.tsx +444 -0
- package/src/components/chip/index.ts +3 -0
- package/src/components/chip/types.ts +15 -0
- package/src/components/chip/utils.ts +34 -0
- package/src/components/empty-state/EmptyState.tsx +209 -0
- package/src/components/empty-state/index.ts +3 -0
- package/src/components/empty-state/types.ts +5 -0
- package/src/components/header/header-buttons.tsx +33 -45
- package/src/components/header/index.ts +2 -0
- package/src/components/header/parts/HeaderGradientOverlay.tsx +29 -0
- package/src/components/header/variants/LargeTitleHeader/index.tsx +68 -102
- package/src/components/header/variants/TitleHeader/index.tsx +47 -56
- package/src/components/icon/Icon.tsx +20 -15
- package/src/components/image/Image.tsx +48 -0
- package/src/components/image/index.ts +2 -0
- package/src/components/image/types.ts +7 -0
- package/src/components/index.ts +4 -0
- package/src/components/portal/Portal.tsx +55 -0
- package/src/components/portal/PortalContext.tsx +14 -22
- package/src/components/portal/PortalHost.tsx +17 -10
- package/src/components/portal/PortalProvider.tsx +58 -21
- package/src/components/portal/index.ts +4 -2
- package/src/components/screen/Screen.tsx +45 -59
- package/src/components/screen/ScreenContext.tsx +19 -12
- package/src/components/screen/ScreenFlashList.tsx +196 -0
- package/src/components/screen/ScreenFlatList.tsx +100 -39
- package/src/components/screen/ScreenScrollView.tsx +114 -53
- package/src/components/screen/index.ts +3 -0
- package/src/components/screen/parts/BottomSafeArea.tsx +60 -0
- package/src/components/screen/parts/ScreenPlaceholder.tsx +55 -0
- package/src/components/screen/types.ts +43 -0
- package/src/components/screen/utils.ts +29 -15
- package/src/components/surface/Surface.tsx +17 -20
- package/src/components/text/Text.tsx +23 -5
- package/src/components/toast/Toast.tsx +8 -8
- package/src/components/toolbar/Toolbar.tsx +353 -0
- package/src/components/toolbar/index.ts +6 -0
- package/src/context/ThemeProvider.tsx +5 -4
- package/src/{components/icon/utils.tsx → theme/defaults/icons.tsx} +1 -1
- package/src/theme/defaults/image.tsx +34 -0
- package/src/theme/index.ts +1 -1
- package/src/theme/{configure.ts → registry.ts} +19 -34
- package/src/theme/runtime.ts +1 -1
- package/src/theme/theme.ts +127 -13
- package/src/theme/tokens/dark.ts +60 -69
- package/src/theme/tokens/light.ts +50 -68
- package/src/theme/tokens/primitives.ts +248 -20
- package/src/theme/types.ts +67 -35
- package/src/utils/accent-utils.ts +4 -4
- package/lib/module/components/icon/utils.js.map +0 -1
- package/lib/module/theme/configure.js.map +0 -1
- package/lib/typescript/src/components/icon/utils.d.ts.map +0 -1
- package/lib/typescript/src/theme/configure.d.ts.map +0 -1
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
type ComponentRef,
|
|
4
|
+
createContext,
|
|
5
|
+
forwardRef,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
useContext,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { View, type ViewProps } from 'react-native';
|
|
10
|
+
|
|
11
|
+
import { StyleSheet, withUnistyles } from 'react-native-unistyles';
|
|
12
|
+
|
|
13
|
+
import type { ColorToken, TerraIconName, TextVariant } from '#theme/types';
|
|
14
|
+
|
|
15
|
+
import { Icon } from '../icon';
|
|
16
|
+
import { Text } from '../text';
|
|
17
|
+
import type { ChipColor, ChipSize, ChipVariant } from './types';
|
|
18
|
+
import { type CustomChipColors, getCustomColorColors } from './utils';
|
|
19
|
+
|
|
20
|
+
export interface ChipProps extends ViewProps {
|
|
21
|
+
size?: ChipSize;
|
|
22
|
+
variant?: ChipVariant;
|
|
23
|
+
color?: ChipColor;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Context (lets Chip.Icon read the resolved foreground + size) ─────────────
|
|
28
|
+
|
|
29
|
+
interface ChipContextValue {
|
|
30
|
+
/**
|
|
31
|
+
* Foreground as a **token** (or a raw literal for a custom color), never a
|
|
32
|
+
* resolved value — `Text` and `Icon` resolve it against the live theme, so
|
|
33
|
+
* each owns its own subscription and the label keeps working however the
|
|
34
|
+
* chip above it is rendered.
|
|
35
|
+
*/
|
|
36
|
+
color: ColorToken;
|
|
37
|
+
size: ChipSize;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const ChipContext = createContext<ChipContextValue>({
|
|
41
|
+
color: 'text.default',
|
|
42
|
+
size: 'md',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ─── Colors ─────────────────────────────────────────────────────────────────
|
|
46
|
+
//
|
|
47
|
+
// The chip's own paint (background + border) is a style, so named schemes
|
|
48
|
+
// (primary/secondary/success/warning/danger) are resolved declaratively via
|
|
49
|
+
// unistyles `compoundVariants` on `styles.base` — one entry per (color, variant)
|
|
50
|
+
// pair, straight from the spec's token table.
|
|
51
|
+
//
|
|
52
|
+
// The foreground is *not* a style on this View — it is inherited by the label
|
|
53
|
+
// and icon. It therefore lives here as a token table and is passed down
|
|
54
|
+
// unresolved, so `Text`/`Icon` resolve it against the live theme on every
|
|
55
|
+
// change.
|
|
56
|
+
//
|
|
57
|
+
// A custom color literal (hex/rgb/hsl) isn't a bounded set of options, so it
|
|
58
|
+
// can't be a compound variant condition — see `getCustomColorColors` in
|
|
59
|
+
// `utils.ts` for how it's derived instead.
|
|
60
|
+
|
|
61
|
+
const SCHEME_NAMES = new Set([
|
|
62
|
+
'primary',
|
|
63
|
+
'secondary',
|
|
64
|
+
'success',
|
|
65
|
+
'warning',
|
|
66
|
+
'danger',
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
type ChipScheme = 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
|
|
70
|
+
type ChipPaintedVariant = Exclude<ChipVariant, 'ghost'>;
|
|
71
|
+
|
|
72
|
+
/** Foreground token per (scheme, variant) — mirrors the spec's token table. */
|
|
73
|
+
const FG_TOKEN: Record<ChipScheme, Record<ChipPaintedVariant, ColorToken>> = {
|
|
74
|
+
primary: {
|
|
75
|
+
solid: 'action.fg.primary',
|
|
76
|
+
soft: 'action.fg.primary.subtle',
|
|
77
|
+
outline: 'text.accent',
|
|
78
|
+
},
|
|
79
|
+
secondary: {
|
|
80
|
+
solid: 'action.fg.secondary',
|
|
81
|
+
soft: 'action.fg.secondary.subtle',
|
|
82
|
+
outline: 'text.default',
|
|
83
|
+
},
|
|
84
|
+
success: {
|
|
85
|
+
solid: 'status.success.fg',
|
|
86
|
+
soft: 'status.success.fg.subtle',
|
|
87
|
+
outline: 'status.success.fg.subtle',
|
|
88
|
+
},
|
|
89
|
+
warning: {
|
|
90
|
+
solid: 'status.warning.fg',
|
|
91
|
+
soft: 'status.warning.fg.subtle',
|
|
92
|
+
outline: 'status.warning.fg.subtle',
|
|
93
|
+
},
|
|
94
|
+
danger: {
|
|
95
|
+
solid: 'status.danger.fg',
|
|
96
|
+
soft: 'status.danger.fg.subtle',
|
|
97
|
+
outline: 'status.danger.fg.subtle',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** The label/icon color as a token, so children resolve it per theme. */
|
|
102
|
+
function resolveForeground(
|
|
103
|
+
color: ChipColor,
|
|
104
|
+
variant: ChipVariant,
|
|
105
|
+
customColors: CustomChipColors | null
|
|
106
|
+
): ColorToken {
|
|
107
|
+
if (customColors) return customColors.fg;
|
|
108
|
+
if (variant === 'ghost') return 'text.default';
|
|
109
|
+
return FG_TOKEN[color as ChipScheme][variant];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Label sub-component ───────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
const LABEL_VARIANT: Record<ChipSize, TextVariant> = {
|
|
115
|
+
sm: 'label-sm',
|
|
116
|
+
md: 'label-md',
|
|
117
|
+
lg: 'label-lg',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const ChipLabel = ({ children }: { children: ReactNode }) => {
|
|
121
|
+
const { color, size } = useContext(ChipContext);
|
|
122
|
+
return (
|
|
123
|
+
<Text variant={LABEL_VARIANT[size]} weight="semibold" color={color}>
|
|
124
|
+
{children}
|
|
125
|
+
</Text>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// ─── Icon sub-component ────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
const ICON_SIZE: Record<ChipSize, number> = {
|
|
132
|
+
sm: 12,
|
|
133
|
+
md: 14,
|
|
134
|
+
lg: 16,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export interface ChipIconProps extends Omit<ViewProps, 'children'> {
|
|
138
|
+
name: TerraIconName;
|
|
139
|
+
size?: number;
|
|
140
|
+
color?: ColorToken;
|
|
141
|
+
strokeWidth?: number;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const ChipIcon = ({
|
|
145
|
+
name,
|
|
146
|
+
size: sizeProp,
|
|
147
|
+
color,
|
|
148
|
+
strokeWidth,
|
|
149
|
+
style,
|
|
150
|
+
...rest
|
|
151
|
+
}: ChipIconProps) => {
|
|
152
|
+
const { color: contextColor, size } = useContext(ChipContext);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Icon
|
|
156
|
+
name={name}
|
|
157
|
+
color={color || contextColor}
|
|
158
|
+
size={sizeProp ?? ICON_SIZE[size]}
|
|
159
|
+
strokeWidth={strokeWidth}
|
|
160
|
+
style={style}
|
|
161
|
+
{...rest}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
function renderChildren(children: ReactNode): ReactNode {
|
|
167
|
+
return Children.map(children, (child) => {
|
|
168
|
+
if (typeof child === 'string') {
|
|
169
|
+
return child.trim().length > 0 ? <ChipLabel>{child}</ChipLabel> : null;
|
|
170
|
+
}
|
|
171
|
+
if (typeof child === 'number') {
|
|
172
|
+
return <ChipLabel>{child}</ChipLabel>;
|
|
173
|
+
}
|
|
174
|
+
return child;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
const ChipRoot = forwardRef<ComponentRef<typeof View>, ChipProps>(function Chip(
|
|
181
|
+
{
|
|
182
|
+
size = 'md',
|
|
183
|
+
variant = 'soft',
|
|
184
|
+
color = 'secondary',
|
|
185
|
+
children,
|
|
186
|
+
style,
|
|
187
|
+
...rest
|
|
188
|
+
},
|
|
189
|
+
ref
|
|
190
|
+
) {
|
|
191
|
+
// Compound variants only match the 5 named schemes above — an arbitrary
|
|
192
|
+
// literal never matches one, so it's cast to satisfy `useVariants`'s
|
|
193
|
+
// literal-union type while still comparing against the real value below.
|
|
194
|
+
styles.useVariants({ color: color as ChipScheme, variant, size });
|
|
195
|
+
|
|
196
|
+
const isCustomColor = variant !== 'ghost' && !SCHEME_NAMES.has(color);
|
|
197
|
+
const customColors = isCustomColor
|
|
198
|
+
? getCustomColorColors(color, variant as 'solid' | 'soft' | 'outline')
|
|
199
|
+
: null;
|
|
200
|
+
|
|
201
|
+
const fg = resolveForeground(color, variant, customColors);
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<ChipContext.Provider value={{ color: fg, size }}>
|
|
205
|
+
<View
|
|
206
|
+
ref={ref}
|
|
207
|
+
style={[
|
|
208
|
+
styles.base,
|
|
209
|
+
customColors
|
|
210
|
+
? {
|
|
211
|
+
backgroundColor: customColors.bg,
|
|
212
|
+
borderColor: customColors.border,
|
|
213
|
+
borderWidth: customColors.borderWidth,
|
|
214
|
+
}
|
|
215
|
+
: null,
|
|
216
|
+
style,
|
|
217
|
+
]}
|
|
218
|
+
{...rest}
|
|
219
|
+
>
|
|
220
|
+
{renderChildren(children)}
|
|
221
|
+
</View>
|
|
222
|
+
</ChipContext.Provider>
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
type ChipComponent = typeof ChipRoot & {
|
|
227
|
+
Icon: typeof ChipIcon;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Subscribes the chip to theme changes. The background below comes from
|
|
231
|
+
// `compoundVariants`, which unistyles applies straight to the native node when
|
|
232
|
+
// the theme switches — no re-render, so React goes on believing the node holds
|
|
233
|
+
// the previous theme's value. React Native only mutates a node when newly
|
|
234
|
+
// rendered props differ from the last ones it rendered, so a later variant
|
|
235
|
+
// change that computes a value equal to that stale record emits no update at
|
|
236
|
+
// all, and the node keeps whatever the theme switch painted. That is reachable
|
|
237
|
+
// whenever one colour fills two roles across themes — an accent equal to
|
|
238
|
+
// another theme's `action.bg.secondary.subtle`, say — and it shows up as a
|
|
239
|
+
// deselected chip still wearing the selected background.
|
|
240
|
+
//
|
|
241
|
+
// Re-rendering on a theme switch commits the new value there and then, so no
|
|
242
|
+
// stale record survives to collide. `Text` already subscribes this way for its
|
|
243
|
+
// colour; this gives the chip's own paint the same guarantee. The cast keeps
|
|
244
|
+
// `ChipProps` and the ref, which `withUnistyles` widens to `Partial<{}>`.
|
|
245
|
+
export const Chip = withUnistyles(ChipRoot) as ChipComponent;
|
|
246
|
+
Chip.Icon = ChipIcon;
|
|
247
|
+
|
|
248
|
+
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
// One compound variant per (color, variant) cell of the spec's token table —
|
|
251
|
+
// `variants` below only registers the axis names/options for the type
|
|
252
|
+
// system; the actual bg/border values all come from `compoundVariants`.
|
|
253
|
+
// `size` has no cross-axis interaction, so it's a plain variant.
|
|
254
|
+
//
|
|
255
|
+
// Foreground is deliberately absent: it belongs to the label and icon, not to
|
|
256
|
+
// this View, and is handed to them as a token via `FG_TOKEN` / context.
|
|
257
|
+
const styles = StyleSheet.create((theme) => {
|
|
258
|
+
const c = theme.color;
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
base: {
|
|
262
|
+
flexDirection: 'row',
|
|
263
|
+
alignItems: 'center',
|
|
264
|
+
alignSelf: 'flex-start',
|
|
265
|
+
borderRadius: theme.radius.full,
|
|
266
|
+
variants: {
|
|
267
|
+
size: {
|
|
268
|
+
sm: {
|
|
269
|
+
minHeight: 24,
|
|
270
|
+
paddingVertical: 6,
|
|
271
|
+
paddingHorizontal: 12,
|
|
272
|
+
gap: 4,
|
|
273
|
+
},
|
|
274
|
+
md: {
|
|
275
|
+
minHeight: 26,
|
|
276
|
+
paddingVertical: 8,
|
|
277
|
+
paddingHorizontal: 14,
|
|
278
|
+
gap: 4,
|
|
279
|
+
},
|
|
280
|
+
lg: {
|
|
281
|
+
minHeight: 28,
|
|
282
|
+
paddingVertical: 8,
|
|
283
|
+
paddingHorizontal: 16,
|
|
284
|
+
gap: 4,
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
color: {
|
|
288
|
+
primary: {},
|
|
289
|
+
secondary: {},
|
|
290
|
+
success: {},
|
|
291
|
+
warning: {},
|
|
292
|
+
danger: {},
|
|
293
|
+
},
|
|
294
|
+
variant: {
|
|
295
|
+
solid: {},
|
|
296
|
+
soft: {},
|
|
297
|
+
outline: {},
|
|
298
|
+
ghost: {
|
|
299
|
+
backgroundColor: 'transparent',
|
|
300
|
+
borderColor: 'transparent',
|
|
301
|
+
borderWidth: 0,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
compoundVariants: [
|
|
306
|
+
{
|
|
307
|
+
color: 'primary',
|
|
308
|
+
variant: 'solid',
|
|
309
|
+
styles: {
|
|
310
|
+
backgroundColor: c['action.bg.primary'],
|
|
311
|
+
borderColor: c['action.bg.primary'],
|
|
312
|
+
borderWidth: 0,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
color: 'primary',
|
|
317
|
+
variant: 'soft',
|
|
318
|
+
styles: {
|
|
319
|
+
backgroundColor: c['action.bg.primary.subtle'],
|
|
320
|
+
borderColor: c['action.bg.primary.subtle'],
|
|
321
|
+
borderWidth: 0,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
color: 'primary',
|
|
326
|
+
variant: 'outline',
|
|
327
|
+
styles: {
|
|
328
|
+
backgroundColor: 'transparent',
|
|
329
|
+
borderColor: c['border.accent'],
|
|
330
|
+
borderWidth: 1,
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
color: 'secondary',
|
|
335
|
+
variant: 'solid',
|
|
336
|
+
styles: {
|
|
337
|
+
backgroundColor: c['action.bg.secondary'],
|
|
338
|
+
borderColor: c['action.bg.secondary'],
|
|
339
|
+
borderWidth: 0,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
color: 'secondary',
|
|
344
|
+
variant: 'soft',
|
|
345
|
+
styles: {
|
|
346
|
+
backgroundColor: c['action.bg.secondary.subtle'],
|
|
347
|
+
borderColor: c['action.bg.secondary.subtle'],
|
|
348
|
+
borderWidth: 0,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
color: 'secondary',
|
|
353
|
+
variant: 'outline',
|
|
354
|
+
styles: {
|
|
355
|
+
backgroundColor: 'transparent',
|
|
356
|
+
borderColor: c['border.default'],
|
|
357
|
+
borderWidth: 1,
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
color: 'success',
|
|
362
|
+
variant: 'solid',
|
|
363
|
+
styles: {
|
|
364
|
+
backgroundColor: c['status.success.bg'],
|
|
365
|
+
borderColor: c['status.success.bg'],
|
|
366
|
+
borderWidth: 0,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
color: 'success',
|
|
371
|
+
variant: 'soft',
|
|
372
|
+
styles: {
|
|
373
|
+
backgroundColor: c['status.success.bg.subtle'],
|
|
374
|
+
borderColor: c['status.success.bg.subtle'],
|
|
375
|
+
borderWidth: 0,
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
color: 'success',
|
|
380
|
+
variant: 'outline',
|
|
381
|
+
styles: {
|
|
382
|
+
backgroundColor: 'transparent',
|
|
383
|
+
borderColor: c['status.success.border'],
|
|
384
|
+
borderWidth: 1,
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
color: 'warning',
|
|
389
|
+
variant: 'solid',
|
|
390
|
+
styles: {
|
|
391
|
+
backgroundColor: c['status.warning.bg'],
|
|
392
|
+
borderColor: c['status.warning.bg'],
|
|
393
|
+
borderWidth: 0,
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
color: 'warning',
|
|
398
|
+
variant: 'soft',
|
|
399
|
+
styles: {
|
|
400
|
+
backgroundColor: c['status.warning.bg.subtle'],
|
|
401
|
+
borderColor: c['status.warning.bg.subtle'],
|
|
402
|
+
borderWidth: 0,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
color: 'warning',
|
|
407
|
+
variant: 'outline',
|
|
408
|
+
styles: {
|
|
409
|
+
backgroundColor: 'transparent',
|
|
410
|
+
borderColor: c['status.warning.border'],
|
|
411
|
+
borderWidth: 1,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
color: 'danger',
|
|
416
|
+
variant: 'solid',
|
|
417
|
+
styles: {
|
|
418
|
+
backgroundColor: c['status.danger.bg'],
|
|
419
|
+
borderColor: c['status.danger.bg'],
|
|
420
|
+
borderWidth: 0,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
color: 'danger',
|
|
425
|
+
variant: 'soft',
|
|
426
|
+
styles: {
|
|
427
|
+
backgroundColor: c['status.danger.bg.subtle'],
|
|
428
|
+
borderColor: c['status.danger.bg.subtle'],
|
|
429
|
+
borderWidth: 0,
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
color: 'danger',
|
|
434
|
+
variant: 'outline',
|
|
435
|
+
styles: {
|
|
436
|
+
backgroundColor: 'transparent',
|
|
437
|
+
borderColor: c['status.danger.border'],
|
|
438
|
+
borderWidth: 1,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ChipSize = 'sm' | 'md' | 'lg';
|
|
2
|
+
export type ChipVariant = 'solid' | 'soft' | 'outline' | 'ghost';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Semantic color scheme, or a raw CSS color literal (`#fff`, `rgb(...)`, `hsl(...)`).
|
|
6
|
+
* Has no effect when `variant="ghost"` — ghost always renders `text.default`.
|
|
7
|
+
*/
|
|
8
|
+
export type ChipColor =
|
|
9
|
+
| 'primary'
|
|
10
|
+
| 'secondary'
|
|
11
|
+
| 'success'
|
|
12
|
+
| 'warning'
|
|
13
|
+
| 'danger'
|
|
14
|
+
// literals last so scheme-name autocomplete still surfaces
|
|
15
|
+
| (string & {});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readableOn, withAlpha } from '#utils/color-utils';
|
|
2
|
+
|
|
3
|
+
// A custom color literal (hex/rgb/hsl) can't be a unistyles compound variant
|
|
4
|
+
// condition (it's not a bounded set of options), so it's derived here
|
|
5
|
+
// instead, the same way `utils/accent-utils.ts` expands a single hue into a
|
|
6
|
+
// full color scheme.
|
|
7
|
+
|
|
8
|
+
export interface CustomChipColors {
|
|
9
|
+
bg: string;
|
|
10
|
+
border: string;
|
|
11
|
+
borderWidth: number;
|
|
12
|
+
fg: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CUSTOM_COLOR_SOFT_ALPHA = 0.16;
|
|
16
|
+
|
|
17
|
+
export function getCustomColorColors(
|
|
18
|
+
literal: string,
|
|
19
|
+
variant: 'solid' | 'soft' | 'outline'
|
|
20
|
+
): CustomChipColors {
|
|
21
|
+
if (variant === 'outline') {
|
|
22
|
+
return { bg: 'transparent', border: literal, borderWidth: 1, fg: literal };
|
|
23
|
+
}
|
|
24
|
+
if (variant === 'soft') {
|
|
25
|
+
const bg = withAlpha(literal, CUSTOM_COLOR_SOFT_ALPHA);
|
|
26
|
+
return { bg, border: bg, borderWidth: 0, fg: literal };
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
bg: literal,
|
|
30
|
+
border: literal,
|
|
31
|
+
borderWidth: 0,
|
|
32
|
+
fg: readableOn(literal),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { type ComponentRef, forwardRef, type ReactNode } from 'react';
|
|
2
|
+
import { View, type ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
+
|
|
6
|
+
import type { ColorToken, TerraIconName, TextVariant } from '#theme/types';
|
|
7
|
+
|
|
8
|
+
import { Icon } from '../icon';
|
|
9
|
+
import { Text } from '../text';
|
|
10
|
+
import type { EmptyStateSize } from './types';
|
|
11
|
+
|
|
12
|
+
export interface EmptyStateProps extends Omit<ViewProps, 'children'> {
|
|
13
|
+
/** Icon rendered in the circular badge above the title. */
|
|
14
|
+
icon?: TerraIconName;
|
|
15
|
+
/** Icon tint. Defaults to `text.subtle`. */
|
|
16
|
+
iconColor?: ColorToken;
|
|
17
|
+
/**
|
|
18
|
+
* Replaces the icon badge entirely — an illustration, `Image`, or any custom
|
|
19
|
+
* node. Takes precedence over `icon`.
|
|
20
|
+
*/
|
|
21
|
+
media?: ReactNode;
|
|
22
|
+
/** Headline. A bare string is wrapped in the size's title `Text`. */
|
|
23
|
+
title: ReactNode;
|
|
24
|
+
/** Supporting copy under the title. A bare string gets a muted body `Text`. */
|
|
25
|
+
description?: ReactNode;
|
|
26
|
+
/** Call to action — typically a `Button`, or a fragment of two. */
|
|
27
|
+
action?: ReactNode;
|
|
28
|
+
size?: EmptyStateSize;
|
|
29
|
+
/**
|
|
30
|
+
* Expand to fill the parent (`flex: 1` plus cross-axis stretch) so the
|
|
31
|
+
* content sits in the middle of it rather than hugging its own height.
|
|
32
|
+
* Inside a `ScrollView`, put the flex on `contentContainerStyle` instead —
|
|
33
|
+
* a scroll view's children cannot flex against its content size.
|
|
34
|
+
*/
|
|
35
|
+
fill?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Per-size lookups ─────────────────────────────────────────────────────────
|
|
39
|
+
//
|
|
40
|
+
// These feed *props* (a `Text` variant, an `Icon` size in dp), not styles, so
|
|
41
|
+
// they stay plain tables here; everything that is a style is a unistyles
|
|
42
|
+
// variant below.
|
|
43
|
+
|
|
44
|
+
const TYPOGRAPHY: Record<
|
|
45
|
+
EmptyStateSize,
|
|
46
|
+
{ title: TextVariant; description: TextVariant }
|
|
47
|
+
> = {
|
|
48
|
+
sm: { title: 'title-sm', description: 'body-sm' },
|
|
49
|
+
md: { title: 'title-md', description: 'body-md' },
|
|
50
|
+
lg: { title: 'title-lg', description: 'body-lg' },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const ICON_SIZE: Record<EmptyStateSize, number> = {
|
|
54
|
+
sm: 28,
|
|
55
|
+
md: 34,
|
|
56
|
+
lg: 44,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Wraps a bare string/number in the slot's themed `Text`; elements pass through. */
|
|
60
|
+
function renderSlot(
|
|
61
|
+
node: ReactNode,
|
|
62
|
+
wrap: (value: ReactNode) => ReactNode
|
|
63
|
+
): ReactNode {
|
|
64
|
+
if (node === null || node === undefined || node === false) return null;
|
|
65
|
+
return typeof node === 'string' || typeof node === 'number'
|
|
66
|
+
? wrap(node)
|
|
67
|
+
: node;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Centered placeholder for a screen, list, or panel with nothing to show:
|
|
72
|
+
* a large icon, a title, optional supporting copy, and an optional action.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <EmptyState
|
|
77
|
+
* icon="status.info"
|
|
78
|
+
* title="No results"
|
|
79
|
+
* description="Try a different search term or clear your filters."
|
|
80
|
+
* action={<Button onPress={reset}>Clear filters</Button>}
|
|
81
|
+
* />
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const EmptyState = forwardRef<
|
|
85
|
+
ComponentRef<typeof View>,
|
|
86
|
+
EmptyStateProps
|
|
87
|
+
>(function EmptyState(
|
|
88
|
+
{
|
|
89
|
+
icon,
|
|
90
|
+
iconColor = 'text.default',
|
|
91
|
+
media,
|
|
92
|
+
title,
|
|
93
|
+
description,
|
|
94
|
+
action,
|
|
95
|
+
size = 'md',
|
|
96
|
+
fill = false,
|
|
97
|
+
style,
|
|
98
|
+
...rest
|
|
99
|
+
},
|
|
100
|
+
ref
|
|
101
|
+
) {
|
|
102
|
+
styles.useVariants({ size, fill });
|
|
103
|
+
|
|
104
|
+
const typography = TYPOGRAPHY[size];
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<View ref={ref} style={[styles.root, style]} {...rest}>
|
|
108
|
+
{media ??
|
|
109
|
+
(icon ? (
|
|
110
|
+
<View style={styles.iconBadge}>
|
|
111
|
+
<Icon name={icon} size={ICON_SIZE[size]} color={iconColor} />
|
|
112
|
+
</View>
|
|
113
|
+
) : null)}
|
|
114
|
+
|
|
115
|
+
<View style={styles.text}>
|
|
116
|
+
{renderSlot(title, (value) => (
|
|
117
|
+
<Text weight="semibold" variant={typography.title} align="center">
|
|
118
|
+
{value}
|
|
119
|
+
</Text>
|
|
120
|
+
))}
|
|
121
|
+
{renderSlot(description, (value) => (
|
|
122
|
+
<Text
|
|
123
|
+
variant={typography.description}
|
|
124
|
+
color="text.subtle"
|
|
125
|
+
align="center"
|
|
126
|
+
>
|
|
127
|
+
{value}
|
|
128
|
+
</Text>
|
|
129
|
+
))}
|
|
130
|
+
</View>
|
|
131
|
+
|
|
132
|
+
{action ? <View style={styles.actions}>{action}</View> : null}
|
|
133
|
+
</View>
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
138
|
+
//
|
|
139
|
+
// `size` and `fill` are the only style axes, and neither interacts with the
|
|
140
|
+
// other, so a plain variant per element does the whole job — no compound
|
|
141
|
+
// variants needed.
|
|
142
|
+
// The badge is sized by padding around the icon rather than an explicit
|
|
143
|
+
// width/height, which keeps it a perfect circle on the spacing scale.
|
|
144
|
+
const styles = StyleSheet.create((theme) => ({
|
|
145
|
+
root: {
|
|
146
|
+
alignItems: 'center',
|
|
147
|
+
justifyContent: 'center',
|
|
148
|
+
variants: {
|
|
149
|
+
// `alignSelf` matters as much as `flex` here: a column parent hands its
|
|
150
|
+
// children the cross-axis size of their content unless they stretch, so
|
|
151
|
+
// without it the state would fill the height and still be pinned to the
|
|
152
|
+
// width of its longest line.
|
|
153
|
+
fill: {
|
|
154
|
+
true: { flex: 1, alignSelf: 'stretch' },
|
|
155
|
+
false: {},
|
|
156
|
+
},
|
|
157
|
+
size: {
|
|
158
|
+
sm: {
|
|
159
|
+
gap: theme.spacing['2'],
|
|
160
|
+
paddingVertical: theme.spacing['4'],
|
|
161
|
+
paddingHorizontal: theme.spacing['4'],
|
|
162
|
+
},
|
|
163
|
+
md: {
|
|
164
|
+
gap: theme.spacing['3'],
|
|
165
|
+
paddingVertical: theme.spacing['6'],
|
|
166
|
+
paddingHorizontal: theme.spacing['6'],
|
|
167
|
+
},
|
|
168
|
+
lg: {
|
|
169
|
+
gap: theme.spacing['4'],
|
|
170
|
+
paddingVertical: theme.spacing['8'],
|
|
171
|
+
paddingHorizontal: theme.spacing['6'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
iconBadge: {
|
|
177
|
+
alignItems: 'center',
|
|
178
|
+
justifyContent: 'center',
|
|
179
|
+
backgroundColor: theme.color['action.bg.primary.subtle'],
|
|
180
|
+
borderRadius: theme.radius.full,
|
|
181
|
+
variants: {
|
|
182
|
+
size: {
|
|
183
|
+
sm: { padding: theme.spacing['3'] },
|
|
184
|
+
md: { padding: theme.spacing['4'] },
|
|
185
|
+
lg: { padding: theme.spacing['5'] },
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
text: {
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
variants: {
|
|
192
|
+
size: {
|
|
193
|
+
sm: { gap: theme.spacing['1'] },
|
|
194
|
+
md: { gap: theme.spacing['1'] },
|
|
195
|
+
lg: { gap: theme.spacing['2'] },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
actions: {
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
variants: {
|
|
202
|
+
size: {
|
|
203
|
+
sm: { gap: theme.spacing['2'], marginTop: theme.spacing['1'] },
|
|
204
|
+
md: { gap: theme.spacing['3'], marginTop: theme.spacing['2'] },
|
|
205
|
+
lg: { gap: theme.spacing['3'], marginTop: theme.spacing['2'] },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}));
|