@stdout-design/cli 0.5.0 → 0.7.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/dist/index.mjs +14 -24
- package/package.json +2 -2
- package/template/README.md +37 -0
- package/template/locales/ar.json +49 -0
- package/template/locales/en.json +49 -0
- package/template/templates/bento-feature.tsx +377 -32
- package/template/templates/launch-hero.tsx +362 -0
- package/template/templates/retro-poster.tsx +371 -0
- package/template/templates/trend-poster.tsx +383 -0
|
@@ -7,72 +7,417 @@ export const propsSchema = defineSchema(
|
|
|
7
7
|
z.object({
|
|
8
8
|
accentColor: z
|
|
9
9
|
.string()
|
|
10
|
-
.default("#
|
|
11
|
-
.describe("The primary accent color used for
|
|
10
|
+
.default("#ff5a36")
|
|
11
|
+
.describe("The primary accent color used for the solid tiles"),
|
|
12
12
|
background: z
|
|
13
13
|
.string()
|
|
14
|
-
.default("#
|
|
15
|
-
.describe("Background color
|
|
14
|
+
.default("#191714")
|
|
15
|
+
.describe("Background color behind the tiles"),
|
|
16
|
+
brand: z.string().default("tally").describe("Wordmark in the logo tile"),
|
|
17
|
+
chartLabel: z
|
|
18
|
+
.string()
|
|
19
|
+
.default("Spending by week")
|
|
20
|
+
.describe("Caption of the bar chart tile"),
|
|
16
21
|
description: z
|
|
17
22
|
.string()
|
|
18
|
-
.default("
|
|
23
|
+
.default("Every card, every bill, sorted the second it lands.")
|
|
19
24
|
.describe("Supporting description text"),
|
|
25
|
+
gaugeLabel: z
|
|
26
|
+
.string()
|
|
27
|
+
.default("Budget used")
|
|
28
|
+
.describe("Caption of the gauge tile"),
|
|
29
|
+
gaugePercent: z
|
|
30
|
+
.number()
|
|
31
|
+
.min(0)
|
|
32
|
+
.max(100)
|
|
33
|
+
.default(72)
|
|
34
|
+
.describe("How full the gauge is, 0–100"),
|
|
20
35
|
headline: z
|
|
21
36
|
.string()
|
|
22
37
|
.min(1)
|
|
23
|
-
.default("
|
|
38
|
+
.default("Know where every dollar goes")
|
|
24
39
|
.describe("Main headline for the feature card"),
|
|
25
40
|
locale: z
|
|
26
41
|
.string()
|
|
27
42
|
.optional()
|
|
28
43
|
.describe("Current locale code for RTL support"),
|
|
44
|
+
metricDelta: z
|
|
45
|
+
.string()
|
|
46
|
+
.default("+18%")
|
|
47
|
+
.describe("Change shown next to the metric"),
|
|
48
|
+
metricLabel: z
|
|
49
|
+
.string()
|
|
50
|
+
.default("Saved this month")
|
|
51
|
+
.describe("Caption of the big metric"),
|
|
52
|
+
metricValue: z.string().default("$1,240").describe("The big metric"),
|
|
53
|
+
rating: z.string().default("4.9").describe("Rating in the reviews tile"),
|
|
54
|
+
ratingLabel: z
|
|
55
|
+
.string()
|
|
56
|
+
.default("App Store rating")
|
|
57
|
+
.describe("Caption of the rating"),
|
|
58
|
+
receipt: z
|
|
59
|
+
.array(z.string())
|
|
60
|
+
.default(["Coffee 4.50", "Groceries 62.10", "Metro 2.75"])
|
|
61
|
+
.describe("Receipt lines — the last word is the amount"),
|
|
62
|
+
receiptNote: z
|
|
63
|
+
.string()
|
|
64
|
+
.default("Sorted automatically")
|
|
65
|
+
.describe("Pill under the receipt"),
|
|
29
66
|
})
|
|
30
67
|
);
|
|
31
68
|
|
|
32
69
|
export type Props = z.infer<typeof propsSchema>;
|
|
33
70
|
|
|
71
|
+
const CREAM = "#f4ede1";
|
|
72
|
+
const INK = "#1f1b16";
|
|
73
|
+
const BUTTER = "#ffd166";
|
|
74
|
+
const SAGE = "#9fd8b3";
|
|
75
|
+
// Latin face first; Arabic glyphs fall through to the Arabic face.
|
|
76
|
+
const FONT = "Bricolage Grotesque, IBM Plex Sans Arabic, sans-serif";
|
|
77
|
+
const MONO = "Space Mono, IBM Plex Sans Arabic, monospace";
|
|
78
|
+
const GAP = 20;
|
|
79
|
+
const WEEKS = [
|
|
80
|
+
[40, 22, 18],
|
|
81
|
+
[52, 30, 12],
|
|
82
|
+
[34, 26, 20],
|
|
83
|
+
[60, 18, 16],
|
|
84
|
+
[46, 34, 10],
|
|
85
|
+
];
|
|
86
|
+
const AVATARS = [
|
|
87
|
+
{ bg: SAGE, initials: "AK" },
|
|
88
|
+
{ bg: BUTTER, initials: "MJ" },
|
|
89
|
+
{ bg: "#ffb4a2", initials: "RS" },
|
|
90
|
+
];
|
|
91
|
+
const GAUGE_RADIUS = 40;
|
|
92
|
+
const GAUGE_LENGTH = Math.PI * GAUGE_RADIUS;
|
|
93
|
+
|
|
94
|
+
const Tile = ({
|
|
95
|
+
children,
|
|
96
|
+
color = CREAM,
|
|
97
|
+
grow,
|
|
98
|
+
}: {
|
|
99
|
+
children: React.ReactNode;
|
|
100
|
+
color?: string;
|
|
101
|
+
grow: number;
|
|
102
|
+
}) => (
|
|
103
|
+
<div
|
|
104
|
+
tw="flex flex-col p-7 rounded-[32px] relative overflow-hidden"
|
|
105
|
+
style={{
|
|
106
|
+
backgroundColor: color,
|
|
107
|
+
flexBasis: 0,
|
|
108
|
+
flexGrow: grow,
|
|
109
|
+
minHeight: 0,
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
{children}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const Column = ({ children }: { children: React.ReactNode }) => (
|
|
117
|
+
<div tw="flex flex-col flex-1" style={{ flexBasis: 0, gap: GAP }}>
|
|
118
|
+
{children}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
/** A cheerful coin mascot — drawn, so it needs no image fetch. */
|
|
123
|
+
const Coin = ({ color }: { color: string }) => (
|
|
124
|
+
<svg width="150" height="150" viewBox="0 0 100 100">
|
|
125
|
+
<ellipse cx="54" cy="54" rx="42" ry="42" fill={INK} />
|
|
126
|
+
<circle cx="50" cy="50" r="42" fill={BUTTER} stroke={INK} strokeWidth="4" />
|
|
127
|
+
<circle
|
|
128
|
+
cx="50"
|
|
129
|
+
cy="50"
|
|
130
|
+
r="31"
|
|
131
|
+
fill="none"
|
|
132
|
+
stroke={INK}
|
|
133
|
+
strokeWidth="3"
|
|
134
|
+
strokeDasharray="4 5"
|
|
135
|
+
/>
|
|
136
|
+
<circle cx="39" cy="44" r="5" fill={INK} />
|
|
137
|
+
<circle cx="61" cy="44" r="5" fill={INK} />
|
|
138
|
+
<path
|
|
139
|
+
d="M36 58 Q50 72 64 58"
|
|
140
|
+
fill="none"
|
|
141
|
+
stroke={INK}
|
|
142
|
+
strokeWidth="5"
|
|
143
|
+
strokeLinecap="round"
|
|
144
|
+
/>
|
|
145
|
+
<circle cx="31" cy="58" r="5" fill={color} opacity="0.7" />
|
|
146
|
+
<circle cx="69" cy="58" r="5" fill={color} opacity="0.7" />
|
|
147
|
+
</svg>
|
|
148
|
+
);
|
|
149
|
+
|
|
34
150
|
const BentoFeature = ({
|
|
35
|
-
headline,
|
|
36
|
-
description,
|
|
37
151
|
accentColor,
|
|
38
152
|
background,
|
|
153
|
+
brand,
|
|
154
|
+
chartLabel,
|
|
155
|
+
description,
|
|
156
|
+
gaugeLabel,
|
|
157
|
+
gaugePercent,
|
|
158
|
+
headline,
|
|
39
159
|
locale,
|
|
160
|
+
metricDelta,
|
|
161
|
+
metricLabel,
|
|
162
|
+
metricValue,
|
|
163
|
+
rating,
|
|
164
|
+
ratingLabel,
|
|
165
|
+
receipt,
|
|
166
|
+
receiptNote,
|
|
40
167
|
}: Props) => {
|
|
41
168
|
const isRtl = locale?.startsWith("ar") ?? false;
|
|
169
|
+
const tracking = (em: number) => (isRtl ? 0 : `${em}em`);
|
|
42
170
|
|
|
43
171
|
return (
|
|
44
172
|
<div
|
|
45
173
|
lang={isRtl ? "ar" : undefined}
|
|
46
174
|
dir={isRtl ? "rtl" : "ltr"}
|
|
47
|
-
tw="flex w-full h-full p-
|
|
175
|
+
tw="flex w-full h-full p-10"
|
|
48
176
|
style={{
|
|
49
177
|
backgroundColor: background,
|
|
50
|
-
color:
|
|
51
|
-
fontFamily:
|
|
178
|
+
color: INK,
|
|
179
|
+
fontFamily: FONT,
|
|
180
|
+
gap: GAP,
|
|
52
181
|
}}
|
|
53
182
|
>
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
183
|
+
<Column>
|
|
184
|
+
<Tile color={accentColor} grow={0.75}>
|
|
185
|
+
<div tw="flex flex-1 items-center" style={{ gap: 10 }}>
|
|
186
|
+
<span
|
|
187
|
+
dir="ltr"
|
|
188
|
+
style={{
|
|
189
|
+
color: CREAM,
|
|
190
|
+
fontSize: 88,
|
|
191
|
+
fontWeight: 700,
|
|
192
|
+
letterSpacing: "-0.05em",
|
|
193
|
+
lineHeight: 1,
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
{brand}
|
|
197
|
+
</span>
|
|
198
|
+
<div tw="flex" style={{ gap: 6 }}>
|
|
199
|
+
<div
|
|
200
|
+
tw="w-5 h-5 rounded-full"
|
|
201
|
+
style={{ backgroundColor: BUTTER }}
|
|
202
|
+
/>
|
|
203
|
+
<div
|
|
204
|
+
tw="w-5 h-5 rounded-full"
|
|
205
|
+
style={{ backgroundColor: CREAM }}
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</Tile>
|
|
210
|
+
<Tile grow={1}>
|
|
211
|
+
<span tw="text-2xl">{metricLabel}</span>
|
|
212
|
+
<div tw="flex items-end mt-auto" style={{ gap: 12 }}>
|
|
213
|
+
<span
|
|
214
|
+
style={{
|
|
215
|
+
fontSize: 76,
|
|
216
|
+
fontWeight: 700,
|
|
217
|
+
letterSpacing: tracking(-0.04),
|
|
218
|
+
lineHeight: 1,
|
|
219
|
+
whiteSpace: "nowrap",
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
{metricValue}
|
|
223
|
+
</span>
|
|
224
|
+
<span
|
|
225
|
+
dir="ltr"
|
|
226
|
+
tw="px-3 py-1 rounded-full text-xl mb-2"
|
|
227
|
+
style={{ backgroundColor: SAGE, fontWeight: 700 }}
|
|
228
|
+
>
|
|
229
|
+
{metricDelta}
|
|
230
|
+
</span>
|
|
231
|
+
</div>
|
|
232
|
+
</Tile>
|
|
233
|
+
<Tile color={BUTTER} grow={1.2}>
|
|
234
|
+
<span tw="text-xl">{chartLabel}</span>
|
|
235
|
+
<div
|
|
236
|
+
tw="flex flex-1 items-end mt-4"
|
|
237
|
+
style={{ gap: 12, minHeight: 0 }}
|
|
238
|
+
>
|
|
239
|
+
{WEEKS.map((week, index) => (
|
|
240
|
+
<div
|
|
241
|
+
key={`week-${index}`}
|
|
242
|
+
tw="flex flex-col flex-1 justify-end"
|
|
243
|
+
style={{ height: "100%" }}
|
|
244
|
+
>
|
|
245
|
+
{week.map((share, part) => (
|
|
246
|
+
<div
|
|
247
|
+
key={`part-${part}`}
|
|
248
|
+
tw="flex w-full"
|
|
249
|
+
style={{
|
|
250
|
+
backgroundColor: [INK, accentColor, CREAM][part],
|
|
251
|
+
borderRadius: part === 0 ? "10px 10px 0 0" : 0,
|
|
252
|
+
height: `${share}%`,
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
</Tile>
|
|
260
|
+
</Column>
|
|
261
|
+
|
|
262
|
+
<Column>
|
|
263
|
+
<Tile grow={0.85}>
|
|
264
|
+
<div tw="flex items-end" style={{ gap: 10 }}>
|
|
265
|
+
<span style={{ fontSize: 72, fontWeight: 700, lineHeight: 1 }}>
|
|
266
|
+
{rating}
|
|
267
|
+
</span>
|
|
268
|
+
<svg
|
|
269
|
+
width="40"
|
|
270
|
+
height="40"
|
|
271
|
+
viewBox="0 0 24 24"
|
|
272
|
+
style={{ marginBottom: 8 }}
|
|
273
|
+
>
|
|
274
|
+
<path
|
|
275
|
+
d="M12 1 15.1 8.3 23 9 17 14.2 18.8 22 12 17.9 5.2 22 7 14.2 1 9 8.9 8.3Z"
|
|
276
|
+
fill={accentColor}
|
|
277
|
+
/>
|
|
278
|
+
</svg>
|
|
279
|
+
</div>
|
|
280
|
+
<span tw="text-lg mt-1" style={{ color: "#6f675b" }}>
|
|
281
|
+
{ratingLabel}
|
|
282
|
+
</span>
|
|
283
|
+
<div tw="flex mt-auto">
|
|
284
|
+
{AVATARS.map((avatar, index) => (
|
|
285
|
+
<div
|
|
286
|
+
key={avatar.initials}
|
|
287
|
+
tw="flex items-center justify-center w-16 h-16 rounded-full text-xl"
|
|
288
|
+
style={{
|
|
289
|
+
backgroundColor: avatar.bg,
|
|
290
|
+
border: `4px solid ${CREAM}`,
|
|
291
|
+
fontWeight: 700,
|
|
292
|
+
[isRtl ? "marginRight" : "marginLeft"]: index === 0 ? 0 : -16,
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
{avatar.initials}
|
|
296
|
+
</div>
|
|
297
|
+
))}
|
|
298
|
+
</div>
|
|
299
|
+
</Tile>
|
|
300
|
+
<Tile grow={2}>
|
|
301
|
+
<span
|
|
302
|
+
style={{
|
|
303
|
+
fontSize: 50,
|
|
304
|
+
fontWeight: 700,
|
|
305
|
+
letterSpacing: tracking(-0.03),
|
|
306
|
+
lineHeight: isRtl ? 1.3 : 1.05,
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
{headline}
|
|
310
|
+
</span>
|
|
311
|
+
<span tw="text-xl mt-4" style={{ color: "#6f675b", lineHeight: 1.4 }}>
|
|
72
312
|
{description}
|
|
73
|
-
</
|
|
74
|
-
|
|
75
|
-
|
|
313
|
+
</span>
|
|
314
|
+
<div
|
|
315
|
+
tw="flex flex-1 items-end justify-center"
|
|
316
|
+
style={{ minHeight: 0 }}
|
|
317
|
+
>
|
|
318
|
+
<Coin color={accentColor} />
|
|
319
|
+
</div>
|
|
320
|
+
</Tile>
|
|
321
|
+
</Column>
|
|
322
|
+
|
|
323
|
+
<Column>
|
|
324
|
+
<Tile color={SAGE} grow={1.25}>
|
|
325
|
+
<div
|
|
326
|
+
tw="flex flex-col px-6 pt-6 pb-8"
|
|
327
|
+
style={{
|
|
328
|
+
backgroundColor: "#fffdf8",
|
|
329
|
+
boxShadow: "0 14px 30px rgba(31,27,22,0.18)",
|
|
330
|
+
fontFamily: MONO,
|
|
331
|
+
gap: 10,
|
|
332
|
+
transform: `rotate(${isRtl ? 3 : -3}deg)`,
|
|
333
|
+
}}
|
|
334
|
+
>
|
|
335
|
+
<span tw="text-sm" style={{ letterSpacing: isRtl ? 0 : "0.2em" }}>
|
|
336
|
+
{brand.toUpperCase()}
|
|
337
|
+
</span>
|
|
338
|
+
{receipt.map((line) => {
|
|
339
|
+
const words = line.split(" ");
|
|
340
|
+
const amount = words.pop() ?? "";
|
|
341
|
+
return (
|
|
342
|
+
<div
|
|
343
|
+
key={line}
|
|
344
|
+
tw="flex justify-between text-lg"
|
|
345
|
+
style={{ gap: 12 }}
|
|
346
|
+
>
|
|
347
|
+
<span>{words.join(" ")}</span>
|
|
348
|
+
<span style={{ fontWeight: 700 }}>{amount}</span>
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
})}
|
|
352
|
+
<div
|
|
353
|
+
tw="flex"
|
|
354
|
+
style={{ borderTop: `2px dashed ${INK}`, marginTop: 4 }}
|
|
355
|
+
/>
|
|
356
|
+
</div>
|
|
357
|
+
<div
|
|
358
|
+
tw="flex self-start items-center px-4 py-2 rounded-full text-lg mt-auto"
|
|
359
|
+
style={{ backgroundColor: INK, color: CREAM, gap: 8 }}
|
|
360
|
+
>
|
|
361
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
|
362
|
+
<path
|
|
363
|
+
d="M4 12.5 9.5 18 20 6"
|
|
364
|
+
stroke={SAGE}
|
|
365
|
+
strokeWidth="3.5"
|
|
366
|
+
strokeLinecap="round"
|
|
367
|
+
strokeLinejoin="round"
|
|
368
|
+
/>
|
|
369
|
+
</svg>
|
|
370
|
+
<span>{receiptNote}</span>
|
|
371
|
+
</div>
|
|
372
|
+
</Tile>
|
|
373
|
+
<Tile color={INK} grow={1}>
|
|
374
|
+
<span tw="text-xl" style={{ color: CREAM }}>
|
|
375
|
+
{gaugeLabel}
|
|
376
|
+
</span>
|
|
377
|
+
<div
|
|
378
|
+
tw="flex flex-1 items-end justify-center"
|
|
379
|
+
style={{ minHeight: 0 }}
|
|
380
|
+
>
|
|
381
|
+
<div tw="flex relative" style={{ height: 110, width: 220 }}>
|
|
382
|
+
<svg
|
|
383
|
+
width="220"
|
|
384
|
+
height="110"
|
|
385
|
+
viewBox="0 0 100 50"
|
|
386
|
+
style={{ left: 0, position: "absolute", top: 0 }}
|
|
387
|
+
>
|
|
388
|
+
<path
|
|
389
|
+
d="M10 50 A40 40 0 0 1 90 50"
|
|
390
|
+
fill="none"
|
|
391
|
+
stroke="#3a342c"
|
|
392
|
+
strokeWidth="14"
|
|
393
|
+
strokeLinecap="round"
|
|
394
|
+
/>
|
|
395
|
+
<path
|
|
396
|
+
d="M10 50 A40 40 0 0 1 90 50"
|
|
397
|
+
fill="none"
|
|
398
|
+
stroke={accentColor}
|
|
399
|
+
strokeWidth="14"
|
|
400
|
+
strokeLinecap="round"
|
|
401
|
+
strokeDasharray={`${(GAUGE_LENGTH * gaugePercent) / 100} ${GAUGE_LENGTH}`}
|
|
402
|
+
transform={isRtl ? "translate(100 0) scale(-1 1)" : undefined}
|
|
403
|
+
/>
|
|
404
|
+
</svg>
|
|
405
|
+
<div tw="flex absolute inset-0 items-end justify-center">
|
|
406
|
+
<span
|
|
407
|
+
style={{
|
|
408
|
+
color: CREAM,
|
|
409
|
+
fontSize: 44,
|
|
410
|
+
fontWeight: 700,
|
|
411
|
+
lineHeight: 1,
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
{`${gaugePercent}%`}
|
|
415
|
+
</span>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
</Tile>
|
|
420
|
+
</Column>
|
|
76
421
|
</div>
|
|
77
422
|
);
|
|
78
423
|
};
|