@krapwoo/ds-viewer 0.6.11 → 0.8.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 +62 -5
- package/dist/cli/dev.js +61 -14
- package/dist/cli/explain.js +13 -8
- package/dist/cli/layout.js +267 -0
- package/dist/cli/main.js +145 -2
- package/dist/cli/render.js +87 -3
- package/dist/native/catalog/comparison.js +37 -4
- package/native/catalog/SpacingScaleGallery.tsx +4 -4
- package/native/catalog/TokenRow.tsx +64 -14
- package/native/catalog/TypeScaleGallery.tsx +4 -4
- package/native/catalog/comparison.ts +38 -4
- package/native/catalog/index.ts +1 -1
- package/package.json +1 -1
- package/starter-kit/components/Tooltip/Tooltip.catalog.tsx +4 -1
- package/starter-kit/pages/Motion.catalog.tsx +151 -83
- package/starter-kit/pages/Radius.catalog.tsx +22 -18
- package/starter-kit/pages/Shadow.catalog.tsx +27 -15
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { View, Text, StyleSheet, Easing } from 'react-native';
|
|
3
|
-
import { defineCatalogPage,
|
|
3
|
+
import { defineCatalogPage, TokenRow, TokenRowGroup, MotionSpecimen } from '@krapwoo/ds-viewer';
|
|
4
4
|
import {
|
|
5
5
|
DS_MOTION_DURATION,
|
|
6
6
|
DS_MOTION_DURATION_STEPS,
|
|
@@ -26,98 +26,127 @@ const styles = StyleSheet.create({
|
|
|
26
26
|
previewLabel: { minWidth: 64, ...DS_TYPOGRAPHY.labelSm, color: DS_SEMANTIC.text.regular },
|
|
27
27
|
previewValue: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted },
|
|
28
28
|
durationBar: { height: 12, borderRadius: DS_RADIUS.small, backgroundColor: DS_SEMANTIC.emphasis.info },
|
|
29
|
-
// Spring config — a wrapping grid of key/value fields, since DS_MOTION_SPRING has 6 fields, too
|
|
30
|
-
// many to lay out as a single previewRow.
|
|
31
|
-
springGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: DS_SPACING[600] },
|
|
32
|
-
springField: { width: 168, gap: DS_SPACING[100] },
|
|
33
29
|
easingValue: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted, fontFamily: 'Menlo' },
|
|
30
|
+
|
|
31
|
+
// A spring is a config object, not a step on a scale: six named fields that only mean anything
|
|
32
|
+
// together. Laid out with the fields down the side and each spring token as its own column, so a
|
|
33
|
+
// second spring adds a column instead of repeating all six names, and `damping` reads across
|
|
34
|
+
// tokens on one line. The field vocabulary is fixed; the number of tokens is what grows.
|
|
35
|
+
springRow: {
|
|
36
|
+
flexDirection: 'row',
|
|
37
|
+
alignItems: 'flex-start',
|
|
38
|
+
gap: DS_SPACING[600],
|
|
39
|
+
paddingVertical: DS_SPACING[400],
|
|
40
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
41
|
+
borderColor: DS_SEMANTIC.border.light,
|
|
42
|
+
},
|
|
43
|
+
springRowLast: { borderBottomWidth: 0 },
|
|
44
|
+
// Wide enough that `restDisplacementThreshold` sits on one line instead of breaking mid-word,
|
|
45
|
+
// which is what the previous fixed 168px field did to it.
|
|
46
|
+
springField: { width: 210, ...DS_TYPOGRAPHY.labelSm, color: DS_SEMANTIC.text.regular },
|
|
47
|
+
springCell: { flex: 1, minWidth: 130 },
|
|
48
|
+
springValue: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.regular },
|
|
49
|
+
springNote: { ...DS_TYPOGRAPHY.bodyXs, color: DS_SEMANTIC.text.muted },
|
|
34
50
|
});
|
|
35
51
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
/** Every spring token on the page. One today — the shape is what lets a second arrive without
|
|
53
|
+
* redesigning the section. */
|
|
54
|
+
const SPRINGS: Record<string, typeof DS_MOTION_SPRING> = { snappy: DS_MOTION_SPRING };
|
|
55
|
+
const SPRING_NOTES: Record<string, string> = { snappy: DS_MOTION_SPRING_USE };
|
|
56
|
+
const SPRING_FIELDS = Object.keys(DS_MOTION_SPRING) as (keyof typeof DS_MOTION_SPRING)[];
|
|
57
|
+
|
|
58
|
+
function SpringTable() {
|
|
59
|
+
const names = Object.keys(SPRINGS);
|
|
60
|
+
const cells = (render: (name: string) => React.ReactNode) =>
|
|
61
|
+
names.map((name) => (
|
|
62
|
+
<View key={name} style={styles.springCell}>
|
|
63
|
+
{render(name)}
|
|
64
|
+
</View>
|
|
65
|
+
));
|
|
39
66
|
return (
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<View style={styles.previewRow}>
|
|
47
|
-
<Text style={styles.previewLabel}>{step}</Text>
|
|
48
|
-
<View style={[styles.durationBar, { width: (DS_MOTION_DURATION[step] / maxDuration) * 120 }]} />
|
|
49
|
-
<Text style={styles.previewValue}>{DS_MOTION_DURATION[step]}ms</Text>
|
|
50
|
-
</View>
|
|
51
|
-
<MotionSpecimen
|
|
52
|
-
kind="timing"
|
|
53
|
-
duration={DS_MOTION_DURATION[step]}
|
|
54
|
-
easing={Easing.bezier(...DS_MOTION_EASING.standard)}
|
|
55
|
-
label={`${DS_MOTION_DURATION[step]}ms · standard easing`}
|
|
56
|
-
/>
|
|
57
|
-
</View>
|
|
58
|
-
</TokenRow>
|
|
59
|
-
))}
|
|
67
|
+
<View>
|
|
68
|
+
{/* A single spring needs no column header — the section title is already its name. */}
|
|
69
|
+
{names.length > 1 ? (
|
|
70
|
+
<View style={styles.springRow}>
|
|
71
|
+
<Text style={styles.springField}>field</Text>
|
|
72
|
+
{cells((name) => <Text style={styles.previewLabel}>{name}</Text>)}
|
|
60
73
|
</View>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<View style={styles.
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
<View style={styles.rowContent}>
|
|
67
|
-
<View style={styles.previewRow}>
|
|
68
|
-
<Text style={styles.previewLabel}>{step}</Text>
|
|
69
|
-
<View style={[styles.durationBar, { width: (DS_MOTION_LOOP_DURATION[step] / maxLoopDuration) * 120 }]} />
|
|
70
|
-
<Text style={styles.previewValue}>{DS_MOTION_LOOP_DURATION[step]}ms</Text>
|
|
71
|
-
</View>
|
|
72
|
-
<MotionSpecimen
|
|
73
|
-
kind="timing"
|
|
74
|
-
duration={DS_MOTION_LOOP_DURATION[step]}
|
|
75
|
-
easing={Easing.bezier(...DS_MOTION_EASING.standard)}
|
|
76
|
-
label={`${DS_MOTION_LOOP_DURATION[step]}ms · one cycle`}
|
|
77
|
-
/>
|
|
78
|
-
</View>
|
|
79
|
-
</TokenRow>
|
|
80
|
-
))}
|
|
74
|
+
) : null}
|
|
75
|
+
{SPRING_FIELDS.map((field) => (
|
|
76
|
+
<View key={String(field)} style={styles.springRow}>
|
|
77
|
+
<Text style={styles.springField}>{String(field)}</Text>
|
|
78
|
+
{cells((name) => <Text style={styles.springValue}>{String(SPRINGS[name][field])}</Text>)}
|
|
81
79
|
</View>
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
))}
|
|
81
|
+
<View style={styles.springRow}>
|
|
82
|
+
<Text style={styles.springField}>preview</Text>
|
|
83
|
+
{cells((name) => (
|
|
84
|
+
<MotionSpecimen kind="spring" spring={SPRINGS[name]} label="Animated.spring(value, DS_MOTION_SPRING)" />
|
|
85
|
+
))}
|
|
86
|
+
</View>
|
|
87
|
+
<View style={[styles.springRow, styles.springRowLast]}>
|
|
88
|
+
<Text style={styles.springField}>when to use</Text>
|
|
89
|
+
{cells((name) => <Text style={styles.springNote}>{SPRING_NOTES[name]}</Text>)}
|
|
90
|
+
</View>
|
|
91
|
+
</View>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A millisecond scale: a bar for the relative length, then the real animation at that duration. */
|
|
96
|
+
function DurationRows({ steps, values, notes, label }: {
|
|
97
|
+
steps: readonly string[];
|
|
98
|
+
values: Record<string, number>;
|
|
99
|
+
notes: Record<string, string>;
|
|
100
|
+
label: (ms: number) => string;
|
|
101
|
+
}) {
|
|
102
|
+
const max = Math.max(...steps.map((step) => values[step]));
|
|
103
|
+
return (
|
|
104
|
+
<TokenRowGroup>
|
|
105
|
+
<View style={styles.tokenStack}>
|
|
106
|
+
{steps.map((step, i) => (
|
|
107
|
+
<TokenRow key={step} use={notes[step]} last={i === steps.length - 1} notePlacement="right">
|
|
108
|
+
<View style={styles.rowContent}>
|
|
109
|
+
<View style={styles.previewRow}>
|
|
110
|
+
<Text style={styles.previewLabel}>{step}</Text>
|
|
111
|
+
<View style={[styles.durationBar, { width: (values[step] / max) * 120 }]} />
|
|
112
|
+
<Text style={styles.previewValue}>{values[step]}ms</Text>
|
|
98
113
|
</View>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
<MotionSpecimen
|
|
115
|
+
kind="timing"
|
|
116
|
+
duration={values[step]}
|
|
117
|
+
easing={Easing.bezier(...DS_MOTION_EASING.standard)}
|
|
118
|
+
label={label(values[step])}
|
|
119
|
+
/>
|
|
120
|
+
</View>
|
|
121
|
+
</TokenRow>
|
|
122
|
+
))}
|
|
123
|
+
</View>
|
|
124
|
+
</TokenRowGroup>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function EasingRows() {
|
|
129
|
+
return (
|
|
130
|
+
<TokenRowGroup>
|
|
131
|
+
<View style={styles.tokenStack}>
|
|
132
|
+
{DS_MOTION_EASING_STEPS.map((step, i) => (
|
|
133
|
+
<TokenRow key={step} use={DS_MOTION_EASING_USE[step]} last={i === DS_MOTION_EASING_STEPS.length - 1} notePlacement="right">
|
|
106
134
|
<View style={styles.rowContent}>
|
|
107
|
-
<View style={styles.
|
|
108
|
-
{
|
|
109
|
-
|
|
110
|
-
<Text style={styles.previewLabel}>{key}</Text>
|
|
111
|
-
<Text style={styles.previewValue}>{String(value)}</Text>
|
|
112
|
-
</View>
|
|
113
|
-
))}
|
|
135
|
+
<View style={styles.previewRow}>
|
|
136
|
+
<Text style={styles.previewLabel}>{step}</Text>
|
|
137
|
+
<Text style={styles.easingValue}>cubic-bezier({DS_MOTION_EASING[step].join(', ')})</Text>
|
|
114
138
|
</View>
|
|
115
|
-
<MotionSpecimen
|
|
139
|
+
<MotionSpecimen
|
|
140
|
+
kind="timing"
|
|
141
|
+
duration={DS_MOTION_DURATION.base}
|
|
142
|
+
easing={Easing.bezier(...DS_MOTION_EASING[step])}
|
|
143
|
+
label={`${DS_MOTION_DURATION.base}ms · ${step}`}
|
|
144
|
+
/>
|
|
116
145
|
</View>
|
|
117
146
|
</TokenRow>
|
|
118
|
-
|
|
119
|
-
</
|
|
120
|
-
</
|
|
147
|
+
))}
|
|
148
|
+
</View>
|
|
149
|
+
</TokenRowGroup>
|
|
121
150
|
);
|
|
122
151
|
}
|
|
123
152
|
|
|
@@ -125,5 +154,44 @@ export default defineCatalogPage({
|
|
|
125
154
|
group: 'Tokens',
|
|
126
155
|
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.',
|
|
127
156
|
tokenGallery: true,
|
|
128
|
-
|
|
157
|
+
// A card per kind of token. Durations are a scale in milliseconds, easings are bezier curves and
|
|
158
|
+
// a spring is a config object: four different things that read as one long box when they share a
|
|
159
|
+
// single card. One column, because every section's rows want the full width.
|
|
160
|
+
tokenColumns: 1,
|
|
161
|
+
tokenSections: [
|
|
162
|
+
{
|
|
163
|
+
title: 'Duration',
|
|
164
|
+
desc: 'one-shot transitions — reach for base (240ms) first',
|
|
165
|
+
render: () => (
|
|
166
|
+
<DurationRows
|
|
167
|
+
steps={DS_MOTION_DURATION_STEPS}
|
|
168
|
+
values={DS_MOTION_DURATION}
|
|
169
|
+
notes={DS_MOTION_DURATION_USE}
|
|
170
|
+
label={(ms) => `${ms}ms · standard easing`}
|
|
171
|
+
/>
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
title: 'Loop duration',
|
|
176
|
+
desc: 'continuous, indeterminate loops — a spinner or a shimmer pulse, not a one-shot transition',
|
|
177
|
+
render: () => (
|
|
178
|
+
<DurationRows
|
|
179
|
+
steps={DS_MOTION_LOOP_DURATION_STEPS}
|
|
180
|
+
values={DS_MOTION_LOOP_DURATION}
|
|
181
|
+
notes={DS_MOTION_LOOP_DURATION_USE}
|
|
182
|
+
label={(ms) => `${ms}ms · one cycle`}
|
|
183
|
+
/>
|
|
184
|
+
),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
title: 'Easing',
|
|
188
|
+
desc: 'cubic-bezier curves — convert with Easing.bezier(...DS_MOTION_EASING.x)',
|
|
189
|
+
render: () => <EasingRows />,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
title: 'Spring',
|
|
193
|
+
desc: 'for Animated.spring(value, DS_MOTION_SPRING) — snap-point transitions, not fixed-duration timing',
|
|
194
|
+
render: () => <SpringTable />,
|
|
195
|
+
},
|
|
196
|
+
],
|
|
129
197
|
});
|
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View,
|
|
3
|
-
import { defineCatalogPage,
|
|
4
|
-
import { DS_RADIUS, DS_RADIUS_STEPS, DS_RADIUS_USE, DS_SEMANTIC
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, TokenGrid, TokenTile } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { DS_RADIUS, DS_RADIUS_STEPS, DS_RADIUS_USE, DS_SEMANTIC } from '../tokens';
|
|
5
5
|
|
|
6
6
|
const styles = StyleSheet.create({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
// Square, so the corner is read as a corner rather than as the end of a bar. Large enough that
|
|
8
|
+
// 4px and 8px are still telling apart at a glance.
|
|
9
|
+
radiusBox: {
|
|
10
|
+
width: 64,
|
|
11
|
+
height: 64,
|
|
12
|
+
backgroundColor: DS_SEMANTIC.emphasis.info,
|
|
13
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
14
|
+
borderColor: DS_SEMANTIC.border.light,
|
|
15
|
+
},
|
|
12
16
|
});
|
|
13
17
|
|
|
14
18
|
function RadiusGallery() {
|
|
15
19
|
return (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</
|
|
20
|
+
// Tiles, not rows: a radius preview is small and self-contained, so the steps belong side by
|
|
21
|
+
// side where `xs` can be compared against `large` directly — which is the only way to read a
|
|
22
|
+
// scale. A row each would put them 100px apart vertically and leave the right half of every
|
|
23
|
+
// row empty.
|
|
24
|
+
<TokenGrid minTileWidth={150}>
|
|
25
|
+
{DS_RADIUS_STEPS.map((step) => (
|
|
26
|
+
<TokenTile key={step} name={step} value={`${DS_RADIUS[step]}px`} note={DS_RADIUS_USE[step]}>
|
|
27
|
+
<View style={[styles.radiusBox, { borderRadius: DS_RADIUS[step] }]} />
|
|
28
|
+
</TokenTile>
|
|
25
29
|
))}
|
|
26
|
-
</
|
|
30
|
+
</TokenGrid>
|
|
27
31
|
);
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View,
|
|
3
|
-
import { defineCatalogPage,
|
|
4
|
-
import { DS_RADIUS, DS_SEMANTIC, DS_SHADOW, DS_SHADOW_USE, DS_SPACING,
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import { defineCatalogPage, TokenGrid, TokenTile } from '@krapwoo/ds-viewer';
|
|
4
|
+
import { DS_RADIUS, DS_SEMANTIC, DS_SHADOW, DS_SHADOW_USE, DS_SPACING, type ShadowToken } from '../tokens';
|
|
5
5
|
|
|
6
6
|
const styles = StyleSheet.create({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// A shadow is cast *onto* something. On the catalog's own white card the elevated surface is
|
|
8
|
+
// white on white and the token reads as an empty space, so each tile gives it a tinted backdrop
|
|
9
|
+
// to fall on — the same reason a product screen sits a card on a page colour, not on paper.
|
|
10
|
+
stage: {
|
|
11
|
+
backgroundColor: DS_SEMANTIC.surface.main,
|
|
12
|
+
borderRadius: DS_RADIUS.small,
|
|
13
|
+
paddingVertical: DS_SPACING[800],
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
},
|
|
16
|
+
surface: {
|
|
17
|
+
width: 76,
|
|
18
|
+
height: 48,
|
|
19
|
+
borderRadius: DS_RADIUS.medium,
|
|
20
|
+
backgroundColor: DS_SEMANTIC.surface.white,
|
|
21
|
+
},
|
|
11
22
|
});
|
|
12
23
|
|
|
13
24
|
function ShadowGallery() {
|
|
14
25
|
const steps = Object.keys(DS_SHADOW) as ShadowToken[];
|
|
15
26
|
return (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
// Tiles: an elevation preview is small and self-contained, and seeing `resting` beside
|
|
28
|
+
// `bottomSheet` is the only way to judge how much stronger one lift is than another.
|
|
29
|
+
<TokenGrid minTileWidth={170}>
|
|
30
|
+
{steps.map((step) => (
|
|
31
|
+
<TokenTile key={step} name={step} note={DS_SHADOW_USE[step]}>
|
|
32
|
+
<View style={styles.stage}>
|
|
33
|
+
<View style={[styles.surface, DS_SHADOW[step]]} />
|
|
22
34
|
</View>
|
|
23
|
-
</
|
|
35
|
+
</TokenTile>
|
|
24
36
|
))}
|
|
25
|
-
</
|
|
37
|
+
</TokenGrid>
|
|
26
38
|
);
|
|
27
39
|
}
|
|
28
40
|
|