radix-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/index.cjs +4323 -0
  4. package/dist/index.d.cts +861 -0
  5. package/dist/index.d.mts +861 -0
  6. package/dist/index.d.ts +861 -0
  7. package/dist/index.mjs +4285 -0
  8. package/package.json +51 -0
  9. package/src/components/actions/Button.tsx +337 -0
  10. package/src/components/actions/Checkbox.tsx +216 -0
  11. package/src/components/actions/CheckboxCards.tsx +257 -0
  12. package/src/components/actions/CheckboxGroup.tsx +249 -0
  13. package/src/components/actions/index.ts +4 -0
  14. package/src/components/layout/Box.tsx +108 -0
  15. package/src/components/layout/Flex.tsx +149 -0
  16. package/src/components/layout/Grid.tsx +224 -0
  17. package/src/components/layout/index.ts +9 -0
  18. package/src/components/playground/ThemeControls.tsx +456 -0
  19. package/src/components/playground/index.ts +1 -0
  20. package/src/components/typography/Blockquote.tsx +137 -0
  21. package/src/components/typography/Code.tsx +164 -0
  22. package/src/components/typography/Em.tsx +68 -0
  23. package/src/components/typography/Heading.tsx +136 -0
  24. package/src/components/typography/Kbd.tsx +162 -0
  25. package/src/components/typography/Link.tsx +173 -0
  26. package/src/components/typography/Quote.tsx +71 -0
  27. package/src/components/typography/Strong.tsx +70 -0
  28. package/src/components/typography/Text.tsx +140 -0
  29. package/src/components/typography/index.ts +9 -0
  30. package/src/hooks/useResolveColor.ts +24 -0
  31. package/src/hooks/useThemeContext.ts +11 -0
  32. package/src/index.ts +63 -0
  33. package/src/theme/Theme.tsx +12 -0
  34. package/src/theme/ThemeContext.ts +4 -0
  35. package/src/theme/ThemeImpl.tsx +54 -0
  36. package/src/theme/ThemeRoot.tsx +65 -0
  37. package/src/theme/createTheme.tsx +17 -0
  38. package/src/theme/resolveGrayColor.ts +38 -0
  39. package/src/theme/theme.types.ts +95 -0
  40. package/src/tokens/colors/amber.ts +28 -0
  41. package/src/tokens/colors/blue.ts +28 -0
  42. package/src/tokens/colors/bronze.ts +28 -0
  43. package/src/tokens/colors/brown.ts +28 -0
  44. package/src/tokens/colors/crimson.ts +28 -0
  45. package/src/tokens/colors/cyan.ts +28 -0
  46. package/src/tokens/colors/gold.ts +28 -0
  47. package/src/tokens/colors/grass.ts +28 -0
  48. package/src/tokens/colors/gray.ts +28 -0
  49. package/src/tokens/colors/green.ts +28 -0
  50. package/src/tokens/colors/index.ts +36 -0
  51. package/src/tokens/colors/indigo.ts +28 -0
  52. package/src/tokens/colors/iris.ts +28 -0
  53. package/src/tokens/colors/jade.ts +28 -0
  54. package/src/tokens/colors/lime.ts +28 -0
  55. package/src/tokens/colors/mauve.ts +28 -0
  56. package/src/tokens/colors/mint.ts +28 -0
  57. package/src/tokens/colors/olive.ts +28 -0
  58. package/src/tokens/colors/orange.ts +28 -0
  59. package/src/tokens/colors/pink.ts +28 -0
  60. package/src/tokens/colors/plum.ts +28 -0
  61. package/src/tokens/colors/purple.ts +28 -0
  62. package/src/tokens/colors/red.ts +28 -0
  63. package/src/tokens/colors/ruby.ts +28 -0
  64. package/src/tokens/colors/sage.ts +28 -0
  65. package/src/tokens/colors/sand.ts +28 -0
  66. package/src/tokens/colors/sky.ts +28 -0
  67. package/src/tokens/colors/slate.ts +28 -0
  68. package/src/tokens/colors/teal.ts +28 -0
  69. package/src/tokens/colors/tomato.ts +28 -0
  70. package/src/tokens/colors/types.ts +69 -0
  71. package/src/tokens/colors/violet.ts +28 -0
  72. package/src/tokens/colors/yellow.ts +28 -0
  73. package/src/tokens/index.ts +5 -0
  74. package/src/tokens/radius.ts +56 -0
  75. package/src/tokens/scaling.ts +10 -0
  76. package/src/tokens/spacing.ts +21 -0
  77. package/src/tokens/typography.ts +60 -0
  78. package/src/utils/applyScaling.ts +6 -0
  79. package/src/utils/classicEffect.ts +46 -0
  80. package/src/utils/resolveColor.ts +69 -0
  81. package/src/utils/resolveSpace.ts +13 -0
