@alveole/storybook 0.26.0

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.
Files changed (41) hide show
  1. package/README.md +255 -0
  2. package/dist/components/FilterBadge.d.ts +7 -0
  3. package/dist/components/FilterBadge.d.ts.map +1 -0
  4. package/dist/components/FilterBadge.js +4 -0
  5. package/dist/components/JsonBlock.d.ts +5 -0
  6. package/dist/components/JsonBlock.d.ts.map +1 -0
  7. package/dist/components/JsonBlock.js +13 -0
  8. package/dist/components/SearchField.d.ts +8 -0
  9. package/dist/components/SearchField.d.ts.map +1 -0
  10. package/dist/components/SearchField.js +16 -0
  11. package/dist/components/StoryCard.d.ts +7 -0
  12. package/dist/components/StoryCard.d.ts.map +1 -0
  13. package/dist/components/StoryCard.js +11 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +9 -0
  17. package/dist/screens/StoriesScreen.d.ts +12 -0
  18. package/dist/screens/StoriesScreen.d.ts.map +1 -0
  19. package/dist/screens/StoriesScreen.js +33 -0
  20. package/dist/screens/StoryDetailScreen.d.ts +7 -0
  21. package/dist/screens/StoryDetailScreen.d.ts.map +1 -0
  22. package/dist/screens/StoryDetailScreen.js +41 -0
  23. package/dist/screens/ThemeConstantDetailScreen.d.ts +6 -0
  24. package/dist/screens/ThemeConstantDetailScreen.d.ts.map +1 -0
  25. package/dist/screens/ThemeConstantDetailScreen.js +9 -0
  26. package/dist/screens/ThemeConstantsScreen.d.ts +11 -0
  27. package/dist/screens/ThemeConstantsScreen.d.ts.map +1 -0
  28. package/dist/screens/ThemeConstantsScreen.js +14 -0
  29. package/dist/screens/ThemePaletteScreen.d.ts +12 -0
  30. package/dist/screens/ThemePaletteScreen.d.ts.map +1 -0
  31. package/dist/screens/ThemePaletteScreen.js +17 -0
  32. package/dist/screens/ThemeTypographyScreen.d.ts +7 -0
  33. package/dist/screens/ThemeTypographyScreen.d.ts.map +1 -0
  34. package/dist/screens/ThemeTypographyScreen.js +11 -0
  35. package/dist/types.d.ts +25 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +1 -0
  38. package/dist/utils.d.ts +14 -0
  39. package/dist/utils.d.ts.map +1 -0
  40. package/dist/utils.js +45 -0
  41. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,255 @@
