@prongsjs/ui 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/classes.d.ts +23 -0
- package/dist/classes.js +98 -0
- package/dist/dev.d.ts +14 -0
- package/dist/dev.js +36 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/profile.d.ts +26 -0
- package/dist/profile.js +101 -0
- package/dist/tooling/analyze.d.ts +42 -0
- package/dist/tooling/analyze.js +175 -0
- package/dist/tooling/components.d.ts +24 -0
- package/dist/tooling/components.js +156 -0
- package/dist/tooling/index.d.ts +17 -0
- package/dist/tooling/index.js +540 -0
- package/dist/tooling/spec.d.ts +24 -0
- package/dist/tooling/spec.js +360 -0
- package/dist/tooling/theme.d.ts +83 -0
- package/dist/tooling/theme.js +288 -0
- package/dist/vocabulary.d.ts +32 -0
- package/dist/vocabulary.js +57 -0
- package/package.json +72 -4
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const UNSAFE = {
|
|
2
|
+
name: 'unsafeClassName',
|
|
3
|
+
type: 'string',
|
|
4
|
+
description: 'Bypasses the intent. Needs a `// prongs-unsafe: <reason>` comment on the line above, and `prongs check` counts it.',
|
|
5
|
+
};
|
|
6
|
+
const VARS = {
|
|
7
|
+
name: 'vars',
|
|
8
|
+
type: 'Record<`--${string}`, string | number>',
|
|
9
|
+
description: "Values that come from data, as CSS variables the intent reads: `vars={{ '--accent': user.color }}`.",
|
|
10
|
+
};
|
|
11
|
+
/** Where an element sits, never how it looks. Spacing between elements comes from the parent <Layout>. */
|
|
12
|
+
export const LAYOUT_PROPS = [
|
|
13
|
+
{ name: 'align', type: "'start' | 'center' | 'end'", description: 'On text, the text alignment. On anything else, pushes it to that end of its row or column.' },
|
|
14
|
+
{ name: 'width', type: "'auto' | 'full' | 'prose' | 'sm' | 'md' | 'lg'", description: 'Full width, or a maximum width.' },
|
|
15
|
+
{ name: 'hideBelow', type: "'sm' | 'md' | 'lg'", description: 'Hidden on screens narrower than this breakpoint.' },
|
|
16
|
+
{ name: 'showBelow', type: "'sm' | 'md' | 'lg'", description: 'Shown only on screens narrower than this breakpoint.' },
|
|
17
|
+
{ name: 'truncate', type: 'boolean', description: 'One line, cut off with an ellipsis.' },
|
|
18
|
+
{ name: 'nowrap', type: 'boolean', description: 'Keeps the text on one line.' },
|
|
19
|
+
];
|
|
20
|
+
export const COMPONENT_DOCS = {
|
|
21
|
+
Text: {
|
|
22
|
+
summary: 'Headings, running text and feedback messages. The intent decides the element (`<h1>`, `<p>`, `<span>`, `<pre>`) and the look.',
|
|
23
|
+
layoutProps: true,
|
|
24
|
+
props: [
|
|
25
|
+
{ name: 'intent', type: 'TextIntent', description: 'What this text is for: `heading.*`, `text.*` or `feedback.*`.', required: true },
|
|
26
|
+
{
|
|
27
|
+
name: 'as',
|
|
28
|
+
type: "'h1'…'h6' | 'p' | 'span' | 'div' | 'label'",
|
|
29
|
+
description: 'Changes the element for document structure only, like `as="label"` for a field label or `as="h2"` for a dialog title. The intent still decides the look.',
|
|
30
|
+
},
|
|
31
|
+
{ name: 'htmlFor', type: 'string', description: 'With `as="label"`: the `id` of the field it labels.' },
|
|
32
|
+
VARS,
|
|
33
|
+
UNSAFE,
|
|
34
|
+
],
|
|
35
|
+
rules: [
|
|
36
|
+
'No `className` or `style`. If text needs to look different, it is a different intent.',
|
|
37
|
+
'Use `as` only to fix document structure, never to change the look.',
|
|
38
|
+
'Emphasis inside a sentence is plain `<strong>` or `<em>`, not another `<Text>`.',
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
Action: {
|
|
42
|
+
summary: 'Everything the user presses: commands, links, navigation and choices. With `href` it renders a link, without one a `<button>`.',
|
|
43
|
+
layoutProps: true,
|
|
44
|
+
props: [
|
|
45
|
+
{ name: 'intent', type: 'ActionIntent', description: 'What pressing it does: `action.*`, `nav.*` or `choice.*`.', required: true },
|
|
46
|
+
{ name: 'href', type: 'string', description: 'Makes it a link (through the `LinkComponent` in `config.ts`, e.g. `next/link`).' },
|
|
47
|
+
{ name: 'pressed', type: 'boolean', description: 'On or off, for toggles, choices and views. Sets `aria-pressed`; the intent styles it.' },
|
|
48
|
+
{ name: 'current', type: 'boolean', description: 'Marks the page or step you are on. Sets `aria-current`; the intent styles it.' },
|
|
49
|
+
{ name: 'icon', type: 'ReactNode', description: 'An icon shown before the text, or alone.' },
|
|
50
|
+
{ name: 'label', type: 'string', description: 'The accessible name. Required when there is no visible text (an icon alone).' },
|
|
51
|
+
{ name: 'disabled', type: 'boolean', description: 'Turns it off. A disabled link loses its `href`.' },
|
|
52
|
+
{ name: 'type', type: "'button' | 'submit' | 'reset'", description: 'Buttons are `type="button"` unless the intent says otherwise (`action.submit` is `submit`).' },
|
|
53
|
+
VARS,
|
|
54
|
+
UNSAFE,
|
|
55
|
+
],
|
|
56
|
+
rules: [
|
|
57
|
+
'Name it for what it does (`action.delete`), not how it looks. `prongs check` warns when its label or handler says something else.',
|
|
58
|
+
'State goes in `pressed` or `current`, never in a different intent or extra classes.',
|
|
59
|
+
'An icon alone needs `label`.',
|
|
60
|
+
'No `className` or `style`.',
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
Field: {
|
|
64
|
+
summary: 'What the user types or picks in a form. The intent decides the element (`<input>`, `<textarea>`, `<select>`) and sets `type`, `inputMode` and `autoComplete`.',
|
|
65
|
+
layoutProps: true,
|
|
66
|
+
props: [
|
|
67
|
+
{ name: 'intent', type: 'FieldIntent', description: 'What is being entered: `field.email`, `field.password`, `field.multiline`...', required: true },
|
|
68
|
+
{ name: 'label', type: 'ReactNode', description: 'The label. With it, Field renders the whole `layout.field`: label, field, hint and error, connected for screen readers.' },
|
|
69
|
+
{ name: 'hint', type: 'ReactNode', description: 'Help under the field (`text.caption`). Needs `label`.' },
|
|
70
|
+
{ name: 'error', type: 'ReactNode', description: 'What is wrong with the value (`feedback.error`). Marks the field invalid. Needs `label`.' },
|
|
71
|
+
{ name: 'rows', type: 'number', description: 'Height of a multiline field, in lines.' },
|
|
72
|
+
{ name: 'children', type: 'ReactNode', description: 'For select fields: the `<option>`s.' },
|
|
73
|
+
{ name: 'ref', type: 'Ref<HTMLInputElement>', description: 'Reaches the underlying element.' },
|
|
74
|
+
UNSAFE,
|
|
75
|
+
],
|
|
76
|
+
rules: [
|
|
77
|
+
'Do not pass `type`: the intent sets it.',
|
|
78
|
+
'Label it with `label` (plus `hint` and `error`), which renders and wires the whole field; or give it an `aria-label` when the label is visible elsewhere, like a search box.',
|
|
79
|
+
'It takes the usual input attributes: `value`, `onChange`, `name`, `placeholder`, `required`...',
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
Layout: {
|
|
83
|
+
summary: 'Page regions and groups. The intent decides the element (`<main>`, `<section>`, `<form>`, `<ul>`, `<li>`) and the arrangement.',
|
|
84
|
+
layoutProps: false,
|
|
85
|
+
props: [
|
|
86
|
+
{ name: 'intent', type: 'LayoutIntent', description: 'What this region is: `layout.page`, `layout.form`, `layout.list`...', required: true },
|
|
87
|
+
{
|
|
88
|
+
name: 'className',
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: 'Layout and spacing only: margins, sizes, flex and grid, gaps, padding, text alignment. Visual classes warn in development and fail `prongs check`.',
|
|
91
|
+
},
|
|
92
|
+
{ name: 'hideBelow', type: "'sm' | 'md' | 'lg'", description: 'Hidden on screens narrower than this breakpoint: the desktop half of a region built twice.' },
|
|
93
|
+
{ name: 'showBelow', type: "'sm' | 'md' | 'lg'", description: 'Shown only on screens narrower than this breakpoint: the phone half.' },
|
|
94
|
+
{ name: 'align', type: "'start' | 'center' | 'end'", description: 'Pushes the region to that end of its row or column, like a group of controls at the end of a header.' },
|
|
95
|
+
{ name: 'ref', type: 'Ref<HTMLElement>', description: 'Reaches the rendered element.' },
|
|
96
|
+
UNSAFE,
|
|
97
|
+
],
|
|
98
|
+
rules: [
|
|
99
|
+
'The only component with a `className`, and only for layout.',
|
|
100
|
+
'Hide or show by screen size with `hideBelow` and `showBelow`, and push a region to one end with `align`, rather than classes.',
|
|
101
|
+
'A box that needs its own background or border is a different intent (`layout.panel`, `layout.notice`), not a class.',
|
|
102
|
+
'Pass `onSubmit` to a `layout.form`.',
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
Media: {
|
|
106
|
+
summary: 'Images that identify a person or illustrate content, and frames that show another page (`media.embed`). Without a source it shows text instead, like initials.',
|
|
107
|
+
layoutProps: true,
|
|
108
|
+
props: [
|
|
109
|
+
{ name: 'intent', type: 'MediaIntent', description: '`media.person` or `media.image`.', required: true },
|
|
110
|
+
{ name: 'alt', type: 'string', description: 'What the image shows (for a frame, its title). Its accessible name, and the source of the default initials.', required: true },
|
|
111
|
+
{ name: 'src', type: 'string | null', description: 'The image URL. When it is missing, the fallback shows.' },
|
|
112
|
+
{ name: 'fallback', type: 'string', description: 'Text shown without an image. Defaults to the initials of `alt`.' },
|
|
113
|
+
{ name: 'size', type: "'xs' | 'sm' | 'md' | 'lg' | 'xl'", description: 'For people: the size of the circle.' },
|
|
114
|
+
{ name: 'loading', type: "'lazy' | 'eager'", description: 'When the image loads. Lazy by default.' },
|
|
115
|
+
{ name: 'onError', type: '() => void', description: 'Called when the image fails, e.g. to clear `src` and show the fallback.' },
|
|
116
|
+
VARS,
|
|
117
|
+
UNSAFE,
|
|
118
|
+
],
|
|
119
|
+
rules: ['`alt` is required.', 'No `className` or `style`.'],
|
|
120
|
+
},
|
|
121
|
+
Dialog: {
|
|
122
|
+
summary: 'Modal interruptions on the native `<dialog>`: focus stays inside, Escape closes it and the page behind is inert.',
|
|
123
|
+
layoutProps: false,
|
|
124
|
+
props: [
|
|
125
|
+
{ name: 'intent', type: 'DialogIntent', description: '`dialog.task`, `dialog.confirm` or `dialog.reader`.', required: true },
|
|
126
|
+
{ name: 'open', type: 'boolean', description: 'Whether it is showing. You keep it in state.', required: true },
|
|
127
|
+
{ name: 'onClose', type: '() => void', description: 'Called on Escape, a click outside, or when it closes itself.', required: true },
|
|
128
|
+
{ name: 'label', type: 'string', description: 'The accessible name: usually the heading text.', required: true },
|
|
129
|
+
{ name: 'children', type: 'ReactNode', description: 'The content. It renders only while the dialog is open.', required: true },
|
|
130
|
+
UNSAFE,
|
|
131
|
+
],
|
|
132
|
+
rules: ['Put a heading, the content and a `layout.actions` row inside.', 'Keep `open` in state and set it to false in `onClose`.'],
|
|
133
|
+
},
|
|
134
|
+
Popover: {
|
|
135
|
+
summary: 'Things that open next to the button that opened them, without blocking the page: menus, pickers, extra detail. Built on the native `popover` attribute, so a click outside or Escape closes it.',
|
|
136
|
+
layoutProps: false,
|
|
137
|
+
props: [
|
|
138
|
+
{ name: 'intent', type: 'PopoverIntent', description: '`popover.menu`, `popover.picker` or `popover.info`.', required: true },
|
|
139
|
+
{ name: 'trigger', type: 'ReactElement', description: 'The `<Action>` that opens it. Popover connects the two and places itself next to it.', required: true },
|
|
140
|
+
{ name: 'label', type: 'string', description: 'The accessible name: what it holds, like "Article actions".', required: true },
|
|
141
|
+
{ name: 'children', type: 'ReactNode', description: 'The content: `<Action>`s for a menu, choices for a picker, text for detail.' },
|
|
142
|
+
{ name: 'options', type: '{ value, label?, disabled? }[]', description: 'A picker’s choices as data, for lists from a database or a CMS. They render as a listbox (`role="option"`, `aria-selected`), announced like a native select.' },
|
|
143
|
+
{ name: 'value', type: 'string | string[]', description: 'A picker’s choice: one value, or several when its intent picks many (`popover.picker.many`).' },
|
|
144
|
+
{ name: 'onChange', type: '(value) => void', description: 'Called with the new choice, or with every picked value when its intent picks many.' },
|
|
145
|
+
{ name: 'keepOpen', type: 'boolean', description: 'Stay open when something inside is pressed. Pickers that pick many stay open anyway.' },
|
|
146
|
+
UNSAFE,
|
|
147
|
+
],
|
|
148
|
+
rules: [
|
|
149
|
+
'The trigger is an `<Action>` with a button, never a link. A picker’s trigger is `choice.open`, with `selectionSummary(options, value, placeholder)` as its text.',
|
|
150
|
+
'Whether a picker picks one or many comes from its intent, never a prop.',
|
|
151
|
+
'The arrow keys, Home, End and first letters move between items; Escape or a press closes it and focus goes back to the trigger.',
|
|
152
|
+
'Inside a menu, actions look like menu items whatever their intent; the menu decides.',
|
|
153
|
+
'Anything that needs the user’s full attention is a `<Dialog>`, not a popover.',
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ProfileTooling } from '@prongsjs/core/engine';
|
|
2
|
+
export type UiToolingOptions = {
|
|
3
|
+
/** Where the Prongs components live, relative to the project root. */
|
|
4
|
+
componentsDir: string;
|
|
5
|
+
/** How to run the CLI in this project, used in suggested commands. */
|
|
6
|
+
cli?: string;
|
|
7
|
+
/** The registry module, relative to the project root. Its classes are checked for raw values and unknown tokens. */
|
|
8
|
+
registryPath?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Every CSS variable the project declares (in its CSS, or as `'--name'` keys it passes through
|
|
11
|
+
* `vars` and `style`). When given, the registry may only reference these.
|
|
12
|
+
*/
|
|
13
|
+
tokens?: ReadonlySet<string>;
|
|
14
|
+
};
|
|
15
|
+
export declare function createUiTooling(options: UiToolingOptions): ProfileTooling;
|
|
16
|
+
export { UI_RULE_DOCS, specExamples, uiSpecTooling } from './spec.js';
|
|
17
|
+
export { themeCss, THEME_TOKENS, THEME_VARIABLES, SURFACE_TOKENS, SURFACE_CLASSES } from './theme.js';
|