@krapwoo/ds-viewer 0.1.0 → 0.2.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.
- package/README.md +100 -78
- package/dist/cli/config.js +26 -1
- package/dist/cli/dev.js +50 -5
- package/dist/cli/glob.js +8 -0
- package/dist/cli/init.js +148 -18
- package/dist/cli/main.js +102 -3
- package/dist/cli/props.js +166 -18
- package/dist/cli/sync.js +17 -1
- package/dist/cli/workspace.js +49 -21
- package/native/catalog/CatalogShell.tsx +6 -1
- package/native/catalog/CatalogSidebar.tsx +98 -5
- package/native/catalog/TokenRow.tsx +3 -2
- package/native/catalog/index.ts +6 -5
- package/native/catalog/logoLayout.ts +12 -0
- package/native/catalog/pageApi.ts +10 -1
- package/native/catalog/tokens.ts +5 -0
- package/package.json +11 -7
- package/starter-kit/WHEN_TO_USE.md +124 -0
- package/starter-kit/components/AnimatedChevron/AnimatedChevron.catalog.tsx +23 -0
- package/starter-kit/components/AnimatedChevron/AnimatedChevron.tsx +46 -0
- package/starter-kit/components/AnimatedChevron/index.ts +2 -0
- package/starter-kit/components/Avatar/Avatar.catalog.tsx +72 -0
- package/starter-kit/components/Avatar/Avatar.tsx +74 -0
- package/starter-kit/components/Avatar/index.ts +2 -0
- package/starter-kit/components/Badge/Badge.catalog.tsx +96 -0
- package/starter-kit/components/Badge/Badge.tsx +56 -0
- package/starter-kit/components/Badge/Badge.types.ts +18 -0
- package/starter-kit/components/Badge/index.ts +2 -0
- package/starter-kit/components/Banner/Banner.catalog.tsx +87 -0
- package/starter-kit/components/Banner/Banner.tsx +257 -0
- package/starter-kit/components/Banner/Banner.types.ts +38 -0
- package/starter-kit/components/Banner/index.ts +2 -0
- package/starter-kit/components/BottomSheet/BottomSheet.catalog.tsx +77 -0
- package/starter-kit/components/BottomSheet/BottomSheet.tsx +151 -0
- package/starter-kit/components/BottomSheet/index.ts +2 -0
- package/starter-kit/components/Button/Button.catalog.tsx +113 -0
- package/starter-kit/components/Button/Button.tsx +173 -0
- package/starter-kit/components/Button/Button.types.ts +52 -0
- package/starter-kit/components/Button/index.ts +2 -0
- package/starter-kit/components/ButtonGroup/ButtonGroup.catalog.tsx +39 -0
- package/starter-kit/components/ButtonGroup/ButtonGroup.tsx +84 -0
- package/starter-kit/components/ButtonGroup/index.ts +2 -0
- package/starter-kit/components/Card/Card.catalog.tsx +68 -0
- package/starter-kit/components/Card/Card.tsx +133 -0
- package/starter-kit/components/Card/index.ts +2 -0
- package/starter-kit/components/Checkbox/Checkbox.catalog.tsx +28 -0
- package/starter-kit/components/Checkbox/Checkbox.tsx +104 -0
- package/starter-kit/components/Checkbox/index.ts +2 -0
- package/starter-kit/components/Dialog/Dialog.catalog.tsx +41 -0
- package/starter-kit/components/Dialog/Dialog.tsx +82 -0
- package/starter-kit/components/Dialog/index.ts +2 -0
- package/starter-kit/components/Divider/Divider.catalog.tsx +35 -0
- package/starter-kit/components/Divider/Divider.tsx +20 -0
- package/starter-kit/components/Divider/index.ts +2 -0
- package/starter-kit/components/Dock/Dock.catalog.tsx +71 -0
- package/starter-kit/components/Dock/Dock.tsx +58 -0
- package/starter-kit/components/Dock/index.ts +2 -0
- package/starter-kit/components/Dropdown/Dropdown.catalog.tsx +69 -0
- package/starter-kit/components/Dropdown/Dropdown.tsx +162 -0
- package/starter-kit/components/Dropdown/index.ts +2 -0
- package/starter-kit/components/EmptyState/EmptyState.catalog.tsx +67 -0
- package/starter-kit/components/EmptyState/EmptyState.tsx +88 -0
- package/starter-kit/components/EmptyState/index.ts +2 -0
- package/starter-kit/components/FieldContainer/FieldContainer.catalog.tsx +76 -0
- package/starter-kit/components/FieldContainer/FieldContainer.tsx +99 -0
- package/starter-kit/components/FieldContainer/FieldContainer.types.ts +27 -0
- package/starter-kit/components/FieldContainer/index.ts +2 -0
- package/starter-kit/components/InputClearButton/InputClearButton.catalog.tsx +18 -0
- package/starter-kit/components/InputClearButton/InputClearButton.tsx +63 -0
- package/starter-kit/components/InputClearButton/index.ts +2 -0
- package/starter-kit/components/InputField/InputField.catalog.tsx +76 -0
- package/starter-kit/components/InputField/InputField.tsx +418 -0
- package/starter-kit/components/InputField/index.ts +2 -0
- package/starter-kit/components/List/List.catalog.tsx +30 -0
- package/starter-kit/components/List/List.tsx +36 -0
- package/starter-kit/components/List/index.ts +2 -0
- package/starter-kit/components/ListItem/ListItem.catalog.tsx +89 -0
- package/starter-kit/components/ListItem/ListItem.tsx +165 -0
- package/starter-kit/components/ListItem/index.ts +2 -0
- package/starter-kit/components/Loading/Loading.catalog.tsx +34 -0
- package/starter-kit/components/Loading/Loading.tsx +121 -0
- package/starter-kit/components/Loading/index.ts +2 -0
- package/starter-kit/components/Pill/Pill.catalog.tsx +93 -0
- package/starter-kit/components/Pill/Pill.tsx +181 -0
- package/starter-kit/components/Pill/Pill.types.ts +30 -0
- package/starter-kit/components/Pill/index.ts +2 -0
- package/starter-kit/components/PillRow/PillRow.catalog.tsx +91 -0
- package/starter-kit/components/PillRow/PillRow.tsx +140 -0
- package/starter-kit/components/PillRow/PillRow.types.ts +24 -0
- package/starter-kit/components/PillRow/index.ts +2 -0
- package/starter-kit/components/ProgressDots/ProgressDots.catalog.tsx +22 -0
- package/starter-kit/components/ProgressDots/ProgressDots.tsx +42 -0
- package/starter-kit/components/ProgressDots/index.ts +2 -0
- package/starter-kit/components/Radio/Radio.catalog.tsx +38 -0
- package/starter-kit/components/Radio/Radio.tsx +114 -0
- package/starter-kit/components/Radio/index.ts +2 -0
- package/starter-kit/components/Radio/usePressScale.ts +33 -0
- package/starter-kit/components/SearchField/SearchField.catalog.tsx +28 -0
- package/starter-kit/components/SearchField/SearchField.tsx +98 -0
- package/starter-kit/components/SearchField/index.ts +2 -0
- package/starter-kit/components/SectionHeader/SectionHeader.catalog.tsx +72 -0
- package/starter-kit/components/SectionHeader/SectionHeader.tsx +139 -0
- package/starter-kit/components/SectionHeader/index.ts +2 -0
- package/starter-kit/components/SegmentedToggle/SegmentedToggle.catalog.tsx +31 -0
- package/starter-kit/components/SegmentedToggle/SegmentedToggle.tsx +163 -0
- package/starter-kit/components/SegmentedToggle/index.ts +2 -0
- package/starter-kit/components/SegmentedToggle/useSlideAnim.ts +29 -0
- package/starter-kit/components/Shimmer/Breathing.catalog.tsx +29 -0
- package/starter-kit/components/Shimmer/Shimmer.catalog.tsx +89 -0
- package/starter-kit/components/Shimmer/Shimmer.tsx +158 -0
- package/starter-kit/components/Shimmer/Shimmer.types.ts +27 -0
- package/starter-kit/components/Shimmer/SkeletonGroup.catalog.tsx +39 -0
- package/starter-kit/components/Shimmer/index.ts +2 -0
- package/starter-kit/components/Surface/Surface.catalog.tsx +44 -0
- package/starter-kit/components/Surface/Surface.tsx +45 -0
- package/starter-kit/components/Surface/index.ts +2 -0
- package/starter-kit/components/Switch/Switch.catalog.tsx +33 -0
- package/starter-kit/components/Switch/Switch.tsx +129 -0
- package/starter-kit/components/Switch/index.ts +2 -0
- package/starter-kit/components/TextArea/TextArea.catalog.tsx +29 -0
- package/starter-kit/components/TextArea/TextArea.tsx +99 -0
- package/starter-kit/components/TextArea/index.ts +2 -0
- package/starter-kit/components/Toast/Toast.catalog.tsx +61 -0
- package/starter-kit/components/Toast/Toast.tsx +144 -0
- package/starter-kit/components/Toast/index.ts +2 -0
- package/starter-kit/components/Tooltip/Tooltip.catalog.tsx +121 -0
- package/starter-kit/components/Tooltip/Tooltip.tsx +195 -0
- package/starter-kit/components/Tooltip/index.ts +2 -0
- package/starter-kit/components/TopNav/TopNav.catalog.tsx +71 -0
- package/starter-kit/components/TopNav/TopNav.tsx +77 -0
- package/starter-kit/components/TopNav/index.ts +2 -0
- package/starter-kit/components/UnderlineTabs/UnderlineTabs.catalog.tsx +30 -0
- package/starter-kit/components/UnderlineTabs/UnderlineTabs.tsx +156 -0
- package/starter-kit/components/UnderlineTabs/index.ts +2 -0
- package/starter-kit/components/index.ts +36 -0
- package/starter-kit/components/statusColors.ts +27 -0
- package/starter-kit/icons/Icon.native.tsx +90 -0
- package/starter-kit/icons/index.ts +10 -0
- package/starter-kit/icons/paths.ts +623 -0
- package/starter-kit/icons/types.ts +69 -0
- package/starter-kit/pages/Colors.catalog.tsx +55 -0
- package/starter-kit/pages/Font.catalog.tsx +45 -0
- package/starter-kit/pages/Icons.catalog.tsx +51 -0
- package/starter-kit/pages/Manifest.catalog.tsx +24 -0
- package/starter-kit/pages/Motion.catalog.tsx +101 -0
- package/starter-kit/pages/Radius.catalog.tsx +35 -0
- package/starter-kit/pages/ReportIssue.catalog.tsx +148 -0
- package/starter-kit/pages/SavedTrips.catalog.tsx +247 -0
- package/starter-kit/pages/Shadow.catalog.tsx +34 -0
- package/starter-kit/pages/Spacing.catalog.tsx +12 -0
- package/starter-kit/pages/TripPlannerForm.catalog.tsx +48 -0
- package/starter-kit/pages/Typography.catalog.tsx +17 -0
- package/starter-kit/tokens/index.ts +50 -0
- package/starter-kit/tokens/motion.ts +90 -0
- package/starter-kit/tokens/palette.ts +111 -0
- package/starter-kit/tokens/scales.ts +106 -0
- package/starter-kit/tokens/semantic.ts +98 -0
- package/starter-kit/tokens/shadow.ts +36 -0
- package/starter-kit/tokens/typography.ts +72 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-neutral icon identity + render props.
|
|
3
|
+
*
|
|
4
|
+
* No react-native / DOM imports here on purpose — this module is shared by `Icon.native.tsx` and
|
|
5
|
+
* the data map in `paths.ts`, and stays ready for a future non-native renderer to import too.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** All icons from the Metro NYC Figma design system. */
|
|
9
|
+
export type IconName =
|
|
10
|
+
| 'home'
|
|
11
|
+
| 'briefcase'
|
|
12
|
+
| 'add'
|
|
13
|
+
| 'pencil'
|
|
14
|
+
| 'flag'
|
|
15
|
+
| 'menu'
|
|
16
|
+
| 'pin'
|
|
17
|
+
| 'pin-filled'
|
|
18
|
+
| 'pin-hollow'
|
|
19
|
+
| 'walk'
|
|
20
|
+
| 'clear'
|
|
21
|
+
| 'locate-fixed'
|
|
22
|
+
| 'chevron-down'
|
|
23
|
+
| 'chevron-up'
|
|
24
|
+
| 'chevron-left'
|
|
25
|
+
| 'chevron-right'
|
|
26
|
+
| 'move-up-right'
|
|
27
|
+
| 'footprints'
|
|
28
|
+
| 'paperclip'
|
|
29
|
+
| 'map'
|
|
30
|
+
| 'info'
|
|
31
|
+
| 'bug'
|
|
32
|
+
| 'bell'
|
|
33
|
+
| 'bell-plus'
|
|
34
|
+
| 'search'
|
|
35
|
+
| 'clock'
|
|
36
|
+
| 'skip-forward'
|
|
37
|
+
| 'circle-slash'
|
|
38
|
+
| 'circle-plus'
|
|
39
|
+
| 'circle-check'
|
|
40
|
+
| 'circle-x'
|
|
41
|
+
| 'alert-circle'
|
|
42
|
+
| 'info-circle'
|
|
43
|
+
| 'check'
|
|
44
|
+
| 'thumb-up'
|
|
45
|
+
| 'thumb-down'
|
|
46
|
+
| 'users'
|
|
47
|
+
// Clock face (hour hand at 8) — used by the arrive-by row
|
|
48
|
+
| 'clock-8'
|
|
49
|
+
| 'triangle-alert'
|
|
50
|
+
| 'door-open'
|
|
51
|
+
| 'navigation'
|
|
52
|
+
| 'waypoints'
|
|
53
|
+
// Transit mode icons
|
|
54
|
+
| 'subway'
|
|
55
|
+
| 'train'
|
|
56
|
+
| 'ferry';
|
|
57
|
+
|
|
58
|
+
/** Shared props consumed by both the native and web `Icon` renderers. */
|
|
59
|
+
export interface IconRenderProps {
|
|
60
|
+
name: IconName;
|
|
61
|
+
/** Render size in points/pixels. SVG scales without artifacts (default 16). */
|
|
62
|
+
size?: number;
|
|
63
|
+
/** Override fill/stroke color. Defaults to the design-system regular text color. */
|
|
64
|
+
color?: string;
|
|
65
|
+
/** Override a stroke-mode icon's line weight. Defaults to the icon's own def (usually 2) — bump
|
|
66
|
+
* it for a bolder look (e.g. Checkbox's checkmark) without needing a second icon definition.
|
|
67
|
+
* Ignored for fill-mode icons, which have no stroke to widen. */
|
|
68
|
+
strokeWidth?: number;
|
|
69
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, DividedStack, VariantGroup, Swatch } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { DS_PALETTE, DS_SEMANTIC, colorValueLabel, type PaletteName } from '../tokens';
|
|
5
|
+
|
|
6
|
+
const styles = StyleSheet.create({
|
|
7
|
+
row: { flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', gap: 12 },
|
|
8
|
+
previewValue: { fontSize: 13, color: DS_SEMANTIC.text.muted },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const SEMANTIC_GROUPS: { name: string; desc: string; entries: [string, string][] }[] = [
|
|
12
|
+
{ name: 'Semantic · surface', desc: 'page and card backgrounds', entries: Object.entries(DS_SEMANTIC.surface) },
|
|
13
|
+
{ name: 'Semantic · text', desc: 'text colors', entries: Object.entries(DS_SEMANTIC.text) },
|
|
14
|
+
{ name: 'Semantic · border', desc: 'dividers and outlines', entries: Object.entries(DS_SEMANTIC.border) },
|
|
15
|
+
{ name: 'Semantic · interaction', desc: 'pressed/hover/focus overlays', entries: Object.entries(DS_SEMANTIC.interaction).filter(([name]) => name !== 'disabledOpacity') as [string, string][] },
|
|
16
|
+
{ name: 'Semantic · element', desc: 'dividers and backdrops', entries: Object.entries(DS_SEMANTIC.element) },
|
|
17
|
+
{ name: 'Semantic · emphasis', desc: 'saturated status accents', entries: Object.entries(DS_SEMANTIC.emphasis) },
|
|
18
|
+
{ name: 'Semantic · shade', desc: 'tinted status fills', entries: Object.entries(DS_SEMANTIC.shade) },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function ColorsGallery() {
|
|
22
|
+
const paletteHues = Object.keys(DS_PALETTE) as PaletteName[];
|
|
23
|
+
return (
|
|
24
|
+
<DividedStack>
|
|
25
|
+
{SEMANTIC_GROUPS.map((group) => (
|
|
26
|
+
<VariantGroup key={group.name} name={group.name} desc={group.desc} align="left">
|
|
27
|
+
<View style={styles.row}>
|
|
28
|
+
{group.entries.map(([name, value]) => (
|
|
29
|
+
<Swatch key={name} name={name} value={value} valueLabel={colorValueLabel(value)} width={100} />
|
|
30
|
+
))}
|
|
31
|
+
</View>
|
|
32
|
+
</VariantGroup>
|
|
33
|
+
))}
|
|
34
|
+
<VariantGroup name="interaction.disabledOpacity" desc="not a color — opacity applied to a whole disabled tappable element" align="left">
|
|
35
|
+
<Text style={styles.previewValue}>{DS_SEMANTIC.interaction.disabledOpacity}</Text>
|
|
36
|
+
</VariantGroup>
|
|
37
|
+
{paletteHues.map((hue) => (
|
|
38
|
+
<VariantGroup key={hue} name={`Palette · ${hue}`} desc="raw ramp, 0 → 800" align="left">
|
|
39
|
+
<View style={styles.row}>
|
|
40
|
+
{Object.entries(DS_PALETTE[hue]).map(([step, value]) => (
|
|
41
|
+
<Swatch key={step} name={`${hue}.${step}`} value={value} />
|
|
42
|
+
))}
|
|
43
|
+
</View>
|
|
44
|
+
</VariantGroup>
|
|
45
|
+
))}
|
|
46
|
+
</DividedStack>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default defineCatalogPage({
|
|
51
|
+
group: 'Tokens',
|
|
52
|
+
description: 'The color tokens the whole system is built from — saturated semantic accents and the raw grey ramp, each shown at its real value.',
|
|
53
|
+
tokenGallery: true,
|
|
54
|
+
render: () => <ColorsGallery />,
|
|
55
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, DividedStack, VariantGroup, TypeScaleGallery } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { DS_FONT_WEIGHT, DS_FONT_WEIGHT_USE, DS_SEMANTIC, DS_SPACING, DS_TYPOGRAPHY, type FontWeightName } from '../tokens';
|
|
5
|
+
|
|
6
|
+
const styles = StyleSheet.create({
|
|
7
|
+
previewRow: { flexDirection: 'row', alignItems: 'center', gap: DS_SPACING[600] },
|
|
8
|
+
previewLabel: { minWidth: 64, ...DS_TYPOGRAPHY.labelSm, color: DS_SEMANTIC.text.regular },
|
|
9
|
+
previewValue: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted },
|
|
10
|
+
// Font page — the "is a typeface actually set" fact needs to read at a glance, not be buried in
|
|
11
|
+
// the explanatory paragraph below it.
|
|
12
|
+
typefaceStatus: { ...DS_TYPOGRAPHY.labelSm, color: DS_SEMANTIC.text.regular },
|
|
13
|
+
typefaceNote: { marginTop: DS_SPACING[400] },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export default defineCatalogPage({
|
|
17
|
+
group: 'Tokens',
|
|
18
|
+
description: 'The typeface in use, and the font-weight scale on its own — most components get their weight via a DS_TYPOGRAPHY token\'s embedded fontWeight rather than DS_FONT_WEIGHT directly.',
|
|
19
|
+
tokenGallery: true,
|
|
20
|
+
render: () => (
|
|
21
|
+
<DividedStack>
|
|
22
|
+
<VariantGroup name="Typeface" desc="which font family renders every token on this page" align="left">
|
|
23
|
+
<View style={styles.previewRow}>
|
|
24
|
+
<Text style={styles.previewLabel}>Font family</Text>
|
|
25
|
+
<Text style={styles.typefaceStatus}>None set — falls back to the OS default</Text>
|
|
26
|
+
</View>
|
|
27
|
+
<Text style={[styles.previewValue, styles.typefaceNote]}>
|
|
28
|
+
No custom typeface is loaded anywhere in this template — every DS_TYPOGRAPHY / DS_FONT_WEIGHT
|
|
29
|
+
token renders in the OS default system font: San Francisco on iOS, Roboto on Android, the
|
|
30
|
+
browser's system-ui stack on web (which is what you're seeing on this page right now). To
|
|
31
|
+
use a custom font instead, load it (e.g. via expo-font's useFonts) and add a fontFamily
|
|
32
|
+
field to each DS_TYPOGRAPHY token — none currently set one.
|
|
33
|
+
</Text>
|
|
34
|
+
</VariantGroup>
|
|
35
|
+
<VariantGroup name="Weight" desc="DS_FONT_WEIGHT, in isolation" align="left">
|
|
36
|
+
<TypeScaleGallery
|
|
37
|
+
steps={Object.keys(DS_FONT_WEIGHT) as FontWeightName[]}
|
|
38
|
+
sampleStyle={(name) => ({ fontSize: 16, fontWeight: DS_FONT_WEIGHT[name] })}
|
|
39
|
+
meta={(name) => DS_FONT_WEIGHT[name]}
|
|
40
|
+
useNotes={DS_FONT_WEIGHT_USE}
|
|
41
|
+
/>
|
|
42
|
+
</VariantGroup>
|
|
43
|
+
</DividedStack>
|
|
44
|
+
),
|
|
45
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, DividedStack, VariantGroup } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { Icon } from '../icons/Icon.native';
|
|
5
|
+
import { ICON_PATHS, type IconName } from '../icons';
|
|
6
|
+
import { DS_ICON_SIZE, DS_ICON_SIZE_STEPS, DS_SEMANTIC, DS_SPACING, DS_TYPOGRAPHY } from '../tokens';
|
|
7
|
+
|
|
8
|
+
const styles = StyleSheet.create({
|
|
9
|
+
// Icons gallery — a size-comparison row, then a grid of every icon at a fixed size.
|
|
10
|
+
iconSizeRow: { flexDirection: 'row', flexWrap: 'wrap', alignItems: 'flex-end', gap: DS_SPACING[1200] },
|
|
11
|
+
iconSizeItem: { alignItems: 'center', gap: DS_SPACING[200] },
|
|
12
|
+
iconSizeLabel: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted },
|
|
13
|
+
iconGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: DS_SPACING[800] },
|
|
14
|
+
iconGridItem: { width: 72, alignItems: 'center', gap: DS_SPACING[200] },
|
|
15
|
+
iconGridLabel: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted, textAlign: 'center' },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function IconsGallery() {
|
|
19
|
+
const names = Object.keys(ICON_PATHS) as IconName[];
|
|
20
|
+
return (
|
|
21
|
+
<DividedStack>
|
|
22
|
+
<VariantGroup name="Sizes" desc="pair an icon with the text scale beside it" align="left">
|
|
23
|
+
<View style={styles.iconSizeRow}>
|
|
24
|
+
{DS_ICON_SIZE_STEPS.map((step) => (
|
|
25
|
+
<View key={step} style={styles.iconSizeItem}>
|
|
26
|
+
<Icon name="home" size={DS_ICON_SIZE[step]} />
|
|
27
|
+
<Text style={styles.iconSizeLabel}>{step} · {DS_ICON_SIZE[step]}px</Text>
|
|
28
|
+
</View>
|
|
29
|
+
))}
|
|
30
|
+
</View>
|
|
31
|
+
</VariantGroup>
|
|
32
|
+
<VariantGroup name={`All icons (${names.length})`} desc="rendered at 24px" align="left">
|
|
33
|
+
<View style={styles.iconGrid}>
|
|
34
|
+
{names.map((name) => (
|
|
35
|
+
<View key={name} style={styles.iconGridItem}>
|
|
36
|
+
<Icon name={name} size={DS_ICON_SIZE.lg} />
|
|
37
|
+
<Text style={styles.iconGridLabel} numberOfLines={1}>{name}</Text>
|
|
38
|
+
</View>
|
|
39
|
+
))}
|
|
40
|
+
</View>
|
|
41
|
+
</VariantGroup>
|
|
42
|
+
</DividedStack>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default defineCatalogPage({
|
|
47
|
+
group: 'Tokens',
|
|
48
|
+
description: 'Every icon available to the Icon component, plus the DS_ICON_SIZE steps it can be rendered at — pair an icon\'s size with the text scale beside it.',
|
|
49
|
+
tokenGallery: true,
|
|
50
|
+
render: () => <IconsGallery />,
|
|
51
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { components, tokens } from '@krapwoo/ds-viewer/generated';
|
|
5
|
+
|
|
6
|
+
const styles = StyleSheet.create({
|
|
7
|
+
box: { maxHeight: 480, overflow: 'hidden' },
|
|
8
|
+
text: { fontSize: 12, fontFamily: 'Menlo', color: '#181818' },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
function ManifestView() {
|
|
12
|
+
return (
|
|
13
|
+
<ScrollView style={styles.box}>
|
|
14
|
+
<Text selectable style={styles.text}>{JSON.stringify({ components, tokens }, null, 2)}</Text>
|
|
15
|
+
</ScrollView>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default defineCatalogPage({
|
|
20
|
+
group: 'Reference',
|
|
21
|
+
description: 'Every component\'s generated props, and every token module\'s generated values, as plain JSON — built live by `sync` from source, so it can\'t drift out of sync. Meant for tooling, not humans: feed it to an LLM (or a lint script) as ground truth for a component\'s real prop interface, instead of it guessing from source. Does not include per-page variants/states — those live in each page\'s own file; see the page\'s own Source link instead.',
|
|
22
|
+
tokenGallery: true,
|
|
23
|
+
render: () => <ManifestView />,
|
|
24
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, DividedStack, VariantGroup, TokenRow } from '@krapwoo/ds-viewer';
|
|
4
|
+
import {
|
|
5
|
+
DS_MOTION_DURATION,
|
|
6
|
+
DS_MOTION_DURATION_STEPS,
|
|
7
|
+
DS_MOTION_DURATION_USE,
|
|
8
|
+
DS_MOTION_EASING,
|
|
9
|
+
DS_MOTION_EASING_STEPS,
|
|
10
|
+
DS_MOTION_EASING_USE,
|
|
11
|
+
DS_MOTION_SPRING,
|
|
12
|
+
DS_MOTION_SPRING_USE,
|
|
13
|
+
DS_MOTION_LOOP_DURATION,
|
|
14
|
+
DS_MOTION_LOOP_DURATION_STEPS,
|
|
15
|
+
DS_MOTION_LOOP_DURATION_USE,
|
|
16
|
+
DS_RADIUS,
|
|
17
|
+
DS_SEMANTIC,
|
|
18
|
+
DS_SPACING,
|
|
19
|
+
DS_TYPOGRAPHY,
|
|
20
|
+
} from '../tokens';
|
|
21
|
+
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
tokenStack: { gap: DS_SPACING[800] },
|
|
24
|
+
previewRow: { flexDirection: 'row', alignItems: 'center', gap: DS_SPACING[600] },
|
|
25
|
+
previewLabel: { minWidth: 64, ...DS_TYPOGRAPHY.labelSm, color: DS_SEMANTIC.text.regular },
|
|
26
|
+
previewValue: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted },
|
|
27
|
+
durationBar: { height: 12, borderRadius: DS_RADIUS.small, backgroundColor: DS_SEMANTIC.emphasis.info },
|
|
28
|
+
// Spring config — a wrapping grid of key/value fields, since DS_MOTION_SPRING has 6 fields, too
|
|
29
|
+
// many to lay out as a single previewRow.
|
|
30
|
+
springGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: DS_SPACING[600] },
|
|
31
|
+
springField: { width: 168, gap: DS_SPACING[100] },
|
|
32
|
+
easingValue: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted, fontFamily: 'Menlo' },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function MotionGallery() {
|
|
36
|
+
const maxDuration = Math.max(...Object.values(DS_MOTION_DURATION));
|
|
37
|
+
const maxLoopDuration = Math.max(...Object.values(DS_MOTION_LOOP_DURATION));
|
|
38
|
+
return (
|
|
39
|
+
<DividedStack>
|
|
40
|
+
<VariantGroup name="Duration" desc="one-shot transitions — reach for base (240ms) first" align="left">
|
|
41
|
+
<View style={styles.tokenStack}>
|
|
42
|
+
{DS_MOTION_DURATION_STEPS.map((step, i) => (
|
|
43
|
+
<TokenRow key={step} use={DS_MOTION_DURATION_USE[step]} last={i === DS_MOTION_DURATION_STEPS.length - 1}>
|
|
44
|
+
<View style={styles.previewRow}>
|
|
45
|
+
<Text style={styles.previewLabel}>{step}</Text>
|
|
46
|
+
<View style={[styles.durationBar, { width: (DS_MOTION_DURATION[step] / maxDuration) * 120 }]} />
|
|
47
|
+
<Text style={styles.previewValue}>{DS_MOTION_DURATION[step]}ms</Text>
|
|
48
|
+
</View>
|
|
49
|
+
</TokenRow>
|
|
50
|
+
))}
|
|
51
|
+
</View>
|
|
52
|
+
</VariantGroup>
|
|
53
|
+
<VariantGroup name="Loop duration" desc="continuous, indeterminate loops — a spinner or a shimmer pulse, not a one-shot transition" align="left">
|
|
54
|
+
<View style={styles.tokenStack}>
|
|
55
|
+
{DS_MOTION_LOOP_DURATION_STEPS.map((step, i) => (
|
|
56
|
+
<TokenRow key={step} use={DS_MOTION_LOOP_DURATION_USE[step]} last={i === DS_MOTION_LOOP_DURATION_STEPS.length - 1}>
|
|
57
|
+
<View style={styles.previewRow}>
|
|
58
|
+
<Text style={styles.previewLabel}>{step}</Text>
|
|
59
|
+
<View style={[styles.durationBar, { width: (DS_MOTION_LOOP_DURATION[step] / maxLoopDuration) * 120 }]} />
|
|
60
|
+
<Text style={styles.previewValue}>{DS_MOTION_LOOP_DURATION[step]}ms</Text>
|
|
61
|
+
</View>
|
|
62
|
+
</TokenRow>
|
|
63
|
+
))}
|
|
64
|
+
</View>
|
|
65
|
+
</VariantGroup>
|
|
66
|
+
<VariantGroup name="Easing" desc="cubic-bezier curves — convert with Easing.bezier(...DS_MOTION_EASING.x)" align="left">
|
|
67
|
+
<View style={styles.tokenStack}>
|
|
68
|
+
{DS_MOTION_EASING_STEPS.map((step, i) => (
|
|
69
|
+
<TokenRow key={step} use={DS_MOTION_EASING_USE[step]} last={i === DS_MOTION_EASING_STEPS.length - 1}>
|
|
70
|
+
<View style={styles.previewRow}>
|
|
71
|
+
<Text style={styles.previewLabel}>{step}</Text>
|
|
72
|
+
<Text style={styles.easingValue}>cubic-bezier({DS_MOTION_EASING[step].join(', ')})</Text>
|
|
73
|
+
</View>
|
|
74
|
+
</TokenRow>
|
|
75
|
+
))}
|
|
76
|
+
</View>
|
|
77
|
+
</VariantGroup>
|
|
78
|
+
<VariantGroup name="Spring" desc="for Animated.spring(value, DS_MOTION_SPRING) — snap-point transitions, not fixed-duration timing">
|
|
79
|
+
<View style={styles.tokenStack}>
|
|
80
|
+
<TokenRow use={DS_MOTION_SPRING_USE} last>
|
|
81
|
+
<View style={styles.springGrid}>
|
|
82
|
+
{(Object.entries(DS_MOTION_SPRING) as [string, number | boolean][]).map(([key, value]) => (
|
|
83
|
+
<View key={key} style={styles.springField}>
|
|
84
|
+
<Text style={styles.previewLabel}>{key}</Text>
|
|
85
|
+
<Text style={styles.previewValue}>{String(value)}</Text>
|
|
86
|
+
</View>
|
|
87
|
+
))}
|
|
88
|
+
</View>
|
|
89
|
+
</TokenRow>
|
|
90
|
+
</View>
|
|
91
|
+
</VariantGroup>
|
|
92
|
+
</DividedStack>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default defineCatalogPage({
|
|
97
|
+
group: 'Tokens',
|
|
98
|
+
description: 'Duration (one-shot transitions), loop duration (continuous, indeterminate loops like Loading/Shimmer), easing, and spring tokens for transitions/animations. Easings are cubic-bezier tuples, not Easing objects, so the token file stays free of any react-native import. The spring config is grounded in the metro-native app this template was extracted from, where the same values drive its BottomSheet\'s snap-point transitions.',
|
|
99
|
+
tokenGallery: true,
|
|
100
|
+
render: () => <MotionGallery />,
|
|
101
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, TokenRow } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { DS_RADIUS, DS_RADIUS_STEPS, DS_RADIUS_USE, DS_SEMANTIC, DS_SPACING, DS_TYPOGRAPHY } from '../tokens';
|
|
5
|
+
|
|
6
|
+
const styles = StyleSheet.create({
|
|
7
|
+
tokenStack: { gap: DS_SPACING[800] },
|
|
8
|
+
previewRow: { flexDirection: 'row', alignItems: 'center', gap: DS_SPACING[600] },
|
|
9
|
+
previewLabel: { minWidth: 64, ...DS_TYPOGRAPHY.labelSm, color: DS_SEMANTIC.text.regular },
|
|
10
|
+
previewValue: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted },
|
|
11
|
+
radiusBox: { width: 40, height: 40, backgroundColor: DS_SEMANTIC.emphasis.info, borderWidth: StyleSheet.hairlineWidth, borderColor: DS_SEMANTIC.border.light },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function RadiusGallery() {
|
|
15
|
+
return (
|
|
16
|
+
<View style={styles.tokenStack}>
|
|
17
|
+
{DS_RADIUS_STEPS.map((step, i) => (
|
|
18
|
+
<TokenRow key={step} use={DS_RADIUS_USE[step]} last={i === DS_RADIUS_STEPS.length - 1}>
|
|
19
|
+
<View style={styles.previewRow}>
|
|
20
|
+
<Text style={styles.previewLabel}>{step}</Text>
|
|
21
|
+
<View style={[styles.radiusBox, { borderRadius: DS_RADIUS[step] }]} />
|
|
22
|
+
<Text style={styles.previewValue}>{DS_RADIUS[step]}px</Text>
|
|
23
|
+
</View>
|
|
24
|
+
</TokenRow>
|
|
25
|
+
))}
|
|
26
|
+
</View>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default defineCatalogPage({
|
|
31
|
+
group: 'Tokens',
|
|
32
|
+
description: 'The corner-radius scale, each step with a grounded "when to use this" note from DS_RADIUS_USE. Reference steps directly — DS_RADIUS.medium, never a raw 12 — the key union is typo-safe by itself.',
|
|
33
|
+
tokenGallery: true,
|
|
34
|
+
render: () => <RadiusGallery />,
|
|
35
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, PhoneFrame } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { Banner } from '../components/Banner';
|
|
5
|
+
import { BottomSheet } from '../components/BottomSheet';
|
|
6
|
+
import { Button } from '../components/Button';
|
|
7
|
+
import { ButtonGroup } from '../components/ButtonGroup';
|
|
8
|
+
import { Checkbox } from '../components/Checkbox';
|
|
9
|
+
import { Dialog } from '../components/Dialog';
|
|
10
|
+
import { Dock } from '../components/Dock';
|
|
11
|
+
import { PillRow, type PillRowItem } from '../components/PillRow';
|
|
12
|
+
import { ProgressDots } from '../components/ProgressDots';
|
|
13
|
+
import { Radio } from '../components/Radio';
|
|
14
|
+
import { Shimmer, SkeletonGroup } from '../components/Shimmer';
|
|
15
|
+
import { Switch } from '../components/Switch';
|
|
16
|
+
import { TextArea } from '../components/TextArea';
|
|
17
|
+
import { TopNav } from '../components/TopNav';
|
|
18
|
+
import { DS_SEMANTIC, DS_SPACING, DS_TYPOGRAPHY } from '../tokens';
|
|
19
|
+
|
|
20
|
+
const styles = StyleSheet.create({
|
|
21
|
+
cardTitle: { ...DS_TYPOGRAPHY.labelMd, color: DS_SEMANTIC.text.regular },
|
|
22
|
+
cardBody: { ...DS_TYPOGRAPHY.bodySm, color: DS_SEMANTIC.text.muted, marginTop: DS_SPACING[200] },
|
|
23
|
+
shimmerLines: { flex: 1, gap: DS_SPACING[400] },
|
|
24
|
+
// Report-an-issue recipe — the BottomSheet's own scrollable content area, so no separate
|
|
25
|
+
// ScrollView is needed here the way SavedTrips' own scroll needs one (BottomSheet already
|
|
26
|
+
// scrolls its `children` past a height cap).
|
|
27
|
+
reportContent: { gap: DS_SPACING[600] },
|
|
28
|
+
reportSwitchRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
|
|
29
|
+
// External field label for controls that have no label prop of their own (PillRow) — controls
|
|
30
|
+
// that DO own a label (Dropdown, InputField) use theirs instead of this.
|
|
31
|
+
recipeFieldLabel: { ...DS_TYPOGRAPHY.labelXs, color: DS_SEMANTIC.text.muted, marginBottom: DS_SPACING[200] },
|
|
32
|
+
radioGroup: { gap: DS_SPACING[600], alignItems: 'flex-start' },
|
|
33
|
+
dialogActions: { marginTop: DS_SPACING[800] },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Report-issue recipe's line picker — a PillRow, not a Dropdown, specifically because this lives
|
|
37
|
+
// inside a BottomSheet: Dropdown opens its own BottomSheet, and stacking two sheets traps the user
|
|
38
|
+
// between backdrops (see BottomSheet's `useInsideBottomSheetWarning`).
|
|
39
|
+
const REPORT_LINE_PILLS: PillRowItem[] = [
|
|
40
|
+
{ id: '4', label: '4 Train' },
|
|
41
|
+
{ id: '6', label: '6 Train' },
|
|
42
|
+
{ id: 'q', label: 'Q Train' },
|
|
43
|
+
];
|
|
44
|
+
// Issue-type category — a PillRow (not SegmentedToggle): this is a data choice being filled into
|
|
45
|
+
// the report, not a view/mode switch. SegmentedToggle is for "which mode is this screen in right
|
|
46
|
+
// now" (e.g. Map vs List); a form's own answer belongs on Pill/Radio instead.
|
|
47
|
+
const REPORT_ISSUE_TYPE_PILLS: PillRowItem[] = [
|
|
48
|
+
{ id: 'delay', label: 'Delay' },
|
|
49
|
+
{ id: 'crowding', label: 'Crowding' },
|
|
50
|
+
{ id: 'safety', label: 'Safety' },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function ReportIssueDemo() {
|
|
54
|
+
const [visible, setVisible] = useState(false);
|
|
55
|
+
const [confirmDiscard, setConfirmDiscard] = useState(false);
|
|
56
|
+
const [issueType, setIssueType] = useState('delay');
|
|
57
|
+
const [line, setLine] = useState('4');
|
|
58
|
+
const [details, setDetails] = useState('');
|
|
59
|
+
const [urgent, setUrgent] = useState(false);
|
|
60
|
+
const [visibility, setVisibility] = useState('public');
|
|
61
|
+
const [notify, setNotify] = useState(true);
|
|
62
|
+
|
|
63
|
+
const requestClose = () => setConfirmDiscard(true);
|
|
64
|
+
const discard = () => {
|
|
65
|
+
setConfirmDiscard(false);
|
|
66
|
+
setVisible(false);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<PhoneFrame>
|
|
71
|
+
{!visible && <Button label="Report an issue" onPress={() => setVisible(true)} />}
|
|
72
|
+
<BottomSheet
|
|
73
|
+
visible={visible}
|
|
74
|
+
onDismiss={requestClose}
|
|
75
|
+
header={
|
|
76
|
+
<TopNav
|
|
77
|
+
title="Report an issue"
|
|
78
|
+
trailing={
|
|
79
|
+
<Button variant="secondary" size="small" showIcon showLabel={false} iconName="clear" accessibilityLabel="Close" onPress={requestClose} />
|
|
80
|
+
}
|
|
81
|
+
/>
|
|
82
|
+
}
|
|
83
|
+
footer={
|
|
84
|
+
<Dock>
|
|
85
|
+
<Button label="Submit report" onPress={() => setVisible(false)} />
|
|
86
|
+
</Dock>
|
|
87
|
+
}
|
|
88
|
+
>
|
|
89
|
+
<View style={styles.reportContent}>
|
|
90
|
+
<ProgressDots active={1} total={3} />
|
|
91
|
+
<Banner
|
|
92
|
+
variant="info"
|
|
93
|
+
title="Delays reported near 14 St"
|
|
94
|
+
description="Other riders flagged a signal problem in the last 10 minutes."
|
|
95
|
+
/>
|
|
96
|
+
<View>
|
|
97
|
+
<Text style={styles.recipeFieldLabel}>Issue type</Text>
|
|
98
|
+
<PillRow
|
|
99
|
+
pills={REPORT_ISSUE_TYPE_PILLS.map((p) => ({ ...p, variant: p.id === issueType ? 'selected' : 'not_selected', onPress: () => setIssueType(p.id) }))}
|
|
100
|
+
showAddPill={false}
|
|
101
|
+
/>
|
|
102
|
+
</View>
|
|
103
|
+
<View>
|
|
104
|
+
<Text style={styles.recipeFieldLabel}>Line</Text>
|
|
105
|
+
<PillRow
|
|
106
|
+
pills={REPORT_LINE_PILLS.map((p) => ({ ...p, variant: p.id === line ? 'selected' : 'not_selected', onPress: () => setLine(p.id) }))}
|
|
107
|
+
showAddPill={false}
|
|
108
|
+
/>
|
|
109
|
+
</View>
|
|
110
|
+
<TextArea value={details} onChangeText={setDetails} placeholder="Describe what happened" />
|
|
111
|
+
<Checkbox checked={urgent} onChange={setUrgent} label="This needs urgent attention" />
|
|
112
|
+
<View style={styles.radioGroup}>
|
|
113
|
+
<Radio selected={visibility === 'public'} onPress={() => setVisibility('public')} label="Share publicly" />
|
|
114
|
+
<Radio selected={visibility === 'anonymous'} onPress={() => setVisibility('anonymous')} label="Report anonymously" />
|
|
115
|
+
</View>
|
|
116
|
+
<Switch value={notify} onValueChange={setNotify} label="Notify me when resolved" style={styles.reportSwitchRow} />
|
|
117
|
+
<View>
|
|
118
|
+
<Text style={styles.cardTitle}>Recent reports near you</Text>
|
|
119
|
+
{/* Last line ~70% of the others' width — see ShimmerProps.variant's `'text'` doc.
|
|
120
|
+
SkeletonGroup so the two lines announce as one "Loading" region, not two. */}
|
|
121
|
+
<SkeletonGroup style={styles.shimmerLines}>
|
|
122
|
+
<Shimmer variant="text" width={200} />
|
|
123
|
+
<Shimmer variant="text" width={140} />
|
|
124
|
+
</SkeletonGroup>
|
|
125
|
+
</View>
|
|
126
|
+
</View>
|
|
127
|
+
</BottomSheet>
|
|
128
|
+
<Dialog visible={confirmDiscard} onDismiss={() => setConfirmDiscard(false)}>
|
|
129
|
+
<Text style={styles.cardTitle}>Discard this report?</Text>
|
|
130
|
+
<Text style={styles.cardBody}>Your answers won't be saved.</Text>
|
|
131
|
+
<View style={styles.dialogActions}>
|
|
132
|
+
<ButtonGroup>
|
|
133
|
+
<Button label="Keep editing" variant="tertiary" onPress={() => setConfirmDiscard(false)} />
|
|
134
|
+
<Button label="Discard" onPress={discard} />
|
|
135
|
+
</ButtonGroup>
|
|
136
|
+
</View>
|
|
137
|
+
</Dialog>
|
|
138
|
+
</PhoneFrame>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default defineCatalogPage({
|
|
143
|
+
group: 'Recipes',
|
|
144
|
+
description: 'A composed real screen — a "report an issue" flow opened from a BottomSheet, combining a step indicator, a service-alert banner, a Pill-based issue-type and line picker (a data choice, not a view switch, so Pill rather than SegmentedToggle), free-text and toggle inputs, and a loading skeleton for recent reports. Dismissing with unsaved changes opens a confirmation Dialog.',
|
|
145
|
+
tokenGallery: true,
|
|
146
|
+
fullWidthLabel: 'Preview',
|
|
147
|
+
render: () => <ReportIssueDemo />,
|
|
148
|
+
});
|