@@ -0,0 +1,257 @@
1
+ import React, { createContext, useCallback, useContext, useMemo } from 'react'
2
+ import { Pressable, View } from 'react-native'
3
+ import type { StyleProp, ViewStyle } from 'react-native'
4
+ import { useThemeContext } from '../../hooks/useThemeContext'
5
+ import { useResolveColor } from '../../hooks/useResolveColor'
6
+ import { resolveSpace } from '../../utils/resolveSpace'
7
+ import { scalingMap } from '../../tokens/scaling'
8
+ import { getRadius } from '../../tokens/radius'
9
+ import type { MarginToken, SpaceToken } from '../../tokens/spacing'
10
+ import type { AccentColor } from '../../tokens/colors/types'
11
+ import { Checkbox, type CheckboxSize } from './Checkbox'
12
+
13
+ // ─── Types ──────────────────────────────────────────────────────────────────────
14
+
15
+ export type CheckboxCardsVariant = 'surface' | 'classic'
16
+
17
+ // ─── Context ──────────────────────────────────────────────────────────────────
18
+
19
+ interface CheckboxCardsContextValue {
20
+ size: CheckboxSize
21
+ variant: CheckboxCardsVariant
22
+ color?: AccentColor
23
+ highContrast?: boolean
24
+ disabled: boolean
25
+ value: string[]
26
+ onItemToggle: (itemValue: string) => void
27
+ }
28
+
29
+ const CheckboxCardsContext = createContext<CheckboxCardsContextValue | null>(null)
30
+
31
+ // ─── Root Types ─────────────────────────────────────────────────────────────────
32
+
33
+ export interface CheckboxCardsProps {
34
+ /** Card size (1–3). Default: 2. */
35
+ size?: CheckboxSize
36
+ /** Visual variant. Default: 'surface'. */
37
+ variant?: CheckboxCardsVariant
38
+ /** Accent color. Default: theme accent. */
39
+ color?: AccentColor
40
+ /** Increases color contrast with the background. */
41
+ highContrast?: boolean
42
+ /** Number of columns. Default: 1. */
43
+ columns?: number
44
+ /** Gap between cards as space token. Default: 4. */
45
+ gap?: SpaceToken
46
+ /** Controlled selected values. */
47
+ value?: string[]
48
+ /** Uncontrolled default selected values. */
49
+ defaultValue?: string[]
50
+ /** Called when selected values change. */
51
+ onValueChange?: (value: string[]) => void
52
+ /** Disables all cards. */
53
+ disabled?: boolean
54
+ /** Card items. */
55
+ children?: React.ReactNode
56
+ // ─── Margin props ──────────────────────────────────────────────────────────
57
+ m?: MarginToken
58
+ mx?: MarginToken
59
+ my?: MarginToken
60
+ mt?: MarginToken
61
+ mr?: MarginToken
62
+ mb?: MarginToken
63
+ ml?: MarginToken
64
+ style?: StyleProp<ViewStyle>
65
+ }
66
+
67
+ // ─── Item Types ─────────────────────────────────────────────────────────────────
68
+
69
+ export interface CheckboxCardsItemProps {
70
+ /** Unique value identifying this card. */
71
+ value: string
72
+ /** Disables this individual card. */
73
+ disabled?: boolean
74
+ /** Card content. */
75
+ children?: React.ReactNode
76
+ style?: StyleProp<ViewStyle>
77
+ }
78
+
79
+ // ─── Size tokens (matching Radix CSS vars) ──────────────────────────────────────
80
+ // Radix uses: padding-left = space-3/4/5, padding-top/bottom = space-3/1.2, space-4*0.875, space-5/1.2
81
+ // checkbox-size = space-4*0.875, space-4, space-4*1.25
82
+ // padding-right = padding-left * 2 + checkbox-size
83
+
84
+ const SIZE_CONFIG: Record<CheckboxSize, {
85
+ paddingLeft: number
86
+ paddingY: number
87
+ checkboxSize: number
88
+ radiusLevel: 3 | 4
89
+ }> = {
90
+ 1: { paddingLeft: 12, paddingY: 10, checkboxSize: 14, radiusLevel: 3 },
91
+ 2: { paddingLeft: 16, paddingY: 14, checkboxSize: 16, radiusLevel: 3 },
92
+ 3: { paddingLeft: 24, paddingY: 20, checkboxSize: 20, radiusLevel: 4 },
93
+ }
94
+
95
+ // ─── Root Component ─────────────────────────────────────────────────────────────
96
+
97
+ function CheckboxCardsRoot({
98
+ size = 2,
99
+ variant = 'surface',
100
+ color,
101
+ highContrast,
102
+ columns = 1,
103
+ gap: gapProp = 4,
104
+ value: valueProp,
105
+ defaultValue = [],
106
+ onValueChange,
107
+ disabled = false,
108
+ children,
109
+ m, mx, my, mt, mr, mb, ml,
110
+ style,
111
+ }: CheckboxCardsProps) {
112
+ const { scaling } = useThemeContext()
113
+
114
+ const [internal, setInternal] = React.useState<string[]>(defaultValue)
115
+ const isControlled = valueProp !== undefined
116
+ const value = isControlled ? valueProp : internal
117
+
118
+ const onItemToggle = useCallback((itemValue: string) => {
119
+ const next = value.includes(itemValue)
120
+ ? value.filter(v => v !== itemValue)
121
+ : [...value, itemValue]
122
+ if (!isControlled) setInternal(next)
123
+ onValueChange?.(next)
124
+ }, [value, isControlled, onValueChange])
125
+
126
+ const ctx = useMemo<CheckboxCardsContextValue>(() => ({
127
+ size, variant, color, highContrast, disabled, value, onItemToggle,
128
+ }), [size, variant, color, highContrast, disabled, value, onItemToggle])
129
+
130
+ const sp = (token: MarginToken | undefined): number | undefined =>
131
+ token !== undefined ? resolveSpace(token, scaling) : undefined
132
+
133
+ const resolvedGap = resolveSpace(gapProp, scaling)
134
+
135
+ return (
136
+ <CheckboxCardsContext.Provider value={ctx}>
137
+ <View
138
+ style={[
139
+ {
140
+ flexDirection: 'row',
141
+ flexWrap: 'wrap',
142
+ gap: resolvedGap,
143
+ marginTop: sp(mt ?? my ?? m),
144
+ marginBottom: sp(mb ?? my ?? m),
145
+ marginLeft: sp(ml ?? mx ?? m),
146
+ marginRight: sp(mr ?? mx ?? m),
147
+ },
148
+ style,
149
+ ]}
150
+ >
151
+ {React.Children.map(children, child => (
152
+ <View style={{
153
+ flexBasis: columns > 1 ? `${100 / columns}%` as unknown as number : undefined,
154
+ flexGrow: 1,
155
+ flexShrink: 1,
156
+ minWidth: 0,
157
+ }}>
158
+ {child}
159
+ </View>
160
+ ))}
161
+ </View>
162
+ </CheckboxCardsContext.Provider>
163
+ )
164
+ }
165
+ CheckboxCardsRoot.displayName = 'CheckboxCards.Root'
166
+
167
+ // ─── Item Component ─────────────────────────────────────────────────────────────
168
+
169
+ function CheckboxCardsItem({
170
+ value: itemValue,
171
+ disabled: disabledProp,
172
+ children,
173
+ style,
174
+ }: CheckboxCardsItemProps) {
175
+ const ctx = useContext(CheckboxCardsContext)
176
+ if (!ctx) throw new Error('CheckboxCards.Item must be used within CheckboxCards.Root')
177
+
178
+ const { size, variant, color, highContrast, disabled: groupDisabled, value, onItemToggle } = ctx
179
+ const { scaling, radius } = useThemeContext()
180
+ const rc = useResolveColor()
181
+
182
+ const isDisabled = disabledProp ?? groupDisabled
183
+ const isChecked = value.includes(itemValue)
184
+
185
+ const handlePress = useCallback(() => {
186
+ if (!isDisabled) onItemToggle(itemValue)
187
+ }, [isDisabled, onItemToggle, itemValue])
188
+
189
+ const scalingFactor = scalingMap[scaling]
190
+ const cfg = SIZE_CONFIG[size]
191
+ const paddingLeft = Math.round(cfg.paddingLeft * scalingFactor)
192
+ const paddingY = Math.round(cfg.paddingY * scalingFactor)
193
+ const checkboxSize = Math.round(cfg.checkboxSize * scalingFactor)
194
+ // Radix CSS: padding-right = padding-left * 2 + checkbox-size
195
+ const paddingRight = paddingLeft * 2 + checkboxSize
196
+ const borderRadius = getRadius(radius, cfg.radiusLevel)
197
+
198
+ type C = Parameters<typeof rc>[0]
199
+
200
+ // Card border & background — does NOT change on checked state (matches Radix)
201
+ // Surface: box-shadow: 0 0 0 1px gray-a5 (simulated with borderWidth)
202
+ // Classic: similar border + outer shadow
203
+ const borderColor = rc('gray-a5' as C)
204
+ const bgColor = rc('gray-surface' as C)
205
+
206
+ const cardStyle: ViewStyle = {
207
+ position: 'relative',
208
+ overflow: 'hidden',
209
+ paddingLeft,
210
+ paddingRight,
211
+ paddingTop: paddingY,
212
+ paddingBottom: paddingY,
213
+ borderRadius,
214
+ borderWidth: 1,
215
+ borderColor,
216
+ backgroundColor: bgColor,
217
+ opacity: isDisabled ? 0.5 : undefined,
218
+ }
219
+
220
+ return (
221
+ <Pressable
222
+ onPress={handlePress}
223
+ disabled={isDisabled}
224
+ accessibilityRole="checkbox"
225
+ accessibilityState={{ checked: isChecked, disabled: isDisabled }}
226
+ style={[cardStyle, style]}
227
+ >
228
+ {children}
229
+ {/* Checkbox absolutely positioned on the right, matching Radix CSS */}
230
+ <View style={{
231
+ position: 'absolute',
232
+ right: paddingLeft,
233
+ top: 0,
234
+ bottom: 0,
235
+ justifyContent: 'center',
236
+ }}>
237
+ <Checkbox
238
+ size={size}
239
+ variant="surface"
240
+ color={color}
241
+ highContrast={highContrast}
242
+ checked={isChecked}
243
+ onCheckedChange={() => onItemToggle(itemValue)}
244
+ disabled={isDisabled}
245
+ />
246
+ </View>
247
+ </Pressable>
248
+ )
249
+ }
250
+ CheckboxCardsItem.displayName = 'CheckboxCards.Item'
251
+
252
+ // ─── Compound export ────────────────────────────────────────────────────────────
253
+
254
+ export const CheckboxCards = Object.assign(CheckboxCardsRoot, {
255
+ Root: CheckboxCardsRoot,
256
+ Item: CheckboxCardsItem,
257
+ })
@@ -0,0 +1,249 @@
1
+ import React, { createContext, useCallback, useContext, useMemo } from 'react'
2
+ import { Pressable, View } from 'react-native'
3
+ import { Text as RNText } from 'react-native'
4
+ import type { StyleProp, ViewStyle, TextStyle } from 'react-native'
5
+ import { useThemeContext } from '../../hooks/useThemeContext'
6
+ import { useResolveColor } from '../../hooks/useResolveColor'
7
+ import { resolveSpace } from '../../utils/resolveSpace'
8
+ import { fontSize, lineHeight, letterSpacingEm } from '../../tokens/typography'
9
+ import { scalingMap } from '../../tokens/scaling'
10
+ import type { MarginToken } from '../../tokens/spacing'
11
+ import type { AccentColor } from '../../tokens/colors/types'
12
+ import { Checkbox, type CheckboxSize, type CheckboxVariant } from './Checkbox'
13
+
14
+ // ─── Context ──────────────────────────────────────────────────────────────────
15
+
16
+ interface CheckboxGroupContextValue {
17
+ size: CheckboxSize
18
+ variant: CheckboxVariant
19
+ color?: AccentColor
20
+ highContrast?: boolean
21
+ disabled: boolean
22
+ value: string[]
23
+ onItemToggle: (itemValue: string) => void
24
+ }
25
+
26
+ const CheckboxGroupContext = createContext<CheckboxGroupContextValue | null>(null)
27
+
28
+ // ─── Root Types ─────────────────────────────────────────────────────────────────
29
+
30
+ export interface CheckboxGroupProps {
31
+ /** Checkbox size (1–3). Default: 2. */
32
+ size?: CheckboxSize
33
+ /** Visual variant. Default: 'surface'. */
34
+ variant?: CheckboxVariant
35
+ /** Accent color. Default: theme accent. */
36
+ color?: AccentColor
37
+ /** Increases color contrast with the background. */
38
+ highContrast?: boolean
39
+ /** Controlled selected values. */
40
+ value?: string[]
41
+ /** Uncontrolled default selected values. */
42
+ defaultValue?: string[]
43
+ /** Called when selected values change. */
44
+ onValueChange?: (value: string[]) => void
45
+ /** Disables all checkboxes in the group. */
46
+ disabled?: boolean
47
+ /** Group children (CheckboxGroup.Item). */
48
+ children?: React.ReactNode
49
+ // ─── Margin props ──────────────────────────────────────────────────────────
50
+ m?: MarginToken
51
+ mx?: MarginToken
52
+ my?: MarginToken
53
+ mt?: MarginToken
54
+ mr?: MarginToken
55
+ mb?: MarginToken
56
+ ml?: MarginToken
57
+ style?: StyleProp<ViewStyle>
58
+ }
59
+
60
+ // ─── Item Types ─────────────────────────────────────────────────────────────────
61
+
62
+ export interface CheckboxGroupItemProps {
63
+ /** Unique value identifying this item. */
64
+ value: string
65
+ /** Disables this individual item. */
66
+ disabled?: boolean
67
+ /** Label text or custom content. */
68
+ children?: React.ReactNode
69
+ // ─── Margin props ──────────────────────────────────────────────────────────
70
+ m?: MarginToken
71
+ mx?: MarginToken
72
+ my?: MarginToken
73
+ mt?: MarginToken
74
+ mr?: MarginToken
75
+ mb?: MarginToken
76
+ ml?: MarginToken
77
+ style?: StyleProp<ViewStyle>
78
+ }
79
+
80
+ // ─── Font size mapping per checkbox size ────────────────────────────────────────
81
+
82
+ const LABEL_FONT_SIZE: Record<CheckboxSize, 1 | 2 | 3> = { 1: 1, 2: 2, 3: 3 }
83
+ const LABEL_GAP: Record<CheckboxSize, number> = { 1: 4, 2: 6, 3: 8 }
84
+
85
+ // ─── Root Component ─────────────────────────────────────────────────────────────
86
+
87
+ function CheckboxGroupRoot({
88
+ size = 2,
89
+ variant = 'surface',
90
+ color,
91
+ highContrast,
92
+ value: valueProp,
93
+ defaultValue = [],
94
+ onValueChange,
95
+ disabled = false,
96
+ children,
97
+ m, mx, my, mt, mr, mb, ml,
98
+ style,
99
+ }: CheckboxGroupProps) {
100
+ const { scaling } = useThemeContext()
101
+
102
+ const [internal, setInternal] = React.useState<string[]>(defaultValue)
103
+ const isControlled = valueProp !== undefined
104
+ const value = isControlled ? valueProp : internal
105
+
106
+ const onItemToggle = useCallback((itemValue: string) => {
107
+ const next = value.includes(itemValue)
108
+ ? value.filter(v => v !== itemValue)
109
+ : [...value, itemValue]
110
+ if (!isControlled) setInternal(next)
111
+ onValueChange?.(next)
112
+ }, [value, isControlled, onValueChange])
113
+
114
+ const ctx = useMemo<CheckboxGroupContextValue>(() => ({
115
+ size, variant, color, highContrast, disabled, value, onItemToggle,
116
+ }), [size, variant, color, highContrast, disabled, value, onItemToggle])
117
+
118
+ const sp = (token: MarginToken | undefined): number | undefined =>
119
+ token !== undefined ? resolveSpace(token, scaling) : undefined
120
+
121
+ return (
122
+ <CheckboxGroupContext.Provider value={ctx}>
123
+ <View
124
+ accessibilityRole="none"
125
+ style={[
126
+ { flexDirection: 'column', gap: resolveSpace(2, scaling) },
127
+ {
128
+ marginTop: sp(mt ?? my ?? m),
129
+ marginBottom: sp(mb ?? my ?? m),
130
+ marginLeft: sp(ml ?? mx ?? m),
131
+ marginRight: sp(mr ?? mx ?? m),
132
+ },
133
+ style,
134
+ ]}
135
+ >
136
+ {children}
137
+ </View>
138
+ </CheckboxGroupContext.Provider>
139
+ )
140
+ }
141
+ CheckboxGroupRoot.displayName = 'CheckboxGroup.Root'
142
+
143
+ // ─── Item Component ─────────────────────────────────────────────────────────────
144
+
145
+ function CheckboxGroupItem({
146
+ value: itemValue,
147
+ disabled: disabledProp,
148
+ children,
149
+ m, mx, my, mt, mr, mb, ml,
150
+ style,
151
+ }: CheckboxGroupItemProps) {
152
+ const ctx = useContext(CheckboxGroupContext)
153
+ if (!ctx) throw new Error('CheckboxGroup.Item must be used within CheckboxGroup.Root')
154
+
155
+ const { size, variant, color, highContrast, disabled: groupDisabled, value, onItemToggle } = ctx
156
+ const { scaling, fonts } = useThemeContext()
157
+ const rc = useResolveColor()
158
+
159
+ const isDisabled = disabledProp ?? groupDisabled
160
+ const isChecked = value.includes(itemValue)
161
+
162
+ const handlePress = useCallback(() => {
163
+ if (!isDisabled) onItemToggle(itemValue)
164
+ }, [isDisabled, onItemToggle, itemValue])
165
+
166
+ const sp = (token: MarginToken | undefined): number | undefined =>
167
+ token !== undefined ? resolveSpace(token, scaling) : undefined
168
+
169
+ const scalingFactor = scalingMap[scaling]
170
+ const fontIdx = LABEL_FONT_SIZE[size]
171
+ const resolvedFontSize = Math.round(fontSize[fontIdx] * scalingFactor)
172
+ const resolvedLineHeight = Math.round(lineHeight[fontIdx] * scalingFactor)
173
+ const resolvedLetterSpacing = letterSpacingEm[fontIdx] * resolvedFontSize
174
+ const gap = Math.round(LABEL_GAP[size] * scalingFactor)
175
+
176
+ type C = Parameters<typeof rc>[0]
177
+ const textColor = rc('gray-12' as C)
178
+ const fontFamily = fonts.regular
179
+
180
+ const hasLabel = children != null
181
+
182
+ if (!hasLabel) {
183
+ return (
184
+ <Checkbox
185
+ size={size}
186
+ variant={variant}
187
+ color={color}
188
+ highContrast={highContrast}
189
+ checked={isChecked}
190
+ onCheckedChange={() => onItemToggle(itemValue)}
191
+ disabled={isDisabled}
192
+ m={m} mx={mx} my={my} mt={mt} mr={mr} mb={mb} ml={ml}
193
+ style={style}
194
+ />
195
+ )
196
+ }
197
+
198
+ const labelStyle: TextStyle = {
199
+ fontSize: resolvedFontSize,
200
+ lineHeight: resolvedLineHeight,
201
+ letterSpacing: resolvedLetterSpacing,
202
+ color: isDisabled ? rc('gray-a8' as C) : textColor,
203
+ fontFamily,
204
+ }
205
+
206
+ return (
207
+ <Pressable
208
+ onPress={handlePress}
209
+ disabled={isDisabled}
210
+ accessibilityRole="checkbox"
211
+ accessibilityState={{ checked: isChecked, disabled: isDisabled }}
212
+ style={[
213
+ {
214
+ flexDirection: 'row',
215
+ alignItems: 'center',
216
+ gap,
217
+ marginTop: sp(mt ?? my ?? m),
218
+ marginBottom: sp(mb ?? my ?? m),
219
+ marginLeft: sp(ml ?? mx ?? m),
220
+ marginRight: sp(mr ?? mx ?? m),
221
+ },
222
+ style,
223
+ ]}
224
+ >
225
+ <Checkbox
226
+ size={size}
227
+ variant={variant}
228
+ color={color}
229
+ highContrast={highContrast}
230
+ checked={isChecked}
231
+ onCheckedChange={() => onItemToggle(itemValue)}
232
+ disabled={isDisabled}
233
+ />
234
+ {typeof children === 'string' || typeof children === 'number' ? (
235
+ <RNText style={labelStyle}>{children}</RNText>
236
+ ) : (
237
+ children
238
+ )}
239
+ </Pressable>
240
+ )
241
+ }
242
+ CheckboxGroupItem.displayName = 'CheckboxGroup.Item'
243
+
244
+ // ─── Compound export ────────────────────────────────────────────────────────────
245
+
246
+ export const CheckboxGroup = Object.assign(CheckboxGroupRoot, {
247
+ Root: CheckboxGroupRoot,
248
+ Item: CheckboxGroupItem,
249
+ })
@@ -0,0 +1,4 @@
1
+ export { Button, type ButtonProps, type ButtonSize, type ButtonVariant } from './Button'
2
+ export { Checkbox, type CheckboxProps, type CheckboxSize, type CheckboxVariant, type CheckedState } from './Checkbox'
3
+ export { CheckboxGroup, type CheckboxGroupProps, type CheckboxGroupItemProps } from './CheckboxGroup'
4
+ export { CheckboxCards, type CheckboxCardsProps, type CheckboxCardsItemProps, type CheckboxCardsVariant } from './CheckboxCards'
@@ -0,0 +1,108 @@
1
+ import React from 'react'
2
+ import { View } from 'react-native'
3
+ import type { ViewProps, ViewStyle } from 'react-native'
4
+ import { useThemeContext } from '../../hooks/useThemeContext'
5
+ import { useResolveColor } from '../../hooks/useResolveColor'
6
+ import { resolveSpace } from '../../utils/resolveSpace'
7
+ import { getRadius, getFullRadius } from '../../tokens/radius'
8
+ import type { SpaceToken, MarginToken } from '../../tokens/spacing'
9
+ import type { ThemeColor, RadiusToken } from '../../theme/theme.types'
10
+
11
+ export interface BoxProps extends ViewProps {
12
+ // ─── Padding ──────────────────────────────────────────────────────
13
+ p?: SpaceToken
14
+ px?: SpaceToken
15
+ py?: SpaceToken
16
+ pt?: SpaceToken
17
+ pr?: SpaceToken
18
+ pb?: SpaceToken
19
+ pl?: SpaceToken
20
+ // ─── Margin ───────────────────────────────────────────────────────
21
+ m?: MarginToken
22
+ mx?: MarginToken
23
+ my?: MarginToken
24
+ mt?: MarginToken
25
+ mr?: MarginToken
26
+ mb?: MarginToken
27
+ ml?: MarginToken
28
+ // ─── Size ─────────────────────────────────────────────────────────
29
+ width?: number | string
30
+ minWidth?: number | string
31
+ maxWidth?: number | string
32
+ height?: number | string
33
+ minHeight?: number | string
34
+ maxHeight?: number | string
35
+ // ─── Position ─────────────────────────────────────────────────────
36
+ position?: 'relative' | 'absolute'
37
+ top?: number | string
38
+ right?: number | string
39
+ bottom?: number | string
40
+ left?: number | string
41
+ // ─── Layout ───────────────────────────────────────────────────────
42
+ overflow?: 'hidden' | 'visible' | 'scroll'
43
+ flexBasis?: number | string
44
+ flexShrink?: number
45
+ flexGrow?: number
46
+ // ─── RN-only theme props ───────────────────────────────────────────
47
+ /** Background color from the theme token system */
48
+ bg?: ThemeColor
49
+ /** Border radius using the theme radius token (level 4 — card-sized) */
50
+ radius?: RadiusToken
51
+ }
52
+
53
+ /** Fundamental layout primitive backed by a View. */
54
+ export function Box({
55
+ p, px, py, pt, pr, pb, pl,
56
+ m, mx, my, mt, mr, mb, ml,
57
+ width, minWidth, maxWidth,
58
+ height, minHeight, maxHeight,
59
+ position,
60
+ top, right, bottom, left,
61
+ overflow,
62
+ flexBasis, flexShrink, flexGrow,
63
+ bg,
64
+ radius,
65
+ style,
66
+ ...rest
67
+ }: BoxProps) {
68
+ const { scaling } = useThemeContext()
69
+ const rc = useResolveColor()
70
+
71
+ const sp = (token: MarginToken | SpaceToken | undefined): number | undefined =>
72
+ token !== undefined ? resolveSpace(token, scaling) : undefined
73
+
74
+ const boxStyle: ViewStyle = {
75
+ // Padding — specific > axis > all
76
+ paddingTop: sp(pt ?? py ?? p),
77
+ paddingBottom: sp(pb ?? py ?? p),
78
+ paddingLeft: sp(pl ?? px ?? p),
79
+ paddingRight: sp(pr ?? px ?? p),
80
+ // Margin — specific > axis > all
81
+ marginTop: sp(mt ?? my ?? m),
82
+ marginBottom: sp(mb ?? my ?? m),
83
+ marginLeft: sp(ml ?? mx ?? m),
84
+ marginRight: sp(mr ?? mx ?? m),
85
+ // Size
86
+ width: width as ViewStyle['width'],
87
+ minWidth: minWidth as ViewStyle['minWidth'],
88
+ maxWidth: maxWidth as ViewStyle['maxWidth'],
89
+ height: height as ViewStyle['height'],
90
+ minHeight: minHeight as ViewStyle['minHeight'],
91
+ maxHeight: maxHeight as ViewStyle['maxHeight'],
92
+ // Position
93
+ position, top: top as ViewStyle['top'], right: right as ViewStyle['right'],
94
+ bottom: bottom as ViewStyle['bottom'], left: left as ViewStyle['left'],
95
+ // Layout
96
+ overflow: overflow as ViewStyle['overflow'],
97
+ flexBasis: flexBasis as ViewStyle['flexBasis'],
98
+ flexShrink, flexGrow,
99
+ // Theme
100
+ backgroundColor: bg ? rc(bg) : undefined,
101
+ borderRadius: radius
102
+ ? (radius === 'full' ? getFullRadius(radius) : getRadius(radius, 4))
103
+ : undefined,
104
+ }
105
+
106
+ return <View style={[boxStyle, style]} {...rest} />
107
+ }
108
+ Box.displayName = 'Box'