silk-compose 0.0.1

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 ADDED
@@ -0,0 +1,124 @@
1
+ # Silk
2
+
3
+ An opinionated, batteries-included rich text editor for React, built on [Lexical](https://lexical.dev).
4
+
5
+ Silk ships as a single component with styles, sensible defaults, and a curated feature set — so you can drop it into a project and have a polished editing experience without wiring together a dozen plugins.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install silk-compose lexical @lexical/react @lexical/rich-text @lexical/code \
11
+ @lexical/code-shiki @lexical/history @lexical/link @lexical/list \
12
+ @lexical/markdown @lexical/selection @lexical/dragon
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```tsx
18
+ import { SilkEditor } from "silk-compose";
19
+ import "silk-compose/styles";
20
+
21
+ function App() {
22
+ return <SilkEditor />;
23
+ }
24
+ ```
25
+
26
+ That's it. You get a fully functional editor with formatting, code blocks, lists, links, images, and more.
27
+
28
+ ## Saving and restoring content
29
+
30
+ Silk uses Lexical's native serialization. Pass an `onChange` callback to receive the editor state as a JSON string on every content change, and `initialEditorState` to restore it.
31
+
32
+ ```tsx
33
+ function App() {
34
+ const [saved, setSaved] = useState<string | undefined>(
35
+ () => localStorage.getItem("doc") ?? undefined,
36
+ );
37
+
38
+ return (
39
+ <SilkEditor
40
+ initialEditorState={saved}
41
+ onChange={(json) => localStorage.setItem("doc", json)}
42
+ />
43
+ );
44
+ }
45
+ ```
46
+
47
+ The JSON string is a complete snapshot of the document — text, formatting, images, code blocks, everything. Store it however you like (database, local storage, file) and pass it back to restore the editor exactly as it was.
48
+
49
+ ## Props
50
+
51
+ | Prop | Type | Default | Description |
52
+ |---|---|---|---|
53
+ | `editable` | `boolean` | `true` | Toggle between edit and read-only mode at runtime. |
54
+ | `onChange` | `(json: string) => void` | — | Called on every content change with the serialized editor state. |
55
+ | `initialEditorState` | `string` | — | JSON string from a previous `onChange` to restore content. |
56
+ | `features` | `SilkFeatures` | All enabled | Toggle feature groups on/off. |
57
+ | `namespace` | `string` | `"silk-composeor"` | Lexical editor namespace. |
58
+ | `className` | `string` | — | Additional CSS class on the container. |
59
+ | `theme` | `EditorThemeClasses` | — | Lexical theme overrides (deep-merged with defaults). |
60
+ | `onError` | `(error: Error) => void` | `console.error` | Error handler for Lexical. |
61
+
62
+ ## Features
63
+
64
+ Everything is enabled by default. Disable individual features via the `features` prop:
65
+
66
+ ```tsx
67
+ <SilkEditor features={{ code: false, dragon: false }} />
68
+ ```
69
+
70
+ | Feature | Key | What it includes |
71
+ |---|---|---|
72
+ | **Rich text** | `richText` | Bold, italic, underline, inline code, headings (H1/H2), block quotes |
73
+ | **Code blocks** | `code` | Syntax-highlighted code blocks powered by Shiki, with a language selector |
74
+ | **Lists** | `lists` | Ordered and unordered lists with Tab/Shift+Tab indentation. Nested ordered lists cycle through numbers, uppercase letters, and lowercase letters. |
75
+ | **History** | `history` | Undo/redo |
76
+ | **Dragon** | `dragon` | Speech dictation accessibility support |
77
+
78
+ ### Always-on capabilities
79
+
80
+ These are not feature-gated and are always available:
81
+
82
+ - **Static toolbar** — formatting, font size, color, font family, links, quotes
83
+ - **Floating toolbar** — appears on text selection with the same controls
84
+ - **Links** — insert via toolbar or `/` menu; Cmd/Ctrl+click to open in edit mode, regular click in read-only mode; always opens in a new tab
85
+ - **Images** — paste or drag-and-drop any image into the editor; click to select, drag corners to resize; images are serialized as base64 in the document JSON
86
+ - **Note blocks** — info, question, and error callout blocks with colored left borders
87
+ - **Horizontal rules** — section dividers
88
+ - **Slash commands** — type `/` to insert headings, code blocks, lists, notes, links, dividers
89
+ - **Markdown shortcuts** — `*`/`-` for bullet lists, `1.` for numbered lists, common text format triggers
90
+ - **Font controls** — size (10–36), family (Inter, SF Mono, Space Grotesk), and a curated color palette
91
+ - **Read-only mode** — pass `editable={false}` to disable editing; toolbars hide, links become directly clickable
92
+
93
+ ## Styling
94
+
95
+ Silk ships a single CSS file with all styles. Import it once:
96
+
97
+ ```tsx
98
+ import "silk-compose/styles";
99
+ ```
100
+
101
+ All CSS classes are prefixed with `silk-` to avoid collisions. The default theme uses a warm, neutral palette with Inter for body text, Space Grotesk for headings, and SF Mono for code and technical labels.
102
+
103
+ To customize the Lexical theme (class names applied to nodes), pass the `theme` prop — it's deep-merged with the defaults.
104
+
105
+ ## Exports
106
+
107
+ ```tsx
108
+ // Component
109
+ import { SilkEditor } from "silk-compose";
110
+ import type { SilkEditorProps, SilkFeatures } from "silk-compose";
111
+
112
+ // Nodes (for advanced Lexical integrations)
113
+ import { NoteNode, $createNoteNode, $isNoteNode } from "silk-compose";
114
+ import { ImageNode, $createImageNode, $isImageNode } from "silk-compose";
115
+ ```
116
+
117
+ ## Requirements
118
+
119
+ - React 18 or 19
120
+ - Lexical 0.41+
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,97 @@
1
+ import { DecoratorNode } from 'lexical';
2
+ import { DOMConversionMap } from 'lexical';
3
+ import { DOMExportOutput } from 'lexical';
4
+ import { EditorConfig } from 'lexical';
5
+ import { EditorThemeClasses } from 'lexical';
6
+ import { ElementNode } from 'lexical';
7
+ import { JSX } from 'react/jsx-runtime';
8
+ import { JSX as JSX_2 } from 'react';
9
+ import { LexicalEditor } from 'lexical';
10
+ import { LexicalNode } from 'lexical';
11
+ import { NodeKey } from 'lexical';
12
+ import { RangeSelection } from 'lexical';
13
+ import { SerializedElementNode } from 'lexical';
14
+ import { SerializedLexicalNode } from 'lexical';
15
+ import { Spread } from 'lexical';
16
+
17
+ export declare function $createImageNode(src: string, altText?: string, width?: number, height?: number): ImageNode;
18
+
19
+ export declare function $createNoteNode(noteType?: NoteType): NoteNode;
20
+
21
+ export declare function $isImageNode(node: LexicalNode | null | undefined): node is ImageNode;
22
+
23
+ export declare function $isNoteNode(node: LexicalNode | null | undefined): node is NoteNode;
24
+
25
+ export declare class ImageNode extends DecoratorNode<JSX_2.Element> {
26
+ __src: string;
27
+ __altText: string;
28
+ __width: number | undefined;
29
+ __height: number | undefined;
30
+ static getType(): string;
31
+ static clone(node: ImageNode): ImageNode;
32
+ constructor(src: string, altText: string, width?: number, height?: number, key?: NodeKey);
33
+ createDOM(): HTMLElement;
34
+ updateDOM(): false;
35
+ isInline(): boolean;
36
+ isKeyboardSelectable(): boolean;
37
+ static importJSON(serializedNode: SerializedImageNode): ImageNode;
38
+ exportJSON(): SerializedImageNode;
39
+ exportDOM(): DOMExportOutput;
40
+ static importDOM(): DOMConversionMap | null;
41
+ decorate(_editor: LexicalEditor): JSX_2.Element;
42
+ }
43
+
44
+ export declare class NoteNode extends ElementNode {
45
+ __noteType: NoteType;
46
+ static getType(): string;
47
+ static clone(node: NoteNode): NoteNode;
48
+ constructor(noteType?: NoteType, key?: NodeKey);
49
+ createDOM(config: EditorConfig): HTMLElement;
50
+ updateDOM(prevNode: NoteNode, dom: HTMLElement): boolean;
51
+ setNoteType(noteType: NoteType): this;
52
+ getNoteType(): NoteType;
53
+ insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): LexicalNode | null;
54
+ collapseAtStart(): boolean;
55
+ static importJSON(serializedNode: SerializedNoteNode): NoteNode;
56
+ exportJSON(): SerializedNoteNode;
57
+ }
58
+
59
+ export declare type NoteType = "info" | "warning" | "error";
60
+
61
+ export declare type SerializedImageNode = Spread<{
62
+ src: string;
63
+ altText: string;
64
+ width?: number;
65
+ height?: number;
66
+ }, SerializedLexicalNode>;
67
+
68
+ declare type SerializedNoteNode = Spread<{
69
+ noteType: NoteType;
70
+ }, SerializedElementNode>;
71
+
72
+ declare function SilkEditor({ features: userFeatures, namespace, className, theme: themeOverrides, editable, onError, onChange, initialEditorState, }: SilkEditorProps): JSX.Element;
73
+ export { SilkEditor }
74
+ export default SilkEditor;
75
+
76
+ export declare interface SilkEditorProps {
77
+ features?: SilkFeatures;
78
+ namespace?: string;
79
+ className?: string;
80
+ theme?: EditorThemeClasses;
81
+ editable?: boolean;
82
+ onError?: (error: Error) => void;
83
+ /** Called on every content change with the serialized editor state as a JSON string. */
84
+ onChange?: (serializedEditorState: string) => void;
85
+ /** JSON string from a previous `onChange` call to restore editor content. */
86
+ initialEditorState?: string;
87
+ }
88
+
89
+ export declare interface SilkFeatures {
90
+ history?: boolean;
91
+ richText?: boolean;
92
+ dragon?: boolean;
93
+ code?: boolean;
94
+ lists?: boolean;
95
+ }
96
+
97
+ export { }
package/dist/silk.css ADDED
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap";.silk-editor-container{position:relative;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:#1a1a1a}.silk-root{position:relative;min-height:150px;outline:none;background-color:#faf9f7}.silk-content-editable{min-height:150px;outline:none;padding:8px 12px}.silk-content-editable:focus{outline:none}.silk-placeholder{position:absolute;top:8px;left:12px;color:#999;pointer-events:none;-webkit-user-select:none;user-select:none}.silk-paragraph{margin:0 0 8px;line-height:1.6}.silk-text-bold{font-weight:600}.silk-text-italic{font-style:italic}.silk-text-underline{text-decoration:underline}.silk-text-strikethrough{text-decoration:line-through}.silk-text-code{font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;background-color:#0000000a;border:1px solid rgba(0,0,0,.1);padding:2px 5px;border-radius:0;font-size:.875em}.silk-heading-h1{font-family:Space Grotesk,Inter,sans-serif;font-size:1.875em;font-weight:600;margin:0 0 12px;line-height:1.3;letter-spacing:-.02em}.silk-heading-h2{font-family:Space Grotesk,Inter,sans-serif;font-size:1.375em;font-weight:600;margin:0 0 10px;line-height:1.35;letter-spacing:-.01em}.silk-heading-h3{font-family:Space Grotesk,Inter,sans-serif;font-size:1.125em;font-weight:600;margin:0 0 8px;line-height:1.4}.silk-quote{margin:12px 0;padding:20px 24px;border-left:4px solid #ac3521;background-color:#f0efeb;color:#31332c;line-height:1.6}.silk-list-ul{margin:0 0 8px;padding-left:24px;list-style-type:disc}.silk-list-ol{margin:0 0 8px;padding-left:48px;list-style:none;counter-reset:silk-ol}.silk-list-item{margin:2px 0;line-height:1.6}.silk-list-ol>.silk-list-item:not(.silk-list-item-nested){counter-increment:silk-ol;position:relative;margin:14px 0}.silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,decimal);position:absolute;left:-40px;color:#ac3521;font-weight:600;font-feature-settings:"tnum";width:28px;text-align:right}.silk-list-item-nested{list-style-type:none}.silk-list-ul .silk-list-ul{list-style-type:circle}.silk-list-ul .silk-list-ul .silk-list-ul{list-style-type:square}.silk-list-ol .silk-list-ol{counter-reset:silk-ol}.silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,upper-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,lower-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,decimal)}.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,upper-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,lower-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol,.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol,.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol,.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol{counter-reset:silk-ol}.silk-code-block{display:block;background-color:#1e1e1e;color:#d4d4d4;font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;font-size:.8125em;line-height:1.6;padding:12px;margin-top:40px;margin-bottom:8px;border-radius:0;border:1px solid #333;border-top:none;overflow-x:auto;-moz-tab-size:2;tab-size:2;white-space:pre;min-height:1.6em}.silk-code-header{display:flex;align-items:center;height:32px;padding:0 8px;border:1px solid #333;border-bottom:none;background-color:#2d2d2d;border-radius:0;-webkit-user-select:none;user-select:none;z-index:1;box-sizing:border-box}.silk-code-language-select{display:flex;align-items:center;gap:4px;background:none;border:none;color:#999;font-size:12px;font-family:inherit;padding:2px 6px;border-radius:0;line-height:1}.silk-code-language-select:hover{background-color:#ffffff14;color:#ccc}.silk-code-language-chevron{font-size:10px;color:#666}.silk-code-language-dropdown{position:absolute;top:100%;left:4px;margin-top:4px;background:#2d2d2d;border:1px solid #444;border-radius:0;padding:4px;min-width:120px;z-index:10;box-shadow:0 4px 12px #0006}.silk-code-language-option{display:block;width:100%;text-align:left;background:none;border:none;color:#ccc;padding:4px 8px;border-radius:0;font-size:12px;cursor:pointer;font-family:inherit}.silk-code-language-option:hover{background:#ffffff1a}.silk-code-language-option--active{color:#fff;background:#ffffff1f}.silk-hr{border:none;margin:28px 0;padding:0;display:flex;align-items:center;gap:16px}.silk-hr:before{content:"";flex:1;height:0;border-top:1px solid #e0ded9}.silk-hr:after{content:"SECTION_START";flex-shrink:0;font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;font-size:10px;letter-spacing:.08em;color:#c5c3be}.silk-image-wrapper{margin:12px 0;line-height:0}.silk-image-container{position:relative;display:inline-block;max-width:100%}.silk-image-container--selected{outline:2px solid #ac3521;outline-offset:2px}.silk-image{max-width:100%;height:auto;display:block;cursor:default}.silk-image-handle{position:absolute;width:10px;height:10px;background:#ac3521;border:1px solid #fbf9f4;z-index:2}.silk-image-handle--nw{top:-5px;left:-5px;cursor:nw-resize}.silk-image-handle--ne{top:-5px;right:-5px;cursor:ne-resize}.silk-image-handle--sw{bottom:-5px;left:-5px;cursor:sw-resize}.silk-image-handle--se{bottom:-5px;right:-5px;cursor:se-resize}.silk-note{margin:8px 0;border-left:4px solid;padding:14px 16px 14px 44px;position:relative;line-height:1.6}.silk-note:before{content:"";position:absolute;left:16px;top:16px;width:18px;height:18px;background-size:contain;background-repeat:no-repeat;background-position:center}.silk-note--info{background-color:#efeeea;border-color:#7d7870}.silk-note--info:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10' fill='%237d7870'/%3E%3Ccircle cx='10' cy='6.5' r='1.5' fill='white'/%3E%3Crect x='8.5' y='9.5' width='3' height='6.5' rx='1' fill='white'/%3E%3C/svg%3E")}.silk-note--warning{background-color:#f5ebe0;border-color:#96734c}.silk-note--warning:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 22 20'%3E%3Cpolygon points='11,0.5 21.5,19.5 0.5,19.5' fill='%23a07850'/%3E%3Ctext x='11' y='16' text-anchor='middle' fill='white' font-family='Inter,sans-serif' font-weight='700' font-size='13'%3E%3F%3C/text%3E%3C/svg%3E")}.silk-note--error{background-color:#fde8e8;border-color:#c44}.silk-note--error:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23cc4444'/%3E%3Crect x='8.75' y='4' width='2.5' height='8' rx='1' fill='white'/%3E%3Ccircle cx='10' cy='14.5' r='1.5' fill='white'/%3E%3C/svg%3E")}.silk-note .silk-paragraph{margin:0}.silk-note-icon-trigger{width:24px;height:24px;background:none;border:none;border-radius:0;cursor:pointer;padding:0;opacity:0;transition:opacity .15s}.silk-note-icon-trigger:hover{background-color:#00000014;opacity:1}.silk-note-icon-dropdown{position:absolute;top:28px;left:0;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:4px;min-width:120px;z-index:10;box-shadow:0 4px 12px #0000001a}.silk-note-type-option{display:flex;align-items:center;gap:6px;width:100%;text-align:left;background:none;border:none;color:#333;padding:5px 8px;border-radius:0;font-size:12px;cursor:pointer;font-family:inherit}.silk-note-type-option:hover{background:#f0f0f0}.silk-note-type-option--active{background:#e8e8e8}.silk-note-type-option--info{color:#2563eb}.silk-note-type-option--warning{color:#a16207}.silk-note-type-option--error{color:#dc2626}.silk-slash-anchor{z-index:1000}.silk-slash-menu{background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:4px;min-width:220px;max-width:300px;box-shadow:0 4px 16px #0000001f;z-index:1000;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.silk-slash-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:6px 10px;border:none;background:none;border-radius:0;cursor:pointer;text-align:left;font-family:inherit}.silk-slash-menu-item:hover,.silk-slash-menu-item--active{background:#faf9f7}.silk-slash-menu-icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;background:#f5f5f5;border:1px solid #eee;border-radius:0;color:#555;flex-shrink:0}.silk-slash-menu-text{display:flex;flex-direction:column;min-width:0}.silk-slash-menu-name{font-size:13px;font-weight:500;color:#1a1a1a}.silk-slash-menu-description{font-size:11px;color:#888;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.silk-link{color:inherit;text-decoration:underline;text-decoration-color:#00000059;text-underline-offset:2px;text-decoration-thickness:1px;cursor:pointer;transition:text-decoration-thickness .1s,text-decoration-color .1s}.silk-link:hover{text-decoration-thickness:2px;text-decoration-color:#0009}.silk-link-dialog-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000001f;z-index:1100;display:flex;align-items:center;justify-content:center;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.silk-link-dialog{background:#fff;border-radius:0;padding:20px;width:360px;box-shadow:0 8px 30px #00000026}.silk-link-dialog-field{margin-bottom:14px}.silk-link-dialog-label{display:block;font-size:12px;font-weight:500;color:#666;margin-bottom:5px}.silk-link-dialog-input{width:100%;padding:8px 10px;border:1px solid #e2e2e2;border-radius:0;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .15s}.silk-link-dialog-input:focus{border-color:#999}.silk-link-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}.silk-link-dialog-cancel{padding:7px 14px;border:1px solid #e2e2e2;border-radius:0;background:#fff;font-size:13px;font-family:inherit;cursor:pointer;color:#333}.silk-link-dialog-cancel:hover{background:#f5f5f5}.silk-link-dialog-insert{padding:7px 14px;border:none;border-radius:0;background:#1a1a1a;color:#fff;font-size:13px;font-family:inherit;cursor:pointer}.silk-link-dialog-insert:hover{background:#333}.silk-link-dialog-input--readonly{background:#f9f9f9;color:#888}.silk-link-dialog-insert:disabled{opacity:.4;cursor:not-allowed}.silk-toolbar{display:flex;align-items:center;padding:6px 8px;gap:1px;background:#fbf9f4;border-bottom:1px solid rgba(121,124,115,.12);font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;-webkit-user-select:none;user-select:none}.silk-toolbar .silk-ft-color-grid,.silk-toolbar .silk-ft-font-dropdown{bottom:auto;top:100%;margin-bottom:0;margin-top:6px}.silk-floating-toolbar{position:absolute;display:flex;align-items:center;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:3px;box-shadow:0 4px 16px #0000001f;z-index:50;gap:1px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.silk-ft-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:none;border-radius:0;cursor:pointer;font-size:13px;color:#444;padding:0;flex-shrink:0}.silk-ft-btn:hover{background:#f0f0f0}.silk-ft-btn--active{background:#e8e8e8;color:#1a1a1a}.silk-ft-btn--sm{width:22px;height:22px;font-size:14px}.silk-ft-btn--sm:disabled{opacity:.3;cursor:default}.silk-ft-btn--mono{font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;font-size:11px}.silk-ft-sep{width:1px;height:18px;background:#e2e2e2;margin:0 3px;flex-shrink:0}.silk-ft-font-size{display:flex;align-items:center;gap:2px}.silk-ft-font-size-value{font-size:12px;color:#444;min-width:20px;text-align:center;-webkit-user-select:none;user-select:none}.silk-ft-color-wrap{position:relative}.silk-ft-btn--color{font-size:14px;font-weight:600}.silk-ft-color-indicator{border-bottom:2.5px solid;padding-bottom:1px;line-height:1}.silk-ft-color-grid{position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:6px;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:6px;display:grid;grid-template-columns:repeat(6,1fr);gap:1px;box-shadow:0 4px 16px #0000001f;z-index:10}.silk-ft-color-swatch{width:22px;height:22px;border-radius:0;border:2px solid transparent;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;color:#999}.silk-ft-color-swatch:hover{border-color:#ccc}.silk-ft-color-swatch--active{border-color:#666}.silk-ft-font-wrap{position:relative}.silk-ft-font-dropdown{position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:6px;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:4px;min-width:160px;box-shadow:0 4px 16px #0000001f;z-index:10}.silk-ft-font-option{display:block;width:100%;text-align:left;background:none;border:none;padding:5px 8px;border-radius:0;cursor:pointer;font-size:12px;color:#444}.silk-ft-font-option:hover{background:#f0f0f0}.silk-ft-font-option--active{background:#e8e8e8;color:#1a1a1a}