@gcforms/editor 0.0.1 → 1.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/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.1] - 2025-04-17
9
+
10
+ ### Changed
11
+
12
+ - Only show contentLength/maxLength indicator when contentLength reaches 80% of maxLength
13
+
14
+ ## [1.0.0] - 2025-04-17
15
+
16
+ ### Changed
17
+
18
+ - Refactored and moved wysiwyg editor to internal package
19
+ - Updated to latest Lexical version
20
+ - Replaced Link Editor with latest from Lexical examples
21
+ - Moved Markdown transforms to Transformer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcforms/editor",
3
- "version": "0.0.1",
3
+ "version": "1.0.1",
4
4
  "author": "Canadian Digital Service",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -8,11 +8,11 @@
8
8
  },
9
9
  "packageManager": "yarn@4.6.0",
10
10
  "dependencies": {
11
- "@lexical/react": "^0.28.0",
12
- "lexical": "^0.28.0"
11
+ "@lexical/react": "^0.30.0",
12
+ "lexical": "^0.30.0"
13
13
  },
14
14
  "peerDependencies": {
15
- "react": "^19.0.0",
16
- "react-dom": "^19.0.0"
15
+ "react": "^19.1.0",
16
+ "react-dom": "^19.1.0"
17
17
  }
18
18
  }
package/src/Editor.tsx CHANGED
@@ -1,11 +1,18 @@
1
1
  "use client";
2
2
  import React, { useId, useState } from "react";
3
+ import {
4
+ $convertFromMarkdownString,
5
+ $convertToMarkdownString,
6
+ TRANSFORMERS,
7
+ } from "@lexical/markdown";
3
8
  import { LexicalComposer } from "@lexical/react/LexicalComposer";
4
9
  import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
5
10
  import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
6
11
  import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
7
12
  import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
8
13
  import { ListPlugin } from "@lexical/react/LexicalListPlugin";
14
+ import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
15
+
9
16
  import ContentEditable from "./ui/ContentEditable";
10
17
  import TabControlPlugin from "./plugins/TabControlPlugin";
11
18
  import ToolbarPlugin from "./plugins/ToolbarPlugin";
@@ -13,14 +20,14 @@ import TreeViewPlugin from "./plugins/TreeViewPlugin";
13
20
  import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentPlugin";
14
21
  import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
15
22
  import { editorConfig } from "./config";
16
- import { $createParagraphNode, $getRoot } from "lexical";
17
- import {
18
- $convertFromMarkdownString,
19
- $convertToMarkdownString,
20
- TRANSFORMERS,
21
- } from "@lexical/markdown";
22
- import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
23
+ import { LINE_BREAK_FIX } from "./transformers";
24
+ import { Language } from "./i18n";
25
+ import { LocaleContext } from "./context/LocaleContext";
26
+
23
27
  import "./styles.css";
28
+ import { ToolbarContext } from "./context/ToolbarContext";
29
+ import ShortcutsPlugin from "./plugins/ShortcutsPlugin";
30
+ import { MaxLengthPlugin } from "./plugins/MaxLengthPlugin";
24
31
 
