@titan-design/react-ui 0.1.1 → 0.2.6
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/docs/WEB_SETUP.md +217 -0
- package/package.json +14 -2
- 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 +1 -0
- package/src/components/custom/stepper/index.ts +2 -2
- package/src/components/ui/avatar/Avatar.test.tsx +17 -0
- package/src/components/ui/avatar/Avatar.tsx +13 -5
- package/src/components/ui/badge/Badge.test.tsx +17 -0
- package/src/components/ui/badge/Badge.tsx +14 -0
- package/src/components/ui/badge/index.ts +1 -0
- package/src/components/ui/button/Button.tsx +55 -0
- package/src/components/ui/card/Card.test.tsx +29 -0
- package/src/components/ui/card/Card.tsx +32 -11
- package/src/components/ui/index.ts +2 -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/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.test.tsx +144 -2
- package/src/components/ui/popover/Popover.tsx +81 -6
- package/src/components/ui/progress/Progress.test.tsx +29 -0
- package/src/components/ui/progress/Progress.tsx +54 -35
- package/src/components/ui/select/Select.test.tsx +20 -0
- package/src/components/ui/select/Select.tsx +9 -2
- package/src/components/ui/toast/index.ts +1 -0
- package/src/components/ui/tooltip/Tooltip.test.tsx +83 -0
- package/src/components/ui/tooltip/Tooltip.tsx +117 -26
- package/src/stories/PresetShowcase.stories.tsx +462 -0
- package/src/stories/ThemePresets.stories.tsx +85 -0
- package/src/theme/global.css +129 -1
- package/src/theme/index.ts +1 -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/utils/avatar-color.test.ts +30 -0
- package/src/utils/avatar-color.ts +20 -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
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import React, { useState, useRef } from 'react'
|
|
2
|
-
import { View, Text, Pressable,
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react'
|
|
2
|
+
import { View, Text, Pressable, Platform, type ViewProps } from 'react-native'
|
|
3
3
|
import { cn } from '../../../utils/cn'
|
|
4
4
|
|
|
5
|
+
let createPortal: typeof import('react-dom').createPortal | undefined
|
|
6
|
+
if (Platform.OS === 'web') {
|
|
7
|
+
try {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
9
|
+
createPortal = require('react-dom').createPortal
|
|
10
|
+
} catch {
|
|
11
|
+
// react-dom unavailable
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type PortalPosition = { top: number; left: number; transform: string }
|
|
16
|
+
|
|
5
17
|
export type TooltipPlacement =
|
|
6
18
|
| 'top'
|
|
7
19
|
| 'top-start'
|
|
@@ -13,8 +25,10 @@ export type TooltipPlacement =
|
|
|
13
25
|
| 'right'
|
|
14
26
|
|
|
15
27
|
export interface TooltipProps extends ViewProps {
|
|
16
|
-
/**
|
|
17
|
-
label
|
|
28
|
+
/** Plain-text tooltip content */
|
|
29
|
+
label?: string
|
|
30
|
+
/** Rich tooltip content (takes precedence over label) */
|
|
31
|
+
content?: React.ReactNode
|
|
18
32
|
/** Trigger element */
|
|
19
33
|
children: React.ReactNode
|
|
20
34
|
/** Tooltip placement */
|
|
@@ -29,6 +43,8 @@ export interface TooltipProps extends ViewProps {
|
|
|
29
43
|
isDisabled?: boolean
|
|
30
44
|
/** Additional className for tooltip */
|
|
31
45
|
className?: string
|
|
46
|
+
/** Render tooltip via portal to escape overflow:hidden ancestors (web only) */
|
|
47
|
+
usePortal?: boolean
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
/**
|
|
@@ -37,12 +53,19 @@ export interface TooltipProps extends ViewProps {
|
|
|
37
53
|
* Note: On native, tooltips appear on long press. On web, they appear on hover.
|
|
38
54
|
*
|
|
39
55
|
* @example
|
|
56
|
+
* // String-only tooltip
|
|
40
57
|
* <Tooltip label="This is a tooltip">
|
|
41
58
|
* <Button><ButtonText>Hover me</ButtonText></Button>
|
|
42
59
|
* </Tooltip>
|
|
60
|
+
*
|
|
61
|
+
* // Rich content tooltip
|
|
62
|
+
* <Tooltip content={<View><Text>Bold tip</Text><Text>with details</Text></View>}>
|
|
63
|
+
* <Button><ButtonText>Hover me</ButtonText></Button>
|
|
64
|
+
* </Tooltip>
|
|
43
65
|
*/
|
|
44
66
|
export function Tooltip({
|
|
45
67
|
label,
|
|
68
|
+
content,
|
|
46
69
|
children,
|
|
47
70
|
placement = 'top',
|
|
48
71
|
openDelay = 0,
|
|
@@ -50,11 +73,52 @@ export function Tooltip({
|
|
|
50
73
|
hasArrow = true,
|
|
51
74
|
isDisabled = false,
|
|
52
75
|
className,
|
|
76
|
+
usePortal: usePortalProp = false,
|
|
53
77
|
...props
|
|
54
78
|
}: TooltipProps) {
|
|
55
79
|
const [isVisible, setIsVisible] = useState(false)
|
|
80
|
+
const [portalPos, setPortalPos] = useState<PortalPosition | null>(null)
|
|
56
81
|
const openTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
57
82
|
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
83
|
+
const triggerRef = useRef<View>(null)
|
|
84
|
+
|
|
85
|
+
const isPortalMode = usePortalProp && Platform.OS === 'web' && !!createPortal
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!isVisible || !isPortalMode || !triggerRef.current) return
|
|
89
|
+
const node = triggerRef.current as unknown as HTMLElement
|
|
90
|
+
const id = requestAnimationFrame(() => {
|
|
91
|
+
const rect = node.getBoundingClientRect()
|
|
92
|
+
const gap = 8
|
|
93
|
+
const cardinal = placement.split('-')[0] as 'top' | 'bottom' | 'left' | 'right'
|
|
94
|
+
|
|
95
|
+
const positions: Record<string, PortalPosition> = {
|
|
96
|
+
top: {
|
|
97
|
+
top: rect.top - gap,
|
|
98
|
+
left: rect.left + rect.width / 2,
|
|
99
|
+
transform: 'translate(-50%, -100%)',
|
|
100
|
+
},
|
|
101
|
+
bottom: {
|
|
102
|
+
top: rect.bottom + gap,
|
|
103
|
+
left: rect.left + rect.width / 2,
|
|
104
|
+
transform: 'translate(-50%, 0%)',
|
|
105
|
+
},
|
|
106
|
+
left: {
|
|
107
|
+
top: rect.top + rect.height / 2,
|
|
108
|
+
left: rect.left - gap,
|
|
109
|
+
transform: 'translate(-100%, -50%)',
|
|
110
|
+
},
|
|
111
|
+
right: {
|
|
112
|
+
top: rect.top + rect.height / 2,
|
|
113
|
+
left: rect.right + gap,
|
|
114
|
+
transform: 'translate(0%, -50%)',
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setPortalPos(positions[cardinal] ?? positions.top)
|
|
119
|
+
})
|
|
120
|
+
return () => cancelAnimationFrame(id)
|
|
121
|
+
}, [isVisible, isPortalMode, placement])
|
|
58
122
|
|
|
59
123
|
const clearTimeouts = () => {
|
|
60
124
|
if (openTimeoutRef.current) clearTimeout(openTimeoutRef.current)
|
|
@@ -104,8 +168,43 @@ export function Tooltip({
|
|
|
104
168
|
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
|
|
105
169
|
}
|
|
106
170
|
|
|
171
|
+
const tooltipContent = (
|
|
172
|
+
<View className="bg-neutral-800 px-3 py-2 rounded-md shadow-lg max-w-xs">
|
|
173
|
+
{content ?? <Text className="text-white text-sm">{label}</Text>}
|
|
174
|
+
{hasArrow && (
|
|
175
|
+
<View
|
|
176
|
+
className={cn(
|
|
177
|
+
'absolute w-0 h-0 border-4',
|
|
178
|
+
arrowStyles[placement]
|
|
179
|
+
)}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
182
|
+
</View>
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const renderPortalTooltip = () => {
|
|
186
|
+
if (!isVisible || !isPortalMode || !createPortal) return null
|
|
187
|
+
return createPortal(
|
|
188
|
+
<div
|
|
189
|
+
style={{
|
|
190
|
+
position: 'fixed',
|
|
191
|
+
top: portalPos?.top ?? 0,
|
|
192
|
+
left: portalPos?.left ?? 0,
|
|
193
|
+
transform: portalPos?.transform ?? 'translate(-50%, -100%)',
|
|
194
|
+
zIndex: 10000,
|
|
195
|
+
pointerEvents: 'none',
|
|
196
|
+
}}
|
|
197
|
+
data-testid="tooltip-portal"
|
|
198
|
+
className={className}
|
|
199
|
+
>
|
|
200
|
+
{tooltipContent}
|
|
201
|
+
</div>,
|
|
202
|
+
document.body
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
107
206
|
return (
|
|
108
|
-
<View className="relative" {...props}>
|
|
207
|
+
<View className="relative" ref={triggerRef} {...props}>
|
|
109
208
|
<Pressable
|
|
110
209
|
onHoverIn={show}
|
|
111
210
|
onHoverOut={hide}
|
|
@@ -116,28 +215,20 @@ export function Tooltip({
|
|
|
116
215
|
{children}
|
|
117
216
|
</Pressable>
|
|
118
217
|
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
218
|
+
{isPortalMode
|
|
219
|
+
? renderPortalTooltip()
|
|
220
|
+
: isVisible && (
|
|
221
|
+
<View
|
|
222
|
+
className={cn(
|
|
223
|
+
'absolute z-50 web:animate-fade-in',
|
|
224
|
+
tooltipPositionStyles[placement],
|
|
225
|
+
className
|
|
226
|
+
)}
|
|
227
|
+
pointerEvents="none"
|
|
228
|
+
>
|
|
229
|
+
{tooltipContent}
|
|
230
|
+
</View>
|
|
125
231
|
)}
|
|
126
|
-
pointerEvents="none"
|
|
127
|
-
>
|
|
128
|
-
<View className="bg-neutral-800 px-3 py-2 rounded-md shadow-lg max-w-xs">
|
|
129
|
-
<Text className="text-white text-sm">{label}</Text>
|
|
130
|
-
{hasArrow && (
|
|
131
|
-
<View
|
|
132
|
-
className={cn(
|
|
133
|
-
'absolute w-0 h-0 border-4',
|
|
134
|
-
arrowStyles[placement]
|
|
135
|
-
)}
|
|
136
|
-
/>
|
|
137
|
-
)}
|
|
138
|
-
</View>
|
|
139
|
-
</View>
|
|
140
|
-
)}
|
|
141
232
|
</View>
|
|
142
233
|
)
|
|
143
234
|
}
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
3
|
+
import { View, Text, Pressable } from 'react-native'
|
|
4
|
+
import { Button, ButtonText } from '../components/ui/button'
|
|
5
|
+
import { Card } from '../components/ui/card'
|
|
6
|
+
import { Input } from '../components/ui/input'
|
|
7
|
+
import { Progress, CircularProgress } from '../components/ui/progress'
|
|
8
|
+
import { Badge } from '../components/ui/badge'
|
|
9
|
+
import { Toast } from '../components/ui/toast'
|
|
10
|
+
import { Switch } from '../components/ui/switch'
|
|
11
|
+
import { applyThemePreset, audiobookPreset, defaultPreset } from '../theme/presets'
|
|
12
|
+
import type { ThemePreset } from '../theme/presets'
|
|
13
|
+
|
|
14
|
+
const presets: Record<string, ThemePreset> = {
|
|
15
|
+
default: defaultPreset,
|
|
16
|
+
audiobook: audiobookPreset,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function PresetSwitcher({
|
|
20
|
+
activePreset,
|
|
21
|
+
onSwitch,
|
|
22
|
+
}: {
|
|
23
|
+
activePreset: string
|
|
24
|
+
onSwitch: (name: string) => void
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<View className="flex-row items-center gap-3 p-4 bg-surface-elevated rounded-lg border border-border-default">
|
|
28
|
+
<Text className="text-text-secondary text-sm font-medium">Active Preset:</Text>
|
|
29
|
+
{Object.keys(presets).map((name) => (
|
|
30
|
+
<Pressable
|
|
31
|
+
key={name}
|
|
32
|
+
onPress={() => onSwitch(name)}
|
|
33
|
+
className={`px-4 py-2 rounded-md border ${
|
|
34
|
+
activePreset === name
|
|
35
|
+
? 'bg-brand-primary border-brand-primary'
|
|
36
|
+
: 'bg-surface-base border-border-default web:hover:bg-interactive-hover'
|
|
37
|
+
}`}
|
|
38
|
+
>
|
|
39
|
+
<Text
|
|
40
|
+
className={`text-sm font-semibold capitalize ${
|
|
41
|
+
activePreset === name ? 'text-on-brand-primary' : 'text-text-primary'
|
|
42
|
+
}`}
|
|
43
|
+
>
|
|
44
|
+
{name}
|
|
45
|
+
</Text>
|
|
46
|
+
</Pressable>
|
|
47
|
+
))}
|
|
48
|
+
</View>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function usePresetToggle(initial = 'default') {
|
|
53
|
+
const [activePreset, setActivePreset] = useState(initial)
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const cleanup = applyThemePreset(presets[activePreset])
|
|
57
|
+
return cleanup
|
|
58
|
+
}, [activePreset])
|
|
59
|
+
|
|
60
|
+
return { activePreset, setActivePreset }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// SideBySide Story
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
function SideBySideShowcase() {
|
|
68
|
+
const { activePreset, setActivePreset } = usePresetToggle()
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<View className="gap-6 p-6 bg-background-default min-h-screen">
|
|
72
|
+
<View className="gap-2">
|
|
73
|
+
<Text className="text-3xl font-bold text-text-primary font-heading">
|
|
74
|
+
Preset Showcase
|
|
75
|
+
</Text>
|
|
76
|
+
<Text className="text-text-secondary text-base">
|
|
77
|
+
Toggle between presets to see how the entire design system adapts.
|
|
78
|
+
</Text>
|
|
79
|
+
</View>
|
|
80
|
+
|
|
81
|
+
<PresetSwitcher activePreset={activePreset} onSwitch={setActivePreset} />
|
|
82
|
+
|
|
83
|
+
{/* Typography */}
|
|
84
|
+
<Section title="Typography">
|
|
85
|
+
<Text className="text-3xl font-bold text-text-primary font-heading">
|
|
86
|
+
Heading (font-heading)
|
|
87
|
+
</Text>
|
|
88
|
+
<Text className="text-xl font-semibold text-text-primary font-heading">
|
|
89
|
+
Subheading (font-heading)
|
|
90
|
+
</Text>
|
|
91
|
+
<Text className="text-base text-text-primary font-body">
|
|
92
|
+
Body text using font-body. The quick brown fox jumps over the lazy dog. This
|
|
93
|
+
demonstrates how paragraph text renders under the current preset.
|
|
94
|
+
</Text>
|
|
95
|
+
<Text className="text-sm text-text-secondary font-body">
|
|
96
|
+
Secondary body text for captions and supporting content.
|
|
97
|
+
</Text>
|
|
98
|
+
<Text className="text-sm text-text-primary font-mono">
|
|
99
|
+
{'const monospace = \'font-mono rendering\''}
|
|
100
|
+
</Text>
|
|
101
|
+
</Section>
|
|
102
|
+
|
|
103
|
+
{/* Buttons */}
|
|
104
|
+
<Section title="Buttons">
|
|
105
|
+
<View className="gap-3">
|
|
106
|
+
<Text className="text-xs font-semibold text-text-tertiary uppercase tracking-wider">
|
|
107
|
+
Primary
|
|
108
|
+
</Text>
|
|
109
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
110
|
+
<Button variant="solid" color="primary">
|
|
111
|
+
<ButtonText>Solid</ButtonText>
|
|
112
|
+
</Button>
|
|
113
|
+
<Button variant="outline" color="primary">
|
|
114
|
+
<ButtonText>Outline</ButtonText>
|
|
115
|
+
</Button>
|
|
116
|
+
<Button variant="ghost" color="primary">
|
|
117
|
+
<ButtonText>Ghost</ButtonText>
|
|
118
|
+
</Button>
|
|
119
|
+
</View>
|
|
120
|
+
<Text className="text-xs font-semibold text-text-tertiary uppercase tracking-wider">
|
|
121
|
+
Secondary
|
|
122
|
+
</Text>
|
|
123
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
124
|
+
<Button variant="solid" color="secondary">
|
|
125
|
+
<ButtonText>Solid</ButtonText>
|
|
126
|
+
</Button>
|
|
127
|
+
<Button variant="outline" color="secondary">
|
|
128
|
+
<ButtonText>Outline</ButtonText>
|
|
129
|
+
</Button>
|
|
130
|
+
<Button variant="ghost" color="secondary">
|
|
131
|
+
<ButtonText>Ghost</ButtonText>
|
|
132
|
+
</Button>
|
|
133
|
+
</View>
|
|
134
|
+
</View>
|
|
135
|
+
</Section>
|
|
136
|
+
|
|
137
|
+
{/* Cards with Badges */}
|
|
138
|
+
<Section title="Cards">
|
|
139
|
+
<View className="flex-row gap-4 flex-wrap">
|
|
140
|
+
<Card className="p-4 gap-3 flex-1 min-w-[260px]">
|
|
141
|
+
<Text className="text-lg font-semibold text-text-primary font-heading">
|
|
142
|
+
Elevated Card
|
|
143
|
+
</Text>
|
|
144
|
+
<Text className="text-text-secondary text-sm">
|
|
145
|
+
Surface elevation and border colors adapt to the active preset.
|
|
146
|
+
</Text>
|
|
147
|
+
<View className="flex-row gap-2 flex-wrap">
|
|
148
|
+
<Badge color="success">Complete</Badge>
|
|
149
|
+
<Badge color="warning">Pending</Badge>
|
|
150
|
+
<Badge color="error">Failed</Badge>
|
|
151
|
+
</View>
|
|
152
|
+
</Card>
|
|
153
|
+
<Card className="p-4 gap-3 flex-1 min-w-[260px]">
|
|
154
|
+
<Text className="text-lg font-semibold text-text-primary font-heading">
|
|
155
|
+
Content Card
|
|
156
|
+
</Text>
|
|
157
|
+
<Text className="text-text-secondary text-sm">
|
|
158
|
+
Brand secondary and info badges shown below for comparison.
|
|
159
|
+
</Text>
|
|
160
|
+
<View className="flex-row gap-2 flex-wrap">
|
|
161
|
+
<Badge color="info">Info</Badge>
|
|
162
|
+
<Badge color="secondary">Secondary</Badge>
|
|
163
|
+
</View>
|
|
164
|
+
</Card>
|
|
165
|
+
</View>
|
|
166
|
+
</Section>
|
|
167
|
+
|
|
168
|
+
{/* Input + Switch */}
|
|
169
|
+
<Section title="Inputs">
|
|
170
|
+
<View className="gap-3 max-w-[400px]">
|
|
171
|
+
<Input placeholder="Default input..." />
|
|
172
|
+
<Input placeholder="Disabled input..." isDisabled />
|
|
173
|
+
<View className="flex-row items-center gap-3">
|
|
174
|
+
<Switch />
|
|
175
|
+
<Text className="text-text-primary text-sm">Toggle switch</Text>
|
|
176
|
+
</View>
|
|
177
|
+
</View>
|
|
178
|
+
</Section>
|
|
179
|
+
|
|
180
|
+
{/* Progress */}
|
|
181
|
+
<Section title="Progress">
|
|
182
|
+
<View className="gap-4">
|
|
183
|
+
<Progress value={65} label="Primary" showValue color="primary" />
|
|
184
|
+
<Progress value={40} label="Secondary" showValue color="secondary" />
|
|
185
|
+
<Progress value={100} label="Success" showValue color="success" />
|
|
186
|
+
<View className="flex-row gap-4 items-center flex-wrap">
|
|
187
|
+
<CircularProgress value={25} size={48} showValue color="primary" />
|
|
188
|
+
<CircularProgress value={50} size={48} showValue color="secondary" />
|
|
189
|
+
<CircularProgress value={75} size={48} showValue color="success" />
|
|
190
|
+
<CircularProgress value={100} size={48} showValue color="error" />
|
|
191
|
+
</View>
|
|
192
|
+
</View>
|
|
193
|
+
</Section>
|
|
194
|
+
|
|
195
|
+
{/* Toasts */}
|
|
196
|
+
<Section title="Toasts">
|
|
197
|
+
<View className="gap-3">
|
|
198
|
+
<Toast title="Operation successful" description="Your changes have been saved." status="success" />
|
|
199
|
+
<Toast title="Something went wrong" description="Please try again later." status="error" />
|
|
200
|
+
<Toast title="Heads up" description="This action cannot be undone." status="warning" />
|
|
201
|
+
<Toast title="New update available" description="Version 2.0 is ready to install." status="info" />
|
|
202
|
+
</View>
|
|
203
|
+
</Section>
|
|
204
|
+
|
|
205
|
+
{/* Status Colors */}
|
|
206
|
+
<Section title="Status Colors">
|
|
207
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
208
|
+
<Badge color="success" size="lg">Success</Badge>
|
|
209
|
+
<Badge color="error" size="lg">Error</Badge>
|
|
210
|
+
<Badge color="warning" size="lg">Warning</Badge>
|
|
211
|
+
<Badge color="info" size="lg">Info</Badge>
|
|
212
|
+
</View>
|
|
213
|
+
</Section>
|
|
214
|
+
</View>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Typography Story
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
function TypographyShowcase() {
|
|
223
|
+
const { activePreset, setActivePreset } = usePresetToggle()
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<View className="gap-6 p-6 bg-background-default min-h-screen">
|
|
227
|
+
<View className="gap-2">
|
|
228
|
+
<Text className="text-3xl font-bold text-text-primary font-heading">
|
|
229
|
+
Typography
|
|
230
|
+
</Text>
|
|
231
|
+
<Text className="text-text-secondary text-base">
|
|
232
|
+
How fonts and text styles change across presets.
|
|
233
|
+
</Text>
|
|
234
|
+
</View>
|
|
235
|
+
|
|
236
|
+
<PresetSwitcher activePreset={activePreset} onSwitch={setActivePreset} />
|
|
237
|
+
|
|
238
|
+
<Section title="Heading Font (font-heading)">
|
|
239
|
+
<Text className="text-4xl font-bold text-text-primary font-heading">
|
|
240
|
+
H1 - The Art of Design Systems
|
|
241
|
+
</Text>
|
|
242
|
+
<Text className="text-3xl font-bold text-text-primary font-heading">
|
|
243
|
+
H2 - Component Architecture
|
|
244
|
+
</Text>
|
|
245
|
+
<Text className="text-2xl font-semibold text-text-primary font-heading">
|
|
246
|
+
H3 - Token-Driven Theming
|
|
247
|
+
</Text>
|
|
248
|
+
<Text className="text-xl font-semibold text-text-primary font-heading">
|
|
249
|
+
H4 - Cross-Platform Consistency
|
|
250
|
+
</Text>
|
|
251
|
+
</Section>
|
|
252
|
+
|
|
253
|
+
<Section title="Body Font (font-body)">
|
|
254
|
+
<Text className="text-base text-text-primary font-body">
|
|
255
|
+
Regular body text. Design systems provide a shared language for product teams.
|
|
256
|
+
By codifying decisions into reusable components and tokens, teams move faster
|
|
257
|
+
while maintaining visual consistency across every surface.
|
|
258
|
+
</Text>
|
|
259
|
+
<Text className="text-base font-semibold text-text-primary font-body">
|
|
260
|
+
Bold body text. Emphasis and hierarchy within paragraph content.
|
|
261
|
+
</Text>
|
|
262
|
+
<Text className="text-base italic text-text-primary font-body">
|
|
263
|
+
Italic body text. Used for quotations, citations, and emphasis.
|
|
264
|
+
</Text>
|
|
265
|
+
<Text className="text-sm text-text-secondary font-body">
|
|
266
|
+
Small / secondary text. Captions, labels, and supporting information
|
|
267
|
+
that supplements the main content without competing for attention.
|
|
268
|
+
</Text>
|
|
269
|
+
<Text className="text-xs text-text-tertiary font-body">
|
|
270
|
+
Extra-small / tertiary text. Timestamps, metadata, fine print.
|
|
271
|
+
</Text>
|
|
272
|
+
</Section>
|
|
273
|
+
|
|
274
|
+
<Section title="Monospace Font (font-mono)">
|
|
275
|
+
<View className="bg-surface-elevated rounded-lg p-4 border border-border-default">
|
|
276
|
+
<Text className="text-sm text-text-primary font-mono">
|
|
277
|
+
{'const theme = applyThemePreset(audiobookPreset)'}
|
|
278
|
+
</Text>
|
|
279
|
+
<Text className="text-sm text-text-secondary font-mono">
|
|
280
|
+
{'// Overrides CSS custom properties on :root'}
|
|
281
|
+
</Text>
|
|
282
|
+
<Text className="text-sm text-text-primary font-mono">
|
|
283
|
+
{'export type ThemePreset = { name: string }'}
|
|
284
|
+
</Text>
|
|
285
|
+
</View>
|
|
286
|
+
</Section>
|
|
287
|
+
|
|
288
|
+
<Section title="Mixed Typography">
|
|
289
|
+
<Card className="p-5 gap-3">
|
|
290
|
+
<Text className="text-2xl font-bold text-text-primary font-heading">
|
|
291
|
+
Chapter One
|
|
292
|
+
</Text>
|
|
293
|
+
<Text className="text-base text-text-secondary font-body">
|
|
294
|
+
It was a bright cold day in April, and the clocks were striking thirteen.
|
|
295
|
+
Winston Smith, his chin nuzzled into his breast in an effort to escape the
|
|
296
|
+
vile wind, slipped quickly through the glass doors of Victory Mansions,
|
|
297
|
+
though not quickly enough to prevent a swirl of gritty dust from entering
|
|
298
|
+
along with him.
|
|
299
|
+
</Text>
|
|
300
|
+
<Text className="text-xs text-text-tertiary font-mono">
|
|
301
|
+
George Orwell, 1984
|
|
302
|
+
</Text>
|
|
303
|
+
</Card>
|
|
304
|
+
</Section>
|
|
305
|
+
</View>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// ColorPalette Story
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
function ColorSwatch({
|
|
314
|
+
label,
|
|
315
|
+
className,
|
|
316
|
+
textClass = 'text-white',
|
|
317
|
+
}: {
|
|
318
|
+
label: string
|
|
319
|
+
className: string
|
|
320
|
+
textClass?: string
|
|
321
|
+
}) {
|
|
322
|
+
return (
|
|
323
|
+
<View className={`rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end ${className}`}>
|
|
324
|
+
<Text className={`text-xs font-mono font-medium ${textClass}`}>{label}</Text>
|
|
325
|
+
</View>
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function ColorPaletteShowcase() {
|
|
330
|
+
const { activePreset, setActivePreset } = usePresetToggle()
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<View className="gap-6 p-6 bg-background-default min-h-screen">
|
|
334
|
+
<View className="gap-2">
|
|
335
|
+
<Text className="text-3xl font-bold text-text-primary font-heading">
|
|
336
|
+
Color Palette
|
|
337
|
+
</Text>
|
|
338
|
+
<Text className="text-text-secondary text-base">
|
|
339
|
+
Semantic color tokens rendered as swatches. Toggle presets to compare palettes.
|
|
340
|
+
</Text>
|
|
341
|
+
</View>
|
|
342
|
+
|
|
343
|
+
<PresetSwitcher activePreset={activePreset} onSwitch={setActivePreset} />
|
|
344
|
+
|
|
345
|
+
<Section title="Brand Colors">
|
|
346
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
347
|
+
<ColorSwatch label="brand-primary" className="bg-brand-primary" />
|
|
348
|
+
<ColorSwatch label="brand-primary-light" className="bg-brand-primary-light" />
|
|
349
|
+
<ColorSwatch label="brand-primary-dark" className="bg-brand-primary-dark" />
|
|
350
|
+
<ColorSwatch label="brand-primary-subtle" className="bg-brand-primary-subtle" textClass="text-text-primary" />
|
|
351
|
+
</View>
|
|
352
|
+
<View className="flex-row gap-3 flex-wrap mt-2">
|
|
353
|
+
<ColorSwatch label="brand-secondary" className="bg-brand-secondary" />
|
|
354
|
+
<ColorSwatch label="brand-secondary-light" className="bg-brand-secondary-light" />
|
|
355
|
+
<ColorSwatch label="brand-secondary-dark" className="bg-brand-secondary-dark" />
|
|
356
|
+
<ColorSwatch label="brand-secondary-subtle" className="bg-brand-secondary-subtle" textClass="text-text-primary" />
|
|
357
|
+
</View>
|
|
358
|
+
</Section>
|
|
359
|
+
|
|
360
|
+
<Section title="Status Colors">
|
|
361
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
362
|
+
<ColorSwatch label="status-success" className="bg-status-success" />
|
|
363
|
+
<ColorSwatch label="status-error" className="bg-status-error" />
|
|
364
|
+
<ColorSwatch label="status-warning" className="bg-status-warning" />
|
|
365
|
+
<ColorSwatch label="status-info" className="bg-status-info" />
|
|
366
|
+
</View>
|
|
367
|
+
</Section>
|
|
368
|
+
|
|
369
|
+
<Section title="Text Colors">
|
|
370
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
371
|
+
<View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-border-default">
|
|
372
|
+
<Text className="text-xs font-mono font-medium text-text-primary">text-primary</Text>
|
|
373
|
+
</View>
|
|
374
|
+
<View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-border-default">
|
|
375
|
+
<Text className="text-xs font-mono font-medium text-text-secondary">text-secondary</Text>
|
|
376
|
+
</View>
|
|
377
|
+
<View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-elevated border border-border-default">
|
|
378
|
+
<Text className="text-xs font-mono font-medium text-text-tertiary">text-tertiary</Text>
|
|
379
|
+
</View>
|
|
380
|
+
</View>
|
|
381
|
+
</Section>
|
|
382
|
+
|
|
383
|
+
<Section title="Surface Colors">
|
|
384
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
385
|
+
<ColorSwatch label="surface-base" className="bg-surface-base border border-border-default" textClass="text-text-primary" />
|
|
386
|
+
<ColorSwatch label="surface-elevated" className="bg-surface-elevated border border-border-default" textClass="text-text-primary" />
|
|
387
|
+
<ColorSwatch label="surface-raised" className="bg-surface-raised border border-border-default" textClass="text-text-primary" />
|
|
388
|
+
<ColorSwatch label="surface-overlay" className="bg-surface-overlay border border-border-default" textClass="text-text-primary" />
|
|
389
|
+
<ColorSwatch label="surface-input" className="bg-surface-input border border-border-default" textClass="text-text-primary" />
|
|
390
|
+
</View>
|
|
391
|
+
</Section>
|
|
392
|
+
|
|
393
|
+
<Section title="Background Colors">
|
|
394
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
395
|
+
<ColorSwatch label="background-base" className="bg-background-base border border-border-default" textClass="text-text-primary" />
|
|
396
|
+
<ColorSwatch label="background-default" className="bg-background-default border border-border-default" textClass="text-text-primary" />
|
|
397
|
+
<ColorSwatch label="background-subtle" className="bg-background-subtle border border-border-default" textClass="text-text-primary" />
|
|
398
|
+
</View>
|
|
399
|
+
</Section>
|
|
400
|
+
|
|
401
|
+
<Section title="Border Colors">
|
|
402
|
+
<View className="flex-row gap-3 flex-wrap">
|
|
403
|
+
<View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border-default">
|
|
404
|
+
<Text className="text-xs font-mono font-medium text-text-primary">border-default</Text>
|
|
405
|
+
</View>
|
|
406
|
+
<View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border-subtle">
|
|
407
|
+
<Text className="text-xs font-mono font-medium text-text-primary">border-subtle</Text>
|
|
408
|
+
</View>
|
|
409
|
+
<View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border-strong">
|
|
410
|
+
<Text className="text-xs font-mono font-medium text-text-primary">border-strong</Text>
|
|
411
|
+
</View>
|
|
412
|
+
<View className="rounded-lg p-3 min-w-[120px] min-h-[60px] justify-end bg-surface-base border-2 border-border-focus">
|
|
413
|
+
<Text className="text-xs font-mono font-medium text-text-primary">border-focus</Text>
|
|
414
|
+
</View>
|
|
415
|
+
</View>
|
|
416
|
+
</Section>
|
|
417
|
+
</View>
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
// Shared Section Component
|
|
423
|
+
// ---------------------------------------------------------------------------
|
|
424
|
+
|
|
425
|
+
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
426
|
+
return (
|
|
427
|
+
<View className="gap-3">
|
|
428
|
+
<Text className="text-lg font-semibold text-text-primary font-heading border-b border-border-subtle pb-2">
|
|
429
|
+
{title}
|
|
430
|
+
</Text>
|
|
431
|
+
{children}
|
|
432
|
+
</View>
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// Storybook Meta & Exports
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
const meta: Meta = {
|
|
441
|
+
title: 'Theme/Preset Showcase',
|
|
442
|
+
parameters: {
|
|
443
|
+
layout: 'fullscreen',
|
|
444
|
+
},
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export default meta
|
|
448
|
+
|
|
449
|
+
export const SideBySide: StoryObj = {
|
|
450
|
+
render: () => <SideBySideShowcase />,
|
|
451
|
+
name: 'Component Showcase',
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export const Typography: StoryObj = {
|
|
455
|
+
render: () => <TypographyShowcase />,
|
|
456
|
+
name: 'Typography',
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export const ColorPalette: StoryObj = {
|
|
460
|
+
render: () => <ColorPaletteShowcase />,
|
|
461
|
+
name: 'Color Palette',
|
|
462
|
+
}
|