@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,250 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import { HelpTip, LabelWithHelp } from './HelpTip'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof HelpTip> = {
|
|
6
|
+
title: 'Components/HelpTip',
|
|
7
|
+
component: HelpTip,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
size: {
|
|
11
|
+
control: 'select',
|
|
12
|
+
options: ['sm', 'md', 'lg'],
|
|
13
|
+
},
|
|
14
|
+
placement: {
|
|
15
|
+
control: 'select',
|
|
16
|
+
options: ['top', 'bottom', 'left', 'right'],
|
|
17
|
+
},
|
|
18
|
+
icon: {
|
|
19
|
+
control: 'select',
|
|
20
|
+
options: ['help', 'info', 'warning'],
|
|
21
|
+
},
|
|
22
|
+
color: {
|
|
23
|
+
control: 'select',
|
|
24
|
+
options: ['default', 'primary', 'secondary', 'muted'],
|
|
25
|
+
},
|
|
26
|
+
hasArrow: {
|
|
27
|
+
control: 'boolean',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default meta
|
|
33
|
+
type Story = StoryObj<typeof HelpTip>
|
|
34
|
+
|
|
35
|
+
export const Default: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
content: 'This is helpful information about the feature.',
|
|
38
|
+
size: 'md',
|
|
39
|
+
placement: 'top',
|
|
40
|
+
icon: 'help',
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const AllIcons: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<View className="flex-row gap-8 items-center">
|
|
47
|
+
<View className="items-center gap-2">
|
|
48
|
+
<HelpTip content="Help icon tooltip" icon="help" />
|
|
49
|
+
<Text className="text-xs text-text-secondary">Help</Text>
|
|
50
|
+
</View>
|
|
51
|
+
<View className="items-center gap-2">
|
|
52
|
+
<HelpTip content="Info icon tooltip" icon="info" />
|
|
53
|
+
<Text className="text-xs text-text-secondary">Info</Text>
|
|
54
|
+
</View>
|
|
55
|
+
<View className="items-center gap-2">
|
|
56
|
+
<HelpTip content="Warning icon tooltip" icon="warning" />
|
|
57
|
+
<Text className="text-xs text-text-secondary">Warning</Text>
|
|
58
|
+
</View>
|
|
59
|
+
</View>
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const AllSizes: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<View className="flex-row gap-8 items-center">
|
|
66
|
+
<View className="items-center gap-2">
|
|
67
|
+
<HelpTip content="Small help tip" size="sm" />
|
|
68
|
+
<Text className="text-xs text-text-secondary">Small</Text>
|
|
69
|
+
</View>
|
|
70
|
+
<View className="items-center gap-2">
|
|
71
|
+
<HelpTip content="Medium help tip" size="md" />
|
|
72
|
+
<Text className="text-xs text-text-secondary">Medium</Text>
|
|
73
|
+
</View>
|
|
74
|
+
<View className="items-center gap-2">
|
|
75
|
+
<HelpTip content="Large help tip" size="lg" />
|
|
76
|
+
<Text className="text-xs text-text-secondary">Large</Text>
|
|
77
|
+
</View>
|
|
78
|
+
</View>
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const AllColors: Story = {
|
|
83
|
+
render: () => (
|
|
84
|
+
<View className="flex-row gap-6 items-center">
|
|
85
|
+
<HelpTip content="Default color" color="default" />
|
|
86
|
+
<HelpTip content="Primary color" color="primary" />
|
|
87
|
+
<HelpTip content="Secondary color" color="secondary" />
|
|
88
|
+
<HelpTip content="Muted color" color="muted" />
|
|
89
|
+
</View>
|
|
90
|
+
),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const AllPlacements: Story = {
|
|
94
|
+
render: () => (
|
|
95
|
+
<View className="gap-8 items-center py-16">
|
|
96
|
+
<View className="flex-row items-center gap-2">
|
|
97
|
+
<Text className="text-text-secondary w-16">Top:</Text>
|
|
98
|
+
<HelpTip content="Tooltip on top" placement="top" />
|
|
99
|
+
</View>
|
|
100
|
+
<View className="flex-row items-center gap-2">
|
|
101
|
+
<Text className="text-text-secondary w-16">Bottom:</Text>
|
|
102
|
+
<HelpTip content="Tooltip on bottom" placement="bottom" />
|
|
103
|
+
</View>
|
|
104
|
+
<View className="flex-row items-center gap-2">
|
|
105
|
+
<Text className="text-text-secondary w-16">Left:</Text>
|
|
106
|
+
<HelpTip content="Tooltip on left" placement="left" />
|
|
107
|
+
</View>
|
|
108
|
+
<View className="flex-row items-center gap-2">
|
|
109
|
+
<Text className="text-text-secondary w-16">Right:</Text>
|
|
110
|
+
<HelpTip content="Tooltip on right" placement="right" />
|
|
111
|
+
</View>
|
|
112
|
+
</View>
|
|
113
|
+
),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const WithLongContent: Story = {
|
|
117
|
+
args: {
|
|
118
|
+
content: 'This is a longer tooltip that contains more detailed information about the feature. It will wrap to multiple lines if needed, and you can customize the max width.',
|
|
119
|
+
maxWidth: 300,
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const WithRichContent: Story = {
|
|
124
|
+
render: () => (
|
|
125
|
+
<HelpTip
|
|
126
|
+
content={
|
|
127
|
+
<View className="gap-1">
|
|
128
|
+
<Text className="text-sm font-semibold text-text-primary">API Rate Limits</Text>
|
|
129
|
+
<Text className="text-xs text-text-secondary">
|
|
130
|
+
• Free tier: 100 requests/hour
|
|
131
|
+
</Text>
|
|
132
|
+
<Text className="text-xs text-text-secondary">
|
|
133
|
+
• Pro tier: 1000 requests/hour
|
|
134
|
+
</Text>
|
|
135
|
+
<Text className="text-xs text-text-secondary">
|
|
136
|
+
• Enterprise: Unlimited
|
|
137
|
+
</Text>
|
|
138
|
+
</View>
|
|
139
|
+
}
|
|
140
|
+
icon="info"
|
|
141
|
+
maxWidth={220}
|
|
142
|
+
/>
|
|
143
|
+
),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const InlineWithLabel: Story = {
|
|
147
|
+
render: () => (
|
|
148
|
+
<View className="gap-4">
|
|
149
|
+
<View className="flex-row items-center gap-1">
|
|
150
|
+
<Text className="text-sm font-medium text-text-primary">Email Address</Text>
|
|
151
|
+
<HelpTip
|
|
152
|
+
content="We'll never share your email with third parties."
|
|
153
|
+
size="sm"
|
|
154
|
+
/>
|
|
155
|
+
</View>
|
|
156
|
+
|
|
157
|
+
<View className="flex-row items-center gap-1">
|
|
158
|
+
<Text className="text-sm font-medium text-text-primary">Password</Text>
|
|
159
|
+
<HelpTip
|
|
160
|
+
content="Must be at least 8 characters with one uppercase, one lowercase, and one number."
|
|
161
|
+
size="sm"
|
|
162
|
+
/>
|
|
163
|
+
</View>
|
|
164
|
+
|
|
165
|
+
<View className="flex-row items-center gap-1">
|
|
166
|
+
<Text className="text-sm font-medium text-text-primary">API Key</Text>
|
|
167
|
+
<HelpTip
|
|
168
|
+
content="Keep this secret! Anyone with this key can access your account."
|
|
169
|
+
icon="warning"
|
|
170
|
+
size="sm"
|
|
171
|
+
color="primary"
|
|
172
|
+
/>
|
|
173
|
+
</View>
|
|
174
|
+
</View>
|
|
175
|
+
),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const LabelWithHelpComponent: Story = {
|
|
179
|
+
render: () => (
|
|
180
|
+
<View className="gap-4">
|
|
181
|
+
<LabelWithHelp
|
|
182
|
+
label="Username"
|
|
183
|
+
helpContent="Choose a unique username for your account."
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
<LabelWithHelp
|
|
187
|
+
label="Email Address"
|
|
188
|
+
helpContent="We'll use this for account notifications."
|
|
189
|
+
isRequired
|
|
190
|
+
/>
|
|
191
|
+
|
|
192
|
+
<LabelWithHelp
|
|
193
|
+
label="Bio"
|
|
194
|
+
helpContent="Tell others a bit about yourself. Max 500 characters."
|
|
195
|
+
helpSize="sm"
|
|
196
|
+
/>
|
|
197
|
+
</View>
|
|
198
|
+
),
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export const FormExample: Story = {
|
|
202
|
+
render: () => (
|
|
203
|
+
<View className="gap-4 p-4 bg-surface-base rounded-lg max-w-sm">
|
|
204
|
+
<Text className="text-lg font-semibold text-text-primary">Account Settings</Text>
|
|
205
|
+
|
|
206
|
+
<View className="gap-1">
|
|
207
|
+
<LabelWithHelp
|
|
208
|
+
label="Display Name"
|
|
209
|
+
helpContent="This is how your name will appear to other users."
|
|
210
|
+
isRequired
|
|
211
|
+
/>
|
|
212
|
+
<View className="border border-border-input rounded-md px-3 py-2 bg-surface-input">
|
|
213
|
+
<Text className="text-text-primary">John Doe</Text>
|
|
214
|
+
</View>
|
|
215
|
+
</View>
|
|
216
|
+
|
|
217
|
+
<View className="gap-1">
|
|
218
|
+
<LabelWithHelp
|
|
219
|
+
label="Timezone"
|
|
220
|
+
helpContent="All dates and times will be displayed in this timezone."
|
|
221
|
+
/>
|
|
222
|
+
<View className="border border-border-input rounded-md px-3 py-2 bg-surface-input">
|
|
223
|
+
<Text className="text-text-primary">America/New_York (EST)</Text>
|
|
224
|
+
</View>
|
|
225
|
+
</View>
|
|
226
|
+
|
|
227
|
+
<View className="gap-1">
|
|
228
|
+
<View className="flex-row items-center gap-1">
|
|
229
|
+
<Text className="text-sm font-medium text-text-primary">Two-Factor Authentication</Text>
|
|
230
|
+
<HelpTip
|
|
231
|
+
content="Enable 2FA for additional account security. You'll need an authenticator app."
|
|
232
|
+
icon="info"
|
|
233
|
+
size="sm"
|
|
234
|
+
color="primary"
|
|
235
|
+
/>
|
|
236
|
+
</View>
|
|
237
|
+
<View className="border border-border-input rounded-md px-3 py-2 bg-surface-input">
|
|
238
|
+
<Text className="text-status-success">Enabled ✓</Text>
|
|
239
|
+
</View>
|
|
240
|
+
</View>
|
|
241
|
+
</View>
|
|
242
|
+
),
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export const NoArrow: Story = {
|
|
246
|
+
args: {
|
|
247
|
+
content: 'This tooltip has no arrow.',
|
|
248
|
+
hasArrow: false,
|
|
249
|
+
},
|
|
250
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { HelpTip, LabelWithHelp } from './HelpTip'
|
|
5
|
+
|
|
6
|
+
describe('HelpTip', () => {
|
|
7
|
+
it('renders the help icon button', () => {
|
|
8
|
+
render(<HelpTip content="Help text" />)
|
|
9
|
+
expect(screen.getByRole('button', { name: 'Show help information' })).toBeInTheDocument()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('shows tooltip content when pressed', () => {
|
|
13
|
+
render(<HelpTip content="This is helpful" />)
|
|
14
|
+
|
|
15
|
+
fireEvent.click(screen.getByRole('button', { name: 'Show help information' }))
|
|
16
|
+
expect(screen.getByText('This is helpful')).toBeInTheDocument()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('hides tooltip content when pressed again', () => {
|
|
20
|
+
render(<HelpTip content="Toggle me" />)
|
|
21
|
+
|
|
22
|
+
const button = screen.getByRole('button', { name: 'Show help information' })
|
|
23
|
+
fireEvent.click(button)
|
|
24
|
+
expect(screen.getByText('Toggle me')).toBeInTheDocument()
|
|
25
|
+
|
|
26
|
+
fireEvent.click(button)
|
|
27
|
+
expect(screen.queryByText('Toggle me')).not.toBeInTheDocument()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('renders custom ReactNode content', () => {
|
|
31
|
+
render(
|
|
32
|
+
<HelpTip content={<span data-testid="custom-content">Rich content</span>} />
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
fireEvent.click(screen.getByRole('button', { name: 'Show help information' }))
|
|
36
|
+
expect(screen.getByTestId('custom-content')).toBeInTheDocument()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('controlled state', () => {
|
|
40
|
+
it('shows tooltip when isOpen is true', () => {
|
|
41
|
+
render(<HelpTip content="Always visible" isOpen />)
|
|
42
|
+
expect(screen.getByText('Always visible')).toBeInTheDocument()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('hides tooltip when isOpen is false', () => {
|
|
46
|
+
render(<HelpTip content="Always hidden" isOpen={false} />)
|
|
47
|
+
expect(screen.queryByText('Always hidden')).not.toBeInTheDocument()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('does not toggle on press when controlled', () => {
|
|
51
|
+
render(<HelpTip content="Controlled" isOpen={false} />)
|
|
52
|
+
|
|
53
|
+
fireEvent.click(screen.getByRole('button', { name: 'Show help information' }))
|
|
54
|
+
expect(screen.queryByText('Controlled')).not.toBeInTheDocument()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('icon types', () => {
|
|
59
|
+
it('renders help icon by default', () => {
|
|
60
|
+
render(<HelpTip content="Help" />)
|
|
61
|
+
expect(screen.getByText('?')).toBeInTheDocument()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('renders info icon', () => {
|
|
65
|
+
render(<HelpTip content="Info" icon="info" />)
|
|
66
|
+
expect(screen.getByRole('button', { name: 'Show help information' })).toBeInTheDocument()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('renders warning icon', () => {
|
|
70
|
+
render(<HelpTip content="Warning" icon="warning" />)
|
|
71
|
+
expect(screen.getByRole('button', { name: 'Show help information' })).toBeInTheDocument()
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('sizes', () => {
|
|
76
|
+
it('renders with sm size', () => {
|
|
77
|
+
render(<HelpTip content="Small" size="sm" />)
|
|
78
|
+
expect(screen.getByRole('button', { name: 'Show help information' })).toBeInTheDocument()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('renders with md size (default)', () => {
|
|
82
|
+
render(<HelpTip content="Medium" size="md" />)
|
|
83
|
+
expect(screen.getByRole('button', { name: 'Show help information' })).toBeInTheDocument()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('renders with lg size', () => {
|
|
87
|
+
render(<HelpTip content="Large" size="lg" />)
|
|
88
|
+
expect(screen.getByRole('button', { name: 'Show help information' })).toBeInTheDocument()
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('colors', () => {
|
|
93
|
+
it('renders with default color', () => {
|
|
94
|
+
render(<HelpTip content="Default" color="default" />)
|
|
95
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('renders with primary color', () => {
|
|
99
|
+
render(<HelpTip content="Primary" color="primary" />)
|
|
100
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('renders with secondary color', () => {
|
|
104
|
+
render(<HelpTip content="Secondary" color="secondary" />)
|
|
105
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('renders with muted color', () => {
|
|
109
|
+
render(<HelpTip content="Muted" color="muted" />)
|
|
110
|
+
expect(screen.getByRole('button')).toBeInTheDocument()
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('placement', () => {
|
|
115
|
+
it('renders with top placement (default)', () => {
|
|
116
|
+
render(<HelpTip content="Top" placement="top" isOpen />)
|
|
117
|
+
expect(screen.getByText('Top')).toBeInTheDocument()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('renders with bottom placement', () => {
|
|
121
|
+
render(<HelpTip content="Bottom" placement="bottom" isOpen />)
|
|
122
|
+
expect(screen.getByText('Bottom')).toBeInTheDocument()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('renders with left placement', () => {
|
|
126
|
+
render(<HelpTip content="Left" placement="left" isOpen />)
|
|
127
|
+
expect(screen.getByText('Left')).toBeInTheDocument()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('renders with right placement', () => {
|
|
131
|
+
render(<HelpTip content="Right" placement="right" isOpen />)
|
|
132
|
+
expect(screen.getByText('Right')).toBeInTheDocument()
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('arrow', () => {
|
|
137
|
+
it('renders with arrow by default', () => {
|
|
138
|
+
render(<HelpTip content="With arrow" isOpen />)
|
|
139
|
+
expect(screen.getByText('With arrow')).toBeInTheDocument()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('renders without arrow when hasArrow is false', () => {
|
|
143
|
+
render(<HelpTip content="No arrow" hasArrow={false} isOpen />)
|
|
144
|
+
expect(screen.getByText('No arrow')).toBeInTheDocument()
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('accessibility', () => {
|
|
149
|
+
it('has no accessibility violations', async () => {
|
|
150
|
+
const { container } = render(<HelpTip content="Accessible help tip" />)
|
|
151
|
+
const results = await axe(container)
|
|
152
|
+
expect(results).toHaveNoViolations()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('has correct accessibility label', () => {
|
|
156
|
+
render(<HelpTip content="Help text" />)
|
|
157
|
+
expect(screen.getByLabelText('Show help information')).toBeInTheDocument()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('sets accessibility hint for string content', () => {
|
|
161
|
+
render(<HelpTip content="This is a hint" />)
|
|
162
|
+
const button = screen.getByRole('button', { name: 'Show help information' })
|
|
163
|
+
expect(button).toBeInTheDocument()
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
describe('LabelWithHelp', () => {
|
|
169
|
+
it('renders label text', () => {
|
|
170
|
+
render(<LabelWithHelp label="API Key" helpContent="Found in settings" />)
|
|
171
|
+
expect(screen.getByText('API Key')).toBeInTheDocument()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('renders help icon alongside label', () => {
|
|
175
|
+
render(<LabelWithHelp label="Field" helpContent="Help text" />)
|
|
176
|
+
expect(screen.getByRole('button', { name: 'Show help information' })).toBeInTheDocument()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('shows required indicator when isRequired is true', () => {
|
|
180
|
+
render(<LabelWithHelp label="Email" helpContent="Help" isRequired />)
|
|
181
|
+
expect(screen.getByText('*')).toBeInTheDocument()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('does not show required indicator by default', () => {
|
|
185
|
+
render(<LabelWithHelp label="Email" helpContent="Help" />)
|
|
186
|
+
expect(screen.queryByText('*')).not.toBeInTheDocument()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('accessibility', () => {
|
|
190
|
+
it('has no accessibility violations', async () => {
|
|
191
|
+
const { container } = render(
|
|
192
|
+
<LabelWithHelp label="Username" helpContent="Choose a unique username" />
|
|
193
|
+
)
|
|
194
|
+
const results = await axe(container)
|
|
195
|
+
expect(results).toHaveNoViolations()
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
})
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { View, Text, Pressable, type ViewProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
|
|
5
|
+
export type HelpTipSize = 'sm' | 'md' | 'lg'
|
|
6
|
+
export type HelpTipPlacement = 'top' | 'bottom' | 'left' | 'right'
|
|
7
|
+
export type HelpTipIcon = 'help' | 'info' | 'warning'
|
|
8
|
+
|
|
9
|
+
export interface HelpTipProps extends ViewProps {
|
|
10
|
+
/** Tooltip content */
|
|
11
|
+
content: React.ReactNode
|
|
12
|
+
/** Size of the icon */
|
|
13
|
+
size?: HelpTipSize
|
|
14
|
+
/** Placement of the tooltip */
|
|
15
|
+
placement?: HelpTipPlacement
|
|
16
|
+
/** Icon type */
|
|
17
|
+
icon?: HelpTipIcon
|
|
18
|
+
/** Icon color */
|
|
19
|
+
color?: 'default' | 'primary' | 'secondary' | 'muted'
|
|
20
|
+
/** Whether the tooltip is always visible (controlled) */
|
|
21
|
+
isOpen?: boolean
|
|
22
|
+
/** Whether to show an arrow */
|
|
23
|
+
hasArrow?: boolean
|
|
24
|
+
/** Max width of the tooltip */
|
|
25
|
+
maxWidth?: number
|
|
26
|
+
/** Additional className for the icon */
|
|
27
|
+
iconClassName?: string
|
|
28
|
+
/** Additional className for the tooltip */
|
|
29
|
+
tooltipClassName?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sizeStyles: Record<HelpTipSize, { icon: string; text: string }> = {
|
|
33
|
+
sm: { icon: 'w-3.5 h-3.5 text-xs', text: 'text-xs' },
|
|
34
|
+
md: { icon: 'w-4 h-4 text-sm', text: 'text-sm' },
|
|
35
|
+
lg: { icon: 'w-5 h-5 text-base', text: 'text-base' },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const colorStyles: Record<string, string> = {
|
|
39
|
+
default: 'text-text-tertiary',
|
|
40
|
+
primary: 'text-brand-primary',
|
|
41
|
+
secondary: 'text-text-secondary',
|
|
42
|
+
muted: 'text-text-tertiary opacity-60',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const iconMap: Record<HelpTipIcon, string> = {
|
|
46
|
+
help: '?',
|
|
47
|
+
info: 'ℹ',
|
|
48
|
+
warning: '⚠',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const placementStyles: Record<HelpTipPlacement, string> = {
|
|
52
|
+
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
|
|
53
|
+
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
|
|
54
|
+
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
|
|
55
|
+
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const arrowStyles: Record<HelpTipPlacement, string> = {
|
|
59
|
+
top: 'bottom-0 left-1/2 -translate-x-1/2 translate-y-full border-t-surface-elevated border-x-transparent border-b-transparent',
|
|
60
|
+
bottom: 'top-0 left-1/2 -translate-x-1/2 -translate-y-full border-b-surface-elevated border-x-transparent border-t-transparent',
|
|
61
|
+
left: 'right-0 top-1/2 -translate-y-1/2 translate-x-full border-l-surface-elevated border-y-transparent border-r-transparent',
|
|
62
|
+
right: 'left-0 top-1/2 -translate-y-1/2 -translate-x-full border-r-surface-elevated border-y-transparent border-l-transparent',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* HelpTip component for displaying contextual help information.
|
|
67
|
+
*
|
|
68
|
+
* A small icon (typically a question mark or info icon) that shows
|
|
69
|
+
* a tooltip with helpful information when hovered or pressed.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Basic usage
|
|
73
|
+
* <HelpTip content="This field is required for form submission." />
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // With custom icon and placement
|
|
77
|
+
* <HelpTip
|
|
78
|
+
* content="API rate limits apply to this endpoint."
|
|
79
|
+
* icon="warning"
|
|
80
|
+
* placement="right"
|
|
81
|
+
* color="primary"
|
|
82
|
+
* />
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // Inline with label
|
|
86
|
+
* <View className="flex-row items-center gap-1">
|
|
87
|
+
* <Text>Email Address</Text>
|
|
88
|
+
* <HelpTip content="We'll never share your email with anyone." size="sm" />
|
|
89
|
+
* </View>
|
|
90
|
+
*/
|
|
91
|
+
export function HelpTip({
|
|
92
|
+
content,
|
|
93
|
+
size = 'md',
|
|
94
|
+
placement = 'top',
|
|
95
|
+
icon = 'help',
|
|
96
|
+
color = 'default',
|
|
97
|
+
isOpen: controlledIsOpen,
|
|
98
|
+
hasArrow = true,
|
|
99
|
+
maxWidth = 250,
|
|
100
|
+
iconClassName,
|
|
101
|
+
tooltipClassName,
|
|
102
|
+
className,
|
|
103
|
+
...props
|
|
104
|
+
}: HelpTipProps) {
|
|
105
|
+
const [internalIsOpen, setInternalIsOpen] = useState(false)
|
|
106
|
+
const isOpen = controlledIsOpen ?? internalIsOpen
|
|
107
|
+
|
|
108
|
+
const { icon: iconSize, text: textSize } = sizeStyles[size]
|
|
109
|
+
|
|
110
|
+
const handlePress = () => {
|
|
111
|
+
if (controlledIsOpen === undefined) {
|
|
112
|
+
setInternalIsOpen((prev) => !prev)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const handleHoverIn = () => {
|
|
117
|
+
if (controlledIsOpen === undefined) {
|
|
118
|
+
setInternalIsOpen(true)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const handleHoverOut = () => {
|
|
123
|
+
if (controlledIsOpen === undefined) {
|
|
124
|
+
setInternalIsOpen(false)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<View className={cn('relative inline-flex', className)} {...props}>
|
|
130
|
+
{/* Icon button */}
|
|
131
|
+
<Pressable
|
|
132
|
+
onPress={handlePress}
|
|
133
|
+
onHoverIn={handleHoverIn}
|
|
134
|
+
onHoverOut={handleHoverOut}
|
|
135
|
+
accessibilityRole="button"
|
|
136
|
+
accessibilityLabel="Show help information"
|
|
137
|
+
accessibilityHint={typeof content === 'string' ? content : undefined}
|
|
138
|
+
className={cn(
|
|
139
|
+
'items-center justify-center rounded-full',
|
|
140
|
+
'border border-current',
|
|
141
|
+
colorStyles[color],
|
|
142
|
+
iconSize,
|
|
143
|
+
'web:hover:opacity-80 web:cursor-help',
|
|
144
|
+
iconClassName
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<Text className={cn('font-semibold', iconSize, colorStyles[color])}>
|
|
148
|
+
{iconMap[icon]}
|
|
149
|
+
</Text>
|
|
150
|
+
</Pressable>
|
|
151
|
+
|
|
152
|
+
{/* Tooltip */}
|
|
153
|
+
{isOpen && (
|
|
154
|
+
<View
|
|
155
|
+
className={cn(
|
|
156
|
+
'absolute z-50',
|
|
157
|
+
'bg-surface-elevated rounded-lg shadow-lg',
|
|
158
|
+
'border border-border-default',
|
|
159
|
+
'px-3 py-2',
|
|
160
|
+
placementStyles[placement],
|
|
161
|
+
tooltipClassName
|
|
162
|
+
)}
|
|
163
|
+
style={{ maxWidth }}
|
|
164
|
+
>
|
|
165
|
+
{typeof content === 'string' ? (
|
|
166
|
+
<Text className={cn('text-text-primary', textSize)}>
|
|
167
|
+
{content}
|
|
168
|
+
</Text>
|
|
169
|
+
) : (
|
|
170
|
+
content
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{/* Arrow */}
|
|
174
|
+
{hasArrow && (
|
|
175
|
+
<View
|
|
176
|
+
className={cn(
|
|
177
|
+
'absolute w-0 h-0',
|
|
178
|
+
'border-4',
|
|
179
|
+
arrowStyles[placement]
|
|
180
|
+
)}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
183
|
+
</View>
|
|
184
|
+
)}
|
|
185
|
+
</View>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface LabelWithHelpProps extends ViewProps {
|
|
190
|
+
/** Label text */
|
|
191
|
+
label: string
|
|
192
|
+
/** Help content */
|
|
193
|
+
helpContent: React.ReactNode
|
|
194
|
+
/** Whether the field is required */
|
|
195
|
+
isRequired?: boolean
|
|
196
|
+
/** Size of the help icon */
|
|
197
|
+
helpSize?: HelpTipSize
|
|
198
|
+
/** Additional className */
|
|
199
|
+
className?: string
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Convenience component combining a label with a help tip.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* <LabelWithHelp
|
|
207
|
+
* label="API Key"
|
|
208
|
+
* helpContent="Your API key can be found in the developer settings."
|
|
209
|
+
* isRequired
|
|
210
|
+
* />
|
|
211
|
+
*/
|
|
212
|
+
export function LabelWithHelp({
|
|
213
|
+
label,
|
|
214
|
+
helpContent,
|
|
215
|
+
isRequired = false,
|
|
216
|
+
helpSize = 'sm',
|
|
217
|
+
className,
|
|
218
|
+
...props
|
|
219
|
+
}: LabelWithHelpProps) {
|
|
220
|
+
return (
|
|
221
|
+
<View className={cn('flex-row items-center gap-1', className)} {...props}>
|
|
222
|
+
<Text className="text-sm font-medium text-text-primary">
|
|
223
|
+
{label}
|
|
224
|
+
{isRequired && <Text className="text-status-error ml-0.5">*</Text>}
|
|
225
|
+
</Text>
|
|
226
|
+
<HelpTip content={helpContent} size={helpSize} />
|
|
227
|
+
</View>
|
|
228
|
+
)
|
|
229
|
+
}
|