25
32
  interface EditorProps {
26
33
  id?: string;
@@ -28,7 +35,10 @@ interface EditorProps {
28
35
  showTreeview?: boolean;
29
36
  ariaLabel?: string;
30
37
  ariaDescribedBy?: string;
31
- lang?: string;
38
+ locale?: string;
39
+ contentLocale?: string;
40
+ className?: string;
41
+ maxLength?: number;
32
42
 
33
43
  onChange?(...args: unknown[]): unknown;
34
44
  }
@@ -40,7 +50,13 @@ export const Editor = ({
40
50
  ariaLabel = "AriaLabel",
41
51
  ariaDescribedBy = "AriaDescribedBy",
42
52
  onChange,
53
+ locale = "en",
54
+ contentLocale = "en",
55
+ className,
56
+ maxLength,
43
57
  }: EditorProps) => {
58
+ contentLocale = contentLocale || locale;
59
+
44
60
  const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | null>(null);
45
61
  const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false);
46
62
 
@@ -54,73 +70,69 @@ export const Editor = ({
54
70
  };
55
71
 
56
72
  return (
57
- <LexicalComposer
58
- initialConfig={{
59
- ...editorConfig,
60
- editorState: () => {
61
- // @TODO: how to make this configurable
62
- if (!content) {
63
- const root = $getRoot();
64
- const paragraphNode = $createParagraphNode();
65
- root.append(paragraphNode);
66
- return;
67
- }
68
- $convertFromMarkdownString(content, TRANSFORMERS);
69
- },
70
- }}
71
- >
72
- <div>
73
- <ToolbarPlugin editorId={id || ""} />
74
-
75
- <RichTextPlugin
76
- contentEditable={
77
- <div className="gc-editor-container" ref={onRef}>
78
- <ContentEditable
79
- placeholder={""}
80
- id={editorId}
81
- ariaLabel={ariaLabel && ariaLabel}
82
- ariaDescribedBy={ariaDescribedBy && ariaDescribedBy}
83
- />
84
- </div>
85
- }
86
- ErrorBoundary={LexicalErrorBoundary}
87
- />
88
- <HistoryPlugin />
89
- {showTreeview && <TreeViewPlugin />}
90
- <OnChangePlugin // @TODO: make the following configurable
91
- onChange={(editorState) => {
92
- editorState.read(() => {
93
- // Read the contents of the EditorState here.
94
- const markdown = $convertToMarkdownString(TRANSFORMERS);
95
-
96
- // Add two spaces to previous line for linebreaks (this is not handled properly by $convertToMarkdownString)
97
- const lines = markdown.split("\n");
98
- lines.forEach((currentLine, i) => {
99
- if (i > 0) {
100
- const previousLine = lines[i - 1];
101
- if (previousLine !== "" && currentLine !== "") {
102
- lines[i - 1] = previousLine.trim() + " ";
103
- }
104
- }
105
- });
106
- onChange && onChange(lines.join("\n"));
107
- });
73
+ <LocaleContext initialLocale={locale as Language}>
74
+ <ToolbarContext>
75
+ <LexicalComposer
76
+ initialConfig={{
77
+ ...editorConfig,
78
+ editorState: () => {
79
+ $convertFromMarkdownString(content, [...TRANSFORMERS]);
80
+ },
108
81
  }}
109
- />
110
- <ListPlugin />
111
- <LinkPlugin />
112
- <TabControlPlugin />
113
- <ListMaxIndentLevelPlugin maxDepth={5} />
114
- {floatingAnchorElem && (
115
- <>
116
- <FloatingLinkEditorPlugin
117
- anchorElem={floatingAnchorElem}
118
- isLinkEditMode={isLinkEditMode}
119
- setIsLinkEditMode={setIsLinkEditMode}
82
+ >
83
+ <div className="gc-editor-container">
84
+ <ToolbarPlugin editorId={editorId} setIsLinkEditMode={setIsLinkEditMode} />
85
+ <ShortcutsPlugin setIsLinkEditMode={setIsLinkEditMode} />
86
+ <RichTextPlugin
87
+ contentEditable={
88
+ <div
89
+ className="gc-editor-container"
90
+ ref={onRef}
91
+ {...(contentLocale && { lang: contentLocale })}
92
+ >
93
+ <ContentEditable
94
+ className={className || ""}
95
+ placeholder={""}
96
+ id={editorId}
97
+ ariaLabel={ariaLabel}
98
+ ariaDescribedBy={ariaDescribedBy}
99
+ />
100
+ </div>
101
+ }
102
+ ErrorBoundary={LexicalErrorBoundary}
120
103
  />
121
- </>
122
- )}
123
- </div>
124
- </LexicalComposer>
104
+ <HistoryPlugin />
105
+ {showTreeview && <TreeViewPlugin />}
106
+ <OnChangePlugin
107
+ onChange={(editorState) => {
108
+ editorState.read(() => {
109
+ const markdown = $convertToMarkdownString([...TRANSFORMERS, LINE_BREAK_FIX]);
110
+ onChange && onChange(markdown);
111
+ });
112
+ }}
113
+ />
114
+ <ListPlugin />
115
+ <LinkPlugin />
116
+ <TabControlPlugin />
117
+ <ListMaxIndentLevelPlugin maxDepth={5} />
118
+
119
+ {floatingAnchorElem && (
120
+ <>
121
+ <FloatingLinkEditorPlugin
122
+ anchorElem={floatingAnchorElem}
123
+ isLinkEditMode={isLinkEditMode}
124
+ setIsLinkEditMode={setIsLinkEditMode}
125
+ />
126
+ </>
127
+ )}
128
+ {maxLength && (
129
+ <>
130
+ <MaxLengthPlugin maxLength={maxLength} />
131
+ </>
132
+ )}
133
+ </div>
134
+ </LexicalComposer>
135
+ </ToolbarContext>
136
+ </LocaleContext>
125
137
  );
126
138
  };
package/src/config.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  import { HeadingNode } from "@lexical/rich-text";
2
2
  import { LinkNode } from "@lexical/link";
3
3
  import { ListItemNode, ListNode } from "@lexical/list";
4
- import basicTheme from "./themes/BasicTheme";
5
4
 
6
5
  export const editorConfig = {
7
6
  namespace: "FormBuilder",
8
- // The editor theme
9
- theme: basicTheme,
7
+ theme: {
8
+ text: {
9
+ bold: "font-bold",
10
+ italic: "italic",
11
+ },
12
+ },
10
13
  // Handling of errors during update
11
14
  onError(error: Error) {
12
15
  throw error;
@@ -0,0 +1,31 @@
1
+ import React, { createContext, useContext } from "react";
2
+ import translations, { Language } from "../i18n";
3
+
4
+ interface LocaleContextProps {
5
+ locale: Language;
6
+ }
7
+
8
+ const Context = createContext<LocaleContextProps | undefined>(undefined);
9
+
10
+ export const LocaleContext: React.FC<{
11
+ initialLocale: Language;
12
+ children: React.ReactNode;
13
+ }> = ({ initialLocale, children }) => {
14
+ let locale = initialLocale;
15
+
16
+ if (!translations[initialLocale as Language]) {
17
+ // eslint-disable-next-line no-console
18
+ console.warn(`The locale "${initialLocale}" is not supported. Defaulting to "en".`);
19
+ locale = "en";
20
+ }
21
+
22
+ return <Context.Provider value={{ locale }}>{children}</Context.Provider>;
23
+ };
24
+
25
+ export const useLocale = (): LocaleContextProps => {
26
+ const context = useContext(Context);
27
+ if (!context) {
28
+ throw new Error("useLocale must be used within a LocaleProvider");
29
+ }
30
+ return context;
31
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+
9
+ import type { JSX } from "react";
10
+
11
+ import React, { createContext, ReactNode, useCallback, useContext, useMemo, useState } from "react";
12
+
13
+ export const MIN_ALLOWED_FONT_SIZE = 8;
14
+ export const MAX_ALLOWED_FONT_SIZE = 72;
15
+ export const DEFAULT_FONT_SIZE = 15;
16
+
17
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
18
+ const rootTypeToRootName = {
19
+ root: "Root",
20
+ table: "Table",
21
+ };
22
+
23
+ export const blockTypeToBlockName = {
24
+ bullet: "Bulleted List",
25
+ check: "Check List",
26
+ h2: "Heading 2",
27
+ h3: "Heading 3",
28
+ number: "Numbered List",
29
+ paragraph: "Normal",
30
+ };
31
+
32
+ const INITIAL_TOOLBAR_STATE = {
33
+ blockType: "paragraph" as keyof typeof blockTypeToBlockName,
34
+ canRedo: false,
35
+ canUndo: false,
36
+ isBold: false,
37
+ isItalic: false,
38
+ isLink: false,
39
+ rootType: "root" as keyof typeof rootTypeToRootName,
40
+ };
41
+
42
+ type ToolbarState = typeof INITIAL_TOOLBAR_STATE;
43
+
44
+ // Utility type to get keys and infer value types
45
+ type ToolbarStateKey = keyof ToolbarState;
46
+ type ToolbarStateValue<Key extends ToolbarStateKey> = ToolbarState[Key];
47
+
48
+ type ContextShape = {
49
+ toolbarState: ToolbarState;
50
+ updateToolbarState<Key extends ToolbarStateKey>(key: Key, value: ToolbarStateValue<Key>): void;
51
+ };
52
+
53
+ const Context = createContext<ContextShape | undefined>(undefined);
54
+
55
+ export const ToolbarContext = ({ children }: { children: ReactNode }): JSX.Element => {
56
+ const [toolbarState, setToolbarState] = useState(INITIAL_TOOLBAR_STATE);
57
+
58
+ const updateToolbarState = useCallback(
59
+ <Key extends ToolbarStateKey>(key: Key, value: ToolbarStateValue<Key>) => {
60
+ setToolbarState((prev) => ({
61
+ ...prev,
62
+ [key]: value,
63
+ }));
64
+ },
65
+ []
66
+ );
67
+
68
+ const contextValue = useMemo(() => {
69
+ return {
70
+ toolbarState,
71
+ updateToolbarState,
72
+ };
73
+ }, [toolbarState, updateToolbarState]);
74
+
75
+ return <Context.Provider value={contextValue}>{children}</Context.Provider>;
76
+ };
77
+
78
+ export const useToolbarState = () => {
79
+ const context = useContext(Context);
80
+
81
+ if (context === undefined) {
82
+ throw new Error("useToolbarState must be used within a ToolbarProvider");
83
+ }
84
+
85
+ return context;
86
+ };
@@ -0,0 +1,19 @@
1
+ import { useCallback } from "react";
2
+ import translations from "../i18n";
3
+ import type { Translations } from "../i18n";
4
+ import type { TranslationKey } from "../i18n";
5
+ import { useLocale } from "../context/LocaleContext";
6
+
7
+ export const useTranslation = () => {
8
+ const { locale } = useLocale();
9
+
10
+ const t = useCallback(
11
+ (key: TranslationKey): string => {
12
+ const langTranslations = translations[locale] as Translations;
13
+ return langTranslations[key] || key;
14
+ },
15
+ [locale]
16
+ );
17
+
18
+ return { t, locale };
19
+ };
@@ -0,0 +1,22 @@
1
+ {
2
+ "textFormatting": "Text formatting",
3
+ "tooltipFormatBold": "Bold",
4
+ "tooltipFormatBulletList": "Bulleted list",
5
+ "tooltipFormatH2": "Heading 2",
6
+ "tooltipFormatH3": "Heading 3",
7
+ "tooltipFormatItalic": "Italic",
8
+ "tooltipFormatNumberedList": "Numbered list",
9
+ "tooltipInsertLink": "Insert link",
10
+ "formatBold": "Format bold",
11
+ "formatBulletList": "Format bulleted list",
12
+ "formatH2": "Format Heading 2",
13
+ "formatH3": "Format Heading 3",
14
+ "formatItalic": "Format italic",
15
+ "formatNumberedList": "Format numbered list",
16
+ "insertLink": "Insert link",
17
+ "cancelEditLink": "Cancel edit link",
18
+ "saveLink": "Save link",
19
+ "editLink": "Edit link",
20
+ "removeLink": "Remove link",
21
+ "pressEnterToEditLink": "Press Enter to edit link"
22
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "textFormatting": "Mise en forme du texte",
3
+ "tooltipFormatBold": "En gras",
4
+ "tooltipFormatBulletList": "Liste à puces",
5
+ "tooltipFormatH2": "En-tête niveau 2",
6
+ "tooltipFormatH3": "En-tête niveau 3",
7
+ "tooltipFormatItalic": "Italique",
8
+ "tooltipFormatNumberedList": "Liste numérotée",
9
+ "tooltipInsertLink": "Insérer un lien",
10
+ "formatBold": "Mettre en gras",
11
+ "formatBulletList": "Mettre en liste à puces",
12
+ "formatH2": "Mettre en en-tête niveau 2",
13
+ "formatH3": "Mettre en en-tête niveau 3",
14
+ "formatItalic": "Mettre en italique",
15
+ "formatNumberedList": "Mettre en liste numérotée",
16
+ "insertLink": "Insérer un lien",
17
+ "cancelEditLink": "Annuler la modification du lien",
18
+ "saveLink": "Enregistrer le lien",
19
+ "editLink": "Modifier le lien",
20
+ "removeLink": "Supprimer le lien",
21
+ "pressEnterToEditLink": "Appuyez sur Entrée pour modifier le lien"
22
+ }
@@ -0,0 +1,12 @@
1
+ import en from "./en.json";
2
+ import fr from "./fr.json";
3
+
4
+ const translations = {
5
+ en,
6
+ fr,
7
+ };
8
+
9
+ export default translations;
10
+ export type Language = keyof typeof translations;
11
+ export type TranslationKey = keyof typeof translations.en;
12
+ export type Translations = typeof translations.en;
@@ -0,0 +1,15 @@
1
+ export const CancelIcon = ({ className, title }: { className?: string; title?: string }) => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ height="24"
5
+ width="24"
6
+ className={className}
7
+ viewBox="0 0 24 24"
8
+ focusable="false"
9
+ aria-hidden={title ? false : true}
10
+ role={title ? "img" : "presentation"}
11
+ >
12
+ {title && <title>{title}</title>}
13
+ <path d="m8.4 17 3.6-3.6 3.6 3.6 1.4-1.4-3.6-3.6L17 8.4 15.6 7 12 10.6 8.4 7 7 8.4l3.6 3.6L7 15.6Zm3.6 5q-2.075 0-3.9-.788-1.825-.787-3.175-2.137-1.35-1.35-2.137-3.175Q2 14.075 2 12t.788-3.9q.787-1.825 2.137-3.175 1.35-1.35 3.175-2.138Q9.925 2 12 2t3.9.787q1.825.788 3.175 2.138 1.35 1.35 2.137 3.175Q22 9.925 22 12t-.788 3.9q-.787 1.825-2.137 3.175-1.35 1.35-3.175 2.137Q14.075 22 12 22Zm0-2q3.35 0 5.675-2.325Q20 15.35 20 12q0-3.35-2.325-5.675Q15.35 4 12 4 8.65 4 6.325 6.325 4 8.65 4 12q0 3.35 2.325 5.675Q8.65 20 12 20Zm0-8Z" />
14
+ </svg>
15
+ );
@@ -0,0 +1,15 @@
1
+ export const CheckIcon = ({ className, title }: { className?: string; title?: string }) => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ height="24px"
5
+ width="24px"
6
+ viewBox="0 -960 960 960"
7
+ className={className}
8
+ focusable="false"
9
+ aria-hidden={title ? false : true}
10
+ role={title ? "img" : "presentation"}
11
+ >
12
+ {title && <title>{title}</title>}
13
+ <path d="m424-296 282-282-56-56-226 226-114-114-56 56 170 170Zm56 216q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
14
+ </svg>
15
+ );
@@ -0,0 +1,15 @@
1
+ export const DeleteIcon = ({ className, title }: { className?: string; title?: string }) => (
2
+ <svg
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ height="48"
5
+ width="48"
6
+ className={className}
7
+ viewBox="0 -960 960 960"
8
+ focusable="false"
9
+ aria-hidden={title ? false : true}
10
+ role={title ? "img" : "presentation"}
11
+ >
12
+ {title && <title>{title}</title>}
13
+ <path d="M261-120q-24.75 0-42.375-17.625T201-180v-570h-41v-60h188v-30h264v30h188v60h-41v570q0 24-18 42t-42 18H261Zm438-630H261v570h438v-570ZM367-266h60v-399h-60v399Zm166 0h60v-399h-60v399ZM261-750v570-570Z" />
14
+ </svg>
15
+ );
@@ -1,14 +1,15 @@
1
1
  export const EditIcon = ({ className, title }: { className?: string; title?: string }) => (
2
2
  <svg
3
3
  xmlns="http://www.w3.org/2000/svg"
4
- height="21"
5
- width="21"
4
+ height="24px"
5
+ width="24px"
6
+ viewBox="0 -960 960 960"
6
7
  className={className}
7
8
  focusable="false"
8
9
  aria-hidden={title ? false : true}
9
10
  role={title ? "img" : "presentation"}
10
11
  >
11
12
  {title && <title>{title}</title>}
12
- <path d="M.582 20.418v-4.256l5.293-5.294L.991 5.957a1.654 1.654 0 0 1-.368-.546c-.082-.2-.123-.4-.123-.6 0-.2.04-.4.123-.6.082-.2.204-.382.368-.546l2.456-2.483c.163-.164.345-.286.546-.368.2-.082.4-.123.6-.123a1.678 1.678 0 0 1 1.173.491l4.911 4.911L16.052.718a.667.667 0 0 1 .273-.177c.091-.027.191-.041.3-.041.11 0 .21.014.3.04a.667.667 0 0 1 .273.178l3.084 3.083c.09.091.15.182.177.273.027.091.041.191.041.3 0 .11-.014.21-.04.3a.667.667 0 0 1-.178.273l-5.376 5.376 4.912 4.911c.164.164.286.35.368.56.082.209.123.413.123.613s-.04.4-.123.6c-.082.2-.204.383-.368.546l-2.456 2.429a1.653 1.653 0 0 1-.545.368c-.2.082-.4.123-.6.123-.2 0-.4-.041-.601-.123a1.653 1.653 0 0 1-.546-.368l-4.911-4.884-5.32 5.32H.581ZM7.048 9.695 9.504 7.24 7.512 5.248l-1.31 1.31L5.058 5.41l1.31-1.31L4.62 2.356 2.164 4.811l4.884 4.884Zm9.114 9.14 2.455-2.455-1.746-1.746-1.31 1.31-1.146-1.147 1.31-1.31-1.992-1.991-2.455 2.456 4.884 4.884Zm-13.943-.054h1.91L15.452 7.458l-1.91-1.91L2.22 16.87v1.91Zm14.38-12.47 1.91-1.91-1.91-1.91-1.91 1.91 1.91 1.91Z" />
13
+ <path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h357l-80 80H200v560h560v-278l80-80v358q0 33-23.5 56.5T760-120H200Zm280-360ZM360-360v-170l367-367q12-12 27-18t30-6q16 0 30.5 6t26.5 18l56 57q11 12 17 26.5t6 29.5q0 15-5.5 29.5T897-728L530-360H360Zm481-424-56-56 56 56ZM440-440h56l232-232-28-28-29-28-231 231v57Zm260-260-29-28 29 28 28 28-28-28Z" />
13
14
  </svg>
14
15
  );
@@ -4,12 +4,12 @@ export const LinkIcon = ({ className, title }: { className?: string; title?: str
4
4
  height="20"
5
5
  width="20"
6
6
  className={className}
7
- viewBox="0 0 20 20"
7
+ viewBox="0 -960 960 960"
8
8
  focusable="false"
9
9
  aria-hidden={title ? false : true}
10
10
  role={title ? "img" : "presentation"}
11
11
  >
12
12
  {title && <title>{title}</title>}
13
- <path d="M9 14H6q-1.667 0-2.833-1.167Q2 11.667 2 10q0-1.667 1.167-2.833Q4.333 6 6 6h3v1.5H6q-1.042 0-1.771.729Q3.5 8.958 3.5 10q0 1.042.729 1.771.729.729 1.771.729h3Zm-2-3.25v-1.5h6v1.5ZM11 14v-1.5h3q1.042 0 1.771-.729.729-.729.729-1.771 0-1.042-.729-1.771Q15.042 7.5 14 7.5h-3V6h3q1.667 0 2.833 1.167Q18 8.333 18 10q0 1.667-1.167 2.833Q15.667 14 14 14Z" />
13
+ <path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z" />
14
14
  </svg>
15
15
  );