@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,152 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { Avatar, AvatarBadge, AvatarGroup } from './Avatar'
|
|
5
|
+
|
|
6
|
+
describe('Avatar', () => {
|
|
7
|
+
it('renders correctly with default props', () => {
|
|
8
|
+
render(<Avatar />)
|
|
9
|
+
expect(screen.getByRole('img')).toBeInTheDocument()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('renders fallback text', () => {
|
|
13
|
+
render(<Avatar fallback="JD" />)
|
|
14
|
+
expect(screen.getByText('JD')).toBeInTheDocument()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('uses fallback as accessibility label when no alt provided', () => {
|
|
18
|
+
render(<Avatar fallback="JD" />)
|
|
19
|
+
expect(screen.getByRole('img')).toHaveAttribute('aria-label', 'JD')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('uses alt as accessibility label', () => {
|
|
23
|
+
render(<Avatar alt="John Doe" fallback="JD" />)
|
|
24
|
+
expect(screen.getByRole('img')).toHaveAttribute('aria-label', 'John Doe')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('uses default accessibility label when no alt or fallback', () => {
|
|
28
|
+
render(<Avatar />)
|
|
29
|
+
expect(screen.getByRole('img')).toHaveAttribute('aria-label', 'Avatar')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('renders with image source', () => {
|
|
33
|
+
render(<Avatar source={{ uri: 'https://example.com/avatar.jpg' }} alt="User" />)
|
|
34
|
+
const imgs = screen.getAllByRole('img')
|
|
35
|
+
expect(imgs.length).toBeGreaterThan(0)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('renders with all size options', () => {
|
|
39
|
+
const sizes = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const
|
|
40
|
+
sizes.forEach((size) => {
|
|
41
|
+
const { unmount } = render(<Avatar size={size} fallback="AB" />)
|
|
42
|
+
expect(screen.getByRole('img')).toBeInTheDocument()
|
|
43
|
+
unmount()
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('renders empty placeholder when no source or fallback', () => {
|
|
48
|
+
const { container } = render(<Avatar />)
|
|
49
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('applies custom className', () => {
|
|
53
|
+
render(<Avatar className="extra" />)
|
|
54
|
+
expect(screen.getByRole('img')).toBeInTheDocument()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('colorFromName', () => {
|
|
58
|
+
it('renders initials from colorFromName', () => {
|
|
59
|
+
render(<Avatar colorFromName="John Doe" />)
|
|
60
|
+
expect(screen.getByText('JD')).toBeInTheDocument()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('generates deterministic color from name', () => {
|
|
64
|
+
const { container } = render(<Avatar colorFromName="Alice" />)
|
|
65
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('colorFromName fallback is overridden by explicit fallback', () => {
|
|
69
|
+
render(<Avatar colorFromName="John Doe" fallback="XX" />)
|
|
70
|
+
expect(screen.getByText('XX')).toBeInTheDocument()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('AvatarBadge', () => {
|
|
75
|
+
it('renders with default success color', () => {
|
|
76
|
+
const { container } = render(<AvatarBadge />)
|
|
77
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('renders with all color options', () => {
|
|
81
|
+
const colors = ['success', 'error', 'warning', 'default'] as const
|
|
82
|
+
colors.forEach((color) => {
|
|
83
|
+
const { unmount } = render(<AvatarBadge color={color} />)
|
|
84
|
+
unmount()
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('accepts custom className', () => {
|
|
89
|
+
const { container } = render(<AvatarBadge className="extra" />)
|
|
90
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('AvatarGroup', () => {
|
|
95
|
+
it('renders children avatars', () => {
|
|
96
|
+
render(
|
|
97
|
+
<AvatarGroup>
|
|
98
|
+
<Avatar fallback="A" />
|
|
99
|
+
<Avatar fallback="B" />
|
|
100
|
+
</AvatarGroup>
|
|
101
|
+
)
|
|
102
|
+
expect(screen.getByText('A')).toBeInTheDocument()
|
|
103
|
+
expect(screen.getByText('B')).toBeInTheDocument()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('limits visible avatars with max prop', () => {
|
|
107
|
+
render(
|
|
108
|
+
<AvatarGroup max={2}>
|
|
109
|
+
<Avatar fallback="A" />
|
|
110
|
+
<Avatar fallback="B" />
|
|
111
|
+
<Avatar fallback="C" />
|
|
112
|
+
<Avatar fallback="D" />
|
|
113
|
+
</AvatarGroup>
|
|
114
|
+
)
|
|
115
|
+
expect(screen.getByText('A')).toBeInTheDocument()
|
|
116
|
+
expect(screen.getByText('B')).toBeInTheDocument()
|
|
117
|
+
expect(screen.getByText('+2')).toBeInTheDocument()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('shows no +N indicator when all avatars fit', () => {
|
|
121
|
+
render(
|
|
122
|
+
<AvatarGroup max={5}>
|
|
123
|
+
<Avatar fallback="A" />
|
|
124
|
+
<Avatar fallback="B" />
|
|
125
|
+
</AvatarGroup>
|
|
126
|
+
)
|
|
127
|
+
expect(screen.queryByText(/\+/)).not.toBeInTheDocument()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('applies custom className', () => {
|
|
131
|
+
const { container } = render(
|
|
132
|
+
<AvatarGroup className="extra">
|
|
133
|
+
<Avatar fallback="A" />
|
|
134
|
+
</AvatarGroup>
|
|
135
|
+
)
|
|
136
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('accessibility', () => {
|
|
141
|
+
it('has no accessibility violations', async () => {
|
|
142
|
+
const { container } = render(<Avatar fallback="JD" alt="John Doe" />)
|
|
143
|
+
const results = await axe(container)
|
|
144
|
+
expect(results).toHaveNoViolations()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('has correct image role', () => {
|
|
148
|
+
render(<Avatar fallback="JD" />)
|
|
149
|
+
expect(screen.getByRole('img')).toBeInTheDocument()
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, Image, type ViewProps, type ImageSourcePropType } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { avatarColor, getInitials } from '../../../utils/avatar-color'
|
|
5
|
+
|
|
6
|
+
export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
7
|
+
|
|
8
|
+
export interface AvatarProps extends ViewProps {
|
|
9
|
+
/** Avatar size */
|
|
10
|
+
size?: AvatarSize
|
|
11
|
+
/** Image source */
|
|
12
|
+
source?: ImageSourcePropType
|
|
13
|
+
/** Fallback text (usually initials) */
|
|
14
|
+
fallback?: string
|
|
15
|
+
/** Alt text for accessibility */
|
|
16
|
+
alt?: string
|
|
17
|
+
/** Generate background color and initials from a name */
|
|
18
|
+
colorFromName?: string
|
|
19
|
+
/** Additional className */
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const sizeStyles: Record<AvatarSize, { container: string; text: string; image: string }> = {
|
|
24
|
+
xs: { container: 'w-6 h-6', text: 'text-xs', image: 'w-6 h-6' },
|
|
25
|
+
sm: { container: 'w-8 h-8', text: 'text-xs', image: 'w-8 h-8' },
|
|
26
|
+
md: { container: 'w-10 h-10', text: 'text-sm', image: 'w-10 h-10' },
|
|
27
|
+
lg: { container: 'w-12 h-12', text: 'text-base', image: 'w-12 h-12' },
|
|
28
|
+
xl: { container: 'w-16 h-16', text: 'text-lg', image: 'w-16 h-16' },
|
|
29
|
+
'2xl': { container: 'w-20 h-20', text: 'text-xl', image: 'w-20 h-20' },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Avatar component for user/entity representation.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // With image
|
|
37
|
+
* <Avatar source={{ uri: 'https://example.com/avatar.jpg' }} alt="User" />
|
|
38
|
+
*
|
|
39
|
+
* // With fallback initials
|
|
40
|
+
* <Avatar fallback="JD" />
|
|
41
|
+
*
|
|
42
|
+
* // With badge
|
|
43
|
+
* <AvatarGroup>
|
|
44
|
+
* <Avatar source={source1} />
|
|
45
|
+
* <Avatar source={source2} />
|
|
46
|
+
* <AvatarBadge />
|
|
47
|
+
* </AvatarGroup>
|
|
48
|
+
*/
|
|
49
|
+
export function Avatar({
|
|
50
|
+
size = 'md',
|
|
51
|
+
source,
|
|
52
|
+
fallback,
|
|
53
|
+
alt,
|
|
54
|
+
colorFromName,
|
|
55
|
+
className,
|
|
56
|
+
...props
|
|
57
|
+
}: AvatarProps) {
|
|
58
|
+
const styles = sizeStyles[size]
|
|
59
|
+
const hasImage = source !== undefined
|
|
60
|
+
|
|
61
|
+
const resolvedFallback = fallback ?? (colorFromName ? getInitials(colorFromName) : undefined)
|
|
62
|
+
const nameColor = colorFromName ? avatarColor(colorFromName) : undefined
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<View
|
|
66
|
+
accessibilityRole="image"
|
|
67
|
+
accessibilityLabel={alt || resolvedFallback || 'Avatar'}
|
|
68
|
+
className={cn(
|
|
69
|
+
'items-center justify-center rounded-full overflow-hidden',
|
|
70
|
+
!nameColor && 'bg-border-strong',
|
|
71
|
+
styles.container,
|
|
72
|
+
className
|
|
73
|
+
)}
|
|
74
|
+
style={nameColor ? { backgroundColor: nameColor } : undefined}
|
|
75
|
+
{...props}
|
|
76
|
+
>
|
|
77
|
+
{hasImage ? (
|
|
78
|
+
<Image
|
|
79
|
+
source={source}
|
|
80
|
+
className={cn('rounded-full', styles.image)}
|
|
81
|
+
accessibilityLabel={alt}
|
|
82
|
+
/>
|
|
83
|
+
) : resolvedFallback ? (
|
|
84
|
+
<Text className={cn('font-semibold text-text-inverse', styles.text)}>
|
|
85
|
+
{resolvedFallback}
|
|
86
|
+
</Text>
|
|
87
|
+
) : (
|
|
88
|
+
<View className="w-full h-full bg-surface-raised" />
|
|
89
|
+
)}
|
|
90
|
+
</View>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface AvatarBadgeProps {
|
|
95
|
+
/** Badge color */
|
|
96
|
+
color?: 'success' | 'error' | 'warning' | 'default'
|
|
97
|
+
/** Additional className */
|
|
98
|
+
className?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const badgeColors: Record<string, string> = {
|
|
102
|
+
success: 'bg-status-success',
|
|
103
|
+
error: 'bg-status-error',
|
|
104
|
+
warning: 'bg-status-warning',
|
|
105
|
+
default: 'bg-border-strong',
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Status badge for Avatar (e.g., online indicator).
|
|
110
|
+
*/
|
|
111
|
+
export function AvatarBadge({ color = 'success', className }: AvatarBadgeProps) {
|
|
112
|
+
return (
|
|
113
|
+
<View
|
|
114
|
+
className={cn(
|
|
115
|
+
'absolute bottom-0 right-0 w-3 h-3 rounded-full border-2 border-surface-base',
|
|
116
|
+
badgeColors[color],
|
|
117
|
+
className
|
|
118
|
+
)}
|
|
119
|
+
/>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface AvatarGroupProps {
|
|
124
|
+
children: React.ReactNode
|
|
125
|
+
/** Maximum avatars to show before +N indicator */
|
|
126
|
+
max?: number
|
|
127
|
+
/** Size of avatars */
|
|
128
|
+
size?: AvatarSize
|
|
129
|
+
/** Additional className */
|
|
130
|
+
className?: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Group of avatars with stacking effect.
|
|
135
|
+
*/
|
|
136
|
+
export function AvatarGroup({
|
|
137
|
+
children,
|
|
138
|
+
max,
|
|
139
|
+
size = 'md',
|
|
140
|
+
className,
|
|
141
|
+
}: AvatarGroupProps) {
|
|
142
|
+
const childArray = React.Children.toArray(children)
|
|
143
|
+
const visibleChildren = max ? childArray.slice(0, max) : childArray
|
|
144
|
+
const remainingCount = max ? Math.max(0, childArray.length - max) : 0
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<View className={cn('flex-row', className)}>
|
|
148
|
+
{visibleChildren.map((child, index) => (
|
|
149
|
+
<View
|
|
150
|
+
key={index}
|
|
151
|
+
className={cn(
|
|
152
|
+
'border-2 border-surface-base rounded-full',
|
|
153
|
+
index > 0 && '-ml-2'
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
{React.isValidElement(child)
|
|
157
|
+
? React.cloneElement(child as React.ReactElement<any>, { size })
|
|
158
|
+
: child}
|
|
159
|
+
</View>
|
|
160
|
+
))}
|
|
161
|
+
{remainingCount > 0 && (
|
|
162
|
+
<View
|
|
163
|
+
className={cn(
|
|
164
|
+
'border-2 border-surface-base rounded-full -ml-2',
|
|
165
|
+
'items-center justify-center bg-surface-raised',
|
|
166
|
+
sizeStyles[size].container
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
<Text className={cn('font-semibold text-text-secondary', sizeStyles[size].text)}>
|
|
170
|
+
+{remainingCount}
|
|
171
|
+
</Text>
|
|
172
|
+
</View>
|
|
173
|
+
)}
|
|
174
|
+
</View>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { Badge, BadgeText } from './Badge'
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Badge> = {
|
|
6
|
+
title: 'Components/Badge',
|
|
7
|
+
component: Badge,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
argTypes: {
|
|
10
|
+
variant: {
|
|
11
|
+
control: 'select',
|
|
12
|
+
options: ['solid', 'subtle', 'outline'],
|
|
13
|
+
description: 'Visual variant',
|
|
14
|
+
},
|
|
15
|
+
color: {
|
|
16
|
+
control: 'select',
|
|
17
|
+
options: ['default', 'primary', 'secondary', 'success', 'error', 'warning', 'info'],
|
|
18
|
+
description: 'Color scheme',
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['sm', 'md', 'lg'],
|
|
23
|
+
description: 'Badge size',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default meta
|
|
29
|
+
type Story = StoryObj<typeof Badge>
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
variant: 'subtle',
|
|
34
|
+
color: 'default',
|
|
35
|
+
size: 'md',
|
|
36
|
+
children: 'Badge',
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const AllVariants: Story = {
|
|
41
|
+
render: () => (
|
|
42
|
+
<View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
|
|
43
|
+
<Badge variant="solid" color="primary">Solid</Badge>
|
|
44
|
+
<Badge variant="subtle" color="primary">Subtle</Badge>
|
|
45
|
+
<Badge variant="outline" color="primary">Outline</Badge>
|
|
46
|
+
</View>
|
|
47
|
+
),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const AllColors: Story = {
|
|
51
|
+
render: () => (
|
|
52
|
+
<View style={{ gap: 12 }}>
|
|
53
|
+
<View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
|
|
54
|
+
<Badge variant="solid" color="default">Default</Badge>
|
|
55
|
+
<Badge variant="solid" color="primary">Primary</Badge>
|
|
56
|
+
<Badge variant="solid" color="secondary">Secondary</Badge>
|
|
57
|
+
<Badge variant="solid" color="success">Success</Badge>
|
|
58
|
+
<Badge variant="solid" color="error">Error</Badge>
|
|
59
|
+
<Badge variant="solid" color="warning">Warning</Badge>
|
|
60
|
+
<Badge variant="solid" color="info">Info</Badge>
|
|
61
|
+
</View>
|
|
62
|
+
<View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
|
|
63
|
+
<Badge variant="subtle" color="default">Default</Badge>
|
|
64
|
+
<Badge variant="subtle" color="primary">Primary</Badge>
|
|
65
|
+
<Badge variant="subtle" color="secondary">Secondary</Badge>
|
|
66
|
+
<Badge variant="subtle" color="success">Success</Badge>
|
|
67
|
+
<Badge variant="subtle" color="error">Error</Badge>
|
|
68
|
+
<Badge variant="subtle" color="warning">Warning</Badge>
|
|
69
|
+
<Badge variant="subtle" color="info">Info</Badge>
|
|
70
|
+
</View>
|
|
71
|
+
<View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}>
|
|
72
|
+
<Badge variant="outline" color="default">Default</Badge>
|
|
73
|
+
<Badge variant="outline" color="primary">Primary</Badge>
|
|
74
|
+
<Badge variant="outline" color="secondary">Secondary</Badge>
|
|
75
|
+
<Badge variant="outline" color="success">Success</Badge>
|
|
76
|
+
<Badge variant="outline" color="error">Error</Badge>
|
|
77
|
+
<Badge variant="outline" color="warning">Warning</Badge>
|
|
78
|
+
<Badge variant="outline" color="info">Info</Badge>
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const AllSizes: Story = {
|
|
85
|
+
render: () => (
|
|
86
|
+
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
|
87
|
+
<Badge size="sm" color="primary">Small</Badge>
|
|
88
|
+
<Badge size="md" color="primary">Medium</Badge>
|
|
89
|
+
<Badge size="lg" color="primary">Large</Badge>
|
|
90
|
+
</View>
|
|
91
|
+
),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const WithBadgeText: Story = {
|
|
95
|
+
render: () => (
|
|
96
|
+
<Badge variant="solid" color="success">
|
|
97
|
+
<BadgeText>Active</BadgeText>
|
|
98
|
+
</Badge>
|
|
99
|
+
),
|
|
100
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { axe } from 'jest-axe'
|
|
4
|
+
import { Badge, BadgeText } from './Badge'
|
|
5
|
+
|
|
6
|
+
describe('Badge', () => {
|
|
7
|
+
it('renders string children correctly', () => {
|
|
8
|
+
render(<Badge>Active</Badge>)
|
|
9
|
+
expect(screen.getByText('Active')).toBeInTheDocument()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('renders BadgeText compound component', () => {
|
|
13
|
+
render(
|
|
14
|
+
<Badge>
|
|
15
|
+
<BadgeText>Status</BadgeText>
|
|
16
|
+
</Badge>
|
|
17
|
+
)
|
|
18
|
+
expect(screen.getByText('Status')).toBeInTheDocument()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('applies default variant (subtle) and color (default)', () => {
|
|
22
|
+
const { container } = render(<Badge>Default</Badge>)
|
|
23
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('renders with all variant options', () => {
|
|
27
|
+
const variants = ['solid', 'subtle', 'outline'] as const
|
|
28
|
+
variants.forEach((variant) => {
|
|
29
|
+
const { unmount } = render(<Badge variant={variant}>Test</Badge>)
|
|
30
|
+
expect(screen.getByText('Test')).toBeInTheDocument()
|
|
31
|
+
unmount()
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders with all color options', () => {
|
|
36
|
+
const colors = ['default', 'primary', 'secondary', 'success', 'error', 'warning', 'info'] as const
|
|
37
|
+
colors.forEach((color) => {
|
|
38
|
+
const { unmount } = render(<Badge color={color}>Test</Badge>)
|
|
39
|
+
expect(screen.getByText('Test')).toBeInTheDocument()
|
|
40
|
+
unmount()
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('renders with all size options', () => {
|
|
45
|
+
const sizes = ['sm', 'md', 'lg'] as const
|
|
46
|
+
sizes.forEach((size) => {
|
|
47
|
+
const { unmount } = render(<Badge size={size}>Test</Badge>)
|
|
48
|
+
expect(screen.getByText('Test')).toBeInTheDocument()
|
|
49
|
+
unmount()
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('applies custom className', () => {
|
|
54
|
+
const { container } = render(<Badge className="custom-class">Test</Badge>)
|
|
55
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('passes additional props through', () => {
|
|
59
|
+
render(<Badge testID="badge-test">Test</Badge>)
|
|
60
|
+
expect(screen.getByText('Test')).toBeInTheDocument()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('BadgeText', () => {
|
|
64
|
+
it('renders children', () => {
|
|
65
|
+
render(<BadgeText>Label</BadgeText>)
|
|
66
|
+
expect(screen.getByText('Label')).toBeInTheDocument()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('accepts custom className', () => {
|
|
70
|
+
render(<BadgeText className="extra">Label</BadgeText>)
|
|
71
|
+
expect(screen.getByText('Label')).toBeInTheDocument()
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('dot indicator', () => {
|
|
76
|
+
it('renders dot when dot prop is true', () => {
|
|
77
|
+
const { container } = render(<Badge dot color="success">Active</Badge>)
|
|
78
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('renders dot with explicit dotColor', () => {
|
|
82
|
+
const { container } = render(<Badge dot dotColor="warning">Pending</Badge>)
|
|
83
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('does not render dot by default', () => {
|
|
87
|
+
const { container } = render(<Badge>No dot</Badge>)
|
|
88
|
+
expect(container.firstChild).toBeInTheDocument()
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('accessibility', () => {
|
|
93
|
+
it('has no accessibility violations', async () => {
|
|
94
|
+
const { container } = render(
|
|
95
|
+
<Badge color="success">
|
|
96
|
+
<BadgeText>Active</BadgeText>
|
|
97
|
+
</Badge>
|
|
98
|
+
)
|
|
99
|
+
const results = await axe(container)
|
|
100
|
+
expect(results).toHaveNoViolations()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, type ViewProps } from 'react-native'
|
|
3
|
+
import { cn } from '../../../utils/cn'
|
|
4
|
+
import { Indicator, type IndicatorColor } from '../indicator'
|
|
5
|
+
|
|
6
|
+
export type BadgeVariant = 'solid' | 'subtle' | 'outline'
|
|
7
|
+
export type BadgeColor = 'default' | 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info'
|
|
8
|
+
export type BadgeSize = 'sm' | 'md' | 'lg'
|
|
9
|
+
|
|
10
|
+
export interface BadgeProps extends ViewProps {
|
|
11
|
+
/** Visual variant */
|
|
12
|
+
variant?: BadgeVariant
|
|
13
|
+
/** Color scheme */
|
|
14
|
+
color?: BadgeColor
|
|
15
|
+
/** Size */
|
|
16
|
+
size?: BadgeSize
|
|
17
|
+
/** Show a leading indicator dot */
|
|
18
|
+
dot?: boolean
|
|
19
|
+
/** Color for the dot indicator (defaults to match badge color) */
|
|
20
|
+
dotColor?: IndicatorColor
|
|
21
|
+
/** Additional className */
|
|
22
|
+
className?: string
|
|
23
|
+
children?: React.ReactNode
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const variantColorStyles: Record<BadgeVariant, Record<BadgeColor, string>> = {
|
|
27
|
+
solid: {
|
|
28
|
+
default: 'bg-border-strong text-text-inverse',
|
|
29
|
+
primary: 'bg-brand-primary text-text-inverse',
|
|
30
|
+
secondary: 'bg-brand-secondary text-text-inverse',
|
|
31
|
+
success: 'bg-status-success text-text-inverse',
|
|
32
|
+
error: 'bg-status-error text-text-inverse',
|
|
33
|
+
warning: 'bg-status-warning text-text-inverse',
|
|
34
|
+
info: 'bg-status-info text-text-inverse',
|
|
35
|
+
},
|
|
36
|
+
subtle: {
|
|
37
|
+
default: 'bg-surface-raised text-text-secondary',
|
|
38
|
+
primary: 'bg-brand-primary-subtle text-brand-primary',
|
|
39
|
+
secondary: 'bg-brand-secondary-subtle text-brand-secondary',
|
|
40
|
+
success: 'bg-status-success-subtle text-status-success',
|
|
41
|
+
error: 'bg-status-error-subtle text-status-error',
|
|
42
|
+
warning: 'bg-status-warning-subtle text-status-warning',
|
|
43
|
+
info: 'bg-status-info-subtle text-status-info',
|
|
44
|
+
},
|
|
45
|
+
outline: {
|
|
46
|
+
default: 'border border-border-strong text-text-secondary',
|
|
47
|
+
primary: 'border border-brand-primary text-brand-primary',
|
|
48
|
+
secondary: 'border border-brand-secondary text-brand-secondary',
|
|
49
|
+
success: 'border border-status-success text-status-success',
|
|
50
|
+
error: 'border border-status-error text-status-error',
|
|
51
|
+
warning: 'border border-status-warning text-status-warning',
|
|
52
|
+
info: 'border border-status-info text-status-info',
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const sizeStyles: Record<BadgeSize, string> = {
|
|
57
|
+
sm: 'px-1.5 py-0.5 text-xs',
|
|
58
|
+
md: 'px-2 py-0.5 text-xs',
|
|
59
|
+
lg: 'px-2.5 py-1 text-sm',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Badge component for status indicators and labels.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* <Badge color="success">Active</Badge>
|
|
67
|
+
* <Badge variant="outline" color="warning">Pending</Badge>
|
|
68
|
+
*/
|
|
69
|
+
export function Badge({
|
|
70
|
+
variant = 'subtle',
|
|
71
|
+
color = 'default',
|
|
72
|
+
size = 'md',
|
|
73
|
+
dot,
|
|
74
|
+
dotColor,
|
|
75
|
+
className,
|
|
76
|
+
children,
|
|
77
|
+
...props
|
|
78
|
+
}: BadgeProps) {
|
|
79
|
+
return (
|
|
80
|
+
<View
|
|
81
|
+
className={cn(
|
|
82
|
+
'inline-flex items-center justify-center rounded-full font-medium',
|
|
83
|
+
variantColorStyles[variant][color],
|
|
84
|
+
sizeStyles[size],
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
{dot && (
|
|
90
|
+
<Indicator
|
|
91
|
+
size="xs"
|
|
92
|
+
color={dotColor ?? (color === 'default' || color === 'secondary' ? 'default' : color as IndicatorColor)}
|
|
93
|
+
className="mr-0.5"
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
{typeof children === 'string' ? (
|
|
97
|
+
<Text className="text-inherit font-medium">{children}</Text>
|
|
98
|
+
) : (
|
|
99
|
+
children
|
|
100
|
+
)}
|
|
101
|
+
</View>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface BadgeTextProps {
|
|
106
|
+
children: React.ReactNode
|
|
107
|
+
className?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Text component for Badge content.
|
|
112
|
+
*/
|
|
113
|
+
export function BadgeText({ children, className }: BadgeTextProps) {
|
|
114
|
+
return (
|
|
115
|
+
<Text className={cn('text-inherit font-medium', className)}>
|
|
116
|
+
{children}
|
|
117
|
+
</Text>
|
|
118
|
+
)
|
|
119
|
+
}
|