1
+ # @alveole/storybook
2
+
3
+ Composants réutilisables pour exposer un catalogue de stories et de tokens de thème.
4
+
5
+ Le package reprend les grandes parties du `ui-kit` de `groove-application/app/ui-kit`, mais sous une forme réutilisable et indépendante d'Expo Router.
6
+
7
+ ## Prérequis
8
+
9
+ Le package est prévu pour être utilisé avec :
10
+
11
+ - `@alveole/components`
12
+ - `@alveole/theme`
13
+ - `react`
14
+ - `react-native`
15
+
16
+ Il doit être rendu dans une app déjà enveloppée par `ThemeProvider`.
17
+
18
+ ## Exports disponibles
19
+
20
+ Le package exporte :
21
+
22
+ - `StoriesScreen`
23
+ - `StoryDetailScreen`
24
+ - `ThemeConstantsScreen`
25
+ - `ThemeConstantDetailScreen`
26
+ - `ThemePaletteScreen`
27
+ - `ThemeTypographyScreen`
28
+ - `JsonBlock`
29
+ - les types `StorybookMeta`, `StorybookModule`, `StorybookFlag`, `StorybookFlagKey`
30
+ - les helpers de `utils`
31
+
32
+ ## Ecriture recommandee des stories
33
+
34
+ Le package `@alveole/storybook` est pense pour consommer directement les stories ecrites comme dans `packages/components`.
35
+
36
+ Le pattern recommande est :
37
+
38
+ - un `export default` qui satisfait le type `Story`
39
+ - des `export const` pour chaque exemple rendu
40
+
41
+ Exemple proche de l'ecriture actuelle de [Box.stories.tsx](/Users/corentincouq/Documents/alveole/packages/components/src/core/Box/Box.stories.tsx) :
42
+
43
+ ```tsx
44
+ import { Story } from '../../type';
45
+ import { Typography } from '../Typography';
46
+ import { Box } from './Box';
47
+
48
+ export default {
49
+ title: 'Box',
50
+ tags: ['Kit'],
51
+ experimental: false,
52
+ description: 'Description du composant',
53
+ component: Box,
54
+ styleFn: () => 'Aucun style applique',
55
+ } satisfies Story;
56
+
57
+ export const BoxDefault = () => (
58
+ <Box tag="box">
59
+ <Typography>Contenu par defaut</Typography>
60
+ </Box>
61
+ );
62
+
63
+ export const BoxPadded = () => (
64
+ <Box p={16}>
65
+ <Typography>Box avec padding</Typography>
66
+ </Box>
67
+ );
68
+ ```
69
+
70
+ Le `default` porte les metadonnees de la story. Les autres exports sont les variantes affichees par `StoryDetailScreen`.
71
+
72
+ Concretement, `@alveole/storybook` n'introduit pas un nouveau format de story. Il reutilise celui que tu ecris deja dans `packages/components/src/**/*.stories.tsx`.
73
+
74
+ ## Structure attendue par le package
75
+
76
+ Quand tu fais :
77
+
78
+ ```tsx
79
+ import * as Stories from '@alveole/components/stories';
80
+ ```
81
+
82
+ chaque module exporte se retrouve sous une forme equivalente a :
83
+
84
+ ```tsx
85
+ import type { StorybookModule } from '@alveole/storybook';
86
+
87
+ const ButtonStory = {
88
+ default: {
89
+ title: 'Button',
90
+ tags: ['Kit'],
91
+ experimental: false,
92
+ description: 'Boutons de l application',
93
+ figmaURL: 'https://figma.com/...',
94
+ styleFn: () => ({}),
95
+ },
96
+ Primary: () => <MyButton label="Primary" />,
97
+ Secondary: () => <MyButton label="Secondary" />,
98
+ } satisfies StorybookModule;
99
+ ```
100
+
101
+ Autrement dit :
102
+
103
+ - `default` doit contenir les metadonnees
104
+ - les autres exports doivent etre des composants React affichables
105
+ - il ne faut pas rendre `StoriesScreen` ou `StoryDetailScreen` a l'interieur d'une story unitaire
106
+ - le bon point d'entree est un ecran catalogue qui consomme `@alveole/components/stories`
107
+
108
+ ## Exemple minimal pour les stories
109
+
110
+ ```tsx
111
+ import * as Stories from '@alveole/components/stories';
112
+ import { StoriesScreen, StoryDetailScreen, type StorybookModule } from '@alveole/storybook';
113
+ import React from 'react';
114
+
115
+ const storyList = Object.values(Stories) as StorybookModule[];
116
+
117
+ export const StoryCatalogPage = () => {
118
+ const [selectedStory, setSelectedStory] = React.useState<StorybookModule | null>(null);
119
+
120
+ if (selectedStory) {
121
+ return <StoryDetailScreen story={selectedStory} />;
122
+ }
123
+
124
+ return (
125
+ <StoriesScreen
126
+ stories={storyList}
127
+ title="UI Kit - Components"
128
+ description="Catalogue des composants"
129
+ onSelectStory={setSelectedStory}
130
+ />
131
+ );
132
+ };
133
+ ```
134
+
135
+ ## Exemple avec Expo Router
136
+
137
+ Le package ne crée pas les routes. Il fournit seulement les écrans. À toi de brancher la navigation.
138
+
139
+ ```tsx
140
+ import * as Stories from '@alveole/components/stories';
141
+ import { StoriesScreen, type StorybookModule } from '@alveole/storybook';
142
+ import { router } from 'expo-router';
143
+
144
+ const storyList = Object.values(Stories) as StorybookModule[];
145
+
146
+ export default function UIKitStoriesRoute() {
147
+ return (
148
+ <StoriesScreen
149
+ stories={storyList}
150
+ onSelectStory={story => {
151
+ router.push(`/ui-kit/components/${encodeURIComponent(story.default.title)}`);
152
+ }}
153
+ />
154
+ );
155
+ }
156
+ ```
157
+
158
+ Puis dans la route de détail :
159
+
160
+ ```tsx
161
+ import * as Stories from '@alveole/components/stories';
162
+ import { StoryDetailScreen, type StorybookModule } from '@alveole/storybook';
163
+ import { useLocalSearchParams } from 'expo-router';
164
+
165
+ export default function UIKitStoryDetailRoute() {
166
+ const { component } = useLocalSearchParams<{ component: string }>();
167
+
168
+ const story = (Object.values(Stories) as StorybookModule[]).find(entry => entry.default.title === component) ?? null;
169
+
170
+ return <StoryDetailScreen story={story} />;
171
+ }
172
+ ```
173
+
174
+ ## Exemple pour les constantes du thème
175
+
176
+ ```tsx
177
+ import * as ThemeConstants from '@alveole/theme';
178
+ import { ThemeConstantsScreen } from '@alveole/storybook';
179
+
180
+ export default function ThemeConstantsPage() {
181
+ return (
182
+ <ThemeConstantsScreen constants={ThemeConstants} title="UI Kit - Constants" description="Constantes du thème" />
183
+ );
184
+ }
185
+ ```
186
+
187
+ ## Exemple pour la palette
188
+
189
+ ```tsx
190
+ import { CustomPalette } from '@alveole/theme';
191
+ import { ThemePaletteScreen } from '@alveole/storybook';
192
+
193
+ export default function ThemePalettePage() {
194
+ return <ThemePaletteScreen palette={CustomPalette} />;
195
+ }
196
+ ```
197
+
198
+ ## Exemple pour la typographie
199
+
200
+ ```tsx
201
+ import { CustomTypography } from '@alveole/theme';
202
+ import { ThemeTypographyScreen } from '@alveole/storybook';
203
+
204
+ export default function ThemeTypographyPage() {
205
+ return <ThemeTypographyScreen typography={CustomTypography} />;
206
+ }
207
+ ```
208
+
209
+ ## Props principales
210
+
211
+ ### `StoriesScreen`
212
+
213
+ - `stories`: liste des stories
214
+ - `title`: titre de page
215
+ - `description`: description de page
216
+ - `emptyMessage`: message affiché si aucun résultat
217
+ - `createLabel`: libellé du bouton d’action
218
+ - `onCreatePress`: callback du bouton d’action
219
+ - `onSelectStory`: callback au clic sur une story
220
+
221
+ ### `StoryDetailScreen`
222
+
223
+ - `story`: story sélectionnée
224
+ - `notFoundMessage`: message si la story est absente
225
+
226
+ ### `ThemeConstantsScreen`
227
+
228
+ - `constants`: objet de constantes
229
+ - `title`: titre de page
230
+ - `description`: description de page
231
+ - `onSelectConstant`: callback au clic sur une constante
232
+
233
+ ### `ThemeConstantDetailScreen`
234
+
235
+ - `name`: nom de la constante
236
+ - `value`: valeur de la constante
237
+
238
+ ### `ThemePaletteScreen`
239
+
240
+ - `palette`: objet de palette
241
+ - `title`: titre de page
242
+ - `description`: description de page
243
+
244
+ ### `ThemeTypographyScreen`
245
+
246
+ - `typography`: objet de tokens typo
247
+ - `title`: titre de page
248
+ - `description`: description de page
249
+
250
+ ## Notes
251
+
252
+ - Le package n’embarque pas de moteur Storybook officiel.
253
+ - Il ne dépend pas d’Expo Router, mais il est pensé pour s’y brancher facilement.
254
+ - `StoryDetailScreen` ouvre le lien Figma via `Linking.openURL`.
255
+ - Les vues de détail affichent les objets sous forme JSON via `JsonBlock`.
@@ -0,0 +1,7 @@
1
+ export type FilterBadgeProps = {
2
+ active: boolean;
3
+ label: string;
4
+ onPress: () => void;
5
+ };
6
+ export declare const FilterBadge: ({ active, label, onPress }: FilterBadgeProps) => import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=FilterBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterBadge.d.ts","sourceRoot":"","sources":["../../src/components/FilterBadge.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,4BAA4B,gBAAgB,4CAQvE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Badge, Box } from '@alveole/components';
3
+ import { Pressable } from 'react-native';
4
+ export const FilterBadge = ({ active, label, onPress }) => (_jsx(Pressable, { onPress: onPress, children: _jsx(Box, { children: _jsx(Badge, { variant: active ? 'new' : 'default', size: "sm", children: label }) }) }));
@@ -0,0 +1,5 @@
1
+ export type JsonBlockProps = {
2
+ value: unknown;
3
+ };
4
+ export declare const JsonBlock: ({ value }: JsonBlockProps) => import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=JsonBlock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonBlock.d.ts","sourceRoot":"","sources":["../../src/components/JsonBlock.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,WAAW,cAAc,4CAyBlD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ export const JsonBlock = ({ value }) => {
5
+ const { text, color, radius } = useTheme();
6
+ return (_jsx(Box, { backgroundColor: color.light.background['alt-grey'], borderColor: color.light.border['plain-grey'], borderRadius: radius('md'), borderWidth: 1, p: '100', width: '100%', children: _jsx(Typography, { style: [
7
+ text['Corps de texte'].SM.Regular,
8
+ {
9
+ fontFamily: 'monospace',
10
+ whiteSpace: 'pre-wrap',
11
+ },
12
+ ], children: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }) }));
13
+ };
@@ -0,0 +1,8 @@
1
+ export type SearchFieldProps = {
2
+ label: string;
3
+ placeholder?: string;
4
+ value: string;
5
+ onChangeText: (value: string) => void;
6
+ };
7
+ export declare const SearchField: ({ label, placeholder, value, onChangeText }: SearchFieldProps) => import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=SearchField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchField.d.ts","sourceRoot":"","sources":["../../src/components/SearchField.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,6CAA6C,gBAAgB,4CAuBxF,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import { TextInput } from 'react-native';
5
+ export const SearchField = ({ label, placeholder, value, onChangeText }) => {
6
+ const { text, color, radius, spacing } = useTheme();
7
+ return (_jsxs(Box, { display: "flex", gap: 8, children: [_jsx(Typography, { style: text['Corps de texte'].SM.SemiBold, children: label }), _jsx(TextInput, { placeholder: placeholder, value: value, onChangeText: onChangeText, placeholderTextColor: color.light.text['mention-grey'], style: {
8
+ borderWidth: 1,
9
+ borderColor: color.light.border['plain-grey'],
10
+ borderRadius: radius('md'),
11
+ paddingVertical: spacing('075'),
12
+ paddingHorizontal: spacing('100'),
13
+ backgroundColor: color.light.background['default-grey'],
14
+ color: color.light.text['default-grey'],
15
+ } })] }));
16
+ };
@@ -0,0 +1,7 @@
1
+ import { StorybookModule } from '../types';
2
+ export type StoryCardProps = {
3
+ story: StorybookModule;
4
+ onPress?: (story: StorybookModule) => void;
5
+ };
6
+ export declare const StoryCard: ({ story, onPress }: StoryCardProps) => import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=StoryCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StoryCard.d.ts","sourceRoot":"","sources":["../../src/components/StoryCard.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG3C,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAC5C,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,oBAAoB,cAAc,4CA2B3D,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge, Box, Card, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import { Pressable } from 'react-native';
5
+ import { getStoryFlags } from '../utils';
6
+ export const StoryCard = ({ story, onPress }) => {
7
+ const { text } = useTheme();
8
+ const meta = story.default;
9
+ const flags = getStoryFlags(meta);
10
+ return (_jsx(Pressable, { onPress: onPress ? () => onPress(story) : undefined, children: _jsx(Card, { height: '100%', children: _jsxs(Card.Section, { display: "flex", gap: 12, p: '100', children: [_jsxs(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 8, children: [meta.tags.map(tag => (_jsx(Badge, { variant: "info", size: "sm", children: tag }, tag))), flags.map(flag => (_jsx(Badge, { variant: "default", size: "sm", children: flag.label }, flag.key)))] }), _jsx(Typography, { style: text.Titres['H5 - XS'], children: meta.title }), _jsx(Typography, { style: text['Corps de texte'].SM.Regular, children: meta.description })] }) }) }));
11
+ };
@@ -0,0 +1,10 @@
1
+ export * from './components/JsonBlock';
2
+ export * from './screens/StoriesScreen';
3
+ export * from './screens/StoryDetailScreen';
4
+ export * from './screens/ThemeConstantDetailScreen';
5
+ export * from './screens/ThemeConstantsScreen';
6
+ export * from './screens/ThemePaletteScreen';
7
+ export * from './screens/ThemeTypographyScreen';
8
+ export * from './types';
9
+ export * from './utils';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qCAAqC,CAAC;AACpD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export * from './components/JsonBlock';
2
+ export * from './screens/StoriesScreen';
3
+ export * from './screens/StoryDetailScreen';
4
+ export * from './screens/ThemeConstantDetailScreen';
5
+ export * from './screens/ThemeConstantsScreen';
6
+ export * from './screens/ThemePaletteScreen';
7
+ export * from './screens/ThemeTypographyScreen';
8
+ export * from './types';
9
+ export * from './utils';
@@ -0,0 +1,12 @@
1
+ import { StorybookModule } from '../types';
2
+ export type StoriesScreenProps = {
3
+ stories: StorybookModule[];
4
+ title?: string;
5
+ description?: string;
6
+ emptyMessage?: string;
7
+ createLabel?: string;
8
+ onCreatePress?: () => void;
9
+ onSelectStory?: (story: StorybookModule) => void;
10
+ };
11
+ export declare const StoriesScreen: ({ stories, title, description, emptyMessage, createLabel, onCreatePress, onSelectStory, }: StoriesScreenProps) => import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=StoriesScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StoriesScreen.d.ts","sourceRoot":"","sources":["../../src/screens/StoriesScreen.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAiB,eAAe,EAAE,MAAM,UAAU,CAAC;AAW1D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,2FAQ3B,kBAAkB,4CAmGpB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Button, Page, Section, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import React from 'react';
5
+ import { useWindowDimensions } from 'react-native';
6
+ import { FilterBadge } from '../components/FilterBadge';
7
+ import { SearchField } from '../components/SearchField';
8
+ import { StoryCard } from '../components/StoryCard';
9
+ import { filterStories, getAllStoryTags, groupStoriesByTag } from '../utils';
10
+ const AVAILABLE_FLAGS = [
11
+ { key: 'figma', label: 'Figma' },
12
+ { key: 'experimental', label: 'Experimental' },
13
+ { key: 'props', label: 'Props' },
14
+ { key: 'webOnly', label: 'Web only' },
15
+ { key: 'mobileOnly', label: 'Mobile only' },
16
+ ];
17
+ export const StoriesScreen = ({ stories, title = 'UI Kit - Components', description = 'Shared component catalog', emptyMessage = 'No story found.', createLabel, onCreatePress, onSelectStory, }) => {
18
+ const { text } = useTheme();
19
+ const { width } = useWindowDimensions();
20
+ const columns = width >= 1200 ? 3 : width >= 768 ? 2 : 1;
21
+ const [query, setQuery] = React.useState('');
22
+ const [selectedTag, setSelectedTag] = React.useState(null);
23
+ const [selectedFlag, setSelectedFlag] = React.useState(null);
24
+ const allTags = React.useMemo(() => getAllStoryTags(stories), [stories]);
25
+ const filteredStories = React.useMemo(() => filterStories({
26
+ stories,
27
+ query,
28
+ selectedTag,
29
+ selectedFlag,
30
+ }), [stories, query, selectedTag, selectedFlag]);
31
+ const groupedStories = React.useMemo(() => groupStoriesByTag(filteredStories, allTags), [filteredStories, allTags]);
32
+ return (_jsxs(Page, { scrollable: true, title: title, description: description, beforeContent: createLabel && onCreatePress ? (_jsx(Section, { withPaddingY: true, children: _jsx(Box, { style: { alignItems: 'flex-end' }, children: _jsx(Button, { title: createLabel, variant: "primary", onPress: onCreatePress }) }) })) : undefined, children: [_jsx(Section, { withPaddingY: true, children: _jsxs(Box, { display: "flex", gap: 16, children: [_jsx(SearchField, { label: "Search", placeholder: "Button, Tabs, Card...", value: query, onChangeText: setQuery }), _jsxs(Box, { display: "flex", gap: 8, children: [_jsx(Typography, { style: text['Corps de texte'].SM.SemiBold, children: "Tags" }), _jsxs(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 8, children: [_jsx(FilterBadge, { active: selectedTag === null, label: "All", onPress: () => setSelectedTag(null) }), allTags.map(tag => (_jsx(FilterBadge, { active: selectedTag === tag, label: tag, onPress: () => setSelectedTag(current => (current === tag ? null : tag)) }, tag)))] })] }), _jsxs(Box, { display: "flex", gap: 8, children: [_jsx(Typography, { style: text['Corps de texte'].SM.SemiBold, children: "Flags" }), _jsxs(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 8, children: [_jsx(FilterBadge, { active: selectedFlag === null, label: "All", onPress: () => setSelectedFlag(null) }), AVAILABLE_FLAGS.map(flag => (_jsx(FilterBadge, { active: selectedFlag === flag.key, label: flag.label, onPress: () => setSelectedFlag(current => (current === flag.key ? null : flag.key)) }, flag.key)))] })] }), _jsxs(Typography, { style: text['Corps de texte'].SM.Regular, children: [filteredStories.length, " result", filteredStories.length > 1 ? 's' : ''] })] }) }), groupedStories.length === 0 ? (_jsx(Section, { withPaddingY: true, children: _jsx(Typography, { style: text['Corps de texte'].MD.Regular, children: emptyMessage }) })) : (groupedStories.map(([tag, taggedStories]) => (_jsx(Section, { withPaddingY: true, children: _jsxs(Box, { display: "flex", gap: 16, children: [_jsx(Typography, { style: text.Titres['H4 - SM'], children: tag }), _jsx(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 16, children: taggedStories.map(story => (_jsx(Box, { width: columns === 1 ? '100%' : columns === 2 ? '48%' : '31%', children: _jsx(StoryCard, { story: story, onPress: onSelectStory }) }, story.default.title))) })] }) }, tag))))] }));
33
+ };
@@ -0,0 +1,7 @@
1
+ import { StorybookModule } from '../types';
2
+ export type StoryDetailScreenProps = {
3
+ story?: StorybookModule | null;
4
+ notFoundMessage?: string;
5
+ };
6
+ export declare const StoryDetailScreen: ({ story, notFoundMessage }: StoryDetailScreenProps) => import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=StoryDetailScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StoryDetailScreen.d.ts","sourceRoot":"","sources":["../../src/screens/StoryDetailScreen.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG3C,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,4BAAiD,sBAAsB,4CA4FxG,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Button, Divider, Page, Section, Tabs, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import { Linking } from 'react-native';
5
+ import { JsonBlock } from '../components/JsonBlock';
6
+ import { getStoryExamples, getStoryFlags } from '../utils';
7
+ export const StoryDetailScreen = ({ story, notFoundMessage = 'Story not found.' }) => {
8
+ const { text } = useTheme();
9
+ if (!story) {
10
+ return (_jsx(Page, { title: "Story not found", description: notFoundMessage, children: _jsx(Section, { withPaddingY: true, children: _jsx(Typography, { style: text['Corps de texte'].MD.Regular, children: notFoundMessage }) }) }));
11
+ }
12
+ const meta = story.default;
13
+ const flags = getStoryFlags(meta);
14
+ const examples = getStoryExamples(story);
15
+ const isTemplate = meta.tags.includes('Template');
16
+ const examplesContent = (_jsx(Box, { display: "flex", gap: 16, children: examples.map(([key, Example]) => (_jsxs(Box, { display: "flex", gap: 12, children: [!isTemplate ? _jsx(Typography, { style: text['Corps de texte'].SM.SemiBold, children: key }) : null, _jsx(Box, { borderColor: '#e8e8e8', borderWidth: 1, borderRadius: 8, p: '100', style: isTemplate ? { minHeight: 400 } : undefined, children: _jsx(Example, {}) }), _jsx(Divider, {})] }, key))) }));
17
+ return (_jsx(Page, { scrollable: true, title: meta.title, description: meta.description, children: _jsx(Section, { withPaddingY: true, children: _jsxs(Box, { display: "flex", gap: 16, children: [_jsxs(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 8, children: [meta.tags.map(tag => (_jsx(Typography, { style: text['Corps de texte'].XS.SemiBold, children: tag }, tag))), flags.map(flag => (_jsx(Typography, { style: text['Corps de texte'].XS.Regular, children: flag.label }, flag.key)))] }), meta.figmaURL ? (_jsx(Box, { style: { alignItems: 'flex-start' }, children: _jsx(Button, { title: "Open Figma", variant: "primary", onPress: () => Linking.openURL(meta.figmaURL) }) })) : null, _jsx(Tabs, { defaultValue: "examples", tabs: [
18
+ {
19
+ value: 'examples',
20
+ label: 'Examples',
21
+ content: examplesContent,
22
+ scrollable: true,
23
+ },
24
+ {
25
+ value: 'styles',
26
+ label: 'Styles',
27
+ content: _jsx(JsonBlock, { value: meta.styleFn() }),
28
+ scrollable: true,
29
+ },
30
+ ...(meta.props != null
31
+ ? [
32
+ {
33
+ value: 'props',
34
+ label: 'Props',
35
+ content: _jsx(JsonBlock, { value: meta.props }),
36
+ scrollable: true,
37
+ },
38
+ ]
39
+ : []),
40
+ ] })] }) }) }));
41
+ };
@@ -0,0 +1,6 @@
1
+ export type ThemeConstantDetailScreenProps = {
2
+ name: string;
3
+ value: unknown;
4
+ };
5
+ export declare const ThemeConstantDetailScreen: ({ name, value }: ThemeConstantDetailScreenProps) => import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=ThemeConstantDetailScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeConstantDetailScreen.d.ts","sourceRoot":"","sources":["../../src/screens/ThemeConstantDetailScreen.tsx"],"names":[],"mappings":"AAIA,MAAM,MAAM,8BAA8B,GAAG;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,iBAAiB,8BAA8B,4CAsBxF,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Page, Section, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import { JsonBlock } from '../components/JsonBlock';
5
+ export const ThemeConstantDetailScreen = ({ name, value }) => {
6
+ const { text } = useTheme();
7
+ const entries = typeof value === 'object' && value != null ? Object.entries(value) : [];
8
+ return (_jsx(Page, { scrollable: true, title: name, description: name, children: _jsx(Section, { withPaddingY: true, children: _jsx(Box, { display: "flex", gap: 16, children: entries.length === 0 ? (_jsx(JsonBlock, { value: value })) : (entries.map(([entryName, entryValue]) => (_jsxs(Box, { display: "flex", gap: 8, children: [_jsx(Typography, { style: text.Titres['H6 - XXS'], children: entryName }), _jsx(JsonBlock, { value: entryValue })] }, entryName)))) }) }) }));
9
+ };
@@ -0,0 +1,11 @@
1
+ export type ThemeConstantsScreenProps = {
2
+ constants: Record<string, unknown>;
3
+ title?: string;
4
+ description?: string;
5
+ onSelectConstant?: (entry: {
6
+ name: string;
7
+ value: unknown;
8
+ }) => void;
9
+ };
10
+ export declare const ThemeConstantsScreen: ({ constants, title, description, onSelectConstant, }: ThemeConstantsScreenProps) => import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=ThemeConstantsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeConstantsScreen.d.ts","sourceRoot":"","sources":["../../src/screens/ThemeConstantsScreen.tsx"],"names":[],"mappings":"AAKA,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CACtE,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,sDAKlC,yBAAyB,4CAgC3B,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Card, Page, Section, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import React from 'react';
5
+ import { Pressable, useWindowDimensions } from 'react-native';
6
+ export const ThemeConstantsScreen = ({ constants, title = 'UI Kit - Constants', description = 'Theme constants', onSelectConstant, }) => {
7
+ const { text } = useTheme();
8
+ const { width } = useWindowDimensions();
9
+ const columns = width >= 1200 ? 3 : width >= 768 ? 2 : 1;
10
+ const entries = React.useMemo(() => Object.entries(constants)
11
+ .filter(([, value]) => typeof value === 'object' && value != null)
12
+ .sort((left, right) => left[0].localeCompare(right[0])), [constants]);
13
+ return (_jsx(Page, { scrollable: true, title: title, description: description, children: _jsx(Section, { withPaddingY: true, children: _jsx(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 16, children: entries.map(([name, value]) => (_jsx(Box, { width: columns === 1 ? '100%' : columns === 2 ? '48%' : '31%', children: _jsx(Pressable, { onPress: onSelectConstant ? () => onSelectConstant({ name, value }) : undefined, children: _jsx(Card, { children: _jsx(Card.Section, { p: '100', children: _jsx(Typography, { style: text.Titres['H5 - XS'], children: name }) }) }) }) }, name))) }) }) }));
14
+ };
@@ -0,0 +1,12 @@
1
+ interface PaletteScale {
2
+ [key: string]: PaletteValue;
3
+ }
4
+ type PaletteValue = string | PaletteScale;
5
+ export type ThemePaletteScreenProps = {
6
+ palette: Record<string, PaletteValue>;
7
+ title?: string;
8
+ description?: string;
9
+ };
10
+ export declare const ThemePaletteScreen: ({ palette, title, description, }: ThemePaletteScreenProps) => import("react/jsx-runtime").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=ThemePaletteScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemePaletteScreen.d.ts","sourceRoot":"","sources":["../../src/screens/ThemePaletteScreen.tsx"],"names":[],"mappings":"AAKA,UAAU,YAAY;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC;CAC7B;AAED,KAAK,YAAY,GAAG,MAAM,GAAG,YAAY,CAAC;AAmB1C,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,kCAIhC,uBAAuB,4CAuBzB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Card, Page, Section, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import { useWindowDimensions } from 'react-native';
5
+ import { JsonBlock } from '../components/JsonBlock';
6
+ const PaletteValueView = ({ label, value }) => {
7
+ const { text } = useTheme();
8
+ return (_jsxs(Box, { display: "flex", gap: 8, children: [label ? _jsx(Typography, { style: text['Corps de texte'].XS.SemiBold, children: label }) : null, typeof value === 'string' ? _jsx(Box, { height: 48, width: 48, borderRadius: 8, backgroundColor: value }) : null, _jsx(JsonBlock, { value: value }), typeof value === 'object'
9
+ ? Object.entries(value).map(([childLabel, childValue]) => (_jsx(PaletteValueView, { label: childLabel, value: childValue }, childLabel)))
10
+ : null] }));
11
+ };
12
+ export const ThemePaletteScreen = ({ palette, title = 'UI Kit - Theme colors', description = 'Theme palette', }) => {
13
+ const { text } = useTheme();
14
+ const { width } = useWindowDimensions();
15
+ const columns = width >= 1200 ? 3 : width >= 768 ? 2 : 1;
16
+ return (_jsx(Page, { scrollable: true, title: title, description: description, children: _jsx(Section, { withPaddingY: true, children: _jsx(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 16, children: Object.entries(palette).map(([key, value]) => (_jsx(Box, { width: columns === 1 ? '100%' : columns === 2 ? '48%' : '31%', children: _jsx(Card, { children: _jsxs(Card.Section, { display: "flex", gap: 16, p: '100', children: [_jsx(Typography, { style: text.Titres['H5 - XS'], children: key }), _jsx(PaletteValueView, { value: value })] }) }) }, key))) }) }) }));
17
+ };
@@ -0,0 +1,7 @@
1
+ export type ThemeTypographyScreenProps = {
2
+ typography: Record<string, unknown>;
3
+ title?: string;
4
+ description?: string;
5
+ };
6
+ export declare const ThemeTypographyScreen: ({ typography, title, description, }: ThemeTypographyScreenProps) => import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=ThemeTypographyScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeTypographyScreen.d.ts","sourceRoot":"","sources":["../../src/screens/ThemeTypographyScreen.tsx"],"names":[],"mappings":"AAKA,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,qCAInC,0BAA0B,4CAgC5B,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Card, Page, Section, Typography } from '@alveole/components';
3
+ import { useTheme } from '@alveole/theme';
4
+ import { useWindowDimensions } from 'react-native';
5
+ import { JsonBlock } from '../components/JsonBlock';
6
+ export const ThemeTypographyScreen = ({ typography, title = 'UI Kit - Theme typography', description = 'Theme text styles', }) => {
7
+ const { text } = useTheme();
8
+ const { width } = useWindowDimensions();
9
+ const columns = width >= 1200 ? 3 : width >= 768 ? 2 : 1;
10
+ return (_jsx(Page, { scrollable: true, title: title, description: description, children: _jsx(Section, { withPaddingY: true, children: _jsx(Box, { display: "flex", flexDirection: "row", flexWrap: "wrap", gap: 16, children: Object.entries(typography).map(([key, value]) => (_jsx(Box, { width: columns === 1 ? '100%' : columns === 2 ? '48%' : '31%', children: _jsx(Card, { children: _jsxs(Card.Section, { display: "flex", gap: 16, p: '100', children: [_jsx(Typography, { style: text.Titres['H5 - XS'], children: key }), typeof value === 'object' && value != null ? (Object.entries(value).map(([subKey, subValue]) => (_jsxs(Box, { display: "flex", gap: 8, children: [_jsx(Typography, { style: text['Corps de texte'].XS.SemiBold, children: subKey }), _jsx(JsonBlock, { value: subValue })] }, subKey)))) : (_jsx(JsonBlock, { value: value }))] }) }) }, key))) }) }) }));
11
+ };
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ export type StorybookTag = 'Kit' | 'Composant' | 'Template' | (string & {});
3
+ export type StorybookMeta = {
4
+ title: string;
5
+ tags: readonly StorybookTag[];
6
+ experimental: boolean;
7
+ figmaURL?: string;
8
+ mobileOnly?: boolean;
9
+ webOnly?: boolean;
10
+ description: string;
11
+ component?: React.ComponentType<any>;
12
+ config?: object;
13
+ props?: unknown;
14
+ styleFn: () => string | object;
15
+ };
16
+ export type StorybookExample = React.ComponentType<any>;
17
+ export type StorybookModule<TMeta extends StorybookMeta = StorybookMeta> = {
18
+ default: TMeta;
19
+ } & Record<string, StorybookExample | TMeta>;
20
+ export type StorybookFlagKey = 'figma' | 'experimental' | 'props' | 'webOnly' | 'mobileOnly';
21
+ export type StorybookFlag = {
22
+ key: StorybookFlagKey;
23
+ label: string;
24
+ };
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE5E,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,YAAY,EAAE,CAAC;IAC9B,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,MAAM,GAAG,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;AAExD,MAAM,MAAM,eAAe,CAAC,KAAK,SAAS,aAAa,GAAG,aAAa,IAAI;IACzE,OAAO,EAAE,KAAK,CAAC;CAChB,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,KAAK,CAAC,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,CAAC;AAE7F,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,gBAAgB,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { StorybookFlag, StorybookMeta, StorybookModule } from './types';
2
+ export declare const normalizeText: (value: string) => string;
3
+ export declare const getStoryFlags: (meta: StorybookMeta) => StorybookFlag[];
4
+ export declare const getAllStoryTags: (stories: StorybookModule[]) => string[];
5
+ export declare const filterStories: (params: {
6
+ stories: StorybookModule[];
7
+ query: string;
8
+ selectedTag: string | null;
9
+ selectedFlag: StorybookFlag["key"] | null;
10
+ }) => StorybookModule[];
11
+ export declare const sortStoriesByTitle: (stories: StorybookModule[]) => StorybookModule[];
12
+ export declare const groupStoriesByTag: (stories: StorybookModule[], tags: string[]) => (readonly [string, StorybookModule[]])[];
13
+ export declare const getStoryExamples: (story: StorybookModule) => [string, Exclude<StorybookModule[keyof StorybookModule], StorybookMeta>][];
14
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAUxE,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,WAIP,CAAC;AAErC,eAAO,MAAM,aAAa,GAAI,MAAM,aAAa,KAAG,aAAa,EAC+B,CAAC;AAEjG,eAAO,MAAM,eAAe,GAAI,SAAS,eAAe,EAAE,KAAG,MAAM,EAOlE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,QAAQ;IACpC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;CAC3C,sBAoBA,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,SAAS,eAAe,EAAE,sBAC8B,CAAC;AAE5F,eAAO,MAAM,iBAAiB,GAAI,SAAS,eAAe,EAAE,EAAE,MAAM,MAAM,EAAE,6CAGd,CAAC;AAE/D,eAAO,MAAM,gBAAgB,GAAI,OAAO,eAAe,KAC+C,CAClG,MAAM,EACN,OAAO,CAAC,eAAe,CAAC,MAAM,eAAe,CAAC,EAAE,aAAa,CAAC,CAC/D,EAAE,CAAC"}
package/dist/utils.js ADDED
@@ -0,0 +1,45 @@
1
+ const FLAG_DEFINITIONS = [
2
+ { key: 'figma', label: 'Figma', isActive: (meta) => Boolean(meta.figmaURL) },
3
+ { key: 'experimental', label: 'Experimental', isActive: (meta) => Boolean(meta.experimental) },
4
+ { key: 'props', label: 'Props', isActive: (meta) => meta.props != null },
5
+ { key: 'webOnly', label: 'Web only', isActive: (meta) => Boolean(meta.webOnly) },
6
+ { key: 'mobileOnly', label: 'Mobile only', isActive: (meta) => Boolean(meta.mobileOnly) },
7
+ ];
8
+ export const normalizeText = (value) => value
9
+ .toLowerCase()
10
+ .normalize('NFD')
11
+ .replace(/[\u0300-\u036f]/g, '');
12
+ export const getStoryFlags = (meta) => FLAG_DEFINITIONS.filter(flag => flag.isActive(meta)).map(({ key, label }) => ({ key, label }));
13
+ export const getAllStoryTags = (stories) => {
14
+ const tags = new Set();
15
+ for (const story of stories) {
16
+ for (const tag of story.default.tags)
17
+ tags.add(tag);
18
+ }
19
+ return Array.from(tags).sort((left, right) => left.localeCompare(right));
20
+ };
21
+ export const filterStories = (params) => {
22
+ const { stories, query, selectedTag, selectedFlag } = params;
23
+ const normalizedQuery = normalizeText(query.trim());
24
+ return stories.filter(story => {
25
+ const meta = story.default;
26
+ const matchesQuery = normalizedQuery.length === 0 ||
27
+ normalizeText(meta.title).includes(normalizedQuery) ||
28
+ normalizeText(meta.description).includes(normalizedQuery);
29
+ if (!matchesQuery)
30
+ return false;
31
+ if (selectedTag && !meta.tags.includes(selectedTag))
32
+ return false;
33
+ if (selectedFlag) {
34
+ const definition = FLAG_DEFINITIONS.find(flag => flag.key === selectedFlag);
35
+ if (definition && !definition.isActive(meta))
36
+ return false;
37
+ }
38
+ return true;
39
+ });
40
+ };
41
+ export const sortStoriesByTitle = (stories) => [...stories].sort((left, right) => left.default.title.localeCompare(right.default.title));
42
+ export const groupStoriesByTag = (stories, tags) => tags
43
+ .map(tag => [tag, stories.filter(story => story.default.tags.includes(tag))])
44
+ .filter(([, groupedStories]) => groupedStories.length > 0);
45
+ export const getStoryExamples = (story) => Object.entries(story).filter(([key, value]) => key !== 'default' && typeof value === 'function');
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@alveole/storybook",
3
+ "version": "0.26.0",
4
+ "description": "Catalog screens and helpers for Alveole stories and theme tokens.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "sideEffects": false,
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "lint": "eslint .",
22
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
23
+ },
24
+ "devDependencies": {
25
+ "@alveole/eslint-config": "file:../eslint-config",
26
+ "eslint": "^9.39.2"
27
+ },
28
+ "peerDependencies": {
29
+ "@alveole/components": "*",
30
+ "@alveole/theme": "*",
31
+ "react": "*",
32
+ "react-native": "*"
33
+ }
34
+ }