@matthiaskrijgsman/mat-ui 0.0.41 → 0.0.42
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 +284 -180
- package/dist/index.js +1121 -1121
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ pnpm install @matthiaskrijgsman/mat-ui
|
|
|
33
33
|
|
|
34
34
|
### Styles
|
|
35
35
|
|
|
36
|
-
Import the mat-ui stylesheet in your CSS entry file
|
|
36
|
+
Import the mat-ui stylesheet in your CSS entry file. Be sure to do this **after** the Tailwind import.
|
|
37
37
|
|
|
38
38
|
```css
|
|
39
39
|
@import "@matthiaskrijgsman/mat-ui/style";
|
|
@@ -49,141 +49,6 @@ function App() {
|
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
## Rich text editor (`InputLexical`)
|
|
53
|
-
|
|
54
|
-
`InputLexical` is a [Lexical](https://lexical.dev)-powered rich text editor styled like the rest of the kit. It ships with two toolbar variants that share the exact same controls:
|
|
55
|
-
|
|
56
|
-
- **`static`** (default) — a light toolbar fixed at the top of the editor.
|
|
57
|
-
- **`floating`** — a dark bar that appears above the editor while it is focused, matching the editor width.
|
|
58
|
-
|
|
59
|
-
Lexical and its plugins are **peer dependencies** — install them alongside the library:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
pnpm add lexical @lexical/react @lexical/rich-text @lexical/list @lexical/link @lexical/selection @lexical/utils
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Basic usage
|
|
66
|
-
|
|
67
|
-
The value is a **serialized Lexical editor state** (a JSON string). Pass the last `onChange` value back as `value` to restore content.
|
|
68
|
-
|
|
69
|
-
```tsx
|
|
70
|
-
import { useState } from "react";
|
|
71
|
-
import { InputLexical } from "@matthiaskrijgsman/mat-ui";
|
|
72
|
-
|
|
73
|
-
function Editor() {
|
|
74
|
-
const [value, setValue] = useState<string>();
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<InputLexical
|
|
78
|
-
label={"Description"}
|
|
79
|
-
placeholder={"Write something…"}
|
|
80
|
-
toolbar={"floating"} // or "static" (default)
|
|
81
|
-
value={value}
|
|
82
|
-
onChange={setValue}
|
|
83
|
-
autogrow // grow with content…
|
|
84
|
-
minRows={4} // …from a 4-row floor…
|
|
85
|
-
maxRows={12} // …up to 12 rows, then scroll
|
|
86
|
-
/>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Sizing mirrors `InputTextArea`: `minRows` sets a height floor, `maxRows` caps the height (content beyond it scrolls), and `autogrow` lets the editor grow with its content between the two. Without `autogrow` the editor is fixed at `minRows`.
|
|
92
|
-
|
|
93
|
-
### Extending the toolbar
|
|
94
|
-
|
|
95
|
-
The toolbar is assembled from exported **building blocks**, so you can reorder, drop, or add controls via the `renderToolbar` slot. It receives `{ editor, state, tone }` and renders into whichever variant is active — the same render function drives both the static and floating bars.
|
|
96
|
-
|
|
97
|
-
```tsx
|
|
98
|
-
import {
|
|
99
|
-
InputLexical,
|
|
100
|
-
LexicalBlockTypeSelect,
|
|
101
|
-
LexicalFormatButtons,
|
|
102
|
-
LexicalListButtons,
|
|
103
|
-
LexicalLinkButton,
|
|
104
|
-
LexicalHistoryButtons,
|
|
105
|
-
LexicalToolbarDivider,
|
|
106
|
-
} from "@matthiaskrijgsman/mat-ui";
|
|
107
|
-
|
|
108
|
-
<InputLexical
|
|
109
|
-
renderToolbar={() => (
|
|
110
|
-
<>
|
|
111
|
-
<LexicalFormatButtons/>
|
|
112
|
-
<LexicalToolbarDivider/>
|
|
113
|
-
<LexicalListButtons/>
|
|
114
|
-
<LexicalLinkButton/>
|
|
115
|
-
<LexicalToolbarDivider/>
|
|
116
|
-
<LexicalHistoryButtons/>
|
|
117
|
-
</>
|
|
118
|
-
)}
|
|
119
|
-
/>;
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Building blocks read the active editor and formatting `state`/`tone` from context, so they work in either variant with no extra wiring. Dividers automatically flip orientation (and the whole toolbar collapses overflowing controls into a vertical `⋮` dropdown) when space runs out — return each control as a top-level child so it stays individually measurable.
|
|
123
|
-
|
|
124
|
-
#### Custom controls
|
|
125
|
-
|
|
126
|
-
Build your own control with `LexicalToolbarButton` plus Lexical's editor context. `useLexicalToolbar()` exposes the current `{ state, tone }`:
|
|
127
|
-
|
|
128
|
-
```tsx
|
|
129
|
-
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
130
|
-
import { FORMAT_TEXT_COMMAND } from "lexical";
|
|
131
|
-
import { IconStrikethrough } from "@tabler/icons-react";
|
|
132
|
-
import { LexicalToolbarButton, useLexicalToolbar } from "@matthiaskrijgsman/mat-ui";
|
|
133
|
-
|
|
134
|
-
const StrikethroughButton = () => {
|
|
135
|
-
const [editor] = useLexicalComposerContext();
|
|
136
|
-
const { tone } = useLexicalToolbar();
|
|
137
|
-
return (
|
|
138
|
-
<LexicalToolbarButton
|
|
139
|
-
Icon={IconStrikethrough}
|
|
140
|
-
tone={tone}
|
|
141
|
-
aria-label={"Strikethrough"}
|
|
142
|
-
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")}
|
|
143
|
-
/>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
#### Registering extra Lexical nodes
|
|
149
|
-
|
|
150
|
-
The built-in set covers headings, lists, links, and quotes. For anything else (tables, mentions, code blocks, …) install the node's package yourself and pass the node via the `nodes` prop — it is registered alongside the built-in set:
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
pnpm add @lexical/code
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
```tsx
|
|
157
|
-
import { CodeNode } from "@lexical/code";
|
|
158
|
-
|
|
159
|
-
<InputLexical nodes={[CodeNode]} renderToolbar={/* … */} />;
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
> mat-ui does not bundle `lexical` or any `@lexical/*` package — they are peer dependencies, so a single shared copy is used. Only register a given node type once: don't pass a node that is already in the built-in set, or Lexical throws a duplicate-type error.
|
|
163
|
-
|
|
164
|
-
#### Adding plugins (the `children` slot)
|
|
165
|
-
|
|
166
|
-
A Lexical feature is usually a **node *plus* a plugin**. Register the node with `nodes`, then mount the plugin(s) as **children** — they run inside the editor alongside the built-ins (history, lists, links). Any `@lexical/react` plugin or your own works:
|
|
167
|
-
|
|
168
|
-
```tsx
|
|
169
|
-
import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
|
|
170
|
-
import { CodeHighlightPlugin } from "@lexical/react/LexicalCodeHighlightPlugin";
|
|
171
|
-
|
|
172
|
-
<InputLexical nodes={[CodeNode]}>
|
|
173
|
-
<CodeHighlightPlugin />
|
|
174
|
-
<TabIndentationPlugin />
|
|
175
|
-
{/* …or your own plugin using useLexicalComposerContext() */}
|
|
176
|
-
</InputLexical>;
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Style custom nodes by merging theme classes over the defaults with the `theme` prop:
|
|
180
|
-
|
|
181
|
-
```tsx
|
|
182
|
-
<InputLexical nodes={[CodeNode]} theme={{ code: "my-code-block" }}>
|
|
183
|
-
<CodeHighlightPlugin />
|
|
184
|
-
</InputLexical>;
|
|
185
|
-
```
|
|
186
|
-
|
|
187
52
|
## Dark Theme
|
|
188
53
|
|
|
189
54
|
mat-ui ships with built-in dark theme support. Add the `dark` class to the `<html>` element to activate it:
|
|
@@ -205,44 +70,140 @@ document.documentElement.classList.toggle('dark', prefersDark);
|
|
|
205
70
|
|
|
206
71
|
All components adapt automatically — no additional props or configuration needed.
|
|
207
72
|
|
|
208
|
-
##
|
|
209
|
-
|
|
210
|
-
mat-ui uses CSS custom properties (design tokens) for all component colors. These are defined on `:root` for light mode and overridden under `.dark` for dark mode. You can customize the look of any component by overriding these tokens in your own CSS.
|
|
73
|
+
## Theming with design tokens
|
|
211
74
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
Define your overrides after importing the mat-ui stylesheet:
|
|
75
|
+
Every visual property in mat-ui — color, shape, type, elevation and size — is driven by CSS custom properties (design tokens) defined on `:root`. There is no Tailwind config to fork and no component props to thread: you retheme the whole kit by overriding tokens in your own CSS, after importing the stylesheet.
|
|
215
76
|
|
|
216
77
|
```css
|
|
217
78
|
@import "@matthiaskrijgsman/mat-ui/style";
|
|
218
79
|
|
|
219
80
|
:root {
|
|
220
|
-
|
|
221
|
-
--
|
|
222
|
-
--
|
|
223
|
-
--color-button-primary-bg-active: #0284c7;
|
|
224
|
-
--color-button-primary-border-active: #0284c7;
|
|
81
|
+
--color-button-primary-bg: #0ea5e9; /* brand color */
|
|
82
|
+
--border-radius-input: 0px; /* square corners everywhere */
|
|
83
|
+
--font-weight-button: 700; /* bolder buttons */
|
|
225
84
|
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### How the tokens are organised
|
|
88
|
+
|
|
89
|
+
Tokens fall into three families:
|
|
226
90
|
|
|
227
|
-
|
|
228
|
-
.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
91
|
+
- **Structure** — typography (family, weight, size), border radius, border width, shadow, ring (focus/hover) width and transition timing. Theme-independent.
|
|
92
|
+
- **Sizing** — the shared `sm | md | lg` control scale (height, padding, gap, icon size).
|
|
93
|
+
- **Color** — every surface, border, text and state color. Defined on `:root` (light) and overridden under `.dark`.
|
|
94
|
+
|
|
95
|
+
**Structure** and **color** tokens use a two-tier model:
|
|
96
|
+
|
|
97
|
+
1. **Base scales** — a small set of primitives (e.g. `--font-weight-strong`, `--radius-xl`). Change one to shift the whole kit at once.
|
|
98
|
+
2. **Semantic aliases** — per-component tokens that point at the base scale by default (e.g. `--font-weight-button: var(--font-weight-strong)`, `--border-radius-dropdown: var(--radius-xl)`). Override one to retheme a single component without touching anything else.
|
|
99
|
+
|
|
100
|
+
So `--font-weight-strong: 700` makes every emphasised element heavier, while `--font-weight-button: 700` changes only buttons. Pick the tier that matches how broad your change is.
|
|
101
|
+
|
|
102
|
+
> Dark mode: add the `dark` class to `<html>` (see [Dark Theme](#dark-theme)). Only **color** tokens differ between themes — structure and sizing are shared, so you never duplicate them under `.dark`.
|
|
103
|
+
|
|
104
|
+
### Worked examples
|
|
105
|
+
|
|
106
|
+
Square, flat, heavier — in four tokens:
|
|
107
|
+
|
|
108
|
+
```css
|
|
109
|
+
:root {
|
|
110
|
+
--border-radius-input: 0px; /* inputs, selects, buttons */
|
|
111
|
+
--border-radius-panel: 0.25rem; /* panels, modals */
|
|
112
|
+
--border-width-input: 2px; /* all control & surface borders */
|
|
113
|
+
--font-weight-strong: 700; /* buttons, badges, tabs, dropdown items… */
|
|
233
114
|
}
|
|
234
115
|
```
|
|
235
116
|
|
|
236
|
-
|
|
117
|
+
Set the kit's typeface in one place:
|
|
237
118
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
| `--border-radius-input` | Border radius for inputs and selects | `var(--radius-xl)` |
|
|
119
|
+
```css
|
|
120
|
+
:root {
|
|
121
|
+
--font-family-base: "Inter", system-ui, sans-serif;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
244
124
|
|
|
245
|
-
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Token reference
|
|
128
|
+
|
|
129
|
+
### Structure — typography
|
|
130
|
+
|
|
131
|
+
Font weights resolve through a three-step base scale; the semantic tokens below point at it by default.
|
|
132
|
+
|
|
133
|
+
| Base token | Default |
|
|
134
|
+
|------------|---------|
|
|
135
|
+
| `--font-weight-normal` | `400` |
|
|
136
|
+
| `--font-weight-medium` | `500` |
|
|
137
|
+
| `--font-weight-strong` | `600` |
|
|
138
|
+
|
|
139
|
+
| Semantic token | Applies to | Default |
|
|
140
|
+
|----------------|-----------|---------|
|
|
141
|
+
| `--font-weight-input-text` | Text typed into inputs, selects, textareas | `--font-weight-normal` |
|
|
142
|
+
| `--font-weight-button` | `Button`, `ButtonIconSquare`, `ButtonIconRound`, file-input action text | `--font-weight-strong` |
|
|
143
|
+
| `--font-weight-badge` | `Badge` | `--font-weight-strong` |
|
|
144
|
+
| `--font-weight-tab` | `TabButtons` | `--font-weight-strong` |
|
|
145
|
+
| `--font-weight-input-label` | `InputLabel` (label above inputs) | `--font-weight-medium` |
|
|
146
|
+
| `--font-weight-input-description` | `InputDescription` | `--font-weight-medium` |
|
|
147
|
+
| `--font-weight-input-error` | `InputError` | `--font-weight-medium` |
|
|
148
|
+
| `--font-weight-input-option-label` | Inline labels on `InputCheck` / `InputRadio` / `InputToggle`, file tile names | `--font-weight-medium` |
|
|
149
|
+
| `--font-weight-dropdown-item` | `DropdownButton`, `PanelLink` | `--font-weight-strong` |
|
|
150
|
+
| `--font-weight-group-header` | Dropdown group labels, select group headers | `--font-weight-strong` |
|
|
151
|
+
| `--font-weight-panel-field` | `PanelField` label | `--font-weight-medium` |
|
|
152
|
+
| `--font-weight-panel-link` | `PanelLink` | `--font-weight-strong` |
|
|
153
|
+
| `--font-weight-table-header` | `Table` header cells, `TableEmpty` title | `--font-weight-medium` |
|
|
154
|
+
| `--font-weight-table-cell` | `Table` body cells | `--font-weight-normal` |
|
|
155
|
+
|
|
156
|
+
| Token | Description | Default |
|
|
157
|
+
|-------|-------------|---------|
|
|
158
|
+
| `--font-family-base` | Typeface for all kit text (defaults to the host font) | `inherit` |
|
|
159
|
+
| `--font-size-label` | Dropdown / select group label size | `var(--text-sm)` |
|
|
160
|
+
| `--font-size-description` | `InputDescription` and `PanelField` label size | `var(--text-sm)` |
|
|
161
|
+
| `--font-size-error` | `InputError` size | `var(--text-sm)` |
|
|
162
|
+
|
|
163
|
+
> The text size of the input/button itself comes from the **control sizing** scale below (`--control-size-{size}-font-size`), not from these tokens.
|
|
164
|
+
|
|
165
|
+
### Structure — border radius
|
|
166
|
+
|
|
167
|
+
Semantic radius tokens map onto Tailwind's radius scale. Override a token to change one group; override the underlying `--radius-*` to change several at once.
|
|
168
|
+
|
|
169
|
+
| Token | Applies to | Default |
|
|
170
|
+
|-------|-----------|---------|
|
|
171
|
+
| `--border-radius-input` | Text inputs, selects, textareas, file inputs, the Lexical editor box | `var(--radius-xl)` |
|
|
172
|
+
| `--border-radius-button` | `Button`, `ButtonIconSquare` (and the file-input "Choose" button) | `var(--border-radius-input)` |
|
|
173
|
+
| `--border-radius-panel` | `Panel`, `PanelStack`, `Modal`, `TableEmpty` icon frame | `var(--radius-2xl)` |
|
|
174
|
+
| `--border-radius-dropdown` | `DropdownPanel`, Lexical floating toolbar | `var(--radius-xl)` |
|
|
175
|
+
| `--border-radius-option` | Select option rows | `var(--radius-xl)` |
|
|
176
|
+
| `--border-radius-menu-item` | `DropdownButton`, `PanelLink`, Lexical toolbar buttons | `var(--radius-lg)` |
|
|
177
|
+
| `--border-radius-badge` | `Badge` | `var(--radius-lg)` |
|
|
178
|
+
| `--border-radius-tab` | `TabButtons` container and pills | `var(--radius-xl)` |
|
|
179
|
+
| `--border-radius-checkbox` | `InputCheck` box | `var(--radius-lg)` |
|
|
180
|
+
| `--border-radius-control-inner` | Color swatch and picker bars in `InputColor` | `var(--radius-md)` |
|
|
181
|
+
|
|
182
|
+
> `ButtonIconRound`, the toggle track/thumb, and radio dots are intentionally fully round (`rounded-full`) and are not tokenized.
|
|
183
|
+
|
|
184
|
+
### Structure — border width & shadow
|
|
185
|
+
|
|
186
|
+
| Token | Applies to | Default |
|
|
187
|
+
|-------|-----------|---------|
|
|
188
|
+
| `--border-width-input` | Border width of inputs, selects, buttons, panels, dropdowns, modals, check/radio | `1px` |
|
|
189
|
+
| `--shadow-control` | Resting elevation of buttons, inputs, panels, tabs | `var(--shadow-sm)` |
|
|
190
|
+
| `--shadow-dropdown` | `DropdownPanel` and the Lexical floating toolbar | `var(--shadow-lg)` |
|
|
191
|
+
| `--shadow-overlay` | `Modal` and `SidebarModal` | `var(--shadow-xl)` |
|
|
192
|
+
|
|
193
|
+
### Structure — ring & transition
|
|
194
|
+
|
|
195
|
+
Controls share a consistent interaction model: a focus/hover "glow" ring, a thinner inset ring on press, and a single transition duration. The ring **color** comes from the per-component `--color-*-ring` tokens (see the color sections); these set its **width** and the animation timing.
|
|
196
|
+
|
|
197
|
+
| Token | Applies to | Default |
|
|
198
|
+
|-------|-----------|---------|
|
|
199
|
+
| `--control-ring-width` | Ring width on hover, focus, focus-within, and the select/dropzone open state | `4px` |
|
|
200
|
+
| `--control-ring-width-active` | Ring width on press (`:active`) | `1px` |
|
|
201
|
+
| `--control-transition-duration` | Duration of hover/focus/press transitions on buttons, inputs, selects, the icon-button press scale, etc. | `150ms` |
|
|
202
|
+
| `--control-transition-duration-fast` | Quicker color-only transitions (table row/header hover, clickable `Badge`) | `100ms` |
|
|
203
|
+
|
|
204
|
+
> The resting state is always ringless (`ring-0`) and a few elements opt out of a focus ring entirely (dropdown items, tabs) — these are intentional and not tokenized.
|
|
205
|
+
|
|
206
|
+
### Control sizing
|
|
246
207
|
|
|
247
208
|
`Button`, `ButtonIconSquare`, `ButtonIconRound`, `Input`, `InputColor`, `InputTextArea`, `InputSelectNative`, `InputSelect`, `InputSelectSearchable`, and `InputSelectSearchableAsync` accept a `size?: 'sm' | 'md' | 'lg'` prop (default `'md'`) and read their dimensions from a single shared scale. Override these to adjust heights, padding, font size, and icon sizing consistently across all controls. Replace `{size}` with `sm`, `md`, or `lg`.
|
|
248
209
|
|
|
@@ -255,7 +216,13 @@ Define your overrides after importing the mat-ui stylesheet:
|
|
|
255
216
|
| `--control-size-{size}-icon` | Icon glyph size inside controls | `1rem` · `1.25rem` · `1.5rem` |
|
|
256
217
|
| `--control-size-{size}-icon-offset` | Distance from the input edge to a leading icon (used when an `Icon` prop is set on `Input`) | `1rem` · `1rem` · `1.25rem` |
|
|
257
218
|
|
|
258
|
-
|
|
219
|
+
### Color — focus ring
|
|
220
|
+
|
|
221
|
+
| Token | Description | Light default |
|
|
222
|
+
|-------|-------------|---------------|
|
|
223
|
+
| `--color-input-focus-ring` | Focus ring color for buttons and inputs | `rgb(17 24 39 / 0.15)` |
|
|
224
|
+
|
|
225
|
+
### Color — buttons
|
|
259
226
|
|
|
260
227
|
Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `tertiary`) uses the same set of tokens. Replace `{variant}` with the variant name.
|
|
261
228
|
|
|
@@ -273,7 +240,7 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
273
240
|
| `--color-button-{variant}-border-disabled` | Border when disabled |
|
|
274
241
|
| `--color-button-{variant}-text-disabled` | Text color when disabled |
|
|
275
242
|
|
|
276
|
-
|
|
243
|
+
### Color — inputs
|
|
277
244
|
|
|
278
245
|
| Token | Description | Light default |
|
|
279
246
|
|-------|-------------|---------------|
|
|
@@ -286,7 +253,7 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
286
253
|
| `--color-input-ring-error` | Ring color in error state | `rgb(220 38 38 / 0.2)` |
|
|
287
254
|
| `--color-input-icon` | Leading icon color | `rgb(17 24 39 / 0.6)` |
|
|
288
255
|
|
|
289
|
-
|
|
256
|
+
### Color — input labels, descriptions & errors
|
|
290
257
|
|
|
291
258
|
| Token | Description | Light default |
|
|
292
259
|
|-------|-------------|---------------|
|
|
@@ -294,16 +261,16 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
294
261
|
| `--color-input-description-text` | Description text color | `#6b7280` |
|
|
295
262
|
| `--color-input-error-text` | Error message text color | `#dc2626` |
|
|
296
263
|
|
|
297
|
-
|
|
264
|
+
### Color — input icon buttons
|
|
298
265
|
|
|
299
266
|
| Token | Description | Light default |
|
|
300
267
|
|-------|-------------|---------------|
|
|
301
268
|
| `--color-input-icon-button-ring` | Ring color for icon buttons inside inputs | `#e5e7eb` |
|
|
302
269
|
| `--color-input-icon-button-icon` | Icon color for icon buttons inside inputs | `#6b7280` |
|
|
303
270
|
|
|
304
|
-
|
|
271
|
+
### Color — file inputs
|
|
305
272
|
|
|
306
|
-
`InputFileSingle` and `UploadFileTile` are composed from existing primitives (input tokens, `Button` for the inset "Choose" button, `ButtonIconSquare` for the remove (X) button) — no dedicated tokens of their own. `InputFileMultiple` adds:
|
|
273
|
+
`InputFileSingle` and `UploadFileTile` are composed from existing primitives (input tokens, `Button` for the inset "Choose" button, `ButtonIconSquare` for the remove (X) button) — no dedicated color tokens of their own. `InputFileMultiple` adds:
|
|
307
274
|
|
|
308
275
|
| Token | Description | Light default |
|
|
309
276
|
|-------|-------------|---------------|
|
|
@@ -311,11 +278,11 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
311
278
|
|
|
312
279
|
All three components also rely on the shared `--color-status-success` / `--color-status-error` tokens for the green check / red error icons in their upload-state slots.
|
|
313
280
|
|
|
314
|
-
|
|
281
|
+
### Color — color input
|
|
315
282
|
|
|
316
283
|
`InputColor` reuses the standard input tokens — the color swatch in the field and the outline of the picker's saturation/value plane both derive from `--color-input-border`, and the field itself uses the same `--color-input-*` tokens as `Input`. The picker's hue/brightness gradients and indicator rings are intrinsic to the color-picking UI (not theme-based) and are intentionally not tokenized.
|
|
317
284
|
|
|
318
|
-
|
|
285
|
+
### Color — select options
|
|
319
286
|
|
|
320
287
|
| Token | Description | Light default |
|
|
321
288
|
|-------|-------------|---------------|
|
|
@@ -327,7 +294,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
327
294
|
| `--color-option-text-disabled` | Disabled option text color | `#9ca3af` |
|
|
328
295
|
| `--color-input-select-placeholder` | Select placeholder text color | `#6b7280` |
|
|
329
296
|
|
|
330
|
-
|
|
297
|
+
### Color — select search bar
|
|
331
298
|
|
|
332
299
|
| Token | Description | Light default |
|
|
333
300
|
|-------|-------------|---------------|
|
|
@@ -335,7 +302,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
335
302
|
| `--color-select-search-bg` | Search input background | `rgb(255 255 255 / 0.5)` |
|
|
336
303
|
| `--color-select-search-icon` | Search icon color | `#6b7280` |
|
|
337
304
|
|
|
338
|
-
|
|
305
|
+
### Color — toggle
|
|
339
306
|
|
|
340
307
|
| Token | Description | Light default |
|
|
341
308
|
|-------|-------------|---------------|
|
|
@@ -345,14 +312,15 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
345
312
|
| `--color-toggle-track-off-border` | Track border when off | `#d1d5db` |
|
|
346
313
|
| `--color-toggle-thumb-bg` | Thumb background | `#ffffff` |
|
|
347
314
|
|
|
348
|
-
|
|
315
|
+
### Color — checkbox & radio
|
|
349
316
|
|
|
350
317
|
| Token | Description | Light default |
|
|
351
318
|
|-------|-------------|---------------|
|
|
352
319
|
| `--color-check-border` | Checkbox/radio border | `#d1d5db` |
|
|
353
320
|
| `--color-check-ring` | Checkbox/radio focus ring | `rgb(17 24 39 / 0.1)` |
|
|
321
|
+
| `--color-check-checked-bg` | Checkbox/radio fill when checked | `#2563eb` |
|
|
354
322
|
|
|
355
|
-
|
|
323
|
+
### Color — dropdown menu
|
|
356
324
|
|
|
357
325
|
| Token | Description | Light default |
|
|
358
326
|
|-------|-------------|---------------|
|
|
@@ -364,7 +332,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
364
332
|
| `--color-dropdown-item-ring` | Item focus ring | `rgb(17 24 39 / 0.1)` |
|
|
365
333
|
| `--color-dropdown-group-label` | Group label text color | `#6b7280` |
|
|
366
334
|
|
|
367
|
-
|
|
335
|
+
### Color — tab buttons
|
|
368
336
|
|
|
369
337
|
| Token | Description | Light default |
|
|
370
338
|
|-------|-------------|---------------|
|
|
@@ -375,7 +343,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
375
343
|
| `--color-tab-active-bg` | Active tab background | `#ffffff` |
|
|
376
344
|
| `--color-tab-active-border` | Active tab border | `#e5e7eb` |
|
|
377
345
|
|
|
378
|
-
|
|
346
|
+
### Color — panel
|
|
379
347
|
|
|
380
348
|
| Token | Description | Light default |
|
|
381
349
|
|-------|-------------|---------------|
|
|
@@ -383,14 +351,14 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
383
351
|
| `--color-panel-border` | Panel border | `#e5e7eb` |
|
|
384
352
|
| `--color-panel-text` | Panel default text color (inherited by `PanelField`) | `#111827` |
|
|
385
353
|
|
|
386
|
-
|
|
354
|
+
### Color — modal & sidebar modal
|
|
387
355
|
|
|
388
356
|
| Token | Description | Light default |
|
|
389
357
|
|-------|-------------|---------------|
|
|
390
358
|
| `--color-modal-overlay` | Backdrop overlay color | `rgb(156 163 175 / 0.3)` |
|
|
391
359
|
| `--color-modal-bg` | Modal content background | `#ffffff` |
|
|
392
360
|
|
|
393
|
-
|
|
361
|
+
### Color — table
|
|
394
362
|
|
|
395
363
|
| Token | Description | Light default |
|
|
396
364
|
|-------|-------------|---------------|
|
|
@@ -406,13 +374,13 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
406
374
|
| `--color-table-resize-handle-hover` | Resize handle on hover | `#d1d5db` |
|
|
407
375
|
| `--color-table-resize-handle-active` | Resize handle while dragging | `#2563eb` |
|
|
408
376
|
|
|
409
|
-
|
|
377
|
+
### Color — divider
|
|
410
378
|
|
|
411
379
|
| Token | Description | Light default |
|
|
412
380
|
|-------|-------------|---------------|
|
|
413
381
|
| `--color-divider` | Divider line color | `#e5e7eb` |
|
|
414
382
|
|
|
415
|
-
|
|
383
|
+
### Color — badge
|
|
416
384
|
|
|
417
385
|
| Token | Description | Light default |
|
|
418
386
|
|-------|-------------|---------------|
|
|
@@ -425,7 +393,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
425
393
|
|
|
426
394
|
> Colored badges (red, blue, green, etc.) use Tailwind color utility classes and are not token-based. They adapt to dark mode automatically via Tailwind's `dark:` variants.
|
|
427
395
|
|
|
428
|
-
|
|
396
|
+
### Color — status
|
|
429
397
|
|
|
430
398
|
Shared color tokens for status/notification indicators. Currently used by `PanelLink`'s `status` prop, but available for any component.
|
|
431
399
|
|
|
@@ -435,3 +403,139 @@ Shared color tokens for status/notification indicators. Currently used by `Panel
|
|
|
435
403
|
| `--color-status-warning` | Warning state | `#f59e0b` |
|
|
436
404
|
| `--color-status-success` | Success state | `#16a34a` |
|
|
437
405
|
| `--color-status-info` | Informational state | `#2563eb` |
|
|
406
|
+
|
|
407
|
+
## Rich text editor (`InputLexical`)
|
|
408
|
+
|
|
409
|
+
`InputLexical` is a [Lexical](https://lexical.dev)-powered rich text editor styled like the rest of the kit. It ships with two toolbar variants that share the exact same controls:
|
|
410
|
+
|
|
411
|
+
- **`static`** (default) — a light toolbar fixed at the top of the editor.
|
|
412
|
+
- **`floating`** — a dark bar that appears above the editor while it is focused, matching the editor width.
|
|
413
|
+
|
|
414
|
+
Lexical and its plugins are **peer dependencies** — install them alongside the library:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
pnpm add lexical @lexical/react @lexical/rich-text @lexical/list @lexical/link @lexical/selection @lexical/utils
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Basic usage
|
|
421
|
+
|
|
422
|
+
The value is a **serialized Lexical editor state** (a JSON string). Pass the last `onChange` value back as `value` to restore content.
|
|
423
|
+
|
|
424
|
+
```tsx
|
|
425
|
+
import { useState } from "react";
|
|
426
|
+
import { InputLexical } from "@matthiaskrijgsman/mat-ui";
|
|
427
|
+
|
|
428
|
+
function Editor() {
|
|
429
|
+
const [value, setValue] = useState<string>();
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<InputLexical
|
|
433
|
+
label={"Description"}
|
|
434
|
+
placeholder={"Write something…"}
|
|
435
|
+
toolbar={"floating"} // or "static" (default)
|
|
436
|
+
value={value}
|
|
437
|
+
onChange={setValue}
|
|
438
|
+
autogrow // grow with content…
|
|
439
|
+
minRows={4} // …from a 4-row floor…
|
|
440
|
+
maxRows={12} // …up to 12 rows, then scroll
|
|
441
|
+
/>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Sizing mirrors `InputTextArea`: `minRows` sets a height floor, `maxRows` caps the height (content beyond it scrolls), and `autogrow` lets the editor grow with its content between the two. Without `autogrow` the editor is fixed at `minRows`.
|
|
447
|
+
|
|
448
|
+
### Extending the toolbar
|
|
449
|
+
|
|
450
|
+
The toolbar is assembled from exported **building blocks**, so you can reorder, drop, or add controls via the `renderToolbar` slot. It receives `{ editor, state, tone }` and renders into whichever variant is active — the same render function drives both the static and floating bars.
|
|
451
|
+
|
|
452
|
+
```tsx
|
|
453
|
+
import {
|
|
454
|
+
InputLexical,
|
|
455
|
+
LexicalBlockTypeSelect,
|
|
456
|
+
LexicalFormatButtons,
|
|
457
|
+
LexicalListButtons,
|
|
458
|
+
LexicalLinkButton,
|
|
459
|
+
LexicalHistoryButtons,
|
|
460
|
+
LexicalToolbarDivider,
|
|
461
|
+
} from "@matthiaskrijgsman/mat-ui";
|
|
462
|
+
|
|
463
|
+
<InputLexical
|
|
464
|
+
renderToolbar={() => (
|
|
465
|
+
<>
|
|
466
|
+
<LexicalFormatButtons/>
|
|
467
|
+
<LexicalToolbarDivider/>
|
|
468
|
+
<LexicalListButtons/>
|
|
469
|
+
<LexicalLinkButton/>
|
|
470
|
+
<LexicalToolbarDivider/>
|
|
471
|
+
<LexicalHistoryButtons/>
|
|
472
|
+
</>
|
|
473
|
+
)}
|
|
474
|
+
/>;
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Building blocks read the active editor and formatting `state`/`tone` from context, so they work in either variant with no extra wiring. Dividers automatically flip orientation (and the whole toolbar collapses overflowing controls into a vertical `⋮` dropdown) when space runs out — return each control as a top-level child so it stays individually measurable.
|
|
478
|
+
|
|
479
|
+
#### Custom controls
|
|
480
|
+
|
|
481
|
+
Build your own control with `LexicalToolbarButton` plus Lexical's editor context. `useLexicalToolbar()` exposes the current `{ state, tone }`:
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
485
|
+
import { FORMAT_TEXT_COMMAND } from "lexical";
|
|
486
|
+
import { IconStrikethrough } from "@tabler/icons-react";
|
|
487
|
+
import { LexicalToolbarButton, useLexicalToolbar } from "@matthiaskrijgsman/mat-ui";
|
|
488
|
+
|
|
489
|
+
const StrikethroughButton = () => {
|
|
490
|
+
const [editor] = useLexicalComposerContext();
|
|
491
|
+
const { tone } = useLexicalToolbar();
|
|
492
|
+
return (
|
|
493
|
+
<LexicalToolbarButton
|
|
494
|
+
Icon={IconStrikethrough}
|
|
495
|
+
tone={tone}
|
|
496
|
+
aria-label={"Strikethrough"}
|
|
497
|
+
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")}
|
|
498
|
+
/>
|
|
499
|
+
);
|
|
500
|
+
};
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
#### Registering extra Lexical nodes
|
|
504
|
+
|
|
505
|
+
The built-in set covers headings, lists, links, and quotes. For anything else (tables, mentions, code blocks, …) install the node's package yourself and pass the node via the `nodes` prop — it is registered alongside the built-in set:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
pnpm add @lexical/code
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
import { CodeNode } from "@lexical/code";
|
|
513
|
+
|
|
514
|
+
<InputLexical nodes={[CodeNode]} renderToolbar={/* … */} />;
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
> mat-ui does not bundle `lexical` or any `@lexical/*` package — they are peer dependencies, so a single shared copy is used. Only register a given node type once: don't pass a node that is already in the built-in set, or Lexical throws a duplicate-type error.
|
|
518
|
+
|
|
519
|
+
#### Adding plugins (the `children` slot)
|
|
520
|
+
|
|
521
|
+
A Lexical feature is usually a **node *plus* a plugin**. Register the node with `nodes`, then mount the plugin(s) as **children** — they run inside the editor alongside the built-ins (history, lists, links). Any `@lexical/react` plugin or your own works:
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
|
|
525
|
+
import { CodeHighlightPlugin } from "@lexical/react/LexicalCodeHighlightPlugin";
|
|
526
|
+
|
|
527
|
+
<InputLexical nodes={[CodeNode]}>
|
|
528
|
+
<CodeHighlightPlugin />
|
|
529
|
+
<TabIndentationPlugin />
|
|
530
|
+
{/* …or your own plugin using useLexicalComposerContext() */}
|
|
531
|
+
</InputLexical>;
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Style custom nodes by merging theme classes over the defaults with the `theme` prop:
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
<InputLexical nodes={[CodeNode]} theme={{ code: "my-code-block" }}>
|
|
538
|
+
<CodeHighlightPlugin />
|
|
539
|
+
</InputLexical>;
|
|
540
|
+
```
|
|
541
|
+
|