@titan-design/react-ui 0.1.0 → 0.2.5
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/README.md +47 -1
- package/dist/{chunk-MGW37MPO.mjs → chunk-UXVRPOME.mjs} +173 -3
- package/dist/chunk-UXVRPOME.mjs.map +1 -0
- package/dist/index.d.mts +101 -12
- package/dist/index.d.ts +101 -12
- package/dist/index.js +1267 -468
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +734 -112
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +86 -1
- package/dist/theme/index.d.ts +86 -1
- package/dist/theme/index.js +166 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +1 -1
- package/package.json +22 -6
- package/src/components/custom/DateTime/DateTime.stories.tsx +180 -0
- package/src/components/custom/DateTime/DateTime.test.tsx +152 -0
- package/src/components/custom/DateTime/DateTime.tsx +235 -0
- package/src/components/custom/DateTime/index.ts +2 -0
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +99 -0
- package/src/components/custom/EmptyState/EmptyState.test.tsx +106 -0
- package/src/components/custom/EmptyState/EmptyState.tsx +68 -0
- package/src/components/custom/EmptyState/index.ts +2 -0
- package/src/components/custom/Metric/Metric.stories.tsx +133 -0
- package/src/components/custom/Metric/Metric.test.tsx +120 -0
- package/src/components/custom/Metric/Metric.tsx +92 -0
- package/src/components/custom/Metric/index.ts +2 -0
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +227 -0
- package/src/components/custom/Sidebar/Sidebar.test.tsx +185 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +257 -0
- package/src/components/custom/Sidebar/index.ts +18 -0
- package/src/components/custom/Table/Table.stories.tsx +481 -0
- package/src/components/custom/Table/Table.test.tsx +531 -0
- package/src/components/custom/Table/Table.tsx +696 -0
- package/src/components/custom/Table/index.ts +22 -0
- package/src/components/custom/Typography/Typography.stories.tsx +153 -0
- package/src/components/custom/Typography/Typography.test.tsx +107 -0
- package/src/components/custom/Typography/Typography.tsx +218 -0
- package/src/components/custom/Typography/index.ts +19 -0
- package/src/components/custom/Workout/RestTimer.stories.tsx +71 -0
- package/src/components/custom/Workout/RestTimer.test.tsx +145 -0
- package/src/components/custom/Workout/RestTimer.tsx +172 -0
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +144 -0
- package/src/components/custom/Workout/SupersetWrapper.test.tsx +130 -0
- package/src/components/custom/Workout/SupersetWrapper.tsx +63 -0
- package/src/components/custom/Workout/index.ts +2 -0
- package/src/components/custom/index.ts +9 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +245 -0
- package/src/components/custom/stepper/Stepper.test.tsx +230 -0
- package/src/components/custom/stepper/Stepper.tsx +250 -0
- package/src/components/custom/stepper/index.ts +10 -0
- package/src/components/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +154 -0
- package/src/components/ui/alert/Alert.test.tsx +202 -0
- package/src/components/ui/alert/Alert.tsx +170 -0
- package/src/components/ui/alert/index.ts +8 -0
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +299 -0
- package/src/components/ui/autocomplete/Autocomplete.test.tsx +247 -0
- package/src/components/ui/autocomplete/Autocomplete.tsx +332 -0
- package/src/components/ui/autocomplete/index.ts +2 -0
- package/src/components/ui/avatar/Avatar.stories.tsx +105 -0
- package/src/components/ui/avatar/Avatar.test.tsx +152 -0
- package/src/components/ui/avatar/Avatar.tsx +176 -0
- package/src/components/ui/avatar/index.ts +2 -0
- package/src/components/ui/badge/Badge.stories.tsx +100 -0
- package/src/components/ui/badge/Badge.test.tsx +103 -0
- package/src/components/ui/badge/Badge.tsx +119 -0
- package/src/components/ui/badge/index.ts +3 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +70 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.test.tsx +156 -0
- package/src/components/ui/breadcrumbs/Breadcrumbs.tsx +127 -0
- package/src/components/ui/breadcrumbs/index.ts +2 -0
- package/src/components/ui/button/Button.stories.tsx +216 -0
- package/src/components/ui/button/Button.test.tsx +132 -0
- package/src/components/ui/button/Button.tsx +297 -0
- package/src/components/ui/button/index.ts +10 -0
- package/src/components/ui/card/Card.stories.tsx +443 -0
- package/src/components/ui/card/Card.test.tsx +294 -0
- package/src/components/ui/card/Card.tsx +401 -0
- package/src/components/ui/card/index.ts +20 -0
- package/src/components/ui/checkbox/Checkbox.stories.tsx +126 -0
- package/src/components/ui/checkbox/Checkbox.test.tsx +152 -0
- package/src/components/ui/checkbox/Checkbox.tsx +163 -0
- package/src/components/ui/checkbox/index.ts +2 -0
- package/src/components/ui/chip/Chip.stories.tsx +112 -0
- package/src/components/ui/chip/Chip.test.tsx +119 -0
- package/src/components/ui/chip/Chip.tsx +151 -0
- package/src/components/ui/chip/index.ts +2 -0
- package/src/components/ui/collapse/Collapse.stories.tsx +313 -0
- package/src/components/ui/collapse/Collapse.test.tsx +298 -0
- package/src/components/ui/collapse/Collapse.tsx +275 -0
- package/src/components/ui/collapse/index.ts +18 -0
- package/src/components/ui/data-row/DataRow.stories.tsx +78 -0
- package/src/components/ui/data-row/DataRow.test.tsx +78 -0
- package/src/components/ui/data-row/DataRow.tsx +29 -0
- package/src/components/ui/data-row/index.ts +2 -0
- package/src/components/ui/divider/Divider.stories.tsx +71 -0
- package/src/components/ui/divider/Divider.test.tsx +55 -0
- package/src/components/ui/divider/Divider.tsx +37 -0
- package/src/components/ui/divider/index.ts +2 -0
- package/src/components/ui/drawer/Drawer.stories.tsx +228 -0
- package/src/components/ui/drawer/Drawer.test.tsx +142 -0
- package/src/components/ui/drawer/Drawer.tsx +228 -0
- package/src/components/ui/drawer/index.ts +9 -0
- package/src/components/ui/form-field/FormField.stories.tsx +260 -0
- package/src/components/ui/form-field/FormField.test.tsx +280 -0
- package/src/components/ui/form-field/FormField.tsx +308 -0
- package/src/components/ui/form-field/index.ts +2 -0
- package/src/components/ui/help-tip/HelpTip.stories.tsx +250 -0
- package/src/components/ui/help-tip/HelpTip.test.tsx +198 -0
- package/src/components/ui/help-tip/HelpTip.tsx +229 -0
- package/src/components/ui/help-tip/index.ts +2 -0
- package/src/components/ui/icon-box/IconBox.stories.tsx +139 -0
- package/src/components/ui/icon-box/IconBox.test.tsx +120 -0
- package/src/components/ui/icon-box/IconBox.tsx +57 -0
- package/src/components/ui/icon-box/index.ts +1 -0
- package/src/components/ui/index.ts +38 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +74 -0
- package/src/components/ui/indicator/Indicator.test.tsx +55 -0
- package/src/components/ui/indicator/Indicator.tsx +73 -0
- package/src/components/ui/indicator/index.ts +2 -0
- package/src/components/ui/input/Input.stories.tsx +192 -0
- package/src/components/ui/input/Input.test.tsx +86 -0
- package/src/components/ui/input/Input.tsx +273 -0
- package/src/components/ui/input/index.ts +2 -0
- package/src/components/ui/link/Link.stories.tsx +78 -0
- package/src/components/ui/link/Link.test.tsx +94 -0
- package/src/components/ui/link/Link.tsx +86 -0
- package/src/components/ui/link/index.ts +2 -0
- package/src/components/ui/list-item/ListItem.stories.tsx +146 -0
- package/src/components/ui/list-item/ListItem.test.tsx +128 -0
- package/src/components/ui/list-item/ListItem.tsx +89 -0
- package/src/components/ui/list-item/index.ts +14 -0
- package/src/components/ui/menu/Menu.stories.tsx +180 -0
- package/src/components/ui/menu/Menu.test.tsx +353 -0
- package/src/components/ui/menu/Menu.tsx +222 -0
- package/src/components/ui/menu/index.ts +2 -0
- package/src/components/ui/modal/Modal.stories.tsx +350 -0
- package/src/components/ui/modal/Modal.test.tsx +159 -0
- package/src/components/ui/modal/Modal.tsx +254 -0
- package/src/components/ui/modal/index.ts +19 -0
- package/src/components/ui/pill/Pill.stories.tsx +71 -0
- package/src/components/ui/pill/Pill.test.tsx +69 -0
- package/src/components/ui/pill/Pill.tsx +104 -0
- package/src/components/ui/pill/index.ts +2 -0
- package/src/components/ui/popover/Popover.stories.tsx +190 -0
- package/src/components/ui/popover/Popover.test.tsx +419 -0
- package/src/components/ui/popover/Popover.tsx +237 -0
- package/src/components/ui/popover/index.ts +2 -0
- package/src/components/ui/progress/Progress.stories.tsx +190 -0
- package/src/components/ui/progress/Progress.test.tsx +210 -0
- package/src/components/ui/progress/Progress.tsx +339 -0
- package/src/components/ui/progress/index.ts +9 -0
- package/src/components/ui/radio/Radio.stories.tsx +199 -0
- package/src/components/ui/radio/Radio.test.tsx +166 -0
- package/src/components/ui/radio/Radio.tsx +200 -0
- package/src/components/ui/radio/index.ts +2 -0
- package/src/components/ui/section/Section.stories.tsx +111 -0
- package/src/components/ui/section/Section.test.tsx +121 -0
- package/src/components/ui/section/Section.tsx +52 -0
- package/src/components/ui/section/index.ts +6 -0
- package/src/components/ui/select/Select.stories.tsx +286 -0
- package/src/components/ui/select/Select.test.tsx +200 -0
- package/src/components/ui/select/Select.tsx +243 -0
- package/src/components/ui/select/index.ts +2 -0
- package/src/components/ui/skeleton/Skeleton.stories.tsx +153 -0
- package/src/components/ui/skeleton/Skeleton.test.tsx +162 -0
- package/src/components/ui/skeleton/Skeleton.tsx +229 -0
- package/src/components/ui/skeleton/index.ts +16 -0
- package/src/components/ui/spinner/Spinner.stories.tsx +67 -0
- package/src/components/ui/spinner/Spinner.test.tsx +67 -0
- package/src/components/ui/spinner/Spinner.tsx +72 -0
- package/src/components/ui/spinner/index.ts +2 -0
- package/src/components/ui/stack/Stack.stories.tsx +127 -0
- package/src/components/ui/stack/Stack.test.tsx +184 -0
- package/src/components/ui/stack/Stack.tsx +76 -0
- package/src/components/ui/stack/index.ts +2 -0
- package/src/components/ui/surface/Surface.stories.tsx +91 -0
- package/src/components/ui/surface/Surface.test.tsx +63 -0
- package/src/components/ui/surface/Surface.tsx +51 -0
- package/src/components/ui/surface/index.ts +1 -0
- package/src/components/ui/switch/Switch.stories.tsx +111 -0
- package/src/components/ui/switch/Switch.test.tsx +112 -0
- package/src/components/ui/switch/Switch.tsx +103 -0
- package/src/components/ui/switch/index.ts +2 -0
- package/src/components/ui/tabs/Tabs.stories.tsx +213 -0
- package/src/components/ui/tabs/Tabs.test.tsx +192 -0
- package/src/components/ui/tabs/Tabs.tsx +253 -0
- package/src/components/ui/tabs/index.ts +16 -0
- package/src/components/ui/toast/Toast.stories.tsx +255 -0
- package/src/components/ui/toast/Toast.test.tsx +207 -0
- package/src/components/ui/toast/Toast.tsx +327 -0
- package/src/components/ui/toast/index.ts +9 -0
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +415 -0
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +224 -0
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +307 -0
- package/src/components/ui/toolbar-button/index.ts +7 -0
- package/src/components/ui/tooltip/Tooltip.stories.tsx +126 -0
- package/src/components/ui/tooltip/Tooltip.test.tsx +262 -0
- package/src/components/ui/tooltip/Tooltip.tsx +234 -0
- package/src/components/ui/tooltip/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/stories/PresetShowcase.stories.tsx +462 -0
- package/src/stories/ThemePresets.stories.tsx +85 -0
- package/src/test/setup.ts +19 -0
- package/src/theme/Colors.stories.tsx +258 -0
- package/src/theme/config.ts +193 -0
- package/src/theme/elevation.stories.tsx +346 -0
- package/src/theme/elevation.test.ts +27 -0
- package/src/theme/elevation.ts +567 -0
- package/src/theme/global.css +129 -1
- package/src/theme/index.ts +40 -0
- package/src/theme/presets/apply.ts +97 -0
- package/src/theme/presets/audiobook.ts +93 -0
- package/src/theme/presets/default.ts +6 -0
- package/src/theme/presets/index.ts +4 -0
- package/src/theme/presets/presets.test.ts +51 -0
- package/src/theme/presets/types.ts +76 -0
- package/src/theme/shadows.stories.tsx +523 -0
- package/src/theme/shadows.ts +439 -0
- package/src/theme/tokens/index.ts +2 -0
- package/src/theme/tokens/primitives.ts +354 -0
- package/src/theme/tokens/semantic.ts +381 -0
- package/src/types/jest-axe.d.ts +16 -0
- package/src/types/nativewind.d.ts +54 -0
- package/src/utils/avatar-color.test.ts +30 -0
- package/src/utils/avatar-color.ts +20 -0
- package/src/utils/cn.ts +14 -0
- package/src/utils/colors.ts +140 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/index.ts +29 -0
- package/src/utils/useTheme.ts +72 -0
- package/src/web-jsx/jsx-dev-runtime.ts +28 -0
- package/src/web-jsx/jsx-runtime.ts +41 -0
- package/tailwind.config.js +53 -3
- package/dist/chunk-MGW37MPO.mjs.map +0 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import { Pressable, Text, View, ActivityIndicator, type PressableProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { semanticColorsDark } from '../../../theme/tokens/semantic'
|
|
5
|
+
|
|
6
|
+
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'link'
|
|
7
|
+
export type ButtonSize = 'sm' | 'md' | 'lg'
|
|
8
|
+
export type ButtonColor = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info'
|
|
9
|
+
|
|
10
|
+
/** Inline color map for RNW where Tailwind text classes get dropped */
|
|
11
|
+
const textColorMap: Record<ButtonVariant, Record<ButtonColor, string>> = {
|
|
12
|
+
solid: {
|
|
13
|
+
primary: '#FFFFFF',
|
|
14
|
+
secondary: '#FFFFFF',
|
|
15
|
+
success: '#FFFFFF',
|
|
16
|
+
error: '#FFFFFF',
|
|
17
|
+
warning: '#FFFFFF',
|
|
18
|
+
info: '#FFFFFF',
|
|
19
|
+
},
|
|
20
|
+
outline: {
|
|
21
|
+
primary: semanticColorsDark['brand-primary'],
|
|
22
|
+
secondary: semanticColorsDark['brand-secondary'],
|
|
23
|
+
success: semanticColorsDark['status-success'],
|
|
24
|
+
error: semanticColorsDark['status-error'],
|
|
25
|
+
warning: semanticColorsDark['status-warning'],
|
|
26
|
+
info: semanticColorsDark['status-info'],
|
|
27
|
+
},
|
|
28
|
+
ghost: {
|
|
29
|
+
primary: semanticColorsDark['brand-primary'],
|
|
30
|
+
secondary: semanticColorsDark['brand-secondary'],
|
|
31
|
+
success: semanticColorsDark['status-success'],
|
|
32
|
+
error: semanticColorsDark['status-error'],
|
|
33
|
+
warning: semanticColorsDark['status-warning'],
|
|
34
|
+
info: semanticColorsDark['status-info'],
|
|
35
|
+
},
|
|
36
|
+
link: {
|
|
37
|
+
primary: semanticColorsDark['brand-primary'],
|
|
38
|
+
secondary: semanticColorsDark['brand-secondary'],
|
|
39
|
+
success: semanticColorsDark['status-success'],
|
|
40
|
+
error: semanticColorsDark['status-error'],
|
|
41
|
+
warning: semanticColorsDark['status-warning'],
|
|
42
|
+
info: semanticColorsDark['status-info'],
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Inline border color map for outline variant */
|
|
47
|
+
const borderColorMap: Record<ButtonColor, string> = {
|
|
48
|
+
primary: semanticColorsDark['brand-primary'],
|
|
49
|
+
secondary: semanticColorsDark['brand-secondary'],
|
|
50
|
+
success: semanticColorsDark['status-success'],
|
|
51
|
+
error: semanticColorsDark['status-error'],
|
|
52
|
+
warning: semanticColorsDark['status-warning'],
|
|
53
|
+
info: semanticColorsDark['status-info'],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ButtonProps extends Omit<PressableProps, 'children'> {
|
|
57
|
+
/** Visual style variant */
|
|
58
|
+
variant?: ButtonVariant
|
|
59
|
+
/** Size of the button */
|
|
60
|
+
size?: ButtonSize
|
|
61
|
+
/** Color scheme */
|
|
62
|
+
color?: ButtonColor
|
|
63
|
+
/** Whether this is an icon-only button (square aspect ratio) */
|
|
64
|
+
isIconButton?: boolean
|
|
65
|
+
/** Whether the button is disabled */
|
|
66
|
+
isDisabled?: boolean
|
|
67
|
+
/** Whether the button is in a loading state */
|
|
68
|
+
isLoading?: boolean
|
|
69
|
+
/** Loading text to display */
|
|
70
|
+
loadingText?: string
|
|
71
|
+
/** Whether the button should take full width */
|
|
72
|
+
fullWidth?: boolean
|
|
73
|
+
/** Additional className for styling */
|
|
74
|
+
className?: string
|
|
75
|
+
/** Button content */
|
|
76
|
+
children?: React.ReactNode
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const variantStyles: Record<ButtonVariant, Record<ButtonColor, string>> = {
|
|
80
|
+
solid: {
|
|
81
|
+
primary: 'bg-brand-primary active:bg-brand-primary-dark active:scale-[0.98] web:hover:bg-brand-primary-dark web:active:scale-[0.98]',
|
|
82
|
+
secondary: 'bg-brand-secondary active:bg-brand-secondary-dark active:scale-[0.98] web:hover:bg-brand-secondary-dark web:active:scale-[0.98]',
|
|
83
|
+
success: 'bg-status-success active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
84
|
+
error: 'bg-status-error active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
85
|
+
warning: 'bg-status-warning active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
86
|
+
info: 'bg-status-info active:opacity-90 active:scale-[0.98] web:hover:opacity-90 web:active:scale-[0.98]',
|
|
87
|
+
},
|
|
88
|
+
outline: {
|
|
89
|
+
primary: 'border-2 border-brand-primary bg-transparent active:bg-brand-primary-subtle active:scale-[0.98] web:hover:bg-brand-primary-subtle web:active:scale-[0.98]',
|
|
90
|
+
secondary: 'border-2 border-brand-secondary bg-transparent active:bg-brand-secondary-subtle active:scale-[0.98] web:hover:bg-brand-secondary-subtle web:active:scale-[0.98]',
|
|
91
|
+
success: 'border-2 border-status-success bg-transparent active:bg-status-success-subtle active:scale-[0.98] web:hover:bg-status-success-subtle web:active:scale-[0.98]',
|
|
92
|
+
error: 'border-2 border-status-error bg-transparent active:bg-status-error-subtle active:scale-[0.98] web:hover:bg-status-error-subtle web:active:scale-[0.98]',
|
|
93
|
+
warning: 'border-2 border-status-warning bg-transparent active:bg-status-warning-subtle active:scale-[0.98] web:hover:bg-status-warning-subtle web:active:scale-[0.98]',
|
|
94
|
+
info: 'border-2 border-status-info bg-transparent active:bg-status-info-subtle active:scale-[0.98] web:hover:bg-status-info-subtle web:active:scale-[0.98]',
|
|
95
|
+
},
|
|
96
|
+
ghost: {
|
|
97
|
+
primary: 'bg-transparent active:bg-brand-primary-subtle active:scale-[0.98] web:hover:bg-brand-primary-subtle web:active:scale-[0.98]',
|
|
98
|
+
secondary: 'bg-transparent active:bg-brand-secondary-subtle active:scale-[0.98] web:hover:bg-brand-secondary-subtle web:active:scale-[0.98]',
|
|
99
|
+
success: 'bg-transparent active:bg-status-success-subtle active:scale-[0.98] web:hover:bg-status-success-subtle web:active:scale-[0.98]',
|
|
100
|
+
error: 'bg-transparent active:bg-status-error-subtle active:scale-[0.98] web:hover:bg-status-error-subtle web:active:scale-[0.98]',
|
|
101
|
+
warning: 'bg-transparent active:bg-status-warning-subtle active:scale-[0.98] web:hover:bg-status-warning-subtle web:active:scale-[0.98]',
|
|
102
|
+
info: 'bg-transparent active:bg-status-info-subtle active:scale-[0.98] web:hover:bg-status-info-subtle web:active:scale-[0.98]',
|
|
103
|
+
},
|
|
104
|
+
link: {
|
|
105
|
+
primary: 'bg-transparent',
|
|
106
|
+
secondary: 'bg-transparent',
|
|
107
|
+
success: 'bg-transparent',
|
|
108
|
+
error: 'bg-transparent',
|
|
109
|
+
warning: 'bg-transparent',
|
|
110
|
+
info: 'bg-transparent',
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const textStyles: Record<ButtonVariant, Record<ButtonColor, string>> = {
|
|
115
|
+
solid: {
|
|
116
|
+
primary: 'text-white',
|
|
117
|
+
secondary: 'text-white',
|
|
118
|
+
success: 'text-white',
|
|
119
|
+
error: 'text-white',
|
|
120
|
+
warning: 'text-white',
|
|
121
|
+
info: 'text-white',
|
|
122
|
+
},
|
|
123
|
+
outline: {
|
|
124
|
+
primary: 'text-brand-primary',
|
|
125
|
+
secondary: 'text-brand-secondary',
|
|
126
|
+
success: 'text-status-success',
|
|
127
|
+
error: 'text-status-error',
|
|
128
|
+
warning: 'text-status-warning',
|
|
129
|
+
info: 'text-status-info',
|
|
130
|
+
},
|
|
131
|
+
ghost: {
|
|
132
|
+
primary: 'text-brand-primary',
|
|
133
|
+
secondary: 'text-brand-secondary',
|
|
134
|
+
success: 'text-status-success',
|
|
135
|
+
error: 'text-status-error',
|
|
136
|
+
warning: 'text-status-warning',
|
|
137
|
+
info: 'text-status-info',
|
|
138
|
+
},
|
|
139
|
+
link: {
|
|
140
|
+
primary: 'text-brand-primary web:hover:underline',
|
|
141
|
+
secondary: 'text-brand-secondary web:hover:underline',
|
|
142
|
+
success: 'text-status-success web:hover:underline',
|
|
143
|
+
error: 'text-status-error web:hover:underline',
|
|
144
|
+
warning: 'text-status-warning web:hover:underline',
|
|
145
|
+
info: 'text-status-info web:hover:underline',
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const sizeStyles: Record<ButtonSize, string> = {
|
|
150
|
+
sm: 'px-4 py-1.5 min-h-[32px]',
|
|
151
|
+
md: 'px-5 py-2 min-h-[40px]',
|
|
152
|
+
lg: 'px-6 py-2.5 min-h-[48px]',
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Icon button sizes (square aspect ratio)
|
|
156
|
+
const iconButtonSizeStyles: Record<ButtonSize, string> = {
|
|
157
|
+
sm: 'w-8 h-8 p-0',
|
|
158
|
+
md: 'w-10 h-10 p-0',
|
|
159
|
+
lg: 'w-12 h-12 p-0',
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const textSizeStyles: Record<ButtonSize, string> = {
|
|
163
|
+
sm: 'text-sm',
|
|
164
|
+
md: 'text-sm',
|
|
165
|
+
lg: 'text-base',
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Button component following compound component pattern.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* <Button variant="solid" color="primary">
|
|
173
|
+
* <ButtonText>Click me</ButtonText>
|
|
174
|
+
* </Button>
|
|
175
|
+
*
|
|
176
|
+
* // Or with icon
|
|
177
|
+
* <Button variant="outline" color="secondary">
|
|
178
|
+
* <ButtonIcon as={PlusIcon} />
|
|
179
|
+
* <ButtonText>Add item</ButtonText>
|
|
180
|
+
* </Button>
|
|
181
|
+
*/
|
|
182
|
+
export const Button = forwardRef<View, ButtonProps>(function Button(
|
|
183
|
+
{
|
|
184
|
+
variant = 'solid',
|
|
185
|
+
size = 'md',
|
|
186
|
+
color = 'primary',
|
|
187
|
+
isIconButton = false,
|
|
188
|
+
isDisabled = false,
|
|
189
|
+
isLoading = false,
|
|
190
|
+
loadingText,
|
|
191
|
+
fullWidth = false,
|
|
192
|
+
className,
|
|
193
|
+
children,
|
|
194
|
+
...props
|
|
195
|
+
},
|
|
196
|
+
ref
|
|
197
|
+
) {
|
|
198
|
+
const disabled = isDisabled || isLoading
|
|
199
|
+
|
|
200
|
+
const inlineStyle: Record<string, string> = {
|
|
201
|
+
color: textColorMap[variant][color],
|
|
202
|
+
}
|
|
203
|
+
if (variant === 'outline') {
|
|
204
|
+
inlineStyle.borderColor = borderColorMap[color]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<Pressable
|
|
209
|
+
ref={ref}
|
|
210
|
+
disabled={disabled}
|
|
211
|
+
accessibilityRole="button"
|
|
212
|
+
accessibilityState={{ disabled }}
|
|
213
|
+
className={cn(
|
|
214
|
+
// Base styles
|
|
215
|
+
'flex-row items-center justify-center rounded-md transition-all duration-150',
|
|
216
|
+
// Variant + color styles
|
|
217
|
+
variantStyles[variant][color],
|
|
218
|
+
// Size styles (icon button vs regular)
|
|
219
|
+
isIconButton ? iconButtonSizeStyles[size] : sizeStyles[size],
|
|
220
|
+
// Full width
|
|
221
|
+
fullWidth && 'w-full',
|
|
222
|
+
// Disabled styles - more refined
|
|
223
|
+
disabled && 'opacity-40 cursor-not-allowed active:scale-100 web:active:scale-100',
|
|
224
|
+
// Link variant has no padding
|
|
225
|
+
variant === 'link' && 'px-0 py-0 min-h-0',
|
|
226
|
+
className
|
|
227
|
+
)}
|
|
228
|
+
style={inlineStyle}
|
|
229
|
+
{...props}
|
|
230
|
+
>
|
|
231
|
+
{isLoading && (
|
|
232
|
+
<ActivityIndicator
|
|
233
|
+
size="small"
|
|
234
|
+
color={variant === 'solid' ? semanticColorsDark['on-brand-primary'] : undefined}
|
|
235
|
+
className="mr-2"
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
{isLoading && loadingText ? (
|
|
239
|
+
<Text
|
|
240
|
+
className={cn(
|
|
241
|
+
'font-semibold',
|
|
242
|
+
textSizeStyles[size],
|
|
243
|
+
textStyles[variant][color]
|
|
244
|
+
)}
|
|
245
|
+
style={{ color: textColorMap[variant][color] }}
|
|
246
|
+
>
|
|
247
|
+
{loadingText}
|
|
248
|
+
</Text>
|
|
249
|
+
) : (
|
|
250
|
+
children
|
|
251
|
+
)}
|
|
252
|
+
</Pressable>
|
|
253
|
+
)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
export interface ButtonTextProps {
|
|
257
|
+
children: React.ReactNode
|
|
258
|
+
className?: string
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Text component for Button. Use inside Button for consistent styling.
|
|
263
|
+
*/
|
|
264
|
+
export function ButtonText({ children, className }: ButtonTextProps) {
|
|
265
|
+
return (
|
|
266
|
+
<Text className={cn('font-semibold text-inherit', className)}>
|
|
267
|
+
{children}
|
|
268
|
+
</Text>
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface ButtonIconProps {
|
|
273
|
+
/** Icon component to render */
|
|
274
|
+
as: React.ComponentType<{ size?: number; color?: string; className?: string }>
|
|
275
|
+
/** Icon size */
|
|
276
|
+
size?: number
|
|
277
|
+
/** Additional className */
|
|
278
|
+
className?: string
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Icon component for Button. Use inside Button for icons.
|
|
283
|
+
*/
|
|
284
|
+
export function ButtonIcon({ as: Icon, size = 16, className }: ButtonIconProps) {
|
|
285
|
+
return <Icon size={size} className={cn('text-inherit', className)} />
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface ButtonSpinnerProps {
|
|
289
|
+
className?: string
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Spinner component for Button loading state.
|
|
294
|
+
*/
|
|
295
|
+
export function ButtonSpinner({ className }: ButtonSpinnerProps) {
|
|
296
|
+
return <ActivityIndicator size="small" className={cn('text-inherit', className)} />
|
|
297
|
+
}
|