@luscii-healthtech/web-ui 54.4.4 → 54.5.0

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.
@@ -29,6 +29,7 @@ var Creatable = require('react-select/creatable');
29
29
  var RadixSwitch = require('@radix-ui/react-switch');
30
30
  var react = require('@tiptap/react');
31
31
  var StarterKit = require('@tiptap/starter-kit');
32
+ var extensionMention = require('@tiptap/extension-mention');
32
33
  var solid = require('@heroicons/react/20/solid');
33
34
  var react$1 = require('@headlessui/react');
34
35
  var ToggleGroup = require('@radix-ui/react-toggle-group');
@@ -5189,6 +5190,15 @@ const TextEditorToolbarButton = ({ option, hasTextSelected, editor, translations
5189
5190
  editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
5190
5191
  }
5191
5192
  }, "aria-pressed": editor.isActive("link"), children: jsxRuntime.jsx(solid.LinkIcon, { width: 16 }) });
5193
+ case "tag":
5194
+ if (!editor.schema.nodes.mention) {
5195
+ return null;
5196
+ }
5197
+ return jsxRuntime.jsx(TertiaryIconButton, { label: translations.tagLabel, className: buttonClasses, onClick: () => {
5198
+ const before = editor.state.selection.$from.nodeBefore;
5199
+ const needsSpace = Boolean((before === null || before === void 0 ? void 0 : before.isText) && before.text && !/\s$/.test(before.text));
5200
+ editor.chain().focus().insertContent(needsSpace ? " @" : "@").run();
5201
+ }, children: jsxRuntime.jsx(solid.AtSymbolIcon, { width: 16 }) });
5192
5202
  default:
5193
5203
  return null;
5194
5204
  }
