@matthiaskrijgsman/mat-ui 0.0.29 → 0.0.31

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 CHANGED
@@ -49,6 +49,141 @@ 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
+
52
187
  ## Dark Theme
53
188
 
54
189
  mat-ui ships with built-in dark theme support. Add the `dark` class to the `<html>` element to activate it:
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ import type { EditorThemeClasses, Klass, LexicalNode } from "lexical";
3
+ import type { LexicalToolbarRender } from "@/components/inputs/input-lexical/use-lexical-toolbar.ts";
4
+ export type Size = "sm" | "md" | "lg";
5
+ export type LexicalToolbarVariant = "static" | "floating";
6
+ export type InputLexicalProps = {
7
+ label?: string | React.ReactNode;
8
+ description?: string | React.ReactNode;
9
+ error?: string | React.ReactNode;
10
+ placeholder?: string;
11
+ /** Serialized editor state (JSON string from a previous onChange), or undefined for empty. */
12
+ value?: string;
13
+ onChange?: (value: string) => void;
14
+ size?: Size;
15
+ toolbar?: LexicalToolbarVariant;
16
+ /** Override the default toolbar content. Drop in the exported building blocks. */
17
+ renderToolbar?: LexicalToolbarRender;
18
+ /** Minimum visible rows — sets a height floor for the editable area. */
19
+ minRows?: number;
20
+ /** Maximum visible rows. Content beyond this scrolls. */
21
+ maxRows?: number;
22
+ /** Grow the editor with its content (between minRows and maxRows). */
23
+ autogrow?: boolean;
24
+ namespace?: string;
25
+ /** Extra Lexical nodes to register alongside the built-in set. */
26
+ nodes?: Array<Klass<LexicalNode>>;
27
+ /** Theme classes merged over the defaults (e.g. to style custom nodes). */
28
+ theme?: EditorThemeClasses;
29
+ autoFocus?: boolean;
30
+ /** Extra Lexical plugins, mounted inside the editor alongside the built-ins. */
31
+ children?: React.ReactNode;
32
+ className?: string;
33
+ };
34
+ export declare const InputLexical: (props: InputLexicalProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const LexicalBlockTypeSelect: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { type LexicalToolbarRender } from "@/components/inputs/input-lexical/use-lexical-toolbar.ts";
2
+ export type LexicalFloatingToolbarProps = {
3
+ render?: LexicalToolbarRender;
4
+ };
5
+ export declare const LexicalFloatingToolbar: (props: LexicalFloatingToolbarProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const LexicalFormatButtons: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const LexicalHistoryButtons: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const LexicalLinkButton: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare const LexicalListButtons: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { type LexicalToolbarRender } from "@/components/inputs/input-lexical/use-lexical-toolbar.ts";
2
+ export declare const lexicalDefaultToolbarItems: () => import("react/jsx-runtime").JSX.Element;
3
+ export declare const LexicalDefaultToolbarContent: () => import("react/jsx-runtime").JSX.Element;
4
+ export type LexicalToolbarProps = {
5
+ render?: LexicalToolbarRender;
6
+ className?: string;
7
+ };
8
+ export declare const LexicalToolbar: (props: LexicalToolbarProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ import type { TablerIcon } from "@tabler/icons-react";
3
+ import type { LexicalToolbarTone } from "@/components/inputs/input-lexical/use-lexical-toolbar.ts";
4
+ export type LexicalToolbarButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
5
+ Icon: TablerIcon;
6
+ active?: boolean;
7
+ tone?: LexicalToolbarTone;
8
+ };
9
+ export declare const LexicalToolbarButton: React.ForwardRefExoticComponent<React.ButtonHTMLAttributes<HTMLButtonElement> & {
10
+ Icon: TablerIcon;
11
+ active?: boolean;
12
+ tone?: LexicalToolbarTone;
13
+ } & React.RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,6 @@
1
+ import { type LexicalToolbarTone } from "@/components/inputs/input-lexical/use-lexical-toolbar.ts";
2
+ export type LexicalToolbarDividerProps = {
3
+ tone?: LexicalToolbarTone;
4
+ className?: string;
5
+ };
6
+ export declare const LexicalToolbarDivider: (props: LexicalToolbarDividerProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ export type LexicalToolbarItemsProps = {
3
+ children: React.ReactNode;
4
+ };
5
+ export declare const LexicalToolbarItems: (props: LexicalToolbarItemsProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import type { Klass, LexicalNode } from "lexical";
2
+ export declare const LEXICAL_NODES: Array<Klass<LexicalNode>>;
3
+ export declare const lexicalTheme: {
4
+ paragraph: string;
5
+ heading: {
6
+ h1: string;
7
+ h2: string;
8
+ h3: string;
9
+ h4: string;
10
+ };
11
+ quote: string;
12
+ list: {
13
+ ul: string;
14
+ ol: string;
15
+ listitem: string;
16
+ nested: {
17
+ listitem: string;
18
+ };
19
+ };
20
+ link: string;
21
+ text: {
22
+ bold: string;
23
+ italic: string;
24
+ underline: string;
25
+ };
26
+ };
@@ -0,0 +1,31 @@
1
+ import * as React from "react";
2
+ import { type LexicalEditor } from "lexical";
3
+ export type LexicalBlockType = "paragraph" | "h1" | "h2" | "h3" | "h4";
4
+ export type LexicalToolbarState = {
5
+ isBold: boolean;
6
+ isItalic: boolean;
7
+ isUnderline: boolean;
8
+ isLink: boolean;
9
+ isUnorderedList: boolean;
10
+ isOrderedList: boolean;
11
+ blockType: LexicalBlockType;
12
+ canUndo: boolean;
13
+ canRedo: boolean;
14
+ };
15
+ export declare const DEFAULT_LEXICAL_TOOLBAR_STATE: LexicalToolbarState;
16
+ export type LexicalToolbarTone = "light" | "dark";
17
+ export type LexicalToolbarOrientation = "horizontal" | "vertical";
18
+ export type LexicalToolbarContextValue = {
19
+ state: LexicalToolbarState;
20
+ tone: LexicalToolbarTone;
21
+ orientation?: LexicalToolbarOrientation;
22
+ };
23
+ export declare const LexicalToolbarContext: React.Context<LexicalToolbarContextValue>;
24
+ export declare const useLexicalToolbar: () => LexicalToolbarContextValue;
25
+ export declare const useLexicalToolbarState: () => LexicalToolbarState;
26
+ export type LexicalToolbarRenderContext = {
27
+ editor: LexicalEditor;
28
+ state: LexicalToolbarState;
29
+ tone: LexicalToolbarTone;
30
+ };
31
+ export type LexicalToolbarRender = (ctx: LexicalToolbarRenderContext) => React.ReactNode;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,19 @@ export { InputIconButtonTray } from "./components/inputs/InputIconButtonTray.tsx
18
18
  export { InputFileSingle } from "./components/inputs/input-file/InputFileSingle.tsx";
19
19
  export { InputFileMultiple } from "./components/inputs/input-file/InputFileMultiple.tsx";
20
20
  export { UploadFileTile } from "./components/inputs/input-file/UploadFileTile.tsx";
21
+ export { InputLexical } from "./components/inputs/input-lexical/InputLexical.tsx";
22
+ export { LexicalToolbar, LexicalDefaultToolbarContent } from "./components/inputs/input-lexical/LexicalToolbar.tsx";
23
+ export { LexicalFloatingToolbar } from "./components/inputs/input-lexical/LexicalFloatingToolbar.tsx";
24
+ export { LexicalToolbarItems } from "./components/inputs/input-lexical/LexicalToolbarItems.tsx";
25
+ export { LexicalToolbarButton } from "./components/inputs/input-lexical/LexicalToolbarButton.tsx";
26
+ export { LexicalToolbarDivider } from "./components/inputs/input-lexical/LexicalToolbarDivider.tsx";
27
+ export { LexicalBlockTypeSelect } from "./components/inputs/input-lexical/LexicalBlockTypeSelect.tsx";
28
+ export { LexicalFormatButtons } from "./components/inputs/input-lexical/LexicalFormatButtons.tsx";
29
+ export { LexicalListButtons } from "./components/inputs/input-lexical/LexicalListButtons.tsx";
30
+ export { LexicalLinkButton } from "./components/inputs/input-lexical/LexicalLinkButton.tsx";
31
+ export { LexicalHistoryButtons } from "./components/inputs/input-lexical/LexicalHistoryButtons.tsx";
32
+ export { useLexicalToolbar, useLexicalToolbarState } from "./components/inputs/input-lexical/use-lexical-toolbar.ts";
33
+ export { lexicalTheme, LEXICAL_NODES } from "./components/inputs/input-lexical/lexical-theme.ts";
21
34
  export { InputSelectNative } from "./components/inputs/InputSelectNative.tsx";
22
35
  export { InputSelect } from "./components/inputs/InputSelect.tsx";
23
36
  export { InputSelectSearchable } from "./components/inputs/InputSelectSearchable.tsx";