@tonycodes-ui/mortar 0.0.0-stage → 0.12.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/AGENTS.md +10 -0
- package/CHANGELOG.md +268 -0
- package/CONTRIBUTING.md +60 -0
- package/DESIGN.md +51 -0
- package/LICENSE +21 -0
- package/README.md +155 -2
- package/TESTING.md +83 -0
- package/baseline-policy.json +16 -0
- package/bin/mortar.mjs +258 -0
- package/catalog.json +9900 -0
- package/cli/audit.mjs +943 -0
- package/cli/data.mjs +176 -0
- package/cli/mcp.mjs +216 -0
- package/cli/migrations.mjs +128 -0
- package/cli/packet.mjs +267 -0
- package/cli/search.mjs +246 -0
- package/cli/tools.mjs +229 -0
- package/design-principles.json +30 -0
- package/dist/agent-data.json +1 -0
- package/dist/application.d.ts +340 -0
- package/dist/application.js +240 -0
- package/dist/auth.d.ts +121 -0
- package/dist/auth.js +97 -0
- package/dist/brand.d.ts +69 -0
- package/dist/brand.js +266 -0
- package/dist/calendar.d.ts +75 -0
- package/dist/calendar.js +310 -0
- package/dist/choice.d.ts +36 -0
- package/dist/choice.js +36 -0
- package/dist/composer.d.ts +28 -0
- package/dist/composer.js +47 -0
- package/dist/display.d.ts +136 -0
- package/dist/display.js +75 -0
- package/dist/docs.d.ts +145 -0
- package/dist/docs.js +141 -0
- package/dist/ecommerce.d.ts +261 -0
- package/dist/ecommerce.js +177 -0
- package/dist/feedback.d.ts +80 -0
- package/dist/feedback.js +98 -0
- package/dist/foundation.d.ts +42 -0
- package/dist/foundation.js +121 -0
- package/dist/heading.d.ts +5 -0
- package/dist/heading.js +9 -0
- package/dist/icons.d.ts +50 -0
- package/dist/icons.js +70 -0
- package/dist/illustration.d.ts +47 -0
- package/dist/illustration.js +47 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +18 -0
- package/dist/inspection.d.ts +24 -0
- package/dist/inspection.js +14 -0
- package/dist/internal.d.ts +514 -0
- package/dist/internal.js +332 -0
- package/dist/layout.d.ts +107 -0
- package/dist/layout.js +66 -0
- package/dist/marketing-sections.d.ts +161 -0
- package/dist/marketing-sections.js +58 -0
- package/dist/marketing.d.ts +277 -0
- package/dist/marketing.js +163 -0
- package/dist/navigation.d.ts +35 -0
- package/dist/navigation.js +44 -0
- package/dist/primitives.d.ts +258 -0
- package/dist/primitives.js +244 -0
- package/dist/showcase.d.ts +29 -0
- package/dist/showcase.js +10 -0
- package/dist/workspace.d.ts +80 -0
- package/dist/workspace.js +48 -0
- package/docs/agents.md +127 -0
- package/docs/auth-flows.md +71 -0
- package/docs/migrating-to-0.6.md +94 -0
- package/docs/theming.md +170 -0
- package/package.json +94 -4
- package/skills/mortar/SKILL.md +36 -0
- package/src/application.css +1487 -0
- package/src/application.tsx +1413 -0
- package/src/auth.tsx +452 -0
- package/src/brand.ts +335 -0
- package/src/calendar.css +468 -0
- package/src/calendar.tsx +868 -0
- package/src/choice.tsx +240 -0
- package/src/composer.tsx +158 -0
- package/src/data.css +690 -0
- package/src/display.tsx +452 -0
- package/src/docs.css +704 -0
- package/src/docs.tsx +737 -0
- package/src/ecommerce.css +810 -0
- package/src/ecommerce.tsx +1033 -0
- package/src/feedback.tsx +388 -0
- package/src/foundation.css +1867 -0
- package/src/foundation.tsx +227 -0
- package/src/heading.ts +13 -0
- package/src/icons.tsx +237 -0
- package/src/illustration.tsx +169 -0
- package/src/index.ts +233 -0
- package/src/inspection.tsx +91 -0
- package/src/internal.tsx +700 -0
- package/src/layout.tsx +342 -0
- package/src/marketing-sections.tsx +621 -0
- package/src/marketing.css +2579 -0
- package/src/marketing.tsx +1235 -0
- package/src/navigation.tsx +118 -0
- package/src/overlays.css +350 -0
- package/src/primitives.tsx +1135 -0
- package/src/showcase.tsx +120 -0
- package/src/tokens.css +449 -0
- package/src/workspace.tsx +333 -0
- package/styles.css +56 -0
package/src/layout.tsx
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { useId, type ElementType, type ReactNode } from "react";
|
|
2
|
+
import { type HeadingLevel } from "./heading.js";
|
|
3
|
+
import {
|
|
4
|
+
componentRoot,
|
|
5
|
+
cx,
|
|
6
|
+
classedIntroSlots,
|
|
7
|
+
titledIntroSlots,
|
|
8
|
+
} from "./internal.js";
|
|
9
|
+
|
|
10
|
+
type Space = "none" | "xs" | "sm" | "md" | "lg" | "xl";
|
|
11
|
+
|
|
12
|
+
export interface StackProps {
|
|
13
|
+
as?: ElementType;
|
|
14
|
+
gap?: Space;
|
|
15
|
+
direction?: "column" | "row";
|
|
16
|
+
align?: "start" | "center" | "end" | "stretch";
|
|
17
|
+
justify?: "start" | "center" | "end" | "between";
|
|
18
|
+
wrap?: boolean;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Vertical or horizontal rhythm without consumer flex/gap utilities. */
|
|
23
|
+
export function Stack({
|
|
24
|
+
as: Tag = "div",
|
|
25
|
+
gap = "md",
|
|
26
|
+
direction = "column",
|
|
27
|
+
align,
|
|
28
|
+
justify,
|
|
29
|
+
wrap = false,
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
}: StackProps) {
|
|
33
|
+
return (
|
|
34
|
+
<Tag
|
|
35
|
+
{...componentRoot("Stack")}
|
|
36
|
+
className={cx("m-layout-stack", className)}
|
|
37
|
+
data-gap={gap === "md" ? undefined : gap}
|
|
38
|
+
data-direction={direction === "column" ? undefined : direction}
|
|
39
|
+
data-align={align}
|
|
40
|
+
data-justify={justify}
|
|
41
|
+
data-wrap={wrap || undefined}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</Tag>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ContainerProps {
|
|
49
|
+
as?: ElementType;
|
|
50
|
+
size?: "sm" | "md" | "lg" | "xl";
|
|
51
|
+
padded?: boolean;
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
/** Centred page width with the shared gutters used by marketing and application pages. */
|
|
56
|
+
export function Container({
|
|
57
|
+
as: Tag = "div",
|
|
58
|
+
size = "lg",
|
|
59
|
+
padded = true,
|
|
60
|
+
children,
|
|
61
|
+
className,
|
|
62
|
+
}: ContainerProps) {
|
|
63
|
+
return (
|
|
64
|
+
<Tag
|
|
65
|
+
{...componentRoot("Container")}
|
|
66
|
+
className={cx("m-container", className)}
|
|
67
|
+
data-size={size === "lg" ? undefined : size}
|
|
68
|
+
data-padded={padded || undefined}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</Tag>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface TextProps {
|
|
76
|
+
as?: ElementType;
|
|
77
|
+
/** Text colour; `data-tone` stays reserved for status and surface tones. */
|
|
78
|
+
color?: "default" | "secondary" | "muted";
|
|
79
|
+
size?: "sm" | "md" | "lg";
|
|
80
|
+
weight?: "regular" | "medium" | "semibold";
|
|
81
|
+
children: ReactNode;
|
|
82
|
+
className?: string;
|
|
83
|
+
}
|
|
84
|
+
/** Body copy with semantic colour and size, instead of text-muted/text-ink utilities. */
|
|
85
|
+
export function Text({
|
|
86
|
+
as: Tag = "p",
|
|
87
|
+
color = "default",
|
|
88
|
+
size = "md",
|
|
89
|
+
weight = "regular",
|
|
90
|
+
children,
|
|
91
|
+
className,
|
|
92
|
+
}: TextProps) {
|
|
93
|
+
return (
|
|
94
|
+
<Tag
|
|
95
|
+
{...componentRoot("Text")}
|
|
96
|
+
className={cx("m-text", className)}
|
|
97
|
+
data-color={color === "default" ? undefined : color}
|
|
98
|
+
data-size={size === "md" ? undefined : size}
|
|
99
|
+
data-weight={weight === "regular" ? undefined : weight}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</Tag>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type SectionTone = "default" | "muted" | "inverse";
|
|
107
|
+
|
|
108
|
+
export interface SectionProps {
|
|
109
|
+
id?: string;
|
|
110
|
+
eyebrow?: string;
|
|
111
|
+
title?: string;
|
|
112
|
+
description?: string;
|
|
113
|
+
tone?: SectionTone;
|
|
114
|
+
align?: "start" | "center";
|
|
115
|
+
headingLevel?: HeadingLevel;
|
|
116
|
+
children?: ReactNode;
|
|
117
|
+
className?: string;
|
|
118
|
+
}
|
|
119
|
+
/** Full-bleed marketing band for content that is not a grid or split feature. */
|
|
120
|
+
export function Section({
|
|
121
|
+
id,
|
|
122
|
+
eyebrow,
|
|
123
|
+
title,
|
|
124
|
+
description,
|
|
125
|
+
tone = "default",
|
|
126
|
+
align = "start",
|
|
127
|
+
headingLevel = 2,
|
|
128
|
+
children,
|
|
129
|
+
className,
|
|
130
|
+
}: SectionProps) {
|
|
131
|
+
return (
|
|
132
|
+
<section
|
|
133
|
+
id={id}
|
|
134
|
+
className={cx("m-marketing-section m-section", className)}
|
|
135
|
+
data-tone={tone === "default" ? undefined : tone}
|
|
136
|
+
data-align={align === "start" ? undefined : align}
|
|
137
|
+
{...componentRoot("Section")}
|
|
138
|
+
>
|
|
139
|
+
{...titledIntroSlots({
|
|
140
|
+
eyebrow,
|
|
141
|
+
title: title || undefined,
|
|
142
|
+
description,
|
|
143
|
+
headingLevel,
|
|
144
|
+
})}
|
|
145
|
+
{children && <div className="m-section-body">{children}</div>}
|
|
146
|
+
</section>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Brand mark: a supplied logo node, or the neutral placeholder square. */
|
|
151
|
+
export function BrandMark({ logo }: { logo?: ReactNode }) {
|
|
152
|
+
return logo ? (
|
|
153
|
+
<span className="m-logo" aria-hidden="true">
|
|
154
|
+
{logo}
|
|
155
|
+
</span>
|
|
156
|
+
) : (
|
|
157
|
+
<span className="m-mark" aria-hidden="true" />
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface DividerProps {
|
|
162
|
+
label?: string;
|
|
163
|
+
soft?: boolean;
|
|
164
|
+
spacing?: "none" | "sm" | "md" | "lg";
|
|
165
|
+
className?: string;
|
|
166
|
+
}
|
|
167
|
+
/** Horizontal rule between groups; a label names the break for assistive technology. */
|
|
168
|
+
export function Divider({
|
|
169
|
+
label,
|
|
170
|
+
soft = false,
|
|
171
|
+
spacing = "md",
|
|
172
|
+
className,
|
|
173
|
+
}: DividerProps) {
|
|
174
|
+
const attributes = {
|
|
175
|
+
...componentRoot("Divider"),
|
|
176
|
+
className: cx("m-divider", className),
|
|
177
|
+
"data-soft": soft || undefined,
|
|
178
|
+
"data-spacing": spacing === "md" ? undefined : spacing,
|
|
179
|
+
};
|
|
180
|
+
return label ? (
|
|
181
|
+
<div {...attributes} role="separator" aria-label={label}>
|
|
182
|
+
<span aria-hidden="true">{label}</span>
|
|
183
|
+
</div>
|
|
184
|
+
) : (
|
|
185
|
+
<hr {...attributes} />
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface CardProps {
|
|
190
|
+
as?: "div" | "section" | "article";
|
|
191
|
+
title?: string;
|
|
192
|
+
description?: string;
|
|
193
|
+
headingLevel?: HeadingLevel;
|
|
194
|
+
actions?: ReactNode;
|
|
195
|
+
footer?: ReactNode;
|
|
196
|
+
padding?: "default" | "compact" | "none";
|
|
197
|
+
/** `muted` sets the card on the sunken surface. */
|
|
198
|
+
tone?: "default" | "muted";
|
|
199
|
+
children?: ReactNode;
|
|
200
|
+
id?: string;
|
|
201
|
+
className?: string;
|
|
202
|
+
}
|
|
203
|
+
/** Bordered surface with optional heading, header actions and footer. */
|
|
204
|
+
export function Card({
|
|
205
|
+
as: Tag = "div",
|
|
206
|
+
title,
|
|
207
|
+
description,
|
|
208
|
+
headingLevel = 2,
|
|
209
|
+
actions,
|
|
210
|
+
footer,
|
|
211
|
+
padding = "default",
|
|
212
|
+
tone = "default",
|
|
213
|
+
children,
|
|
214
|
+
id,
|
|
215
|
+
className,
|
|
216
|
+
}: CardProps) {
|
|
217
|
+
const headingId = useId();
|
|
218
|
+
return (
|
|
219
|
+
<Tag
|
|
220
|
+
id={id}
|
|
221
|
+
{...componentRoot("Card")}
|
|
222
|
+
className={cx("m-card", className)}
|
|
223
|
+
data-padding={padding === "default" ? undefined : padding}
|
|
224
|
+
data-tone={tone === "default" ? undefined : tone}
|
|
225
|
+
aria-labelledby={title && Tag !== "div" ? headingId : undefined}
|
|
226
|
+
>
|
|
227
|
+
{(title || actions) && (
|
|
228
|
+
<header className="m-card-header">
|
|
229
|
+
{title && (
|
|
230
|
+
<div className="m-card-heading">
|
|
231
|
+
{...classedIntroSlots(
|
|
232
|
+
{
|
|
233
|
+
title,
|
|
234
|
+
description,
|
|
235
|
+
id: headingId,
|
|
236
|
+
headingLevel,
|
|
237
|
+
descriptionClassName: null,
|
|
238
|
+
},
|
|
239
|
+
"m-card-title",
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
{actions && <div className="m-card-actions">{actions}</div>}
|
|
244
|
+
</header>
|
|
245
|
+
)}
|
|
246
|
+
{children && <div className="m-card-body">{children}</div>}
|
|
247
|
+
{footer && <footer className="m-card-footer">{footer}</footer>}
|
|
248
|
+
</Tag>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface MediaObjectProps {
|
|
253
|
+
media: ReactNode;
|
|
254
|
+
title?: string;
|
|
255
|
+
align?: "start" | "center";
|
|
256
|
+
reverse?: boolean;
|
|
257
|
+
children?: ReactNode;
|
|
258
|
+
className?: string;
|
|
259
|
+
}
|
|
260
|
+
/** Image, avatar or icon beside text, e.g. comments and list rows. */
|
|
261
|
+
export function MediaObject({
|
|
262
|
+
media,
|
|
263
|
+
title,
|
|
264
|
+
align = "start",
|
|
265
|
+
reverse = false,
|
|
266
|
+
children,
|
|
267
|
+
className,
|
|
268
|
+
}: MediaObjectProps) {
|
|
269
|
+
return (
|
|
270
|
+
<div
|
|
271
|
+
{...componentRoot("MediaObject")}
|
|
272
|
+
className={cx("m-media-object", className)}
|
|
273
|
+
data-align={align === "start" ? undefined : align}
|
|
274
|
+
data-reverse={reverse || undefined}
|
|
275
|
+
>
|
|
276
|
+
<div className="m-media-object-media">{media}</div>
|
|
277
|
+
<div className="m-media-object-body">
|
|
278
|
+
{title && <p className="m-media-object-title">{title}</p>}
|
|
279
|
+
{children}
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface SectionHeadingProps {
|
|
286
|
+
title: string;
|
|
287
|
+
description?: string;
|
|
288
|
+
actions?: ReactNode;
|
|
289
|
+
headingLevel?: HeadingLevel;
|
|
290
|
+
divider?: boolean;
|
|
291
|
+
/** Page tabs (e.g. TabNav) below the title row; their underline replaces the divider. */
|
|
292
|
+
tabs?: ReactNode;
|
|
293
|
+
id?: string;
|
|
294
|
+
className?: string;
|
|
295
|
+
}
|
|
296
|
+
/** Heading for a section within a page, with optional description and actions. */
|
|
297
|
+
export function SectionHeading({
|
|
298
|
+
title,
|
|
299
|
+
description,
|
|
300
|
+
actions,
|
|
301
|
+
headingLevel = 2,
|
|
302
|
+
divider = true,
|
|
303
|
+
tabs,
|
|
304
|
+
id,
|
|
305
|
+
className,
|
|
306
|
+
}: SectionHeadingProps) {
|
|
307
|
+
return (
|
|
308
|
+
<div
|
|
309
|
+
id={id}
|
|
310
|
+
{...componentRoot("SectionHeading")}
|
|
311
|
+
className={cx("m-section-heading", className)}
|
|
312
|
+
data-divider={divider || undefined}
|
|
313
|
+
>
|
|
314
|
+
<div className="m-section-heading-text">
|
|
315
|
+
{...classedIntroSlots(
|
|
316
|
+
{
|
|
317
|
+
title,
|
|
318
|
+
description,
|
|
319
|
+
headingLevel,
|
|
320
|
+
descriptionClassName: null,
|
|
321
|
+
},
|
|
322
|
+
"m-section-heading-title",
|
|
323
|
+
)}
|
|
324
|
+
</div>
|
|
325
|
+
{actions && <div className="m-actions">{actions}</div>}
|
|
326
|
+
{tabs && <div className="m-section-heading-tabs">{tabs}</div>}
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface ProseProps {
|
|
332
|
+
children: ReactNode;
|
|
333
|
+
className?: string;
|
|
334
|
+
}
|
|
335
|
+
/** Long-form copy with the kit's typographic rhythm. */
|
|
336
|
+
export function Prose({ children, className }: ProseProps) {
|
|
337
|
+
return (
|
|
338
|
+
<div {...componentRoot("Prose")} className={cx("m-prose", className)}>
|
|
339
|
+
{children}
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
}
|