@@ -5224,6 +5234,123 @@ const TextEditorToolbar = ({ toolbarId, toolbar, hasTextSelected, editor, transl
5224
5234
  return jsxRuntime.jsx(Stack, { axis: "x", gap: "m", _px: "xs", id: toolbarId, children: toolbar.map((group) => jsxRuntime.jsx(Stack, { axis: "x", _gap: "xs", children: group.map((option, optionIndex) => jsxRuntime.jsx(TextEditorToolbarButton, { option, hasTextSelected, editor, translations }, getOptionKey(option, optionIndex))) }, getGroupKey(group))) });
5225
5235
  };
5226
5236
 
5237
+ const MentionList = React.forwardRef(({ items, command }, ref) => {
5238
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
5239
+ React.useEffect(() => {
5240
+ setSelectedIndex(0);
5241
+ }, [items]);
5242
+ const selectItem = (index) => {
5243
+ const item = items[index];
5244
+ if (item) {
5245
+ command({ id: item.id, label: item.label });
5246
+ }
5247
+ };
5248
+ React.useImperativeHandle(ref, () => ({
5249
+ onKeyDown: ({ event }) => {
5250
+ if (items.length === 0) {
5251
+ return false;
5252
+ }
5253
+ if (event.key === "ArrowUp") {
5254
+ setSelectedIndex((selectedIndex + items.length - 1) % items.length);
5255
+ return true;
5256
+ }
5257
+ if (event.key === "ArrowDown") {
5258
+ setSelectedIndex((selectedIndex + 1) % items.length);
5259
+ return true;
5260
+ }
5261
+ if (event.key === "Enter") {
5262
+ selectItem(selectedIndex);
5263
+ return true;
5264
+ }
5265
+ return false;
5266
+ }
5267
+ }));
5268
+ if (items.length === 0) {
5269
+ return null;
5270
+ }
5271
+ return jsxRuntime.jsx(Card, { borderRadius: "s", elevation: "large", padding: false, border: true, className: "ui:max-w-80 ui:overflow-hidden", children: jsxRuntime.jsx(Stack, { width: "full", align: "stretch", className: "ui:p-1", children: items.map((item, index) => jsxRuntime.jsx(Box, {
5272
+ as: "button",
5273
+ type: "button",
5274
+ // Use mousedown so selection happens before the editor loses focus.
5275
+ onMouseDown: (event) => {
5276
+ event.preventDefault();
5277
+ selectItem(index);
5278
+ },
5279
+ onMouseEnter: () => setSelectedIndex(index),
5280
+ borderRadius: "xs",
5281
+ className: classNames__default.default("ui:w-full ui:text-left ui:cursor-pointer ui:px-2 ui:py-1.5", {
5282
+ "ui:bg-background-neutral-secondary-hovered": index === selectedIndex
5283
+ }),
5284
+ children: jsxRuntime.jsx(Text, { truncate: true, children: item.label })
5285
+ }, item.id)) }) });
5286
+ });
5287
+ MentionList.displayName = "MentionList";
5288
+
5289
+ const MAX_RESULTS = 8;
5290
+ const createMentionSuggestion = (getTags) => ({
5291
+ items: ({ query }) => {
5292
+ const normalizedQuery = query.toLowerCase();
5293
+ return getTags().filter((tag) => tag.label.toLowerCase().includes(normalizedQuery)).slice(0, MAX_RESULTS);
5294
+ },
5295
+ render: () => {
5296
+ let component = null;
5297
+ let popup = null;
5298
+ const reposition = (clientRect) => {
5299
+ if (!popup || !clientRect) {
5300
+ return;
5301
+ }
5302
+ const rect = clientRect();
5303
+ if (!rect) {
5304
+ return;
5305
+ }
5306
+ popup.style.top = `${rect.bottom + 4}px`;
5307
+ popup.style.left = `${rect.left}px`;
5308
+ };
5309
+ return {
5310
+ onStart: (props) => {
5311
+ component = new react.ReactRenderer(MentionList, {
5312
+ props,
5313
+ editor: props.editor
5314
+ });
5315
+ if (!props.clientRect) {
5316
+ return;
5317
+ }
5318
+ popup = document.createElement("div");
5319
+ popup.style.position = "fixed";
5320
+ popup.style.zIndex = "1000";
5321
+ popup.appendChild(component.element);
5322
+ document.body.appendChild(popup);
5323
+ reposition(props.clientRect);
5324
+ },
5325
+ onUpdate: (props) => {
5326
+ component === null || component === void 0 ? void 0 : component.updateProps(props);
5327
+ reposition(props.clientRect);
5328
+ },
5329
+ onKeyDown: (props) => {
5330
+ var _a, _b;
5331
+ return (_b = (_a = component === null || component === void 0 ? void 0 : component.ref) === null || _a === void 0 ? void 0 : _a.onKeyDown(props)) !== null && _b !== void 0 ? _b : false;
5332
+ },
5333
+ onExit: () => {
5334
+ popup === null || popup === void 0 ? void 0 : popup.remove();
5335
+ popup = null;
5336
+ component === null || component === void 0 ? void 0 : component.destroy();
5337
+ component = null;
5338
+ }
5339
+ };
5340
+ }
5341
+ });
5342
+
5343
+ const MentionView = ({ node }) => {
5344
+ var _a, _b;
5345
+ const id = node.attrs.id;
5346
+ const label = (_a = node.attrs.label) !== null && _a !== void 0 ? _a : id;
5347
+ const char = (_b = node.attrs.mentionSuggestionChar) !== null && _b !== void 0 ? _b : "@";
5348
+ const display = `${char}${label}`;
5349
+ const tooltipText = id && id !== label ? id : null;
5350
+ const pill = jsxRuntime.jsx("span", { className: "web-ui-mention", children: display });
5351
+ return jsxRuntime.jsx(react.NodeViewWrapper, { as: "span", children: tooltipText ? jsxRuntime.jsx(Tooltip$1.Provider, { children: jsxRuntime.jsxs(Tooltip.Root, { children: [jsxRuntime.jsx(Tooltip.Trigger, { asChild: true, children: pill }), jsxRuntime.jsx(Tooltip.Content, { children: jsxRuntime.jsx(Tooltip.Text, { children: tooltipText }) })] }) }) : pill });
5352
+ };
5353
+
5227
5354
  const TEXT_EDITOR_TRANSLATIONS = {
5228
5355
  "en-GB": {
5229
5356
  boldLabel: "bold",
@@ -5231,6 +5358,7 @@ const TEXT_EDITOR_TRANSLATIONS = {
5231
5358
  underlineLabel: "underline",
5232
5359
  strikeLabel: "strike",
5233
5360
  linkLabel: "link",
5361
+ tagLabel: "insert tag",
5234
5362
  orderedListLabel: "ordered list",
5235
5363
  bulletListLabel: "bullet list",
5236
5364
  enterUrlPrompt: "Enter URL:",
@@ -5246,6 +5374,7 @@ const TEXT_EDITOR_TRANSLATIONS = {
5246
5374
  underlineLabel: "onderstreept",
5247
5375
  strikeLabel: "doorgestreept",
5248
5376
  linkLabel: "link",
5377
+ tagLabel: "tag invoegen",
5249
5378
  orderedListLabel: "genummerde lijst",
5250
5379
  bulletListLabel: "opsommingslijst",
5251
5380
  enterUrlPrompt: "Voer URL in:",
@@ -5261,6 +5390,7 @@ const TEXT_EDITOR_TRANSLATIONS = {
5261
5390
  underlineLabel: "unterstrichen",
5262
5391
  strikeLabel: "durchgestrichen",
5263
5392
  linkLabel: "Link",
5393
+ tagLabel: "Tag einf\xFCgen",
5264
5394
  orderedListLabel: "nummerierte Liste",
5265
5395
  bulletListLabel: "Aufz\xE4hlungsliste",
5266
5396
  enterUrlPrompt: "URL eingeben:",
@@ -5276,6 +5406,7 @@ const TEXT_EDITOR_TRANSLATIONS = {
5276
5406
  underlineLabel: "soulign\xE9",
5277
5407
  strikeLabel: "barr\xE9",
5278
5408
  linkLabel: "lien",
5409
+ tagLabel: "ins\xE9rer une balise",
5279
5410
  orderedListLabel: "liste num\xE9rot\xE9e",
5280
5411
  bulletListLabel: "liste \xE0 puces",
5281
5412
  enterUrlPrompt: "Entrer l'URL :",
@@ -5291,6 +5422,7 @@ const TEXT_EDITOR_TRANSLATIONS = {
5291
5422
  underlineLabel: "sublinhado",
5292
5423
  strikeLabel: "riscado",
5293
5424
  linkLabel: "liga\xE7\xE3o",
5425
+ tagLabel: "inserir etiqueta",
5294
5426
  orderedListLabel: "lista numerada",
5295
5427
  bulletListLabel: "lista com marcadores",
5296
5428
  enterUrlPrompt: "Introduzir URL:",
@@ -5305,29 +5437,46 @@ const getTextEditorTranslations = (locale) => {
5305
5437
  return TEXT_EDITOR_TRANSLATIONS[locale] || TEXT_EDITOR_TRANSLATIONS["en-GB"];
5306
5438
  };
5307
5439
 
5308
- var css_248z$1 = "/**\n * --- DEPRECATED ---\n * DON'T USE ANYTHING FROM THIS FILE IN FUTURE CHANGES. WE SHOULD BE\n * USING TAILWIND CLASSES DIRECTLY IN OUR COMPONENTS.\n */\n.web-ui-text-editor {\n resize: vertical;\n min-height: 10rem;\n padding: 1rem;\n font-size: 0.8rem;\n line-height: 1.5;\n outline: none;\n}\n.web-ui-text-editor.tiptap.ProseMirror p.is-editor-empty:first-child::before {\n color: var(--ui-color-text-neutral-primary-disabled);\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n}\n.web-ui-text-editor a {\n color: var(--ui-color-text-brand-primary-default);\n text-decoration: underline;\n cursor: pointer;\n}\n.web-ui-text-editor ul,\n.web-ui-text-editor ol {\n padding-left: 1.5rem;\n}\n.web-ui-text-editor ul {\n list-style-type: disc;\n}\n.web-ui-text-editor ol {\n list-style-type: decimal;\n}\n.web-ui-text-editor li {\n margin-bottom: 0.25rem;\n}\n.web-ui-text-editor strong {\n font-weight: 600;\n}\n.web-ui-text-editor em {\n font-style: italic;\n}\n.web-ui-text-editor u {\n text-decoration: underline;\n}\n.web-ui-text-editor s {\n text-decoration: line-through;\n}\n.web-ui-text-editor h1,\n.web-ui-text-editor h2,\n.web-ui-text-editor h3,\n.web-ui-text-editor h4,\n.web-ui-text-editor h5,\n.web-ui-text-editor h6 {\n font-weight: 600;\n margin-bottom: 0.75rem;\n margin-top: 1rem;\n}\n.web-ui-text-editor h1:first-child,\n.web-ui-text-editor h2:first-child,\n.web-ui-text-editor h3:first-child,\n.web-ui-text-editor h4:first-child,\n.web-ui-text-editor h5:first-child,\n.web-ui-text-editor h6:first-child {\n margin-top: 0;\n}\n.web-ui-text-editor h1 {\n font-size: 2rem;\n}\n.web-ui-text-editor h2 {\n font-size: 1.5rem;\n}\n.web-ui-text-editor h3 {\n font-size: 1.25rem;\n}\n.web-ui-text-editor h4 {\n font-size: 1.125rem;\n}\n.web-ui-text-editor h5,\n.web-ui-text-editor h6 {\n font-size: 1rem;\n}";
5440
+ var css_248z$1 = "/**\n * --- DEPRECATED ---\n * DON'T USE ANYTHING FROM THIS FILE IN FUTURE CHANGES. WE SHOULD BE\n * USING TAILWIND CLASSES DIRECTLY IN OUR COMPONENTS.\n */\n.web-ui-text-editor {\n resize: vertical;\n min-height: 10rem;\n padding: 1rem;\n font-size: 0.8rem;\n line-height: 1.5;\n outline: none;\n}\n.web-ui-text-editor.tiptap.ProseMirror p.is-editor-empty:first-child::before {\n color: var(--ui-color-text-neutral-primary-disabled);\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n}\n.web-ui-text-editor a {\n color: var(--ui-color-text-brand-primary-default);\n text-decoration: underline;\n cursor: pointer;\n}\n.web-ui-text-editor .web-ui-mention {\n display: inline-block;\n padding: 0.125rem 0.375rem;\n border-radius: 0.25rem;\n background-color: var(--ui-primitive-color-pink-100);\n color: var(--ui-primitive-color-pink-700);\n font-weight: 500;\n white-space: nowrap;\n}\n.web-ui-text-editor ul,\n.web-ui-text-editor ol {\n padding-left: 1.5rem;\n}\n.web-ui-text-editor ul {\n list-style-type: disc;\n}\n.web-ui-text-editor ol {\n list-style-type: decimal;\n}\n.web-ui-text-editor li {\n margin-bottom: 0.25rem;\n}\n.web-ui-text-editor strong {\n font-weight: 600;\n}\n.web-ui-text-editor em {\n font-style: italic;\n}\n.web-ui-text-editor u {\n text-decoration: underline;\n}\n.web-ui-text-editor s {\n text-decoration: line-through;\n}\n.web-ui-text-editor h1,\n.web-ui-text-editor h2,\n.web-ui-text-editor h3,\n.web-ui-text-editor h4,\n.web-ui-text-editor h5,\n.web-ui-text-editor h6 {\n font-weight: 600;\n margin-bottom: 0.75rem;\n margin-top: 1rem;\n}\n.web-ui-text-editor h1:first-child,\n.web-ui-text-editor h2:first-child,\n.web-ui-text-editor h3:first-child,\n.web-ui-text-editor h4:first-child,\n.web-ui-text-editor h5:first-child,\n.web-ui-text-editor h6:first-child {\n margin-top: 0;\n}\n.web-ui-text-editor h1 {\n font-size: 2rem;\n}\n.web-ui-text-editor h2 {\n font-size: 1.5rem;\n}\n.web-ui-text-editor h3 {\n font-size: 1.25rem;\n}\n.web-ui-text-editor h4 {\n font-size: 1.125rem;\n}\n.web-ui-text-editor h5,\n.web-ui-text-editor h6 {\n font-size: 1rem;\n}";
5309
5441
  styleInject(css_248z$1);
5310
5442
 
5443
+ const MentionWithTooltip = extensionMention.Mention.extend({
5444
+ addNodeView() {
5445
+ return react.ReactNodeViewRenderer(MentionView);
5446
+ }
5447
+ });
5311
5448
  const sanitize = (html) => DOMPurify__default.default.sanitize(html, {
5312
- ALLOWED_TAGS: ["u", "a", "s", "ul", "ol", "li", "p", "strong", "em"],
5313
- ALLOWED_ATTR: ["href", "target"]
5449
+ ALLOWED_TAGS: ["u", "a", "s", "ul", "ol", "li", "p", "strong", "em", "span"],
5450
+ ALLOWED_ATTR: [
5451
+ "href",
5452
+ "target",
5453
+ // Mention/tag nodes render as a span carrying these attributes.
5454
+ "class",
5455
+ "data-type",
5456
+ "data-id",
5457
+ "data-label",
5458
+ "data-mention-suggestion-char"
5459
+ ]
5314
5460
  });
5315
5461
  const TextEditor = (_a) => {
5316
5462
  var { defaultValue, formats = ["bold", "italic", "underline", "strike", "list", "link"], toolbar = [
5317
5463
  ["bold", "italic", "underline", "strike"],
5318
5464
  [{ list: "ordered" }, { list: "bullet" }],
5319
5465
  ["link"]
5320
- ], placeholder, onValueChange } = _a, attrs = __rest(_a, ["defaultValue", "formats", "toolbar", "placeholder", "onValueChange"]);
5466
+ ], placeholder, tags, onValueChange } = _a, attrs = __rest(_a, ["defaultValue", "formats", "toolbar", "placeholder", "tags", "onValueChange"]);
5321
5467
  const rawId = React.useId();
5322
5468
  const toolbarId = `toolbar-${rawId.replace(/:/g, "")}`;
5323
5469
  const defaultValueRef = React.useRef(sanitize(defaultValue !== null && defaultValue !== void 0 ? defaultValue : ""));
5324
5470
  const onTextChangeRef = React.useRef(onValueChange);
5471
+ const tagsRef = React.useRef(tags !== null && tags !== void 0 ? tags : []);
5472
+ const mentionsEnabled = tags !== void 0;
5325
5473
  const [linkTooltip, setLinkTooltip] = React.useState(null);
5326
5474
  const [hasTextSelected, setHasTextSelected] = React.useState(false);
5327
5475
  const locale = useLocaleContext();
5328
5476
  const translations = getTextEditorTranslations(locale);
5329
5477
  React.useLayoutEffect(() => {
5330
5478
  onTextChangeRef.current = onValueChange;
5479
+ tagsRef.current = tags !== null && tags !== void 0 ? tags : [];
5331
5480
  });
5332
5481
  const editor = react.useEditor({
5333
5482
  extensions: [
@@ -5354,7 +5503,13 @@ const TextEditor = (_a) => {
5354
5503
  }
5355
5504
  } : false,
5356
5505
  codeBlock: (formats === null || formats === void 0 ? void 0 : formats.includes("code")) ? {} : false
5357
- })
5506
+ }),
5507
+ ...mentionsEnabled ? [
5508
+ MentionWithTooltip.configure({
5509
+ HTMLAttributes: { class: "web-ui-mention" },
5510
+ suggestion: createMentionSuggestion(() => tagsRef.current)
5511
+ })
5512
+ ] : []
5358
5513
  ],
5359
5514
  content: defaultValueRef.current,
5360
5515
  editorProps: {