@urbint/cl 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules +313 -0
- package/.rnstorybook/index.ts +11 -0
- package/.rnstorybook/main.ts +8 -0
- package/.rnstorybook/preview.tsx +14 -0
- package/.rnstorybook/storybook.requires.ts +49 -0
- package/.storybook/main.ts +16 -0
- package/.storybook/preview.ts +32 -0
- package/.storybook/vitest.setup.ts +7 -0
- package/App.tsx +422 -0
- package/README.md +229 -0
- package/app.json +33 -0
- package/assets/adaptive-icon.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/splash-icon.png +0 -0
- package/babel.config.js +16 -0
- package/docs/components/CodeBlock.tsx +80 -0
- package/docs/components/PropTable.tsx +93 -0
- package/docs/components/Sidebar.tsx +199 -0
- package/docs/components/index.ts +8 -0
- package/docs/data/colorTokens.ts +70 -0
- package/docs/data/componentData.tsx +1685 -0
- package/docs/data/index.ts +7 -0
- package/docs/index.ts +19 -0
- package/docs/navigation.ts +94 -0
- package/docs/pages/ColorsPage.tsx +226 -0
- package/docs/pages/ComponentPage.tsx +235 -0
- package/docs/pages/InstallationPage.tsx +232 -0
- package/docs/pages/IntroductionPage.tsx +163 -0
- package/docs/pages/ThemingPage.tsx +251 -0
- package/docs/pages/index.ts +10 -0
- package/docs/theme.ts +64 -0
- package/docs/types.ts +54 -0
- package/index.ts +8 -0
- package/llms.txt +1893 -0
- package/mcp-config.example.json +10 -0
- package/mcp-server/README.md +192 -0
- package/mcp-server/package-lock.json +1707 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/src/index.ts +1136 -0
- package/mcp-server/src/registry/components.ts +1446 -0
- package/mcp-server/src/registry/index.ts +3 -0
- package/mcp-server/src/registry/tokens.ts +256 -0
- package/mcp-server/tsconfig.json +19 -0
- package/package.json +92 -0
- package/src/components/Accordion/Accordion.stories.tsx +226 -0
- package/src/components/Accordion/Accordion.tsx +255 -0
- package/src/components/Accordion/index.ts +12 -0
- package/src/components/ActionSheet/ActionSheet.stories.tsx +393 -0
- package/src/components/ActionSheet/ActionSheet.tsx +258 -0
- package/src/components/ActionSheet/index.ts +2 -0
- package/src/components/Alert/Alert.stories.tsx +165 -0
- package/src/components/Alert/Alert.tsx +164 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/AlertDialog/AlertDialog.stories.tsx +330 -0
- package/src/components/AlertDialog/AlertDialog.tsx +234 -0
- package/src/components/AlertDialog/index.ts +2 -0
- package/src/components/Avatar/Avatar.stories.tsx +154 -0
- package/src/components/Avatar/Avatar.tsx +219 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Badge/Badge.stories.tsx +146 -0
- package/src/components/Badge/Badge.tsx +125 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Box/Box.stories.tsx +192 -0
- package/src/components/Box/Box.tsx +184 -0
- package/src/components/Box/index.ts +2 -0
- package/src/components/Button/Button.stories.tsx +157 -0
- package/src/components/Button/Button.tsx +180 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Card/Card.stories.tsx +145 -0
- package/src/components/Card/Card.tsx +169 -0
- package/src/components/Card/index.ts +11 -0
- package/src/components/Center/Center.stories.tsx +215 -0
- package/src/components/Center/Center.tsx +29 -0
- package/src/components/Center/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +94 -0
- package/src/components/Checkbox/Checkbox.tsx +242 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +623 -0
- package/src/components/DatePicker/DatePicker.tsx +1228 -0
- package/src/components/DatePicker/index.ts +8 -0
- package/src/components/Divider/Divider.stories.tsx +224 -0
- package/src/components/Divider/Divider.tsx +73 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/Drawer/Drawer.stories.tsx +414 -0
- package/src/components/Drawer/Drawer.tsx +342 -0
- package/src/components/Drawer/index.ts +11 -0
- package/src/components/Fab/Fab.stories.tsx +360 -0
- package/src/components/Fab/Fab.tsx +185 -0
- package/src/components/Fab/index.ts +2 -0
- package/src/components/FormControl/FormControl.stories.tsx +276 -0
- package/src/components/FormControl/FormControl.tsx +185 -0
- package/src/components/FormControl/index.ts +12 -0
- package/src/components/Grid/Grid.stories.tsx +244 -0
- package/src/components/Grid/Grid.tsx +93 -0
- package/src/components/Grid/index.ts +2 -0
- package/src/components/HStack/HStack.stories.tsx +230 -0
- package/src/components/HStack/HStack.tsx +80 -0
- package/src/components/HStack/index.ts +2 -0
- package/src/components/Heading/Heading.stories.tsx +111 -0
- package/src/components/Heading/Heading.tsx +85 -0
- package/src/components/Heading/index.ts +2 -0
- package/src/components/Icon/Icon.stories.tsx +320 -0
- package/src/components/Icon/Icon.tsx +117 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/Image/Image.stories.tsx +357 -0
- package/src/components/Image/Image.tsx +168 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/Input/Input.stories.tsx +164 -0
- package/src/components/Input/Input.tsx +274 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Link/Link.stories.tsx +187 -0
- package/src/components/Link/Link.tsx +104 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/Menu/Menu.stories.tsx +363 -0
- package/src/components/Menu/Menu.tsx +238 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/Modal/Modal.stories.tsx +156 -0
- package/src/components/Modal/Modal.tsx +280 -0
- package/src/components/Modal/index.ts +11 -0
- package/src/components/Popover/Popover.stories.tsx +330 -0
- package/src/components/Popover/Popover.tsx +315 -0
- package/src/components/Popover/index.ts +11 -0
- package/src/components/Portal/Portal.stories.tsx +376 -0
- package/src/components/Portal/Portal.tsx +100 -0
- package/src/components/Portal/index.ts +2 -0
- package/src/components/Pressable/Pressable.stories.tsx +338 -0
- package/src/components/Pressable/Pressable.tsx +71 -0
- package/src/components/Pressable/index.ts +2 -0
- package/src/components/Progress/Progress.stories.tsx +131 -0
- package/src/components/Progress/Progress.tsx +219 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/Radio/Radio.stories.tsx +101 -0
- package/src/components/Radio/Radio.tsx +234 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/Select/Select.stories.tsx +908 -0
- package/src/components/Select/Select.tsx +659 -0
- package/src/components/Select/index.ts +8 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +154 -0
- package/src/components/Skeleton/Skeleton.tsx +192 -0
- package/src/components/Skeleton/index.ts +8 -0
- package/src/components/Slider/Slider.stories.tsx +363 -0
- package/src/components/Slider/Slider.tsx +209 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/Spinner/Spinner.stories.tsx +108 -0
- package/src/components/Spinner/Spinner.tsx +121 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Switch/Switch.stories.tsx +116 -0
- package/src/components/Switch/Switch.tsx +172 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.stories.tsx +417 -0
- package/src/components/Table/Table.tsx +233 -0
- package/src/components/Table/index.ts +2 -0
- package/src/components/Text/Text.stories.tsx +93 -0
- package/src/components/Text/Text.tsx +119 -0
- package/src/components/Text/index.ts +2 -0
- package/src/components/Textarea/Textarea.stories.tsx +280 -0
- package/src/components/Textarea/Textarea.tsx +212 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Toast/Toast.stories.tsx +446 -0
- package/src/components/Toast/Toast.tsx +221 -0
- package/src/components/Toast/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +354 -0
- package/src/components/Tooltip/Tooltip.tsx +261 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/VStack/VStack.stories.tsx +183 -0
- package/src/components/VStack/VStack.tsx +76 -0
- package/src/components/VStack/index.ts +2 -0
- package/src/components/index.ts +62 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useControllableState.ts +41 -0
- package/src/hooks/useDisclosure.ts +51 -0
- package/src/index.ts +22 -0
- package/src/stories/Button.stories.tsx +53 -0
- package/src/stories/Button.tsx +101 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.stories.tsx +33 -0
- package/src/stories/Header.tsx +75 -0
- package/src/stories/Page.stories.tsx +25 -0
- package/src/stories/Page.tsx +154 -0
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +1 -0
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +1 -0
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +1 -0
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +1 -0
- package/src/stories/assets/youtube.svg +1 -0
- package/src/styles/index.ts +7 -0
- package/src/styles/tokens.ts +318 -0
- package/src/styles/unistyles.ts +254 -0
- package/src/utils/createContext.tsx +25 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/mergeRefs.ts +21 -0
- package/tsconfig.json +26 -0
- package/urbint-cl-1.0.0.tgz +0 -0
- package/vitest.config.ts +37 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { Avatar, AvatarGroup } from './Avatar';
|
|
4
|
+
import { VStack } from '../VStack';
|
|
5
|
+
import { HStack } from '../HStack';
|
|
6
|
+
import { Text } from '../Text';
|
|
7
|
+
import { colors, spacing, borderRadius } from '../../styles/tokens';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Story container with design system tokens
|
|
11
|
+
*/
|
|
12
|
+
const StoryContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
13
|
+
<View style={styles.container}>{children}</View>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const meta: Meta<typeof Avatar> = {
|
|
17
|
+
title: 'Components/Avatar',
|
|
18
|
+
component: Avatar,
|
|
19
|
+
decorators: [
|
|
20
|
+
(Story) => (
|
|
21
|
+
<StoryContainer>
|
|
22
|
+
<Story />
|
|
23
|
+
</StoryContainer>
|
|
24
|
+
),
|
|
25
|
+
],
|
|
26
|
+
argTypes: {
|
|
27
|
+
size: {
|
|
28
|
+
control: 'select',
|
|
29
|
+
options: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'],
|
|
30
|
+
},
|
|
31
|
+
showBadge: {
|
|
32
|
+
control: 'boolean',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
args: {
|
|
36
|
+
name: 'John Doe',
|
|
37
|
+
size: 'md',
|
|
38
|
+
showBadge: false,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
|
|
44
|
+
type Story = StoryObj<typeof Avatar>;
|
|
45
|
+
|
|
46
|
+
export const Default: Story = {};
|
|
47
|
+
|
|
48
|
+
export const Sizes: Story = {
|
|
49
|
+
render: () => (
|
|
50
|
+
<VStack space={spacing.lg}>
|
|
51
|
+
<Text weight="semiBold">Avatar Sizes</Text>
|
|
52
|
+
<HStack space={spacing.sm} alignItems="center">
|
|
53
|
+
<Avatar name="XS" size="xs" />
|
|
54
|
+
<Avatar name="SM" size="sm" />
|
|
55
|
+
<Avatar name="MD" size="md" />
|
|
56
|
+
<Avatar name="LG" size="lg" />
|
|
57
|
+
<Avatar name="XL" size="xl" />
|
|
58
|
+
<Avatar name="2XL" size="2xl" />
|
|
59
|
+
</HStack>
|
|
60
|
+
</VStack>
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const WithInitials: Story = {
|
|
65
|
+
render: () => (
|
|
66
|
+
<VStack space={spacing.lg}>
|
|
67
|
+
<Text weight="semiBold">Avatars with Initials</Text>
|
|
68
|
+
<HStack space={spacing.sm}>
|
|
69
|
+
<Avatar name="John Doe" size="lg" />
|
|
70
|
+
<Avatar name="Jane Smith" size="lg" />
|
|
71
|
+
<Avatar name="Bob" size="lg" />
|
|
72
|
+
<Avatar name="Alice Williams" size="lg" />
|
|
73
|
+
</HStack>
|
|
74
|
+
</VStack>
|
|
75
|
+
),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const WithImage: Story = {
|
|
79
|
+
render: () => (
|
|
80
|
+
<VStack space={spacing.lg}>
|
|
81
|
+
<Text weight="semiBold">Avatars with Images</Text>
|
|
82
|
+
<HStack space={spacing.sm}>
|
|
83
|
+
<Avatar
|
|
84
|
+
src="https://i.pravatar.cc/150?img=1"
|
|
85
|
+
name="User 1"
|
|
86
|
+
size="lg"
|
|
87
|
+
/>
|
|
88
|
+
<Avatar
|
|
89
|
+
src="https://i.pravatar.cc/150?img=2"
|
|
90
|
+
name="User 2"
|
|
91
|
+
size="lg"
|
|
92
|
+
/>
|
|
93
|
+
<Avatar
|
|
94
|
+
src="https://i.pravatar.cc/150?img=3"
|
|
95
|
+
name="User 3"
|
|
96
|
+
size="lg"
|
|
97
|
+
/>
|
|
98
|
+
</HStack>
|
|
99
|
+
</VStack>
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const WithBadge: Story = {
|
|
104
|
+
render: () => (
|
|
105
|
+
<VStack space={spacing.lg}>
|
|
106
|
+
<Text weight="semiBold">Avatars with Status Badge</Text>
|
|
107
|
+
<HStack space={spacing.md}>
|
|
108
|
+
<Avatar name="Online User" size="lg" showBadge badgeColor={colors.feedback.success.content} />
|
|
109
|
+
<Avatar name="Away User" size="lg" showBadge badgeColor={colors.feedback.warning.content} />
|
|
110
|
+
<Avatar name="Offline User" size="lg" showBadge badgeColor={colors.feedback.error.content} />
|
|
111
|
+
</HStack>
|
|
112
|
+
</VStack>
|
|
113
|
+
),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const Group: Story = {
|
|
117
|
+
render: () => (
|
|
118
|
+
<VStack space={spacing.lg}>
|
|
119
|
+
<Text weight="semiBold">Avatar Group</Text>
|
|
120
|
+
<AvatarGroup max={3} size="md">
|
|
121
|
+
<Avatar name="John Doe" />
|
|
122
|
+
<Avatar name="Jane Smith" />
|
|
123
|
+
<Avatar name="Bob Wilson" />
|
|
124
|
+
<Avatar name="Alice Brown" />
|
|
125
|
+
<Avatar name="Charlie Davis" />
|
|
126
|
+
</AvatarGroup>
|
|
127
|
+
</VStack>
|
|
128
|
+
),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const Placeholder: Story = {
|
|
132
|
+
render: () => (
|
|
133
|
+
<VStack space={spacing.lg}>
|
|
134
|
+
<Text weight="semiBold">Avatar Placeholder</Text>
|
|
135
|
+
<HStack space={spacing.sm}>
|
|
136
|
+
<Avatar size="lg" />
|
|
137
|
+
<Avatar size="md" />
|
|
138
|
+
<Avatar size="sm" />
|
|
139
|
+
</HStack>
|
|
140
|
+
</VStack>
|
|
141
|
+
),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Styles using design system tokens
|
|
146
|
+
*/
|
|
147
|
+
const styles = StyleSheet.create({
|
|
148
|
+
container: {
|
|
149
|
+
padding: spacing.lg,
|
|
150
|
+
backgroundColor: colors.background.default,
|
|
151
|
+
borderRadius: borderRadius.lg,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatar Component
|
|
3
|
+
* User profile picture or initials display
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { forwardRef, useState } from 'react';
|
|
7
|
+
import { View, ViewProps, Image, Text, StyleSheet } from 'react-native';
|
|
8
|
+
import { colors, typography } from '../../styles/tokens';
|
|
9
|
+
|
|
10
|
+
export interface AvatarProps extends ViewProps {
|
|
11
|
+
/** Image source */
|
|
12
|
+
src?: string;
|
|
13
|
+
/** Alt text / name for initials */
|
|
14
|
+
name?: string;
|
|
15
|
+
/** Avatar size */
|
|
16
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
17
|
+
/** Show status badge */
|
|
18
|
+
showBadge?: boolean;
|
|
19
|
+
/** Badge color */
|
|
20
|
+
badgeColor?: string;
|
|
21
|
+
/** Badge position */
|
|
22
|
+
badgePosition?: 'top-right' | 'bottom-right';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sizeMap = {
|
|
26
|
+
xs: 24,
|
|
27
|
+
sm: 32,
|
|
28
|
+
md: 40,
|
|
29
|
+
lg: 48,
|
|
30
|
+
xl: 64,
|
|
31
|
+
'2xl': 96,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
const getInitials = (name: string): string => {
|
|
35
|
+
const parts = name.trim().split(' ');
|
|
36
|
+
if (parts.length >= 2) {
|
|
37
|
+
return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
|
|
38
|
+
}
|
|
39
|
+
return name.slice(0, 2).toUpperCase();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Avatar = forwardRef<View, AvatarProps>(
|
|
43
|
+
(
|
|
44
|
+
{
|
|
45
|
+
style,
|
|
46
|
+
src,
|
|
47
|
+
name,
|
|
48
|
+
size = 'md',
|
|
49
|
+
showBadge = false,
|
|
50
|
+
badgeColor,
|
|
51
|
+
badgePosition = 'bottom-right',
|
|
52
|
+
...props
|
|
53
|
+
},
|
|
54
|
+
ref
|
|
55
|
+
) => {
|
|
56
|
+
const [imageError, setImageError] = useState(false);
|
|
57
|
+
|
|
58
|
+
const avatarSize = sizeMap[size];
|
|
59
|
+
const badgeSize = Math.max(avatarSize * 0.25, 8);
|
|
60
|
+
|
|
61
|
+
const showImage = src && !imageError;
|
|
62
|
+
const showInitials = !showImage && name;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<View
|
|
66
|
+
ref={ref}
|
|
67
|
+
style={[
|
|
68
|
+
styles.container,
|
|
69
|
+
{ width: avatarSize, height: avatarSize, borderRadius: avatarSize / 2 },
|
|
70
|
+
style,
|
|
71
|
+
]}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{showImage ? (
|
|
75
|
+
<Image
|
|
76
|
+
source={{ uri: src }}
|
|
77
|
+
style={[styles.image, { width: avatarSize, height: avatarSize, borderRadius: avatarSize / 2 }]}
|
|
78
|
+
onError={() => setImageError(true)}
|
|
79
|
+
/>
|
|
80
|
+
) : showInitials ? (
|
|
81
|
+
<View style={[styles.initialsContainer, { width: avatarSize, height: avatarSize, borderRadius: avatarSize / 2 }]}>
|
|
82
|
+
<Text style={[styles.initials, styles[`${size}Text` as keyof typeof styles]]}>{getInitials(name)}</Text>
|
|
83
|
+
</View>
|
|
84
|
+
) : (
|
|
85
|
+
<View style={[styles.placeholder, { width: avatarSize, height: avatarSize, borderRadius: avatarSize / 2 }]}>
|
|
86
|
+
<Text style={[styles.placeholderIcon, styles[`${size}Text` as keyof typeof styles]]}>👤</Text>
|
|
87
|
+
</View>
|
|
88
|
+
)}
|
|
89
|
+
{showBadge && (
|
|
90
|
+
<View
|
|
91
|
+
style={[
|
|
92
|
+
styles.badge,
|
|
93
|
+
{
|
|
94
|
+
width: badgeSize,
|
|
95
|
+
height: badgeSize,
|
|
96
|
+
borderRadius: badgeSize / 2,
|
|
97
|
+
backgroundColor: badgeColor || colors.feedback.success.content,
|
|
98
|
+
},
|
|
99
|
+
badgePosition === 'top-right' ? styles.badgeTopRight : styles.badgeBottomRight,
|
|
100
|
+
]}
|
|
101
|
+
/>
|
|
102
|
+
)}
|
|
103
|
+
</View>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
Avatar.displayName = 'Avatar';
|
|
109
|
+
|
|
110
|
+
export const AvatarGroup = forwardRef<View, ViewProps & { max?: number; size?: AvatarProps['size'] }>(
|
|
111
|
+
({ children, max = 3, size = 'md', style, ...props }, ref) => {
|
|
112
|
+
const childArray = React.Children.toArray(children);
|
|
113
|
+
const visibleCount = Math.min(childArray.length, max);
|
|
114
|
+
const remainingCount = childArray.length - visibleCount;
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<View ref={ref} style={[styles.group, style]} {...props}>
|
|
118
|
+
{childArray.slice(0, visibleCount).map((child, index) => (
|
|
119
|
+
<View key={index} style={[styles.groupItem, { marginLeft: index > 0 ? -sizeMap[size] / 4 : 0 }]}>
|
|
120
|
+
{React.isValidElement(child)
|
|
121
|
+
? React.cloneElement(child, { size } as any)
|
|
122
|
+
: child}
|
|
123
|
+
</View>
|
|
124
|
+
))}
|
|
125
|
+
{remainingCount > 0 && (
|
|
126
|
+
<View style={[styles.groupItem, { marginLeft: -sizeMap[size] / 4 }]}>
|
|
127
|
+
<View
|
|
128
|
+
style={[
|
|
129
|
+
styles.remainingBadge,
|
|
130
|
+
{ width: sizeMap[size], height: sizeMap[size], borderRadius: sizeMap[size] / 2 },
|
|
131
|
+
]}
|
|
132
|
+
>
|
|
133
|
+
<Text style={styles.remainingText}>+{remainingCount}</Text>
|
|
134
|
+
</View>
|
|
135
|
+
</View>
|
|
136
|
+
)}
|
|
137
|
+
</View>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
AvatarGroup.displayName = 'AvatarGroup';
|
|
143
|
+
|
|
144
|
+
const styles = StyleSheet.create({
|
|
145
|
+
container: {
|
|
146
|
+
position: 'relative',
|
|
147
|
+
overflow: 'hidden',
|
|
148
|
+
},
|
|
149
|
+
image: {
|
|
150
|
+
backgroundColor: colors.background.secondary,
|
|
151
|
+
},
|
|
152
|
+
initialsContainer: {
|
|
153
|
+
backgroundColor: colors.brand.blue,
|
|
154
|
+
alignItems: 'center',
|
|
155
|
+
justifyContent: 'center',
|
|
156
|
+
},
|
|
157
|
+
initials: {
|
|
158
|
+
color: colors.white,
|
|
159
|
+
fontWeight: typography.fontWeight.semiBold,
|
|
160
|
+
},
|
|
161
|
+
placeholder: {
|
|
162
|
+
backgroundColor: colors.background.tertiary,
|
|
163
|
+
alignItems: 'center',
|
|
164
|
+
justifyContent: 'center',
|
|
165
|
+
},
|
|
166
|
+
placeholderIcon: {
|
|
167
|
+
opacity: 0.5,
|
|
168
|
+
},
|
|
169
|
+
badge: {
|
|
170
|
+
position: 'absolute',
|
|
171
|
+
borderWidth: 2,
|
|
172
|
+
borderColor: colors.background.default,
|
|
173
|
+
},
|
|
174
|
+
badgeTopRight: {
|
|
175
|
+
top: 0,
|
|
176
|
+
right: 0,
|
|
177
|
+
},
|
|
178
|
+
badgeBottomRight: {
|
|
179
|
+
bottom: 0,
|
|
180
|
+
right: 0,
|
|
181
|
+
},
|
|
182
|
+
xsText: {
|
|
183
|
+
fontSize: 8,
|
|
184
|
+
},
|
|
185
|
+
smText: {
|
|
186
|
+
fontSize: 10,
|
|
187
|
+
},
|
|
188
|
+
mdText: {
|
|
189
|
+
fontSize: 12,
|
|
190
|
+
},
|
|
191
|
+
lgText: {
|
|
192
|
+
fontSize: 14,
|
|
193
|
+
},
|
|
194
|
+
xlText: {
|
|
195
|
+
fontSize: 18,
|
|
196
|
+
},
|
|
197
|
+
'2xlText': {
|
|
198
|
+
fontSize: 28,
|
|
199
|
+
},
|
|
200
|
+
group: {
|
|
201
|
+
flexDirection: 'row',
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
},
|
|
204
|
+
groupItem: {
|
|
205
|
+
borderWidth: 2,
|
|
206
|
+
borderColor: colors.background.default,
|
|
207
|
+
borderRadius: 9999,
|
|
208
|
+
},
|
|
209
|
+
remainingBadge: {
|
|
210
|
+
backgroundColor: colors.background.tertiary,
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
justifyContent: 'center',
|
|
213
|
+
},
|
|
214
|
+
remainingText: {
|
|
215
|
+
fontSize: typography.fontSize.small,
|
|
216
|
+
fontWeight: typography.fontWeight.semiBold,
|
|
217
|
+
color: colors.text.secondary,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { Badge } from './Badge';
|
|
4
|
+
import { VStack } from '../VStack';
|
|
5
|
+
import { HStack } from '../HStack';
|
|
6
|
+
import { Text } from '../Text';
|
|
7
|
+
import { colors, spacing, borderRadius } from '../../styles/tokens';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Story container with design system tokens
|
|
11
|
+
*/
|
|
12
|
+
const StoryContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
13
|
+
<View style={styles.container}>{children}</View>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const meta: Meta<typeof Badge> = {
|
|
17
|
+
title: 'Components/Badge',
|
|
18
|
+
component: Badge,
|
|
19
|
+
decorators: [
|
|
20
|
+
(Story) => (
|
|
21
|
+
<StoryContainer>
|
|
22
|
+
<Story />
|
|
23
|
+
</StoryContainer>
|
|
24
|
+
),
|
|
25
|
+
],
|
|
26
|
+
argTypes: {
|
|
27
|
+
variant: {
|
|
28
|
+
control: 'select',
|
|
29
|
+
options: ['gray', 'red', 'orange', 'yellow', 'blue', 'green', 'darkGray', 'neonGreen', 'purple'],
|
|
30
|
+
},
|
|
31
|
+
size: {
|
|
32
|
+
control: 'select',
|
|
33
|
+
options: ['sm', 'md', 'lg'],
|
|
34
|
+
},
|
|
35
|
+
dot: {
|
|
36
|
+
control: 'boolean',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
args: {
|
|
40
|
+
children: 'Badge',
|
|
41
|
+
variant: 'gray',
|
|
42
|
+
size: 'md',
|
|
43
|
+
dot: false,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default meta;
|
|
48
|
+
|
|
49
|
+
type Story = StoryObj<typeof Badge>;
|
|
50
|
+
|
|
51
|
+
export const Default: Story = {};
|
|
52
|
+
|
|
53
|
+
export const AllVariants: Story = {
|
|
54
|
+
render: () => (
|
|
55
|
+
<VStack space={spacing.lg}>
|
|
56
|
+
<Text weight="semiBold">Badge Variants</Text>
|
|
57
|
+
<HStack space={spacing.sm} wrap>
|
|
58
|
+
<Badge variant="gray">Gray</Badge>
|
|
59
|
+
<Badge variant="red">Red</Badge>
|
|
60
|
+
<Badge variant="orange">Orange</Badge>
|
|
61
|
+
<Badge variant="yellow">Yellow</Badge>
|
|
62
|
+
<Badge variant="blue">Blue</Badge>
|
|
63
|
+
<Badge variant="green">Green</Badge>
|
|
64
|
+
<Badge variant="darkGray">Dark Gray</Badge>
|
|
65
|
+
<Badge variant="neonGreen">Neon Green</Badge>
|
|
66
|
+
<Badge variant="purple">Purple</Badge>
|
|
67
|
+
</HStack>
|
|
68
|
+
</VStack>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Sizes: Story = {
|
|
73
|
+
render: () => (
|
|
74
|
+
<VStack space={spacing.lg}>
|
|
75
|
+
<Text weight="semiBold">Badge Sizes</Text>
|
|
76
|
+
<HStack space={spacing.sm} alignItems="center">
|
|
77
|
+
<Badge size="sm" variant="blue">Small</Badge>
|
|
78
|
+
<Badge size="md" variant="blue">Medium</Badge>
|
|
79
|
+
<Badge size="lg" variant="blue">Large</Badge>
|
|
80
|
+
</HStack>
|
|
81
|
+
</VStack>
|
|
82
|
+
),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const StatusBadges: Story = {
|
|
86
|
+
render: () => (
|
|
87
|
+
<VStack space={spacing.lg}>
|
|
88
|
+
<Text weight="semiBold">Status Badges</Text>
|
|
89
|
+
<HStack space={spacing.sm}>
|
|
90
|
+
<Badge variant="green">Active</Badge>
|
|
91
|
+
<Badge variant="yellow">Pending</Badge>
|
|
92
|
+
<Badge variant="red">Inactive</Badge>
|
|
93
|
+
<Badge variant="gray">Draft</Badge>
|
|
94
|
+
</HStack>
|
|
95
|
+
</VStack>
|
|
96
|
+
),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const RiskLevels: Story = {
|
|
100
|
+
render: () => (
|
|
101
|
+
<VStack space={spacing.lg}>
|
|
102
|
+
<Text weight="semiBold">Risk Levels</Text>
|
|
103
|
+
<HStack space={spacing.sm}>
|
|
104
|
+
<Badge variant="red">Very High</Badge>
|
|
105
|
+
<Badge variant="orange">High</Badge>
|
|
106
|
+
<Badge variant="yellow">Medium</Badge>
|
|
107
|
+
<Badge variant="blue">Low</Badge>
|
|
108
|
+
<Badge variant="green">Minimal</Badge>
|
|
109
|
+
</HStack>
|
|
110
|
+
</VStack>
|
|
111
|
+
),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const DotBadges: Story = {
|
|
115
|
+
render: () => (
|
|
116
|
+
<VStack space={spacing.lg}>
|
|
117
|
+
<Text weight="semiBold">Dot Badges</Text>
|
|
118
|
+
<HStack space={spacing.lg} alignItems="center">
|
|
119
|
+
<HStack space={spacing.sm} alignItems="center">
|
|
120
|
+
<Badge dot variant="green" size="sm" />
|
|
121
|
+
<Text variant="caption">Online</Text>
|
|
122
|
+
</HStack>
|
|
123
|
+
<HStack space={spacing.sm} alignItems="center">
|
|
124
|
+
<Badge dot variant="yellow" size="sm" />
|
|
125
|
+
<Text variant="caption">Away</Text>
|
|
126
|
+
</HStack>
|
|
127
|
+
<HStack space={spacing.sm} alignItems="center">
|
|
128
|
+
<Badge dot variant="red" size="sm" />
|
|
129
|
+
<Text variant="caption">Offline</Text>
|
|
130
|
+
</HStack>
|
|
131
|
+
</HStack>
|
|
132
|
+
</VStack>
|
|
133
|
+
),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Styles using design system tokens
|
|
138
|
+
*/
|
|
139
|
+
const styles = StyleSheet.create({
|
|
140
|
+
container: {
|
|
141
|
+
padding: spacing.lg,
|
|
142
|
+
backgroundColor: colors.background.default,
|
|
143
|
+
borderRadius: borderRadius.lg,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Badge Component
|
|
3
|
+
* Status indicator and label component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { forwardRef } from 'react';
|
|
7
|
+
import { View, ViewProps, Text, StyleSheet } from 'react-native';
|
|
8
|
+
import { colors, spacing, typography, borderRadius } from '../../styles/tokens';
|
|
9
|
+
|
|
10
|
+
export interface BadgeProps extends ViewProps {
|
|
11
|
+
/** Badge variant/color */
|
|
12
|
+
variant?: 'gray' | 'red' | 'orange' | 'yellow' | 'blue' | 'green' | 'darkGray' | 'neonGreen' | 'purple';
|
|
13
|
+
/** Badge size */
|
|
14
|
+
size?: 'sm' | 'md' | 'lg';
|
|
15
|
+
/** Badge content */
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
/** Show as dot only */
|
|
18
|
+
dot?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const bgColors: Record<string, string> = {
|
|
22
|
+
gray: colors.badge.gray,
|
|
23
|
+
red: colors.badge.red,
|
|
24
|
+
orange: colors.badge.orange,
|
|
25
|
+
yellow: colors.badge.yellow,
|
|
26
|
+
blue: colors.badge.blue,
|
|
27
|
+
green: colors.badge.green,
|
|
28
|
+
darkGray: colors.badge.darkGray,
|
|
29
|
+
neonGreen: colors.badge.neonGreen,
|
|
30
|
+
purple: colors.badge.purple,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const textColors: Record<string, string> = {
|
|
34
|
+
gray: colors.text.default,
|
|
35
|
+
red: colors.white,
|
|
36
|
+
orange: colors.white,
|
|
37
|
+
yellow: colors.text.default,
|
|
38
|
+
blue: colors.white,
|
|
39
|
+
green: colors.white,
|
|
40
|
+
darkGray: colors.white,
|
|
41
|
+
neonGreen: colors.white,
|
|
42
|
+
purple: colors.white,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const Badge = forwardRef<View, BadgeProps>(
|
|
46
|
+
({ style, variant = 'gray', size = 'md', dot = false, children, ...props }, ref) => {
|
|
47
|
+
if (dot) {
|
|
48
|
+
return (
|
|
49
|
+
<View
|
|
50
|
+
ref={ref}
|
|
51
|
+
style={[
|
|
52
|
+
styles.dot,
|
|
53
|
+
styles[`dot${size.charAt(0).toUpperCase() + size.slice(1)}` as keyof typeof styles],
|
|
54
|
+
{ backgroundColor: bgColors[variant] },
|
|
55
|
+
style,
|
|
56
|
+
]}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<View
|
|
64
|
+
ref={ref}
|
|
65
|
+
style={[styles.badge, styles[size], { backgroundColor: bgColors[variant] }, style]}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
<Text style={[styles.text, styles[`${size}Text` as keyof typeof styles], { color: textColors[variant] }]}>
|
|
69
|
+
{children}
|
|
70
|
+
</Text>
|
|
71
|
+
</View>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
Badge.displayName = 'Badge';
|
|
77
|
+
|
|
78
|
+
const styles = StyleSheet.create({
|
|
79
|
+
badge: {
|
|
80
|
+
flexDirection: 'row',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
justifyContent: 'center',
|
|
83
|
+
borderRadius: borderRadius.full,
|
|
84
|
+
},
|
|
85
|
+
sm: {
|
|
86
|
+
paddingHorizontal: spacing['2x'],
|
|
87
|
+
paddingVertical: spacing['0.5x'],
|
|
88
|
+
},
|
|
89
|
+
md: {
|
|
90
|
+
paddingHorizontal: spacing['3x'],
|
|
91
|
+
paddingVertical: spacing.base,
|
|
92
|
+
},
|
|
93
|
+
lg: {
|
|
94
|
+
paddingHorizontal: spacing['4x'],
|
|
95
|
+
paddingVertical: spacing['2x'],
|
|
96
|
+
},
|
|
97
|
+
dot: {
|
|
98
|
+
borderRadius: borderRadius.full,
|
|
99
|
+
},
|
|
100
|
+
dotSm: {
|
|
101
|
+
width: 6,
|
|
102
|
+
height: 6,
|
|
103
|
+
},
|
|
104
|
+
dotMd: {
|
|
105
|
+
width: 8,
|
|
106
|
+
height: 8,
|
|
107
|
+
},
|
|
108
|
+
dotLg: {
|
|
109
|
+
width: 10,
|
|
110
|
+
height: 10,
|
|
111
|
+
},
|
|
112
|
+
text: {
|
|
113
|
+
fontWeight: typography.fontWeight.semiBold,
|
|
114
|
+
},
|
|
115
|
+
smText: {
|
|
116
|
+
fontSize: 10,
|
|
117
|
+
},
|
|
118
|
+
mdText: {
|
|
119
|
+
fontSize: typography.fontSize.small,
|
|
120
|
+
},
|
|
121
|
+
lgText: {
|
|
122
|
+
fontSize: typography.fontSize.componentLabel,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|