@matthiaskrijgsman/mat-ui 0.0.41 → 0.0.43
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 +290 -180
- package/dist/components/TabButtons.d.ts +3 -0
- package/dist/index.js +1366 -1360
- 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,142 @@ 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-tab-count` | `TabButtons` count chip | `--font-weight-strong` |
|
|
146
|
+
| `--font-weight-input-label` | `InputLabel` (label above inputs) | `--font-weight-medium` |
|
|
147
|
+
| `--font-weight-input-description` | `InputDescription` | `--font-weight-medium` |
|
|
148
|
+
| `--font-weight-input-error` | `InputError` | `--font-weight-medium` |
|
|
149
|
+
| `--font-weight-input-option-label` | Inline labels on `InputCheck` / `InputRadio` / `InputToggle`, file tile names | `--font-weight-medium` |
|
|
150
|
+
| `--font-weight-dropdown-item` | `DropdownButton`, `PanelLink` | `--font-weight-strong` |
|
|
151
|
+
| `--font-weight-group-header` | Dropdown group labels, select group headers | `--font-weight-strong` |
|
|
152
|
+
| `--font-weight-panel-field` | `PanelField` label | `--font-weight-medium` |
|
|
153
|
+
| `--font-weight-panel-link` | `PanelLink` | `--font-weight-strong` |
|
|
154
|
+
| `--font-weight-table-header` | `Table` header cells, `TableEmpty` title | `--font-weight-medium` |
|
|
155
|
+
| `--font-weight-table-cell` | `Table` body cells | `--font-weight-normal` |
|
|
156
|
+
|
|
157
|
+
| Token | Description | Default |
|
|
158
|
+
|-------|-------------|---------|
|
|
159
|
+
| `--font-family-base` | Typeface for all kit text (defaults to the host font) | `inherit` |
|
|
160
|
+
| `--font-size-label` | Dropdown / select group label size | `var(--text-sm)` |
|
|
161
|
+
| `--font-size-description` | `InputDescription` and `PanelField` label size | `var(--text-sm)` |
|
|
162
|
+
| `--font-size-error` | `InputError` size | `var(--text-sm)` |
|
|
163
|
+
| `--font-size-tab-count` | `TabButtons` count chip size | `var(--text-xs)` |
|
|
164
|
+
|
|
165
|
+
> The text size of the input/button itself comes from the **control sizing** scale below (`--control-size-{size}-font-size`), not from these tokens.
|
|
166
|
+
|
|
167
|
+
### Structure — border radius
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
171
|
+
| Token | Applies to | Default |
|
|
172
|
+
|-------|-----------|---------|
|
|
173
|
+
| `--border-radius-input` | Text inputs, selects, textareas, file inputs, the Lexical editor box | `var(--radius-xl)` |
|
|
174
|
+
| `--border-radius-button` | `Button`, `ButtonIconSquare` (and the file-input "Choose" button) | `var(--border-radius-input)` |
|
|
175
|
+
| `--border-radius-panel` | `Panel`, `PanelStack`, `Modal`, `TableEmpty` icon frame | `var(--radius-2xl)` |
|
|
176
|
+
| `--border-radius-dropdown` | `DropdownPanel`, Lexical floating toolbar | `var(--radius-xl)` |
|
|
177
|
+
| `--border-radius-option` | Select option rows | `var(--radius-xl)` |
|
|
178
|
+
| `--border-radius-menu-item` | `DropdownButton`, `PanelLink`, Lexical toolbar buttons | `var(--radius-lg)` |
|
|
179
|
+
| `--border-radius-badge` | `Badge` | `var(--radius-lg)` |
|
|
180
|
+
| `--border-radius-tab` | `TabButtons` container and pills | `var(--radius-xl)` |
|
|
181
|
+
| `--border-radius-checkbox` | `InputCheck` box | `var(--radius-lg)` |
|
|
182
|
+
| `--border-radius-control-inner` | Color swatch and picker bars in `InputColor` | `var(--radius-md)` |
|
|
183
|
+
|
|
184
|
+
> `ButtonIconRound`, the toggle track/thumb, and radio dots are intentionally fully round (`rounded-full`) and are not tokenized.
|
|
185
|
+
|
|
186
|
+
### Structure — border width & shadow
|
|
187
|
+
|
|
188
|
+
| Token | Applies to | Default |
|
|
189
|
+
|-------|-----------|---------|
|
|
190
|
+
| `--border-width-input` | Border width of inputs, selects, buttons, panels, dropdowns, modals, check/radio | `1px` |
|
|
191
|
+
| `--shadow-control` | Resting elevation of buttons, inputs, panels, tabs | `var(--shadow-sm)` |
|
|
192
|
+
| `--shadow-dropdown` | `DropdownPanel` and the Lexical floating toolbar | `var(--shadow-lg)` |
|
|
193
|
+
| `--shadow-overlay` | `Modal` and `SidebarModal` | `var(--shadow-xl)` |
|
|
194
|
+
|
|
195
|
+
### Structure — ring & transition
|
|
196
|
+
|
|
197
|
+
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.
|
|
198
|
+
|
|
199
|
+
| Token | Applies to | Default |
|
|
200
|
+
|-------|-----------|---------|
|
|
201
|
+
| `--control-ring-width` | Ring width on hover, focus, focus-within, and the select/dropzone open state | `4px` |
|
|
202
|
+
| `--control-ring-width-active` | Ring width on press (`:active`) | `1px` |
|
|
203
|
+
| `--control-transition-duration` | Duration of hover/focus/press transitions on buttons, inputs, selects, the icon-button press scale, etc. | `150ms` |
|
|
204
|
+
| `--control-transition-duration-fast` | Quicker color-only transitions (table row/header hover, clickable `Badge`) | `100ms` |
|
|
205
|
+
|
|
206
|
+
> 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.
|
|
207
|
+
|
|
208
|
+
### Control sizing
|
|
246
209
|
|
|
247
210
|
`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
211
|
|
|
@@ -255,7 +218,13 @@ Define your overrides after importing the mat-ui stylesheet:
|
|
|
255
218
|
| `--control-size-{size}-icon` | Icon glyph size inside controls | `1rem` · `1.25rem` · `1.5rem` |
|
|
256
219
|
| `--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
220
|
|
|
258
|
-
|
|
221
|
+
### Color — focus ring
|
|
222
|
+
|
|
223
|
+
| Token | Description | Light default |
|
|
224
|
+
|-------|-------------|---------------|
|
|
225
|
+
| `--color-input-focus-ring` | Focus ring color for buttons and inputs | `rgb(17 24 39 / 0.15)` |
|
|
226
|
+
|
|
227
|
+
### Color — buttons
|
|
259
228
|
|
|
260
229
|
Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `tertiary`) uses the same set of tokens. Replace `{variant}` with the variant name.
|
|
261
230
|
|
|
@@ -273,7 +242,7 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
273
242
|
| `--color-button-{variant}-border-disabled` | Border when disabled |
|
|
274
243
|
| `--color-button-{variant}-text-disabled` | Text color when disabled |
|
|
275
244
|
|
|
276
|
-
|
|
245
|
+
### Color — inputs
|
|
277
246
|
|
|
278
247
|
| Token | Description | Light default |
|
|
279
248
|
|-------|-------------|---------------|
|
|
@@ -286,7 +255,7 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
286
255
|
| `--color-input-ring-error` | Ring color in error state | `rgb(220 38 38 / 0.2)` |
|
|
287
256
|
| `--color-input-icon` | Leading icon color | `rgb(17 24 39 / 0.6)` |
|
|
288
257
|
|
|
289
|
-
|
|
258
|
+
### Color — input labels, descriptions & errors
|
|
290
259
|
|
|
291
260
|
| Token | Description | Light default |
|
|
292
261
|
|-------|-------------|---------------|
|
|
@@ -294,16 +263,16 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
294
263
|
| `--color-input-description-text` | Description text color | `#6b7280` |
|
|
295
264
|
| `--color-input-error-text` | Error message text color | `#dc2626` |
|
|
296
265
|
|
|
297
|
-
|
|
266
|
+
### Color — input icon buttons
|
|
298
267
|
|
|
299
268
|
| Token | Description | Light default |
|
|
300
269
|
|-------|-------------|---------------|
|
|
301
270
|
| `--color-input-icon-button-ring` | Ring color for icon buttons inside inputs | `#e5e7eb` |
|
|
302
271
|
| `--color-input-icon-button-icon` | Icon color for icon buttons inside inputs | `#6b7280` |
|
|
303
272
|
|
|
304
|
-
|
|
273
|
+
### Color — file inputs
|
|
305
274
|
|
|
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:
|
|
275
|
+
`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
276
|
|
|
308
277
|
| Token | Description | Light default |
|
|
309
278
|
|-------|-------------|---------------|
|
|
@@ -311,11 +280,11 @@ Each button variant (`primary`, `white`, `black`, `transparent`, `secondary`, `t
|
|
|
311
280
|
|
|
312
281
|
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
282
|
|
|
314
|
-
|
|
283
|
+
### Color — color input
|
|
315
284
|
|
|
316
285
|
`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
286
|
|
|
318
|
-
|
|
287
|
+
### Color — select options
|
|
319
288
|
|
|
320
289
|
| Token | Description | Light default |
|
|
321
290
|
|-------|-------------|---------------|
|
|
@@ -327,7 +296,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
327
296
|
| `--color-option-text-disabled` | Disabled option text color | `#9ca3af` |
|
|
328
297
|
| `--color-input-select-placeholder` | Select placeholder text color | `#6b7280` |
|
|
329
298
|
|
|
330
|
-
|
|
299
|
+
### Color — select search bar
|
|
331
300
|
|
|
332
301
|
| Token | Description | Light default |
|
|
333
302
|
|-------|-------------|---------------|
|
|
@@ -335,7 +304,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
335
304
|
| `--color-select-search-bg` | Search input background | `rgb(255 255 255 / 0.5)` |
|
|
336
305
|
| `--color-select-search-icon` | Search icon color | `#6b7280` |
|
|
337
306
|
|
|
338
|
-
|
|
307
|
+
### Color — toggle
|
|
339
308
|
|
|
340
309
|
| Token | Description | Light default |
|
|
341
310
|
|-------|-------------|---------------|
|
|
@@ -345,14 +314,15 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
345
314
|
| `--color-toggle-track-off-border` | Track border when off | `#d1d5db` |
|
|
346
315
|
| `--color-toggle-thumb-bg` | Thumb background | `#ffffff` |
|
|
347
316
|
|
|
348
|
-
|
|
317
|
+
### Color — checkbox & radio
|
|
349
318
|
|
|
350
319
|
| Token | Description | Light default |
|
|
351
320
|
|-------|-------------|---------------|
|
|
352
321
|
| `--color-check-border` | Checkbox/radio border | `#d1d5db` |
|
|
353
322
|
| `--color-check-ring` | Checkbox/radio focus ring | `rgb(17 24 39 / 0.1)` |
|
|
323
|
+
| `--color-check-checked-bg` | Checkbox/radio fill when checked | `#2563eb` |
|
|
354
324
|
|
|
355
|
-
|
|
325
|
+
### Color — dropdown menu
|
|
356
326
|
|
|
357
327
|
| Token | Description | Light default |
|
|
358
328
|
|-------|-------------|---------------|
|
|
@@ -364,7 +334,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
364
334
|
| `--color-dropdown-item-ring` | Item focus ring | `rgb(17 24 39 / 0.1)` |
|
|
365
335
|
| `--color-dropdown-group-label` | Group label text color | `#6b7280` |
|
|
366
336
|
|
|
367
|
-
|
|
337
|
+
### Color — tab buttons
|
|
368
338
|
|
|
369
339
|
| Token | Description | Light default |
|
|
370
340
|
|-------|-------------|---------------|
|
|
@@ -374,8 +344,12 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
374
344
|
| `--color-tab-bg-active` | Tab background on press | `rgb(209 213 219 / 0.8)` |
|
|
375
345
|
| `--color-tab-active-bg` | Active tab background | `#ffffff` |
|
|
376
346
|
| `--color-tab-active-border` | Active tab border | `#e5e7eb` |
|
|
347
|
+
| `--color-tab-count-bg` | Count chip background (inactive tab) | `#e5e7eb` |
|
|
348
|
+
| `--color-tab-count-text` | Count chip text (inactive tab) | `#374151` |
|
|
349
|
+
| `--color-tab-active-count-bg` | Count chip background (active tab) | `#111827` |
|
|
350
|
+
| `--color-tab-active-count-text` | Count chip text (active tab) | `#ffffff` |
|
|
377
351
|
|
|
378
|
-
|
|
352
|
+
### Color — panel
|
|
379
353
|
|
|
380
354
|
| Token | Description | Light default |
|
|
381
355
|
|-------|-------------|---------------|
|
|
@@ -383,14 +357,14 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
383
357
|
| `--color-panel-border` | Panel border | `#e5e7eb` |
|
|
384
358
|
| `--color-panel-text` | Panel default text color (inherited by `PanelField`) | `#111827` |
|
|
385
359
|
|
|
386
|
-
|
|
360
|
+
### Color — modal & sidebar modal
|
|
387
361
|
|
|
388
362
|
| Token | Description | Light default |
|
|
389
363
|
|-------|-------------|---------------|
|
|
390
364
|
| `--color-modal-overlay` | Backdrop overlay color | `rgb(156 163 175 / 0.3)` |
|
|
391
365
|
| `--color-modal-bg` | Modal content background | `#ffffff` |
|
|
392
366
|
|
|
393
|
-
|
|
367
|
+
### Color — table
|
|
394
368
|
|
|
395
369
|
| Token | Description | Light default |
|
|
396
370
|
|-------|-------------|---------------|
|
|
@@ -406,13 +380,13 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
406
380
|
| `--color-table-resize-handle-hover` | Resize handle on hover | `#d1d5db` |
|
|
407
381
|
| `--color-table-resize-handle-active` | Resize handle while dragging | `#2563eb` |
|
|
408
382
|
|
|
409
|
-
|
|
383
|
+
### Color — divider
|
|
410
384
|
|
|
411
385
|
| Token | Description | Light default |
|
|
412
386
|
|-------|-------------|---------------|
|
|
413
387
|
| `--color-divider` | Divider line color | `#e5e7eb` |
|
|
414
388
|
|
|
415
|
-
|
|
389
|
+
### Color — badge
|
|
416
390
|
|
|
417
391
|
| Token | Description | Light default |
|
|
418
392
|
|-------|-------------|---------------|
|
|
@@ -425,7 +399,7 @@ All three components also rely on the shared `--color-status-success` / `--color
|
|
|
425
399
|
|
|
426
400
|
> 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
401
|
|
|
428
|
-
|
|
402
|
+
### Color — status
|
|
429
403
|
|
|
430
404
|
Shared color tokens for status/notification indicators. Currently used by `PanelLink`'s `status` prop, but available for any component.
|
|
431
405
|
|
|
@@ -435,3 +409,139 @@ Shared color tokens for status/notification indicators. Currently used by `Panel
|
|
|
435
409
|
| `--color-status-warning` | Warning state | `#f59e0b` |
|
|
436
410
|
| `--color-status-success` | Success state | `#16a34a` |
|
|
437
411
|
| `--color-status-info` | Informational state | `#2563eb` |
|
|
412
|
+
|
|
413
|
+
## Rich text editor (`InputLexical`)
|
|
414
|
+
|
|
415
|
+
`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:
|
|
416
|
+
|
|
417
|
+
- **`static`** (default) — a light toolbar fixed at the top of the editor.
|
|
418
|
+
- **`floating`** — a dark bar that appears above the editor while it is focused, matching the editor width.
|
|
419
|
+
|
|
420
|
+
Lexical and its plugins are **peer dependencies** — install them alongside the library:
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
pnpm add lexical @lexical/react @lexical/rich-text @lexical/list @lexical/link @lexical/selection @lexical/utils
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Basic usage
|
|
427
|
+
|
|
428
|
+
The value is a **serialized Lexical editor state** (a JSON string). Pass the last `onChange` value back as `value` to restore content.
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
import { useState } from "react";
|
|
432
|
+
import { InputLexical } from "@matthiaskrijgsman/mat-ui";
|
|
433
|
+
|
|
434
|
+
function Editor() {
|
|
435
|
+
const [value, setValue] = useState<string>();
|
|
436
|
+
|
|
437
|
+
return (
|
|
438
|
+
<InputLexical
|
|
439
|
+
label={"Description"}
|
|
440
|
+
placeholder={"Write something…"}
|
|
441
|
+
toolbar={"floating"} // or "static" (default)
|
|
442
|
+
value={value}
|
|
443
|
+
onChange={setValue}
|
|
444
|
+
autogrow // grow with content…
|
|
445
|
+
minRows={4} // …from a 4-row floor…
|
|
446
|
+
maxRows={12} // …up to 12 rows, then scroll
|
|
447
|
+
/>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
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`.
|
|
453
|
+
|
|
454
|
+
### Extending the toolbar
|
|
455
|
+
|
|
456
|
+
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.
|
|
457
|
+
|
|
458
|
+
```tsx
|
|
459
|
+
import {
|
|
460
|
+
InputLexical,
|
|
461
|
+
LexicalBlockTypeSelect,
|
|
462
|
+
LexicalFormatButtons,
|
|
463
|
+
LexicalListButtons,
|
|
464
|
+
LexicalLinkButton,
|
|
465
|
+
LexicalHistoryButtons,
|
|
466
|
+
LexicalToolbarDivider,
|
|
467
|
+
} from "@matthiaskrijgsman/mat-ui";
|
|
468
|
+
|
|
469
|
+
<InputLexical
|
|
470
|
+
renderToolbar={() => (
|
|
471
|
+
<>
|
|
472
|
+
<LexicalFormatButtons/>
|
|
473
|
+
<LexicalToolbarDivider/>
|
|
474
|
+
<LexicalListButtons/>
|
|
475
|
+
<LexicalLinkButton/>
|
|
476
|
+
<LexicalToolbarDivider/>
|
|
477
|
+
<LexicalHistoryButtons/>
|
|
478
|
+
</>
|
|
479
|
+
)}
|
|
480
|
+
/>;
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
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.
|
|
484
|
+
|
|
485
|
+
#### Custom controls
|
|
486
|
+
|
|
487
|
+
Build your own control with `LexicalToolbarButton` plus Lexical's editor context. `useLexicalToolbar()` exposes the current `{ state, tone }`:
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
491
|
+
import { FORMAT_TEXT_COMMAND } from "lexical";
|
|
492
|
+
import { IconStrikethrough } from "@tabler/icons-react";
|
|
493
|
+
import { LexicalToolbarButton, useLexicalToolbar } from "@matthiaskrijgsman/mat-ui";
|
|
494
|
+
|
|
495
|
+
const StrikethroughButton = () => {
|
|
496
|
+
const [editor] = useLexicalComposerContext();
|
|
497
|
+
const { tone } = useLexicalToolbar();
|
|
498
|
+
return (
|
|
499
|
+
<LexicalToolbarButton
|
|
500
|
+
Icon={IconStrikethrough}
|
|
501
|
+
tone={tone}
|
|
502
|
+
aria-label={"Strikethrough"}
|
|
503
|
+
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")}
|
|
504
|
+
/>
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### Registering extra Lexical nodes
|
|
510
|
+
|
|
511
|
+
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:
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
pnpm add @lexical/code
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
```tsx
|
|
518
|
+
import { CodeNode } from "@lexical/code";
|
|
519
|
+
|
|
520
|
+
<InputLexical nodes={[CodeNode]} renderToolbar={/* … */} />;
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
> 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.
|
|
524
|
+
|
|
525
|
+
#### Adding plugins (the `children` slot)
|
|
526
|
+
|
|
527
|
+
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:
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
|
|
531
|
+
import { CodeHighlightPlugin } from "@lexical/react/LexicalCodeHighlightPlugin";
|
|
532
|
+
|
|
533
|
+
<InputLexical nodes={[CodeNode]}>
|
|
534
|
+
<CodeHighlightPlugin />
|
|
535
|
+
<TabIndentationPlugin />
|
|
536
|
+
{/* …or your own plugin using useLexicalComposerContext() */}
|
|
537
|
+
</InputLexical>;
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Style custom nodes by merging theme classes over the defaults with the `theme` prop:
|
|
541
|
+
|
|
542
|
+
```tsx
|
|
543
|
+
<InputLexical nodes={[CodeNode]} theme={{ code: "my-code-block" }}>
|
|
544
|
+
<CodeHighlightPlugin />
|
|
545
|
+
</InputLexical>;
|
|
546
|
+
```
|
|
547
|
+
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { TablerIcon } from "@tabler/icons-react";
|
|
3
|
+
export type Size = 'sm' | 'md' | 'lg';
|
|
3
4
|
export type TabButton = {
|
|
4
5
|
label: string | React.ReactNode;
|
|
5
6
|
active?: boolean;
|
|
6
7
|
onClick?: () => void;
|
|
7
8
|
href?: string;
|
|
8
9
|
Icon?: TablerIcon;
|
|
10
|
+
count?: React.ReactNode;
|
|
9
11
|
};
|
|
10
12
|
export type TabButtonsProps = {
|
|
11
13
|
className?: string;
|
|
12
14
|
tabs: TabButton[];
|
|
15
|
+
size?: Size;
|
|
13
16
|
};
|
|
14
17
|
export declare const TabButtons: (props: TabButtonsProps) => import("react/jsx-runtime").JSX.Element;
|