@itsammarb/mention-editor 1.0.3 → 1.0.5

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
@@ -1,11 +1,11 @@
1
- # @nexcore/mention-editor
1
+ # @itsammarb/mention-editor
2
2
 
3
3
  A Discord-style `@mention` rich-text editor built on [Slate.js](https://www.slatejs.org/). Mentions are atomic (void + inline) nodes: backspacing next to one deletes it whole, and the suggestion menu tracks the real caret position via `ReactEditor.toDOMRange`.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```sh
8
- npm install @nexcore/mention-editor slate slate-react slate-history
8
+ npm install @itsammarb/mention-editor slate slate-react slate-history
9
9
  ```
10
10
 
11
11
  Peer dependencies: React 18 or 19, `slate` and `slate-react` `^0.94.0`. No Tailwind setup required on your end — see [Styling](#styling).
@@ -13,8 +13,8 @@ Peer dependencies: React 18 or 19, `slate` and `slate-react` `^0.94.0`. No Tailw
13
13
  ## Usage
14
14
 
15
15
  ```tsx
16
- import { MentionEditor } from '@nexcore/mention-editor';
17
- import '@nexcore/mention-editor/styles.css';
16
+ import { MentionEditor } from '@itsammarb/mention-editor';
17
+ import '@itsammarb/mention-editor/styles.css';
18
18
 
19
19
  const fields = [
20
20
  { id: 'a1b2c3d4-0000-0000-0000-000000000001', label: 'Landlord Name' },
@@ -60,16 +60,18 @@ Hello <@a1b2c3d4-0000-0000-0000-000000000001>, welcome.
60
60
  | `placeholder` | `string` | `undefined` (no placeholder) | Shown only when the document is fully empty (a single empty paragraph) — see [Behavior notes](#behavior-notes). |
61
61
  | `rows` | `number` | `undefined` (intrinsic `min-h-11`, ~44px) | Sets `min-height` to `rows * 1.5em`, textarea-`rows`-equivalent. The editor still grows taller than this if content wraps to more lines. |
62
62
  | `className` | `string` | `undefined` | Extra class name(s) appended to the root container, after the built-in ones — use this for layout (width, margin) or to override the built-in border/background. |
63
+ | `colors` | `MentionEditorColors` | `undefined` (built-in defaults) | Per-instance color overrides — see [Theme colors](#theme-colors). Omitted keys keep their default. |
63
64
 
64
65
  ## Exported utilities
65
66
 
66
- Everything importable from `@nexcore/mention-editor`:
67
+ Everything importable from `@itsammarb/mention-editor`:
67
68
 
68
69
  | Export | Kind | Description |
69
70
  | --- | --- | --- |
70
71
  | `MentionEditor` | component | The editor itself; see [Props](#props). |
71
72
  | `MentionEditorProps` | type | Prop types for `MentionEditor`. |
72
73
  | `MentionFieldOption` | type | Shape of an entry in `fields`: `{ id: string; label: string }`. |
74
+ | `MentionEditorColors` | type | Shape of the `colors` prop — see [Theme colors](#theme-colors). |
73
75
  | `serialize(nodes: Descendant[]): string` | function | Converts a Slate `Descendant[]` tree to the wire-format string. Useful if you need to inspect/generate content outside the component (e.g. server-side). |
74
76
  | `deserialize(value: string, fields?: MentionFieldOption[]): Descendant[]` | function | Inverse of `serialize`. `fields` resolves each mention's label; an id not found in `fields` still renders, falling back to the raw id as its label. |
75
77
  | `serializeToDiscordMarkup` | function | Deprecated alias of `serialize`, kept for backwards compatibility with the pre-rename API. Prefer `serialize`. |
@@ -93,9 +95,47 @@ Every rendered part carries a stable class name, at normal specificity (plain si
93
95
 
94
96
  Override any of these by targeting the class directly in your own CSS, or via `className` on the root for layout/border/background changes.
95
97
 
96
- **Dark mode**: the built-in `dark:` utilities respond to the OS/browser's `prefers-color-scheme: dark`, *not* a manually-toggled `.dark` class (e.g. from `next-themes` or a similar library). If your app drives dark mode via a class rather than OS preference, the shipped dark colors won't follow it — override `.mention-editor`, `.mention-editor__mention`, etc. directly with your own class-scoped CSS in that case.
98
+ ### Theme colors
97
99
 
98
- If your own app happens to use Tailwind and you want to reuse its utility classes against this component's internal DOM (beyond what `className` on the root reaches), you can optionally point your app's Tailwind config/`@source` at `node_modules/@nexcore/mention-editor/dist` — but this is purely an opt-in extra, not required for the component to work or look right.
100
+ Pass a `colors` prop to override any of the built-in colors, per instance no external CSS needed:
101
+
102
+ ```tsx
103
+ <MentionEditor
104
+ value={value}
105
+ fields={fields}
106
+ onChange={setValue}
107
+ colors={{
108
+ mentionColor: '#16a34a', // green mention text/underline
109
+ mentionBg: '#dcfce7', // optional pill-style highlight behind a mention
110
+ borderColor: '#db2777',
111
+ menuHighlightBg: '#fde68a', // selected/hovered suggestion row
112
+ }}
113
+ />
114
+ ```
115
+
116
+ Omitted keys keep their built-in default, and different instances on the same page can have entirely different `colors` without affecting each other — including each instance's own suggestion menu, even though the menu renders through a React portal into `document.body` (a sibling of the editor in the real DOM, not a descendant): `MentionEditor` forwards `colors` to it directly rather than relying on CSS inheritance.
117
+
118
+ | `colors` key | Controls | Light default | Dark default |
119
+ | --- | --- | --- | --- |
120
+ | `bg` | Editor container background | white | neutral-900 |
121
+ | `textColor` | Editor body text color | gray-900 | gray-100 |
122
+ | `borderColor` | Container border (normal state) | gray-300 | neutral-700 |
123
+ | `borderColorError` | Container border when `isError` | red-500 | red-500 (same in both) |
124
+ | `placeholderColor` | Placeholder text color | gray-400 | neutral-500 |
125
+ | `mentionColor` | A mention's text/underline color | blue-600 | blue-400 |
126
+ | `mentionBg` | Background behind a mention (e.g. a pill highlight) | transparent | transparent |
127
+ | `menuBg` | Suggestion menu background | white | neutral-800 |
128
+ | `menuBorderColor` | Suggestion menu border | gray-300 | neutral-700 |
129
+ | `menuTextColor` | Suggestion menu row text | gray-900 | gray-100 |
130
+ | `menuHighlightBg` | Selected/hovered suggestion row background | indigo-50 | neutral-700 |
131
+
132
+ A `colors` value applies in both light and dark mode (the fallback is what differs by scheme, not your override) — if you want genuinely different colors per scheme, read `window.matchMedia('(prefers-color-scheme: dark)')` (or your app's own dark-mode state) and pass a different `colors` object accordingly.
133
+
134
+ **Dark mode**: the built-in dark defaults (used when `colors` doesn't set a given key) respond to the OS/browser's `prefers-color-scheme: dark`, *not* a manually-toggled `.dark` class (e.g. from `next-themes` or a similar library). If your app drives dark mode via a class rather than OS preference, either pass `colors` explicitly (bypassing the scheme-based default entirely), or drive it from your own dark-mode state as described above.
135
+
136
+ **Advanced — global CSS override**: under the hood, `colors` works by setting CSS custom properties (`--mention-editor-*`) inline. You can also set these directly in your own global CSS (`:root { --mention-editor-mention-color: ...; }`) as a page-wide fallback for instances that don't pass `colors` at all — an inline `colors` value always takes precedence over a global CSS one. Global CSS declarations must be scoped to `:root`/`html`/`body` (not `.mention-editor`) for the same portal reason as above.
137
+
138
+ If your own app happens to use Tailwind and you want to reuse its utility classes against this component's internal DOM (beyond what `className` on the root reaches), you can optionally point your app's Tailwind config/`@source` at `node_modules/@itsammarb/mention-editor/dist` — but this is purely an opt-in extra, not required for the component to work or look right.
99
139
 
100
140
  ## Behavior notes
101
141
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { MentionFieldOption } from './types';
2
+ import { MentionFieldOption, MentionEditorColors } from './types';
3
3
  export interface MentionEditorProps {
4
4
  /** Wire-format string, e.g. `"Hello <@a1b2c3d4-...>, pay rent."` */
5
5
  value: string;
@@ -13,5 +13,7 @@ export interface MentionEditorProps {
13
13
  /** Approximate visible height, textarea-`rows`-equivalent. */
14
14
  rows?: number;
15
15
  className?: string;
16
+ /** Per-instance color overrides; omitted keys keep the built-in default. */
17
+ colors?: MentionEditorColors;
16
18
  }
17
19
  export declare function MentionEditor(props: MentionEditorProps): React.JSX.Element;
@@ -8,9 +8,19 @@ const slate_react_1 = require("slate-react");
8
8
  const slate_history_1 = require("slate-history");
9
9
  const types_1 = require("./types");
10
10
  const serialize_1 = require("./serialize");
11
+ const colors_1 = require("./colors");
11
12
  const MentionMenu_1 = require("./MentionMenu");
13
+ const EDITOR_COLOR_KEYS = [
14
+ 'bg',
15
+ 'textColor',
16
+ 'borderColor',
17
+ 'borderColorError',
18
+ 'placeholderColor',
19
+ 'mentionColor',
20
+ 'mentionBg',
21
+ ];
12
22
  function MentionEditor(props) {
13
- const { value, fields, onChange, dir, disabled = false, isError = false, placeholder, rows, className, } = props;
23
+ const { value, fields, onChange, dir, disabled = false, isError = false, placeholder, rows, className, colors, } = props;
14
24
  // `generation` forces a full remount of the Slate editor instance whenever the
15
25
  // `value` prop changes for a reason *other* than our own onChange echoing back
16
26
  // down (e.g. the consumer swaps to editing a different record). See the
@@ -138,20 +148,26 @@ function MentionEditor(props) {
138
148
  }
139
149
  }, [target, index, filteredFields, selectField]);
140
150
  const rootClassName = [
141
- 'mention-editor relative rounded-md border bg-white dark:bg-neutral-900',
142
- isError ? 'border-red-500' : 'border-gray-300 dark:border-neutral-700',
151
+ 'mention-editor relative rounded-md border',
152
+ 'bg-(--mention-editor-bg,var(--color-white)) dark:bg-(--mention-editor-bg,var(--color-neutral-900))',
153
+ isError
154
+ ? 'border-(--mention-editor-border-color-error,var(--color-red-500))'
155
+ : 'border-(--mention-editor-border-color,var(--color-gray-300)) dark:border-(--mention-editor-border-color,var(--color-neutral-700))',
143
156
  disabled && 'opacity-60',
144
157
  className,
145
158
  ]
146
159
  .filter(Boolean)
147
160
  .join(' ');
148
161
  const editableStyle = rows ? { minHeight: `${rows * 1.5}em` } : undefined;
149
- return ((0, jsx_runtime_1.jsx)(slate_react_1.Slate, { editor: editor, value: slateValue, onChange: handleChange, children: (0, jsx_runtime_1.jsxs)("div", { className: rootClassName, children: [(0, jsx_runtime_1.jsx)(slate_react_1.Editable, { className: "mention-editor__editable min-h-11 px-3 py-2 text-base leading-6 text-gray-900 outline-none dark:text-gray-100", style: editableStyle, dir: dir, readOnly: disabled, onKeyDown: handleKeyDown, placeholder: placeholder, renderElement: (elementProps) => (0, jsx_runtime_1.jsx)(Element, { ...elementProps }), renderPlaceholder: renderPlaceholder }), target && ((0, jsx_runtime_1.jsx)(MentionMenu_1.MentionMenu, { fields: filteredFields, selectedIndex: index, onSelect: selectField, onHover: setIndex, targetRect: menuTargetRect }))] }) }, generation));
162
+ // Set on the root: cascades naturally to Editable/mention/placeholder since
163
+ // they're real DOM descendants (unlike the portaled menu -- see MentionMenu).
164
+ const rootStyle = (0, colors_1.colorsToCssVars)(colors, EDITOR_COLOR_KEYS);
165
+ return ((0, jsx_runtime_1.jsx)(slate_react_1.Slate, { editor: editor, value: slateValue, onChange: handleChange, children: (0, jsx_runtime_1.jsxs)("div", { className: rootClassName, style: rootStyle, children: [(0, jsx_runtime_1.jsx)(slate_react_1.Editable, { className: "mention-editor__editable min-h-11 px-3 py-2 text-base leading-6 outline-none text-(--mention-editor-text-color,var(--color-gray-900)) dark:text-(--mention-editor-text-color,var(--color-gray-100))", style: editableStyle, dir: dir, readOnly: disabled, onKeyDown: handleKeyDown, placeholder: placeholder, renderElement: (elementProps) => (0, jsx_runtime_1.jsx)(Element, { ...elementProps }), renderPlaceholder: renderPlaceholder }), target && ((0, jsx_runtime_1.jsx)(MentionMenu_1.MentionMenu, { fields: filteredFields, selectedIndex: index, onSelect: selectField, onHover: setIndex, targetRect: menuTargetRect, colors: colors }))] }) }, generation));
150
166
  }
151
167
  // slate-react's default placeholder renders at a fixed dim-black opacity,
152
168
  // which reads poorly in dark mode. Overriding it lets the placeholder use
153
169
  // real (theme-aware) Tailwind colors at full opacity instead.
154
- const renderPlaceholder = ({ attributes, children }) => ((0, jsx_runtime_1.jsx)("span", { ...attributes, style: { ...attributes.style, opacity: 1 }, className: "text-gray-400 dark:text-neutral-500", children: children }));
170
+ const renderPlaceholder = ({ attributes, children }) => ((0, jsx_runtime_1.jsx)("span", { ...attributes, style: { ...attributes.style, opacity: 1 }, className: "text-(--mention-editor-placeholder-color,var(--color-gray-400)) dark:text-(--mention-editor-placeholder-color,var(--color-neutral-500))", children: children }));
155
171
  // --- TRIGGER DETECTION ---
156
172
  const MAX_MENTION_SEARCH_LENGTH = 50;
157
173
  /**
@@ -236,7 +252,7 @@ const Element = ({ attributes, children, element }) => {
236
252
  var _a;
237
253
  switch (element.type) {
238
254
  case 'mention':
239
- return ((0, jsx_runtime_1.jsxs)("span", { ...attributes, contentEditable: false, className: "mention-editor__mention cursor-default text-blue-600 underline decoration-1 underline-offset-2 [unicode-bidi:isolate] dark:text-blue-400", children: ["@", (_a = element.field) === null || _a === void 0 ? void 0 : _a.label, children] }));
255
+ return ((0, jsx_runtime_1.jsxs)("span", { ...attributes, contentEditable: false, className: "mention-editor__mention cursor-default underline decoration-1 underline-offset-2 [unicode-bidi:isolate] text-(--mention-editor-mention-color,var(--color-blue-600)) dark:text-(--mention-editor-mention-color,var(--color-blue-400)) bg-(--mention-editor-mention-bg,transparent)", children: ["@", (_a = element.field) === null || _a === void 0 ? void 0 : _a.label, children] }));
240
256
  default:
241
257
  return ((0, jsx_runtime_1.jsx)("p", { ...attributes, className: "mention-editor__paragraph m-0 [&+&]:mt-1", children: children }));
242
258
  }
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
- import { MentionFieldOption } from './types';
2
+ import { MentionFieldOption, MentionEditorColors } from './types';
3
3
  interface MentionMenuProps {
4
4
  fields: MentionFieldOption[];
5
5
  selectedIndex: number;
6
6
  onSelect: (field: MentionFieldOption) => void;
7
7
  onHover: (index: number) => void;
8
8
  targetRect: DOMRect | null;
9
+ colors?: MentionEditorColors;
9
10
  }
10
11
  export declare const MentionMenu: React.FC<MentionMenuProps>;
11
12
  export {};
@@ -3,7 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MentionMenu = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_dom_1 = require("react-dom");
6
- const MentionMenu = ({ fields, selectedIndex, onSelect, onHover, targetRect, }) => {
6
+ const colors_1 = require("./colors");
7
+ const MENU_COLOR_KEYS = [
8
+ 'menuBg',
9
+ 'menuBorderColor',
10
+ 'menuTextColor',
11
+ 'menuHighlightBg',
12
+ ];
13
+ const MentionMenu = ({ fields, selectedIndex, onSelect, onHover, targetRect, colors, }) => {
7
14
  if (!targetRect || fields.length === 0)
8
15
  return null;
9
16
  // Rendered into document.body via a portal and positioned with `fixed` using
@@ -13,12 +20,22 @@ const MentionMenu = ({ fields, selectedIndex, onSelect, onHover, targetRect, })
13
20
  // another modal: portaling to <body> means there's no transformed/positioned
14
21
  // ancestor between the menu and the viewport to throw off `fixed` coordinates.
15
22
  // The caller (MentionEditor) keeps `targetRect` fresh across scroll/resize.
16
- return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { className: "mention-editor__menu z-[9999] w-[280px] max-h-[300px] overflow-y-auto rounded-md border border-gray-300 bg-white py-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-800", style: {
23
+ //
24
+ // `colors` has to be applied here too, not just on `.mention-editor` -- this
25
+ // element is a portal child of `document.body`, a sibling of `.mention-editor`
26
+ // in the real DOM, so it wouldn't inherit color overrides set there.
27
+ return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { className: "mention-editor__menu z-9999 w-70 max-h-75 overflow-y-auto rounded-md border py-1 shadow-lg border-(--mention-editor-menu-border-color,var(--color-gray-300)) dark:border-(--mention-editor-menu-border-color,var(--color-neutral-700)) bg-(--mention-editor-menu-bg,var(--color-white)) dark:bg-(--mention-editor-menu-bg,var(--color-neutral-800))", style: {
17
28
  position: 'fixed',
18
29
  top: targetRect.bottom + 8,
19
30
  left: targetRect.left,
20
- }, children: fields.map((field, i) => ((0, jsx_runtime_1.jsx)("div", { className: 'mention-editor__menu-item cursor-pointer px-3 py-2 text-gray-900 dark:text-gray-100' +
21
- (i === selectedIndex ? ' bg-indigo-50 dark:bg-neutral-700' : ''), onClick: (e) => {
31
+ ...(0, colors_1.colorsToCssVars)(colors, MENU_COLOR_KEYS),
32
+ }, children: fields.map((field, i) => ((0, jsx_runtime_1.jsx)("div", { className: 'mention-editor__menu-item cursor-pointer px-3 py-2' +
33
+ ' text-(--mention-editor-menu-text-color,var(--color-gray-900))' +
34
+ ' dark:text-(--mention-editor-menu-text-color,var(--color-gray-100))' +
35
+ (i === selectedIndex
36
+ ? ' bg-(--mention-editor-menu-highlight-bg,var(--color-indigo-50))' +
37
+ ' dark:bg-(--mention-editor-menu-highlight-bg,var(--color-neutral-700))'
38
+ : ''), onClick: (e) => {
22
39
  e.preventDefault();
23
40
  onSelect(field);
24
41
  }, onMouseDown: (e) => e.preventDefault(), onMouseEnter: () => onHover(i), children: field.label }, field.id))) }), document.body);
@@ -0,0 +1,9 @@
1
+ import type { CSSProperties } from 'react';
2
+ import { MentionEditorColors } from './types';
3
+ /**
4
+ * Builds an inline `style` object setting only the CSS custom properties for
5
+ * the given keys that are actually present in `colors` -- omitted keys are
6
+ * left unset entirely, so the built-in fallback in the compiled CSS still
7
+ * applies for them.
8
+ */
9
+ export declare const colorsToCssVars: (colors: MentionEditorColors | undefined, keys: (keyof MentionEditorColors)[]) => CSSProperties;
package/dist/colors.js ADDED
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.colorsToCssVars = void 0;
4
+ // Maps each `colors` prop key to the CSS custom property the compiled
5
+ // Tailwind classes read via `var(--x, <built-in fallback>)`. Setting the
6
+ // property inline (rather than requiring a consumer to write global CSS)
7
+ // wins over the fallback automatically -- and, being inline, wins regardless
8
+ // of stylesheet load order, since there's no competing external rule to race.
9
+ const COLOR_VARS = {
10
+ bg: '--mention-editor-bg',
11
+ textColor: '--mention-editor-text-color',
12
+ borderColor: '--mention-editor-border-color',
13
+ borderColorError: '--mention-editor-border-color-error',
14
+ placeholderColor: '--mention-editor-placeholder-color',
15
+ mentionColor: '--mention-editor-mention-color',
16
+ mentionBg: '--mention-editor-mention-bg',
17
+ menuBg: '--mention-editor-menu-bg',
18
+ menuBorderColor: '--mention-editor-menu-border-color',
19
+ menuTextColor: '--mention-editor-menu-text-color',
20
+ menuHighlightBg: '--mention-editor-menu-highlight-bg',
21
+ };
22
+ /**
23
+ * Builds an inline `style` object setting only the CSS custom properties for
24
+ * the given keys that are actually present in `colors` -- omitted keys are
25
+ * left unset entirely, so the built-in fallback in the compiled CSS still
26
+ * applies for them.
27
+ */
28
+ const colorsToCssVars = (colors, keys) => {
29
+ if (!colors)
30
+ return {};
31
+ const style = {};
32
+ for (const key of keys) {
33
+ const value = colors[key];
34
+ if (value)
35
+ style[COLOR_VARS[key]] = value;
36
+ }
37
+ return style;
38
+ };
39
+ exports.colorsToCssVars = colorsToCssVars;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { MentionEditor } from './MentionEditor';
2
2
  export type { MentionEditorProps } from './MentionEditor';
3
3
  export { serialize, deserialize, serializeToDiscordMarkup } from './serialize';
4
- export type { MentionFieldOption } from './types';
4
+ export type { MentionFieldOption, MentionEditorColors } from './types';
5
5
  export { INITIAL_VALUE, MENTION_OPEN, MENTION_CLOSE } from './types';
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}:root,:host{--color-red-500:oklch(63.7% .237 25.331);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-50:oklch(96.2% .018 272.314);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-900:oklch(21% .034 264.665);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-white:#fff;--spacing:.25rem;--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--radius-md:.375rem}.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.isolate{isolation:isolate}.z-\[9999\]{z-index:9999}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:0}.block{display:block}.contents{display:contents}.inline{display:inline}.max-h-\[300px\]{max-height:300px}.min-h-11{min-height:calc(var(--spacing) * 11)}.w-\[280px\]{width:280px}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.overflow-y-auto{overflow-y:auto}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-300{border-color:var(--color-gray-300)}.border-red-500{border-color:var(--color-red-500)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-white{background-color:var(--color-white)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.text-blue-600{color:var(--color-blue-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-900{color:var(--color-gray-900)}.underline{text-decoration-line:underline}.decoration-1{text-decoration-thickness:1px}.underline-offset-2{text-underline-offset:2px}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.outline-none{--tw-outline-style:none;outline-style:none}.\[unicode-bidi\:isolate\]{unicode-bidi:isolate}@media (prefers-color-scheme:dark){.dark\:border-neutral-700{border-color:var(--color-neutral-700)}.dark\:bg-neutral-700{background-color:var(--color-neutral-700)}.dark\:bg-neutral-800{background-color:var(--color-neutral-800)}.dark\:bg-neutral-900{background-color:var(--color-neutral-900)}.dark\:text-blue-400{color:var(--color-blue-400)}.dark\:text-gray-100{color:var(--color-gray-100)}.dark\:text-neutral-500{color:var(--color-neutral-500)}}.\[\&\+\&\]\:mt-1+.\[\&\+\&\]\:mt-1{margin-top:var(--spacing)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}:root,:host{--color-red-500:oklch(63.7% .237 25.331);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-50:oklch(96.2% .018 272.314);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-900:oklch(21% .034 264.665);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-white:#fff;--spacing:.25rem;--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--radius-md:.375rem}.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.isolate{isolation:isolate}.z-9999{z-index:9999}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:0}.block{display:block}.contents{display:contents}.inline{display:inline}.max-h-75{max-height:calc(var(--spacing) * 75)}.min-h-11{min-height:calc(var(--spacing) * 11)}.w-70{width:calc(var(--spacing) * 70)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.overflow-y-auto{overflow-y:auto}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-\(--mention-editor-border-color\,var\(--color-gray-300\)\){border-color:var(--mention-editor-border-color,var(--color-gray-300))}.border-\(--mention-editor-border-color-error\,var\(--color-red-500\)\){border-color:var(--mention-editor-border-color-error,var(--color-red-500))}.border-\(--mention-editor-menu-border-color\,var\(--color-gray-300\)\){border-color:var(--mention-editor-menu-border-color,var(--color-gray-300))}.border-red-500{border-color:var(--color-red-500)}.bg-\(--mention-editor-bg\,var\(--color-white\)\){background-color:var(--mention-editor-bg,var(--color-white))}.bg-\(--mention-editor-mention-bg\,transparent\){background-color:var(--mention-editor-mention-bg,transparent)}.bg-\(--mention-editor-menu-bg\,var\(--color-white\)\){background-color:var(--mention-editor-menu-bg,var(--color-white))}.bg-\(--mention-editor-menu-highlight-bg\,var\(--color-indigo-50\)\){background-color:var(--mention-editor-menu-highlight-bg,var(--color-indigo-50))}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.text-\(--mention-editor-mention-color\,var\(--color-blue-600\)\){color:var(--mention-editor-mention-color,var(--color-blue-600))}.text-\(--mention-editor-menu-text-color\,var\(--color-gray-900\)\){color:var(--mention-editor-menu-text-color,var(--color-gray-900))}.text-\(--mention-editor-placeholder-color\,var\(--color-gray-400\)\){color:var(--mention-editor-placeholder-color,var(--color-gray-400))}.text-\(--mention-editor-text-color\,var\(--color-gray-900\)\){color:var(--mention-editor-text-color,var(--color-gray-900))}.underline{text-decoration-line:underline}.decoration-1{text-decoration-thickness:1px}.underline-offset-2{text-underline-offset:2px}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.outline-none{--tw-outline-style:none;outline-style:none}.\[unicode-bidi\:isolate\]{unicode-bidi:isolate}@media (prefers-color-scheme:dark){.dark\:border-\(--mention-editor-border-color\,var\(--color-neutral-700\)\){border-color:var(--mention-editor-border-color,var(--color-neutral-700))}.dark\:border-\(--mention-editor-menu-border-color\,var\(--color-neutral-700\)\){border-color:var(--mention-editor-menu-border-color,var(--color-neutral-700))}.dark\:bg-\(--mention-editor-bg\,var\(--color-neutral-900\)\){background-color:var(--mention-editor-bg,var(--color-neutral-900))}.dark\:bg-\(--mention-editor-menu-bg\,var\(--color-neutral-800\)\){background-color:var(--mention-editor-menu-bg,var(--color-neutral-800))}.dark\:bg-\(--mention-editor-menu-highlight-bg\,var\(--color-neutral-700\)\){background-color:var(--mention-editor-menu-highlight-bg,var(--color-neutral-700))}.dark\:text-\(--mention-editor-mention-color\,var\(--color-blue-400\)\){color:var(--mention-editor-mention-color,var(--color-blue-400))}.dark\:text-\(--mention-editor-menu-text-color\,var\(--color-gray-100\)\){color:var(--mention-editor-menu-text-color,var(--color-gray-100))}.dark\:text-\(--mention-editor-placeholder-color\,var\(--color-neutral-500\)\){color:var(--mention-editor-placeholder-color,var(--color-neutral-500))}.dark\:text-\(--mention-editor-text-color\,var\(--color-gray-100\)\){color:var(--mention-editor-text-color,var(--color-gray-100))}}.\[\&\+\&\]\:mt-1+.\[\&\+\&\]\:mt-1{margin-top:var(--spacing)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
package/dist/types.d.ts CHANGED
@@ -27,3 +27,16 @@ declare module 'slate' {
27
27
  export declare const INITIAL_VALUE: Descendant[];
28
28
  export declare const MENTION_OPEN = "<@";
29
29
  export declare const MENTION_CLOSE = ">";
30
+ export interface MentionEditorColors {
31
+ bg?: string;
32
+ textColor?: string;
33
+ borderColor?: string;
34
+ borderColorError?: string;
35
+ placeholderColor?: string;
36
+ mentionColor?: string;
37
+ mentionBg?: string;
38
+ menuBg?: string;
39
+ menuBorderColor?: string;
40
+ menuTextColor?: string;
41
+ menuHighlightBg?: string;
42
+ }
package/llms.txt CHANGED
@@ -45,10 +45,11 @@ function Example() {
45
45
  | `placeholder` | `string` | no | Shows only when the doc is exactly one empty paragraph. |
46
46
  | `rows` | `number` | no | `min-height = rows * 1.5em`, textarea-rows-equivalent; grows taller if content wraps further. |
47
47
  | `className` | `string` | no | Appended to the root container's class list. |
48
+ | `colors` | `MentionEditorColors` | no | Per-instance color overrides; see Theme colors below. Omitted keys keep the default. |
48
49
 
49
50
  ## Other exports
50
51
 
51
- `MentionEditorProps` (type), `MentionFieldOption` (type), `INITIAL_VALUE` (empty-doc Slate value), `MENTION_OPEN` / `MENTION_CLOSE` (the `'<@'` / `'>'` delimiter constants), `serializeToDiscordMarkup` (deprecated alias of `serialize`).
52
+ `MentionEditorProps` (type), `MentionFieldOption` (type), `MentionEditorColors` (type), `INITIAL_VALUE` (empty-doc Slate value), `MENTION_OPEN` / `MENTION_CLOSE` (the `'<@'` / `'>'` delimiter constants), `serializeToDiscordMarkup` (deprecated alias of `serialize`).
52
53
 
53
54
  ## Behavior that is fixed, not configurable via props
54
55
 
@@ -64,6 +65,12 @@ function Example() {
64
65
 
65
66
  `.mention-editor` (root), `.mention-editor__editable`, `.mention-editor__paragraph`, `.mention-editor__mention`, `.mention-editor__menu`, `.mention-editor__menu-item`.
66
67
 
68
+ ## Theme colors
69
+
70
+ Use the `colors` prop, not CSS, to retheme -- e.g. `colors={{ mentionColor: '#16a34a', mentionBg: '#dcfce7', borderColor: '#db2777', menuHighlightBg: '#fde68a' }}`. Keys: `bg`, `textColor`, `borderColor`, `borderColorError`, `placeholderColor`, `mentionColor`, `mentionBg`, `menuBg`, `menuBorderColor`, `menuTextColor`, `menuHighlightBg`. Omitted keys keep the built-in default (which itself differs light vs. dark, following OS `prefers-color-scheme`). Each editor instance's `colors` is independent, and is correctly forwarded to that instance's own suggestion menu even though the menu renders through a React portal into `document.body`.
71
+
72
+ Advanced: `colors` works by setting `--mention-editor-*` CSS custom properties inline; you can also set these directly in your own global CSS at `:root`/`html`/`body` scope (not `.mention-editor` -- the portaled menu sits outside it) as a page-wide fallback. An inline `colors` value always wins over a global CSS one.
73
+
67
74
  ## Full docs
68
75
 
69
76
  See `README.md` in this package for the complete prop/export/styling reference and more detail on each point above.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itsammarb/mention-editor",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "A Discord-style @mention rich-text editor built on Slate.js",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",