@uva-glass/component-library 3.55.6 → 3.55.7
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/dist/Accordion-DvblEUVu.js.map +1 -1
- package/dist/AccordionCard-BXy-plB7.js.map +1 -1
- package/dist/ActionList-O9w1hqNg.js.map +1 -1
- package/dist/AddCatalogueLinkForm-rph4K9P0.js.map +1 -1
- package/dist/CheckboxButtonBar-5UMIFMKH.js.map +1 -1
- package/dist/ClipboardButton-geHjMl2J.js.map +1 -1
- package/dist/ControlsButton-B1XRpMUM.js.map +1 -1
- package/dist/CopyableText-DkyhZ6wk.js.map +1 -1
- package/dist/Drawer-e7UFHVg9.js.map +1 -1
- package/dist/EmptyStateDataDisplay-D0QiI8nE.js.map +1 -1
- package/dist/ExpandableInfoPanel-CnBVVNlJ.js.map +1 -1
- package/dist/FeedbackBar-ByKp32HE.js.map +1 -1
- package/dist/FieldHint-fxnEOsVZ.js.map +1 -1
- package/dist/Input-Bn-arMaC.js.map +1 -1
- package/dist/InputField-BWAy19w9.js.map +1 -1
- package/dist/Lexical.prod-CqPLSi0R.js.map +1 -1
- package/dist/LexicalClipboard.prod-BVw_sFpo.js.map +1 -1
- package/dist/LexicalComposerContext.prod-Ca-YN_fj.js.map +1 -1
- package/dist/LexicalExtension.prod-CUQTgbur.js.map +1 -1
- package/dist/LexicalLink.prod-DMhcqD9x.js.map +1 -1
- package/dist/LexicalList.prod-eYlcb5Ao.js.map +1 -1
- package/dist/LexicalTable.prod-DlOxUQou.js.map +1 -1
- package/dist/LexicalUtils.prod-BqcriFWV.js.map +1 -1
- package/dist/LinkContextMenu-ePn1iZ_z.js.map +1 -1
- package/dist/MenuLink-Cm1yZO_5.js.map +1 -1
- package/dist/ModalDialog-N50NGnd4.js.map +1 -1
- package/dist/MultiSelectBox-Cuh_o-A1.js.map +1 -1
- package/dist/MultiSelectItem-BF5xRjz5.js.map +1 -1
- package/dist/Notification-CIhsHuF0.js.map +1 -1
- package/dist/OverlayStepBar-CjaoYdsh.js.map +1 -1
- package/dist/PageOverlay-S4xsqJha.js.map +1 -1
- package/dist/PasteHandlerPlugin-BNp8ST0q.js.map +1 -1
- package/dist/Periods-BYETtB6R.js.map +1 -1
- package/dist/ProgrammeCard-76gG7ksx.js.map +1 -1
- package/dist/RadioGroup-JeTjVp5B.js.map +1 -1
- package/dist/Slider-BlLyrET9.js.map +1 -1
- package/dist/SortableProvider-8HYiBXJS.js.map +1 -1
- package/dist/Spinner-BpElTDIu.js.map +1 -1
- package/dist/StatusPill-DM7n3I4W.js.map +1 -1
- package/dist/TableCellContextMenu-D0IMQr3E.js.map +1 -1
- package/dist/TableCellResizerPlugin-Bc5y-Qbx.js.map +1 -1
- package/dist/TextArea-CuASC9bX.js.map +1 -1
- package/dist/ToggleSwitch-C7z7JVtx.js.map +1 -1
- package/dist/Tooltip-D27p9kh4.js.map +1 -1
- package/dist/VarIndicator-C-GueOZD.js.map +1 -1
- package/dist/assets/{src/css/fonts.css → fonts.css} +4 -4
- package/dist/chunk-SZQXB3JV-D6GR5B4F.js.map +1 -1
- package/dist/{client-8jeXrH7N.js → client-b7M3CmlC.js} +9 -9
- package/dist/{client-8jeXrH7N.js.map → client-b7M3CmlC.js.map} +1 -1
- package/dist/clsx-BD2qMUU1.js.map +1 -1
- package/dist/components/Accordion/Accordion.test.js +2 -2
- package/dist/components/Accordion/Accordion.test.js.map +1 -1
- package/dist/components/AccordionCard/AccordionCard.test.js +2 -2
- package/dist/components/AccordionCard/AccordionCard.test.js.map +1 -1
- package/dist/components/ActionList/ActionList.test.js +2 -2
- package/dist/components/ActionList/ActionList.test.js.map +1 -1
- package/dist/components/AppStatusBar/AppStatusBar.test.js +1 -1
- package/dist/components/AppStatusBar/AppStatusBar.test.js.map +1 -1
- package/dist/components/Attention/Attention.test.js +1 -1
- package/dist/components/Attention/Attention.test.js.map +1 -1
- package/dist/components/Backdrop/Backdrop.test.js +1 -1
- package/dist/components/Backdrop/Backdrop.test.js.map +1 -1
- package/dist/components/Buttons/Button.test.js +1 -1
- package/dist/components/Buttons/Button.test.js.map +1 -1
- package/dist/components/Buttons/LinkButton.test.js +1 -1
- package/dist/components/Buttons/LinkButton.test.js.map +1 -1
- package/dist/components/Buttons/MenuButton.js.map +1 -1
- package/dist/components/Buttons/MenuButton.test.js +1 -1
- package/dist/components/Buttons/MenuButton.test.js.map +1 -1
- package/dist/components/Card/Card.test.js +1 -1
- package/dist/components/Card/Card.test.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.test.js +2 -2
- package/dist/components/Checkbox/Checkbox.test.js.map +1 -1
- package/dist/components/CheckboxButtonBar/CheckboxButtonBar.test.js +1 -1
- package/dist/components/CheckboxButtonBar/CheckboxButtonBar.test.js.map +1 -1
- package/dist/components/CheckboxTree/CheckboxTree.stories.js.map +1 -1
- package/dist/components/Drawer/Drawer.stories.js.map +1 -1
- package/dist/components/FeedbackBar/FeedbackBar.stories.js.map +1 -1
- package/dist/components/FeedbackBar/FeedbackBar.test.js +1 -1
- package/dist/components/FeedbackBar/FeedbackBar.test.js.map +1 -1
- package/dist/components/Icon/Icon.js.map +1 -1
- package/dist/components/IconButton/IconButton.stories.js.map +1 -1
- package/dist/components/InputField/InputField.stories.js.map +1 -1
- package/dist/components/Label/Label.test.js +1 -1
- package/dist/components/Label/Label.test.js.map +1 -1
- package/dist/components/MetaDataBar/MetaDataBar.test.js +1 -1
- package/dist/components/MetaDataBar/MetaDataBar.test.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.stories.js.map +1 -1
- package/dist/components/MultiSelectBox/MultiSelectBox.stories.js.map +1 -1
- package/dist/components/MultiSelectBox/MultiSelectBox.test.js +1 -1
- package/dist/components/MultiSelectBox/MultiSelectBox.test.js.map +1 -1
- package/dist/components/Notification/Notification.test.js +1 -1
- package/dist/components/Notification/Notification.test.js.map +1 -1
- package/dist/components/OverlayStepBar/OverlayStepBar.stories.js.map +1 -1
- package/dist/components/PageOverlay/PageOverlay.stories.js +2 -2
- package/dist/components/PageOverlay/PageOverlay.stories.js.map +1 -1
- package/dist/components/RadioGroup/Radiogroup.test.js +1 -1
- package/dist/components/RadioGroup/Radiogroup.test.js.map +1 -1
- package/dist/components/RteEditor/Plugins/BlockFormatPlugin/BlockFormatPlugin.js.map +1 -1
- package/dist/components/RteEditor/Plugins/CourseCataloguePlugin/CourseCataloguePlugin.js.map +1 -1
- package/dist/components/RteEditor/Plugins/ListPlugin/ListPlugin.js.map +1 -1
- package/dist/components/RteEditor/Plugins/TablePlugin/helpers.js.map +1 -1
- package/dist/components/RteEditor/Providers/LanguageProvider.js.map +1 -1
- package/dist/components/RteEditor/RteEditor.stories.js.map +1 -1
- package/dist/components/RteEditor/helpers.js.map +1 -1
- package/dist/components/RteEditor/hooks/useFlyout.js.map +1 -1
- package/dist/components/RteEditor/hooks/useTranslation.js.map +1 -1
- package/dist/components/RteEditor/nodes/CourseCatalogueNode/CourseCatalogueNode.js.map +1 -1
- package/dist/components/RteEditor/nodes/TableWrapperNode/TableWrapperNode.js.map +1 -1
- package/dist/components/SearchField/SearchField.js.map +1 -1
- package/dist/components/SearchField/SearcheField.stories.js.map +1 -1
- package/dist/components/SearchField/components/ResultsOption.js.map +1 -1
- package/dist/components/SelectListbox/SelectListBox.stories.js.map +1 -1
- package/dist/components/SelectListbox/SelectListbox.js.map +1 -1
- package/dist/components/SelectListbox/SelectProvider.js.map +1 -1
- package/dist/components/SelectListbox/components/SelectButton.js.map +1 -1
- package/dist/components/SelectListbox/components/SelectContainer.js.map +1 -1
- package/dist/components/SelectListbox/components/SelectOption.js.map +1 -1
- package/dist/components/SelectListbox/components/SelectOptionBox.js.map +1 -1
- package/dist/components/Slider/Slider.stories.js.map +1 -1
- package/dist/components/Sortable/Sortable.js.map +1 -1
- package/dist/components/Sortable/components/SortableHandle.js.map +1 -1
- package/dist/components/Sortable/components/SortableItem.js.map +1 -1
- package/dist/components/Sortable/sortableReducer.js.map +1 -1
- package/dist/components/StatusBadge/StatusBadge.test.js +1 -1
- package/dist/components/StatusBadge/StatusBadge.test.js.map +1 -1
- package/dist/components/TextArea/TextArea.test.js +2 -2
- package/dist/components/TextArea/TextArea.test.js.map +1 -1
- package/dist/components/VarIndicator/VarIndicator.test.js +2 -2
- package/dist/components/VarIndicator/VarIndicator.test.js.map +1 -1
- package/dist/components/WeekSelector/WeekSelector.js.map +1 -1
- package/dist/components/WeekSelector/WeekSelector.stories.js.map +1 -1
- package/dist/components/WeekSelector/components/WeekSelectorOption.js.map +1 -1
- package/dist/components/hooks/useDebounce.js.map +1 -1
- package/dist/components/hooks/useOnClickOutside.js.map +1 -1
- package/dist/components/hooks/usePositionedFloaters.js.map +1 -1
- package/dist/components/hooks/useResponsive.js.map +1 -1
- package/dist/components-BzxAULjf.js.map +1 -1
- package/dist/dist-V_v3kC0L.js.map +1 -1
- package/dist/{esm-BwHoYxVb.js → esm-BoujHjK5.js} +2 -2
- package/dist/{esm-BwHoYxVb.js.map → esm-BoujHjK5.js.map} +1 -1
- package/dist/helpers-B1VK8xTF.js.map +1 -1
- package/dist/lib-BvOQt8El.js.map +1 -1
- package/dist/{react-18-B7MZmX_Z.js → react-18-DxAWL8_m.js} +2 -2
- package/dist/{react-18-B7MZmX_Z.js.map → react-18-DxAWL8_m.js.map} +1 -1
- package/dist/{react.esm-D_F72rY1.js → react.esm-CweFe1CK.js} +2 -2
- package/dist/{react.esm-D_F72rY1.js.map → react.esm-CweFe1CK.js.map} +1 -1
- package/dist/setupTests.js +2 -2
- package/dist/setupTests.js.map +1 -1
- package/dist/storybook__react.d.js +1 -1
- package/dist/storybook__react.d.js.map +1 -1
- package/dist/test-Pa9Lwbsq.js.map +1 -1
- package/dist/test-utils-Cmn06m_x.js.map +1 -1
- package/package.json +7 -6
- /package/dist/assets/favicon/{src/assets/favicon/android-chrome-192x192.png → android-chrome-192x192.png} +0 -0
- /package/dist/assets/favicon/{src/assets/favicon/android-chrome-512x512.png → android-chrome-512x512.png} +0 -0
- /package/dist/assets/favicon/{src/assets/favicon/apple-touch-icon.png → apple-touch-icon.png} +0 -0
- /package/dist/assets/favicon/{src/assets/favicon/favicon-16x16.png → favicon-16x16.png} +0 -0
- /package/dist/assets/favicon/{src/assets/favicon/favicon-32x32.png → favicon-32x32.png} +0 -0
- /package/dist/assets/favicon/{src/assets/favicon/favicon.ico → favicon.ico} +0 -0
- /package/dist/assets/favicon/{src/assets/favicon/favicon.svg → favicon.svg} +0 -0
- /package/dist/assets/favicon/{src/assets/favicon/site.webmanifest → site.webmanifest} +0 -0
- /package/dist/assets/fonts/{src/assets/fonts/SourceSans3-Regular.otf.woff → SourceSans3-Regular.otf.woff} +0 -0
- /package/dist/assets/fonts/{src/assets/fonts/SourceSans3-Regular.otf.woff2 → SourceSans3-Regular.otf.woff2} +0 -0
- /package/dist/assets/fonts/{src/assets/fonts/SourceSans3-Semibold.otf.woff → SourceSans3-Semibold.otf.woff} +0 -0
- /package/dist/assets/fonts/{src/assets/fonts/SourceSans3-Semibold.otf.woff2 → SourceSans3-Semibold.otf.woff2} +0 -0
- /package/dist/assets/{src/css/globals.css → globals.css} +0 -0
- /package/dist/assets/{src/css/variables.css → variables.css} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFlyout.js","names":[],"sources":["../../../../src/components/RteEditor/hooks/useFlyout.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\n\nimport type { ReactNode, RefObject } from 'react';\n\nimport { Flyout } from './components/Flyout';\n\nimport { useOnClickOutside } from 'components/hooks/useOnClickOutside';\n\ninterface FlyoutOptions {\n position: { top: number; left: number };\n children?: ReactNode;\n}\n\nexport interface FlyoutHookResult {\n flyout: ReactNode;\n showFlyout: (options: FlyoutOptions) => void;\n hideFlyout: () => void;\n isVisible: boolean;\n}\n\nexport const useFlyout = (): FlyoutHookResult => {\n const flyoutRef = useRef<HTMLDivElement>(null);\n const [visible, setVisible] = useState(false);\n const [options, setOptions] = useState<FlyoutOptions>({\n position: { top: 0, left: 0 },\n children: null,\n });\n\n const showFlyout = (customOptions: FlyoutOptions): void => {\n setVisible(true);\n setOptions({ ...options, ...customOptions });\n };\n\n const hideFlyout = (): void => {\n setVisible(false);\n setOptions({ ...options });\n };\n\n useOnClickOutside(flyoutRef as RefObject<HTMLElement>, hideFlyout);\n\n const flyout = (\n <Flyout visible={visible} position={options.position} ref={flyoutRef}>\n {options.children}\n </Flyout>\n );\n\n return {\n flyout,\n showFlyout,\n hideFlyout,\n isVisible: visible,\n };\n};\n"],"mappings":";;;;;AAoBA,IAAa,UAAoC;CAC/C,IAAM,IAAY,EAAuB,KAAK,EACxC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAS,KAAc,EAAwB;EACpD,UAAU;GAAE,KAAK;GAAG,MAAM;GAAG;EAC7B,UAAU;EACX,CAAC,EAEI,KAAc,MAAuC;
|
|
1
|
+
{"version":3,"file":"useFlyout.js","names":[],"sources":["../../../../src/components/RteEditor/hooks/useFlyout.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\n\nimport type { ReactNode, RefObject } from 'react';\n\nimport { Flyout } from './components/Flyout';\n\nimport { useOnClickOutside } from 'components/hooks/useOnClickOutside';\n\ninterface FlyoutOptions {\n position: { top: number; left: number };\n children?: ReactNode;\n}\n\nexport interface FlyoutHookResult {\n flyout: ReactNode;\n showFlyout: (options: FlyoutOptions) => void;\n hideFlyout: () => void;\n isVisible: boolean;\n}\n\nexport const useFlyout = (): FlyoutHookResult => {\n const flyoutRef = useRef<HTMLDivElement>(null);\n const [visible, setVisible] = useState(false);\n const [options, setOptions] = useState<FlyoutOptions>({\n position: { top: 0, left: 0 },\n children: null,\n });\n\n const showFlyout = (customOptions: FlyoutOptions): void => {\n setVisible(true);\n setOptions({ ...options, ...customOptions });\n };\n\n const hideFlyout = (): void => {\n setVisible(false);\n setOptions({ ...options });\n };\n\n useOnClickOutside(flyoutRef as RefObject<HTMLElement>, hideFlyout);\n\n const flyout = (\n <Flyout visible={visible} position={options.position} ref={flyoutRef}>\n {options.children}\n </Flyout>\n );\n\n return {\n flyout,\n showFlyout,\n hideFlyout,\n isVisible: visible,\n };\n};\n"],"mappings":";;;;;AAoBA,IAAa,UAAoC;CAC/C,IAAM,IAAY,EAAuB,KAAK,EACxC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAS,KAAc,EAAwB;EACpD,UAAU;GAAE,KAAK;GAAG,MAAM;GAAG;EAC7B,UAAU;EACX,CAAC,EAEI,KAAc,MAAuC;EAEzD,AADA,EAAW,GAAK,EAChB,EAAW;GAAE,GAAG;GAAS,GAAG;GAAe,CAAC;IAGxC,UAAyB;EAE7B,AADA,EAAW,GAAM,EACjB,EAAW,EAAE,GAAG,GAAS,CAAC;;CAW5B,OARA,EAAkB,GAAqC,EAAW,EAQ3D;EACL,QANA,kBAAC,GAAD;GAAiB;GAAS,UAAU,EAAQ;GAAU,KAAK;aACxD,EAAQ;GACF,CAIT;EACA;EACA;EACA,WAAW;EACZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTranslation.js","names":[],"sources":["../../../../src/components/RteEditor/hooks/useTranslation.tsx"],"sourcesContent":["import { translations } from 'components/RteEditor/translations';\nimport { useLanguage } from 'components/RteEditor/Providers/LanguageProvider';\n\nexport const useTranslation = () => {\n const { currentLanguage } = useLanguage() as { currentLanguage: 'NL' | 'EN' };\n return translations[currentLanguage];\n};\n"],"mappings":";;;AAGA,IAAa,UAAuB;CAClC,IAAM,EAAE,uBAAoB,GAAa;
|
|
1
|
+
{"version":3,"file":"useTranslation.js","names":[],"sources":["../../../../src/components/RteEditor/hooks/useTranslation.tsx"],"sourcesContent":["import { translations } from 'components/RteEditor/translations';\nimport { useLanguage } from 'components/RteEditor/Providers/LanguageProvider';\n\nexport const useTranslation = () => {\n const { currentLanguage } = useLanguage() as { currentLanguage: 'NL' | 'EN' };\n return translations[currentLanguage];\n};\n"],"mappings":";;;AAGA,IAAa,UAAuB;CAClC,IAAM,EAAE,uBAAoB,GAAa;CACzC,OAAO,EAAa"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CourseCatalogueNode.js","names":[],"sources":["../../../../../src/components/RteEditor/nodes/CourseCatalogueNode/CourseCatalogueNode.tsx"],"sourcesContent":["import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';\r\nimport {\r\n COMMAND_PRIORITY_NORMAL,\r\n createCommand,\r\n ElementNode,\r\n $insertNodes,\r\n $createTextNode,\r\n $isRangeSelection,\r\n $getSelection,\r\n $isTextNode,\r\n} from 'lexical';\r\nimport { useEffect } from 'react';\r\nimport { $getNearestNodeOfType } from '@lexical/utils';\r\n\r\nimport type { DOMConversionMap, EditorConfig, LexicalNode, NodeKey, SerializedElementNode, Spread } from 'lexical';\r\n\r\nexport type SerializedCourseCatalogueNode = Spread<\r\n {\r\n linkType: 'course' | 'programme';\r\n code: string;\r\n label: string;\r\n },\r\n SerializedElementNode\r\n>;\r\n\r\nexport class CourseCatalogueNode extends ElementNode {\r\n __linkType: 'course' | 'programme';\r\n __code: string;\r\n __label: string;\r\n\r\n constructor(linkType: 'course' | 'programme', code: string, label: string, key?: NodeKey) {\r\n super(key);\r\n this.__linkType = linkType;\r\n this.__code = code;\r\n this.__label = label;\r\n }\r\n\r\n isInline(): boolean {\r\n return true;\r\n }\r\n\r\n canBeEmpty(): boolean {\r\n return false;\r\n }\r\n\r\n canInsertTextBefore(): boolean {\r\n return false;\r\n }\r\n\r\n canInsertTextAfter(): boolean {\r\n return true;\r\n }\r\n\r\n static clone(node: CourseCatalogueNode): CourseCatalogueNode {\r\n return new CourseCatalogueNode(node.__linkType, node.__code, node.__label, node.__key);\r\n }\r\n\r\n static getType(): string {\r\n return 'courseCatalogueLink';\r\n }\r\n\r\n createDOM(config: EditorConfig): HTMLElement {\r\n const element = document.createElement('a');\r\n element.className = config.theme.courseCatalogueLink;\r\n // eslint-disable-next-line no-script-url\r\n element.href = 'javascript:void(0);';\r\n element.setAttribute('data-linktype', this.__linkType);\r\n element.setAttribute('data-code', this.__code);\r\n return element;\r\n }\r\n\r\n updateDOM(): false {\r\n return false;\r\n }\r\n\r\n static importDOM(): DOMConversionMap<HTMLAnchorElement> | null {\r\n return {\r\n // eslint-disable-next-line id-length\r\n a: (domNode: HTMLAnchorElement) => {\r\n const linkTypeAttr = domNode.getAttribute('data-linktype');\r\n if (linkTypeAttr !== 'course' && linkTypeAttr !== 'programme') {\r\n return null;\r\n }\r\n const linkType = linkTypeAttr;\r\n const code = domNode.getAttribute('data-code');\r\n if (!code) {\r\n return null;\r\n }\r\n const label = domNode.textContent || '';\r\n if (!label) {\r\n return null;\r\n }\r\n return {\r\n conversion: () => {\r\n const node = $createCourseCatalogueNode(linkType, code, label);\r\n return { node };\r\n },\r\n priority: 2,\r\n };\r\n },\r\n };\r\n }\r\n\r\n static importJSON(serializedNode: SerializedCourseCatalogueNode): CourseCatalogueNode {\r\n const catalogueNode = $createCourseCatalogueNode(\r\n serializedNode.linkType,\r\n serializedNode.code,\r\n serializedNode.label\r\n );\r\n catalogueNode.setFormat(serializedNode.format);\r\n return catalogueNode;\r\n }\r\n\r\n exportJSON(): SerializedCourseCatalogueNode {\r\n return {\r\n ...super.exportJSON(),\r\n type: 'courseCatalogueLink',\r\n version: 1,\r\n linkType: this.__linkType,\r\n code: this.__code,\r\n label: this.__label,\r\n };\r\n }\r\n}\r\n\r\nexport function $createCourseCatalogueNode(\r\n linkType: 'course' | 'programme',\r\n code: string,\r\n label: string = ''\r\n): CourseCatalogueNode {\r\n return new CourseCatalogueNode(linkType, code, label);\r\n}\r\n\r\nexport function $isCourseCatalogueNode(node: LexicalNode): node is CourseCatalogueNode {\r\n return node instanceof CourseCatalogueNode;\r\n}\r\n\r\nexport function $getLinkTypeFromNode(node: LexicalNode): 'course' | 'programme' | null {\r\n if ($isCourseCatalogueNode(node)) {\r\n return node.__linkType;\r\n }\r\n return null;\r\n}\r\n\r\nexport const INSERT_COURSE_CATALOGUE_LINK = createCommand('insertCourseCatalogueLink');\r\n\r\nexport const REMOVE_COURSE_CATALOGUE_LINK = createCommand('removeCourseCatalogueLink');\r\n\r\nexport function CourseCatalogueLinkPlugin(): null {\r\n const [editor] = useLexicalComposerContext();\r\n if (!editor.hasNodes([CourseCatalogueNode])) {\r\n throw new Error('CourseCataloguePlugin: CourseCatalogueNode is not registered on editor');\r\n }\r\n useEffect(() => {\r\n const insertCourseCatalogueLink = editor.registerCommand(\r\n INSERT_COURSE_CATALOGUE_LINK,\r\n (payload: { linkType: 'course' | 'programme'; code: string; label: string }) => {\r\n const courseCatalogueNode = $createCourseCatalogueNode(payload.linkType, payload.code, payload.label);\r\n const textNode = $createTextNode(payload.label);\r\n courseCatalogueNode.append(textNode);\r\n $insertNodes([courseCatalogueNode]);\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n const removeCourseCatalogueLink = editor.registerCommand(\r\n REMOVE_COURSE_CATALOGUE_LINK,\r\n () => {\r\n const selection = $getSelection();\r\n if ($isRangeSelection(selection)) {\r\n const node = selection.anchor.getNode();\r\n const offset = selection.anchor.offset;\r\n let courseCatalogueNode = $getNearestNodeOfType(node, CourseCatalogueNode);\r\n\r\n if (\r\n $isTextNode(node) &&\r\n offset === node.getTextContentSize() &&\r\n node.getNextSibling() instanceof CourseCatalogueNode\r\n ) {\r\n courseCatalogueNode = node.getNextSibling();\r\n }\r\n\r\n if (courseCatalogueNode) {\r\n const parent = courseCatalogueNode.getParent();\r\n courseCatalogueNode.remove();\r\n if (parent) {\r\n parent.append($createTextNode(' '));\r\n }\r\n }\r\n }\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n\r\n return () => {\r\n insertCourseCatalogueLink();\r\n removeCourseCatalogueLink();\r\n };\r\n }, [editor]);\r\n\r\n return null;\r\n}\r\n"],"mappings":";;;;;AAyBA,IAAa,IAAb,MAAa,UAA4B,EAAY;CACnD;CACA;CACA;CAEA,YAAY,GAAkC,GAAc,GAAe,GAAe;
|
|
1
|
+
{"version":3,"file":"CourseCatalogueNode.js","names":[],"sources":["../../../../../src/components/RteEditor/nodes/CourseCatalogueNode/CourseCatalogueNode.tsx"],"sourcesContent":["import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';\r\nimport {\r\n COMMAND_PRIORITY_NORMAL,\r\n createCommand,\r\n ElementNode,\r\n $insertNodes,\r\n $createTextNode,\r\n $isRangeSelection,\r\n $getSelection,\r\n $isTextNode,\r\n} from 'lexical';\r\nimport { useEffect } from 'react';\r\nimport { $getNearestNodeOfType } from '@lexical/utils';\r\n\r\nimport type { DOMConversionMap, EditorConfig, LexicalNode, NodeKey, SerializedElementNode, Spread } from 'lexical';\r\n\r\nexport type SerializedCourseCatalogueNode = Spread<\r\n {\r\n linkType: 'course' | 'programme';\r\n code: string;\r\n label: string;\r\n },\r\n SerializedElementNode\r\n>;\r\n\r\nexport class CourseCatalogueNode extends ElementNode {\r\n __linkType: 'course' | 'programme';\r\n __code: string;\r\n __label: string;\r\n\r\n constructor(linkType: 'course' | 'programme', code: string, label: string, key?: NodeKey) {\r\n super(key);\r\n this.__linkType = linkType;\r\n this.__code = code;\r\n this.__label = label;\r\n }\r\n\r\n isInline(): boolean {\r\n return true;\r\n }\r\n\r\n canBeEmpty(): boolean {\r\n return false;\r\n }\r\n\r\n canInsertTextBefore(): boolean {\r\n return false;\r\n }\r\n\r\n canInsertTextAfter(): boolean {\r\n return true;\r\n }\r\n\r\n static clone(node: CourseCatalogueNode): CourseCatalogueNode {\r\n return new CourseCatalogueNode(node.__linkType, node.__code, node.__label, node.__key);\r\n }\r\n\r\n static getType(): string {\r\n return 'courseCatalogueLink';\r\n }\r\n\r\n createDOM(config: EditorConfig): HTMLElement {\r\n const element = document.createElement('a');\r\n element.className = config.theme.courseCatalogueLink;\r\n // eslint-disable-next-line no-script-url\r\n element.href = 'javascript:void(0);';\r\n element.setAttribute('data-linktype', this.__linkType);\r\n element.setAttribute('data-code', this.__code);\r\n return element;\r\n }\r\n\r\n updateDOM(): false {\r\n return false;\r\n }\r\n\r\n static importDOM(): DOMConversionMap<HTMLAnchorElement> | null {\r\n return {\r\n // eslint-disable-next-line id-length\r\n a: (domNode: HTMLAnchorElement) => {\r\n const linkTypeAttr = domNode.getAttribute('data-linktype');\r\n if (linkTypeAttr !== 'course' && linkTypeAttr !== 'programme') {\r\n return null;\r\n }\r\n const linkType = linkTypeAttr;\r\n const code = domNode.getAttribute('data-code');\r\n if (!code) {\r\n return null;\r\n }\r\n const label = domNode.textContent || '';\r\n if (!label) {\r\n return null;\r\n }\r\n return {\r\n conversion: () => {\r\n const node = $createCourseCatalogueNode(linkType, code, label);\r\n return { node };\r\n },\r\n priority: 2,\r\n };\r\n },\r\n };\r\n }\r\n\r\n static importJSON(serializedNode: SerializedCourseCatalogueNode): CourseCatalogueNode {\r\n const catalogueNode = $createCourseCatalogueNode(\r\n serializedNode.linkType,\r\n serializedNode.code,\r\n serializedNode.label\r\n );\r\n catalogueNode.setFormat(serializedNode.format);\r\n return catalogueNode;\r\n }\r\n\r\n exportJSON(): SerializedCourseCatalogueNode {\r\n return {\r\n ...super.exportJSON(),\r\n type: 'courseCatalogueLink',\r\n version: 1,\r\n linkType: this.__linkType,\r\n code: this.__code,\r\n label: this.__label,\r\n };\r\n }\r\n}\r\n\r\nexport function $createCourseCatalogueNode(\r\n linkType: 'course' | 'programme',\r\n code: string,\r\n label: string = ''\r\n): CourseCatalogueNode {\r\n return new CourseCatalogueNode(linkType, code, label);\r\n}\r\n\r\nexport function $isCourseCatalogueNode(node: LexicalNode): node is CourseCatalogueNode {\r\n return node instanceof CourseCatalogueNode;\r\n}\r\n\r\nexport function $getLinkTypeFromNode(node: LexicalNode): 'course' | 'programme' | null {\r\n if ($isCourseCatalogueNode(node)) {\r\n return node.__linkType;\r\n }\r\n return null;\r\n}\r\n\r\nexport const INSERT_COURSE_CATALOGUE_LINK = createCommand('insertCourseCatalogueLink');\r\n\r\nexport const REMOVE_COURSE_CATALOGUE_LINK = createCommand('removeCourseCatalogueLink');\r\n\r\nexport function CourseCatalogueLinkPlugin(): null {\r\n const [editor] = useLexicalComposerContext();\r\n if (!editor.hasNodes([CourseCatalogueNode])) {\r\n throw new Error('CourseCataloguePlugin: CourseCatalogueNode is not registered on editor');\r\n }\r\n useEffect(() => {\r\n const insertCourseCatalogueLink = editor.registerCommand(\r\n INSERT_COURSE_CATALOGUE_LINK,\r\n (payload: { linkType: 'course' | 'programme'; code: string; label: string }) => {\r\n const courseCatalogueNode = $createCourseCatalogueNode(payload.linkType, payload.code, payload.label);\r\n const textNode = $createTextNode(payload.label);\r\n courseCatalogueNode.append(textNode);\r\n $insertNodes([courseCatalogueNode]);\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n const removeCourseCatalogueLink = editor.registerCommand(\r\n REMOVE_COURSE_CATALOGUE_LINK,\r\n () => {\r\n const selection = $getSelection();\r\n if ($isRangeSelection(selection)) {\r\n const node = selection.anchor.getNode();\r\n const offset = selection.anchor.offset;\r\n let courseCatalogueNode = $getNearestNodeOfType(node, CourseCatalogueNode);\r\n\r\n if (\r\n $isTextNode(node) &&\r\n offset === node.getTextContentSize() &&\r\n node.getNextSibling() instanceof CourseCatalogueNode\r\n ) {\r\n courseCatalogueNode = node.getNextSibling();\r\n }\r\n\r\n if (courseCatalogueNode) {\r\n const parent = courseCatalogueNode.getParent();\r\n courseCatalogueNode.remove();\r\n if (parent) {\r\n parent.append($createTextNode(' '));\r\n }\r\n }\r\n }\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n\r\n return () => {\r\n insertCourseCatalogueLink();\r\n removeCourseCatalogueLink();\r\n };\r\n }, [editor]);\r\n\r\n return null;\r\n}\r\n"],"mappings":";;;;;AAyBA,IAAa,IAAb,MAAa,UAA4B,EAAY;CACnD;CACA;CACA;CAEA,YAAY,GAAkC,GAAc,GAAe,GAAe;EAIxF,AAHA,MAAM,EAAI,EACV,KAAK,aAAa,GAClB,KAAK,SAAS,GACd,KAAK,UAAU;;CAGjB,WAAoB;EAClB,OAAO;;CAGT,aAAsB;EACpB,OAAO;;CAGT,sBAA+B;EAC7B,OAAO;;CAGT,qBAA8B;EAC5B,OAAO;;CAGT,OAAO,MAAM,GAAgD;EAC3D,OAAO,IAAI,EAAoB,EAAK,YAAY,EAAK,QAAQ,EAAK,SAAS,EAAK,MAAM;;CAGxF,OAAO,UAAkB;EACvB,OAAO;;CAGT,UAAU,GAAmC;EAC3C,IAAM,IAAU,SAAS,cAAc,IAAI;EAM3C,OALA,EAAQ,YAAY,EAAO,MAAM,qBAEjC,EAAQ,OAAO,uBACf,EAAQ,aAAa,iBAAiB,KAAK,WAAW,EACtD,EAAQ,aAAa,aAAa,KAAK,OAAO,EACvC;;CAGT,YAAmB;EACjB,OAAO;;CAGT,OAAO,YAAwD;EAC7D,OAAO,EAEL,IAAI,MAA+B;GACjC,IAAM,IAAe,EAAQ,aAAa,gBAAgB;GAC1D,IAAI,MAAiB,YAAY,MAAiB,aAChD,OAAO;GAET,IAAM,IAAW,GACX,IAAO,EAAQ,aAAa,YAAY;GAC9C,IAAI,CAAC,GACH,OAAO;GAET,IAAM,IAAQ,EAAQ,eAAe;GAIrC,OAHK,IAGE;IACL,mBAES,EAAE,MADI,EAA2B,GAAU,GAAM,EAC/C,EAAM;IAEjB,UAAU;IACX,GARQ;KAUZ;;CAGH,OAAO,WAAW,GAAoE;EACpF,IAAM,IAAgB,EACpB,EAAe,UACf,EAAe,MACf,EAAe,MAChB;EAED,OADA,EAAc,UAAU,EAAe,OAAO,EACvC;;CAGT,aAA4C;EAC1C,OAAO;GACL,GAAG,MAAM,YAAY;GACrB,MAAM;GACN,SAAS;GACT,UAAU,KAAK;GACf,MAAM,KAAK;GACX,OAAO,KAAK;GACb;;;AAIL,SAAgB,EACd,GACA,GACA,IAAgB,IACK;CACrB,OAAO,IAAI,EAAoB,GAAU,GAAM,EAAM;;AAGvD,SAAgB,EAAuB,GAAgD;CACrF,OAAO,aAAgB;;AAGzB,SAAgB,EAAqB,GAAkD;CAIrF,OAHI,EAAuB,EAAK,GACvB,EAAK,aAEP;;AAGT,IAAa,IAA+B,EAAc,4BAA4B,EAEzE,IAA+B,EAAc,4BAA4B;AAEtF,SAAgB,IAAkC;CAChD,IAAM,CAAC,KAAU,GAA2B;CAC5C,IAAI,CAAC,EAAO,SAAS,CAAC,EAAoB,CAAC,EACzC,MAAU,MAAM,yEAAyE;CAkD3F,OAhDA,QAAgB;EACd,IAAM,IAA4B,EAAO,gBACvC,IACC,MAA+E;GAC9E,IAAM,IAAsB,EAA2B,EAAQ,UAAU,EAAQ,MAAM,EAAQ,MAAM,EAC/F,IAAW,EAAgB,EAAQ,MAAM;GAG/C,OAFA,EAAoB,OAAO,EAAS,EACpC,EAAa,CAAC,EAAoB,CAAC,EAC5B;OAGV,EACK,IAA4B,EAAO,gBACvC,SACM;GACJ,IAAM,IAAY,GAAe;GACjC,IAAI,EAAkB,EAAU,EAAE;IAChC,IAAM,IAAO,EAAU,OAAO,SAAS,EACjC,IAAS,EAAU,OAAO,QAC5B,IAAsB,EAAsB,GAAM,EAAoB;IAU1E,IAPE,EAAY,EAAK,IACjB,MAAW,EAAK,oBAAoB,IACpC,EAAK,gBAAgB,YAAY,MAEjC,IAAsB,EAAK,gBAAgB,GAGzC,GAAqB;KACvB,IAAM,IAAS,EAAoB,WAAW;KAE9C,AADA,EAAoB,QAAQ,EACxB,KACF,EAAO,OAAO,EAAgB,IAAI,CAAC;;;GAIzC,OAAO;OAGV;EAED,aAAa;GAEX,AADA,GAA2B,EAC3B,GAA2B;;IAE5B,CAAC,EAAO,CAAC,EAEL"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TableWrapperNode.js","names":[],"sources":["../../../../../src/components/RteEditor/nodes/TableWrapperNode/TableWrapperNode.tsx"],"sourcesContent":["import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';\r\nimport {\r\n $createParagraphNode,\r\n $getSelection,\r\n $isElementNode,\r\n $isRangeSelection,\r\n COMMAND_PRIORITY_NORMAL,\r\n createCommand,\r\n ElementNode,\r\n} from 'lexical';\r\nimport { $getNearestNodeOfType } from '@lexical/utils';\r\nimport { $setBlocksType } from '@lexical/selection';\r\nimport { useEffect } from 'react';\r\n\r\nimport type { SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';\r\nimport type { EditorConfig, LexicalNode, RangeSelection } from 'lexical';\r\n\r\nexport type SerializedTableWrapperNode = SerializedDecoratorBlockNode;\r\n\r\nexport class TableWrapperNode extends ElementNode {\r\n static getType(): string {\r\n return 'tableWrapper';\r\n }\r\n\r\n static clone(node: TableWrapperNode): TableWrapperNode {\r\n return new TableWrapperNode(node.__key);\r\n }\r\n\r\n createDOM(config: EditorConfig): HTMLElement {\r\n const element = document.createElement('div');\r\n element.className = config.theme.tableWrapper;\r\n return element;\r\n }\r\n\r\n updateDOM(): false {\r\n return false;\r\n }\r\n\r\n insertNewAfter(_selection: RangeSelection, restoreSelection?: boolean | undefined): LexicalNode | null {\r\n const newBlock = $createParagraphNode();\r\n const direction = this.getDirection();\r\n newBlock.setDirection(direction);\r\n this.insertAfter(newBlock, restoreSelection);\r\n return newBlock;\r\n }\r\n}\r\n\r\nexport function $createTableWrapperNode(): TableWrapperNode {\r\n return new TableWrapperNode();\r\n}\r\n\r\nexport function $isTableWrapperNode(node: LexicalNode): node is TableWrapperNode {\r\n return node instanceof TableWrapperNode;\r\n}\r\n\r\nexport const INSERT_TABLE_WRAPPER = createCommand('insertTableWrapperNode');\r\n\r\nexport const REMOVE_TABLE_WRAPPER = createCommand('removeTableWrapperNode');\r\n\r\nexport function TableWrapperPlugin(): null {\r\n const [editor] = useLexicalComposerContext();\r\n if (!editor.hasNodes([TableWrapperNode])) {\r\n throw new Error('TableWrapperPlugin: TableWrapperNode is not registered on editor');\r\n }\r\n useEffect(() => {\r\n const insertTableWrapper = editor.registerCommand(\r\n INSERT_TABLE_WRAPPER,\r\n () => {\r\n const selection = $getSelection();\r\n if ($isRangeSelection(selection)) {\r\n $setBlocksType(selection, $createTableWrapperNode);\r\n }\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n\r\n const removeTableWrapper = editor.registerCommand(\r\n REMOVE_TABLE_WRAPPER,\r\n () => {\r\n const selection = $getSelection();\r\n if ($isRangeSelection(selection)) {\r\n const node = selection.anchor.getNode();\r\n if (!$isElementNode(node)) return false;\r\n const tableWrapperNode = $getNearestNodeOfType(node, TableWrapperNode);\r\n if (!tableWrapperNode) return false;\r\n const childrenNodes = tableWrapperNode?.getChildren();\r\n if (!childrenNodes) return false;\r\n const paragraphNode = $createParagraphNode();\r\n childrenNodes.forEach((child) => paragraphNode.append(child));\r\n tableWrapperNode.replace(paragraphNode);\r\n }\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n\r\n return () => {\r\n insertTableWrapper();\r\n removeTableWrapper();\r\n };\r\n }, [editor]);\r\n return null;\r\n}\r\n"],"mappings":";;;;;AAmBA,IAAa,IAAb,MAAa,UAAyB,EAAY;CAChD,OAAO,UAAkB;
|
|
1
|
+
{"version":3,"file":"TableWrapperNode.js","names":[],"sources":["../../../../../src/components/RteEditor/nodes/TableWrapperNode/TableWrapperNode.tsx"],"sourcesContent":["import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';\r\nimport {\r\n $createParagraphNode,\r\n $getSelection,\r\n $isElementNode,\r\n $isRangeSelection,\r\n COMMAND_PRIORITY_NORMAL,\r\n createCommand,\r\n ElementNode,\r\n} from 'lexical';\r\nimport { $getNearestNodeOfType } from '@lexical/utils';\r\nimport { $setBlocksType } from '@lexical/selection';\r\nimport { useEffect } from 'react';\r\n\r\nimport type { SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';\r\nimport type { EditorConfig, LexicalNode, RangeSelection } from 'lexical';\r\n\r\nexport type SerializedTableWrapperNode = SerializedDecoratorBlockNode;\r\n\r\nexport class TableWrapperNode extends ElementNode {\r\n static getType(): string {\r\n return 'tableWrapper';\r\n }\r\n\r\n static clone(node: TableWrapperNode): TableWrapperNode {\r\n return new TableWrapperNode(node.__key);\r\n }\r\n\r\n createDOM(config: EditorConfig): HTMLElement {\r\n const element = document.createElement('div');\r\n element.className = config.theme.tableWrapper;\r\n return element;\r\n }\r\n\r\n updateDOM(): false {\r\n return false;\r\n }\r\n\r\n insertNewAfter(_selection: RangeSelection, restoreSelection?: boolean | undefined): LexicalNode | null {\r\n const newBlock = $createParagraphNode();\r\n const direction = this.getDirection();\r\n newBlock.setDirection(direction);\r\n this.insertAfter(newBlock, restoreSelection);\r\n return newBlock;\r\n }\r\n}\r\n\r\nexport function $createTableWrapperNode(): TableWrapperNode {\r\n return new TableWrapperNode();\r\n}\r\n\r\nexport function $isTableWrapperNode(node: LexicalNode): node is TableWrapperNode {\r\n return node instanceof TableWrapperNode;\r\n}\r\n\r\nexport const INSERT_TABLE_WRAPPER = createCommand('insertTableWrapperNode');\r\n\r\nexport const REMOVE_TABLE_WRAPPER = createCommand('removeTableWrapperNode');\r\n\r\nexport function TableWrapperPlugin(): null {\r\n const [editor] = useLexicalComposerContext();\r\n if (!editor.hasNodes([TableWrapperNode])) {\r\n throw new Error('TableWrapperPlugin: TableWrapperNode is not registered on editor');\r\n }\r\n useEffect(() => {\r\n const insertTableWrapper = editor.registerCommand(\r\n INSERT_TABLE_WRAPPER,\r\n () => {\r\n const selection = $getSelection();\r\n if ($isRangeSelection(selection)) {\r\n $setBlocksType(selection, $createTableWrapperNode);\r\n }\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n\r\n const removeTableWrapper = editor.registerCommand(\r\n REMOVE_TABLE_WRAPPER,\r\n () => {\r\n const selection = $getSelection();\r\n if ($isRangeSelection(selection)) {\r\n const node = selection.anchor.getNode();\r\n if (!$isElementNode(node)) return false;\r\n const tableWrapperNode = $getNearestNodeOfType(node, TableWrapperNode);\r\n if (!tableWrapperNode) return false;\r\n const childrenNodes = tableWrapperNode?.getChildren();\r\n if (!childrenNodes) return false;\r\n const paragraphNode = $createParagraphNode();\r\n childrenNodes.forEach((child) => paragraphNode.append(child));\r\n tableWrapperNode.replace(paragraphNode);\r\n }\r\n return true;\r\n },\r\n COMMAND_PRIORITY_NORMAL\r\n );\r\n\r\n return () => {\r\n insertTableWrapper();\r\n removeTableWrapper();\r\n };\r\n }, [editor]);\r\n return null;\r\n}\r\n"],"mappings":";;;;;AAmBA,IAAa,IAAb,MAAa,UAAyB,EAAY;CAChD,OAAO,UAAkB;EACvB,OAAO;;CAGT,OAAO,MAAM,GAA0C;EACrD,OAAO,IAAI,EAAiB,EAAK,MAAM;;CAGzC,UAAU,GAAmC;EAC3C,IAAM,IAAU,SAAS,cAAc,MAAM;EAE7C,OADA,EAAQ,YAAY,EAAO,MAAM,cAC1B;;CAGT,YAAmB;EACjB,OAAO;;CAGT,eAAe,GAA4B,GAA4D;EACrG,IAAM,IAAW,GAAsB,EACjC,IAAY,KAAK,cAAc;EAGrC,OAFA,EAAS,aAAa,EAAU,EAChC,KAAK,YAAY,GAAU,EAAiB,EACrC;;;AAIX,SAAgB,IAA4C;CAC1D,OAAO,IAAI,GAAkB;;AAG/B,SAAgB,EAAoB,GAA6C;CAC/E,OAAO,aAAgB;;AAGzB,IAAa,IAAuB,EAAc,yBAAyB,EAE9D,IAAuB,EAAc,yBAAyB;AAE3E,SAAgB,IAA2B;CACzC,IAAM,CAAC,KAAU,GAA2B;CAC5C,IAAI,CAAC,EAAO,SAAS,CAAC,EAAiB,CAAC,EACtC,MAAU,MAAM,mEAAmE;CAwCrF,OAtCA,QAAgB;EACd,IAAM,IAAqB,EAAO,gBAChC,SACM;GACJ,IAAM,IAAY,GAAe;GAIjC,OAHI,EAAkB,EAAU,IAC9B,EAAe,GAAW,EAAwB,EAE7C;OAGV,EAEK,IAAqB,EAAO,gBAChC,SACM;GACJ,IAAM,IAAY,GAAe;GACjC,IAAI,EAAkB,EAAU,EAAE;IAChC,IAAM,IAAO,EAAU,OAAO,SAAS;IACvC,IAAI,CAAC,EAAe,EAAK,EAAE,OAAO;IAClC,IAAM,IAAmB,EAAsB,GAAM,EAAiB;IACtE,IAAI,CAAC,GAAkB,OAAO;IAC9B,IAAM,IAAgB,GAAkB,aAAa;IACrD,IAAI,CAAC,GAAe,OAAO;IAC3B,IAAM,IAAgB,GAAsB;IAE5C,AADA,EAAc,SAAS,MAAU,EAAc,OAAO,EAAM,CAAC,EAC7D,EAAiB,QAAQ,EAAc;;GAEzC,OAAO;OAGV;EAED,aAAa;GAEX,AADA,GAAoB,EACpB,GAAoB;;IAErB,CAAC,EAAO,CAAC,EACL"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchField.js","names":[],"sources":["../../../src/components/SearchField/SearchField.tsx"],"sourcesContent":["import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react';\r\nimport { clsx } from 'clsx';\r\n\r\nimport type { KeyboardEvent, ReactNode, RefObject } from 'react';\r\nimport type { ResultOptionProps } from './components/ResultsOption';\r\n\r\nimport { ResultOption } from './components/ResultsOption';\r\nimport styles from './SearchField.module.css';\r\nimport { ResultOptionBox } from './components/ResultOptionBox';\r\n\r\nimport { Icon } from 'components/Icon';\r\nimport { useOnClickOutside } from 'components/hooks/useOnClickOutside';\r\n\r\nexport interface SearchFieldProps {\r\n /** Unique identifier for the search field. */\r\n id: string;\r\n /** Placeholder text for the search input. */\r\n placeholder: string;\r\n /** Indicates if the search field is in a loading state. */\r\n isLoading?: boolean;\r\n /** If true, the search field is displayed in a compact style. */\r\n isCompact?: boolean;\r\n /** Callback function triggered when the input value changes. */\r\n onChange: (query: string) => void;\r\n /** The children elements, typically ResultOption components. */\r\n children: ReactNode;\r\n /** Optional row to display the count of results. */\r\n resultCountRow?: ReactNode;\r\n}\r\n\r\nexport const SearchField = ({\r\n id,\r\n placeholder,\r\n isLoading = false,\r\n isCompact,\r\n onChange,\r\n children,\r\n resultCountRow,\r\n}: SearchFieldProps) => {\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const [showField, setShowField] = useState<boolean>(false);\r\n const optionListRef = useRef<HTMLDivElement>(null);\r\n\r\n const handleReset = () => {\r\n if (inputRef.current) {\r\n inputRef.current.value = '';\r\n inputRef.current.focus();\r\n }\r\n };\r\n\r\n const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {\r\n const target = event.target;\r\n const value = target.value;\r\n target.setSelectionRange(value.length, value.length);\r\n if (value) {\r\n onChange(value);\r\n }\r\n };\r\n\r\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\r\n switch (event.key) {\r\n case 'Escape':\r\n event.preventDefault();\r\n onChange('');\r\n if (inputRef.current) {\r\n inputRef.current.value = '';\r\n }\r\n if (isCompact) {\r\n setShowField(false);\r\n inputRef.current?.blur();\r\n }\r\n break;\r\n case 'ArrowDown': {\r\n event.preventDefault();\r\n const container = optionListRef.current;\r\n const firstFocusable = container?.querySelector<HTMLElement>('li[role=\"option\"]:not([tabindex=\"-1\"])');\r\n if (firstFocusable) {\r\n let currentOption: HTMLElement | null = firstFocusable;\r\n while (currentOption && currentOption.getAttribute('tabindex') === '-1') {\r\n currentOption = currentOption.nextElementSibling as HTMLElement;\r\n }\r\n currentOption?.focus();\r\n }\r\n break;\r\n }\r\n }\r\n };\r\n\r\n useEffect(() => {\r\n if (!Children.count(children) && inputRef.current?.value.length === 0 && isCompact && resultCountRow === null) {\r\n setShowField(false);\r\n }\r\n }, [children, isCompact, resultCountRow]);\r\n\r\n useEffect(() => {\r\n if (inputRef.current === null) return;\r\n\r\n const handleBlur = () => {\r\n if (isCompact && !Children.count(children)) {\r\n setShowField(false);\r\n }\r\n };\r\n\r\n const currentInput = inputRef.current;\r\n currentInput.addEventListener('blur', handleBlur);\r\n\r\n return () => {\r\n currentInput.removeEventListener('blur', handleBlur);\r\n };\r\n }, [isCompact, children]);\r\n\r\n useEffect(() => {\r\n if (isCompact && showField && inputRef.current) {\r\n inputRef.current.focus();\r\n }\r\n }, [isCompact, showField]);\r\n\r\n useOnClickOutside(optionListRef as RefObject<HTMLElement>, onChange.bind(null, ''));\r\n\r\n return (\r\n <div className={styles['search-field__input-wrapper']}>\r\n {isCompact && (\r\n <button\r\n type=\"button\"\r\n className={styles['search-field__compact-button']}\r\n onClick={() => setShowField(true)}\r\n aria-label={placeholder}\r\n >\r\n <Icon name=\"Magnify\" className={styles['search-field__compact-icon']} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n <div\r\n className={clsx(styles['search-field__input-container'], {\r\n [styles['search-field__input-container--hidden']]: isCompact && !showField,\r\n [styles['search-field__input-container--visible']]: isCompact && showField,\r\n })}\r\n >\r\n <Icon name=\"Magnify\" className={styles['search-field__icon']} aria-hidden=\"true\" />\r\n <input\r\n type=\"text\"\r\n className={styles['search-field__input-field']}\r\n placeholder={placeholder}\r\n id={id}\r\n onChange={(event) => onChange(event.target.value)}\r\n onFocus={handleFocus}\r\n onKeyDown={handleKeyDown}\r\n role=\"combobox\"\r\n aria-expanded={Boolean(Children.count(children)) || isLoading}\r\n aria-controls={`${id}-results`}\r\n aria-autocomplete=\"list\"\r\n aria-haspopup=\"listbox\"\r\n aria-label={placeholder}\r\n autoComplete=\"off\"\r\n ref={inputRef}\r\n />\r\n </div>\r\n {(children || isLoading) && (\r\n <ResultOptionBox resultCountRow={resultCountRow ? resultCountRow : null} ref={optionListRef}>\r\n {Children.map(children, (child, index) => {\r\n if (isValidElement<ResultOptionProps>(child)) {\r\n return cloneElement(child, {\r\n key: index,\r\n onReset: handleReset,\r\n });\r\n }\r\n return child;\r\n })}\r\n </ResultOptionBox>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nSearchField.ResultOption = ResultOption;\r\n"],"mappings":";;;;;;;;;AA8BA,IAAa,KAAe,EAC1B,OACA,gBACA,eAAY,IACZ,cACA,aACA,aACA,wBACsB;CACtB,IAAM,IAAW,EAAyB,KAAK,EACzC,CAAC,GAAW,KAAgB,EAAkB,GAAM,EACpD,IAAgB,EAAuB,KAAK,EAE5C,UAAoB;
|
|
1
|
+
{"version":3,"file":"SearchField.js","names":[],"sources":["../../../src/components/SearchField/SearchField.tsx"],"sourcesContent":["import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react';\r\nimport { clsx } from 'clsx';\r\n\r\nimport type { KeyboardEvent, ReactNode, RefObject } from 'react';\r\nimport type { ResultOptionProps } from './components/ResultsOption';\r\n\r\nimport { ResultOption } from './components/ResultsOption';\r\nimport styles from './SearchField.module.css';\r\nimport { ResultOptionBox } from './components/ResultOptionBox';\r\n\r\nimport { Icon } from 'components/Icon';\r\nimport { useOnClickOutside } from 'components/hooks/useOnClickOutside';\r\n\r\nexport interface SearchFieldProps {\r\n /** Unique identifier for the search field. */\r\n id: string;\r\n /** Placeholder text for the search input. */\r\n placeholder: string;\r\n /** Indicates if the search field is in a loading state. */\r\n isLoading?: boolean;\r\n /** If true, the search field is displayed in a compact style. */\r\n isCompact?: boolean;\r\n /** Callback function triggered when the input value changes. */\r\n onChange: (query: string) => void;\r\n /** The children elements, typically ResultOption components. */\r\n children: ReactNode;\r\n /** Optional row to display the count of results. */\r\n resultCountRow?: ReactNode;\r\n}\r\n\r\nexport const SearchField = ({\r\n id,\r\n placeholder,\r\n isLoading = false,\r\n isCompact,\r\n onChange,\r\n children,\r\n resultCountRow,\r\n}: SearchFieldProps) => {\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const [showField, setShowField] = useState<boolean>(false);\r\n const optionListRef = useRef<HTMLDivElement>(null);\r\n\r\n const handleReset = () => {\r\n if (inputRef.current) {\r\n inputRef.current.value = '';\r\n inputRef.current.focus();\r\n }\r\n };\r\n\r\n const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {\r\n const target = event.target;\r\n const value = target.value;\r\n target.setSelectionRange(value.length, value.length);\r\n if (value) {\r\n onChange(value);\r\n }\r\n };\r\n\r\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\r\n switch (event.key) {\r\n case 'Escape':\r\n event.preventDefault();\r\n onChange('');\r\n if (inputRef.current) {\r\n inputRef.current.value = '';\r\n }\r\n if (isCompact) {\r\n setShowField(false);\r\n inputRef.current?.blur();\r\n }\r\n break;\r\n case 'ArrowDown': {\r\n event.preventDefault();\r\n const container = optionListRef.current;\r\n const firstFocusable = container?.querySelector<HTMLElement>('li[role=\"option\"]:not([tabindex=\"-1\"])');\r\n if (firstFocusable) {\r\n let currentOption: HTMLElement | null = firstFocusable;\r\n while (currentOption && currentOption.getAttribute('tabindex') === '-1') {\r\n currentOption = currentOption.nextElementSibling as HTMLElement;\r\n }\r\n currentOption?.focus();\r\n }\r\n break;\r\n }\r\n }\r\n };\r\n\r\n useEffect(() => {\r\n if (!Children.count(children) && inputRef.current?.value.length === 0 && isCompact && resultCountRow === null) {\r\n setShowField(false);\r\n }\r\n }, [children, isCompact, resultCountRow]);\r\n\r\n useEffect(() => {\r\n if (inputRef.current === null) return;\r\n\r\n const handleBlur = () => {\r\n if (isCompact && !Children.count(children)) {\r\n setShowField(false);\r\n }\r\n };\r\n\r\n const currentInput = inputRef.current;\r\n currentInput.addEventListener('blur', handleBlur);\r\n\r\n return () => {\r\n currentInput.removeEventListener('blur', handleBlur);\r\n };\r\n }, [isCompact, children]);\r\n\r\n useEffect(() => {\r\n if (isCompact && showField && inputRef.current) {\r\n inputRef.current.focus();\r\n }\r\n }, [isCompact, showField]);\r\n\r\n useOnClickOutside(optionListRef as RefObject<HTMLElement>, onChange.bind(null, ''));\r\n\r\n return (\r\n <div className={styles['search-field__input-wrapper']}>\r\n {isCompact && (\r\n <button\r\n type=\"button\"\r\n className={styles['search-field__compact-button']}\r\n onClick={() => setShowField(true)}\r\n aria-label={placeholder}\r\n >\r\n <Icon name=\"Magnify\" className={styles['search-field__compact-icon']} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n <div\r\n className={clsx(styles['search-field__input-container'], {\r\n [styles['search-field__input-container--hidden']]: isCompact && !showField,\r\n [styles['search-field__input-container--visible']]: isCompact && showField,\r\n })}\r\n >\r\n <Icon name=\"Magnify\" className={styles['search-field__icon']} aria-hidden=\"true\" />\r\n <input\r\n type=\"text\"\r\n className={styles['search-field__input-field']}\r\n placeholder={placeholder}\r\n id={id}\r\n onChange={(event) => onChange(event.target.value)}\r\n onFocus={handleFocus}\r\n onKeyDown={handleKeyDown}\r\n role=\"combobox\"\r\n aria-expanded={Boolean(Children.count(children)) || isLoading}\r\n aria-controls={`${id}-results`}\r\n aria-autocomplete=\"list\"\r\n aria-haspopup=\"listbox\"\r\n aria-label={placeholder}\r\n autoComplete=\"off\"\r\n ref={inputRef}\r\n />\r\n </div>\r\n {(children || isLoading) && (\r\n <ResultOptionBox resultCountRow={resultCountRow ? resultCountRow : null} ref={optionListRef}>\r\n {Children.map(children, (child, index) => {\r\n if (isValidElement<ResultOptionProps>(child)) {\r\n return cloneElement(child, {\r\n key: index,\r\n onReset: handleReset,\r\n });\r\n }\r\n return child;\r\n })}\r\n </ResultOptionBox>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nSearchField.ResultOption = ResultOption;\r\n"],"mappings":";;;;;;;;;AA8BA,IAAa,KAAe,EAC1B,OACA,gBACA,eAAY,IACZ,cACA,aACA,aACA,wBACsB;CACtB,IAAM,IAAW,EAAyB,KAAK,EACzC,CAAC,GAAW,KAAgB,EAAkB,GAAM,EACpD,IAAgB,EAAuB,KAAK,EAE5C,UAAoB;EACxB,AAAI,EAAS,YACX,EAAS,QAAQ,QAAQ,IACzB,EAAS,QAAQ,OAAO;IAItB,KAAe,MAA8C;EACjE,IAAM,IAAS,EAAM,QACf,IAAQ,EAAO;EAErB,AADA,EAAO,kBAAkB,EAAM,QAAQ,EAAM,OAAO,EAChD,KACF,EAAS,EAAM;IAIb,KAAiB,MAA2C;EAChE,QAAQ,EAAM,KAAd;GACE,KAAK;IAMH,AALA,EAAM,gBAAgB,EACtB,EAAS,GAAG,EACR,EAAS,YACX,EAAS,QAAQ,QAAQ,KAEvB,MACF,EAAa,GAAM,EACnB,EAAS,SAAS,MAAM;IAE1B;GACF,KAAK,aAAa;IAChB,EAAM,gBAAgB;IAEtB,IAAM,IADY,EAAc,SACE,cAA2B,6CAAyC;IACtG,IAAI,GAAgB;KAClB,IAAI,IAAoC;KACxC,OAAO,KAAiB,EAAc,aAAa,WAAW,KAAK,OACjE,IAAgB,EAAc;KAEhC,GAAe,OAAO;;IAExB;;;;CAoCN,OA/BA,QAAgB;EACd,AAAI,CAAC,EAAS,MAAM,EAAS,IAAI,EAAS,SAAS,MAAM,WAAW,KAAK,KAAa,MAAmB,QACvG,EAAa,GAAM;IAEpB;EAAC;EAAU;EAAW;EAAe,CAAC,EAEzC,QAAgB;EACd,IAAI,EAAS,YAAY,MAAM;EAE/B,IAAM,UAAmB;GACvB,AAAI,KAAa,CAAC,EAAS,MAAM,EAAS,IACxC,EAAa,GAAM;KAIjB,IAAe,EAAS;EAG9B,OAFA,EAAa,iBAAiB,QAAQ,EAAW,QAEpC;GACX,EAAa,oBAAoB,QAAQ,EAAW;;IAErD,CAAC,GAAW,EAAS,CAAC,EAEzB,QAAgB;EACd,AAAI,KAAa,KAAa,EAAS,WACrC,EAAS,QAAQ,OAAO;IAEzB,CAAC,GAAW,EAAU,CAAC,EAE1B,EAAkB,GAAyC,EAAS,KAAK,MAAM,GAAG,CAAC,EAGjF,kBAAC,OAAD;EAAK,WAAW,EAAO;YAAvB;GACG,KACC,kBAAC,UAAD;IACE,MAAK;IACL,WAAW,EAAO;IAClB,eAAe,EAAa,GAAK;IACjC,cAAY;cAEZ,kBAAC,GAAD;KAAM,MAAK;KAAU,WAAW,EAAO;KAA+B,eAAY;KAAS,CAAA;IACpF,CAAA;GAEX,kBAAC,OAAD;IACE,WAAW,EAAK,EAAO,kCAAkC;MACtD,EAAO,2CAA2C,KAAa,CAAC;MAChE,EAAO,4CAA4C,KAAa;KAClE,CAAC;cAJJ,CAME,kBAAC,GAAD;KAAM,MAAK;KAAU,WAAW,EAAO;KAAuB,eAAY;KAAS,CAAA,EACnF,kBAAC,SAAD;KACE,MAAK;KACL,WAAW,EAAO;KACL;KACT;KACJ,WAAW,MAAU,EAAS,EAAM,OAAO,MAAM;KACjD,SAAS;KACT,WAAW;KACX,MAAK;KACL,iBAAe,EAAQ,EAAS,MAAM,EAAS,IAAK;KACpD,iBAAe,GAAG,EAAG;KACrB,qBAAkB;KAClB,iBAAc;KACd,cAAY;KACZ,cAAa;KACb,KAAK;KACL,CAAA,CACE;;IACJ,KAAY,MACZ,kBAAC,GAAD;IAAiB,gBAAgB,KAAkC;IAAM,KAAK;cAC3E,EAAS,IAAI,IAAW,GAAO,MAC1B,EAAkC,EAAM,GACnC,EAAa,GAAO;KACzB,KAAK;KACL,SAAS;KACV,CAAC,GAEG,EACP;IACc,CAAA;GAEhB;;;AAIV,EAAY,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearcheField.stories.js","names":[],"sources":["../../../src/components/SearchField/SearcheField.stories.tsx"],"sourcesContent":["import { useState } from 'react';\r\n\r\nimport type { Meta, StoryFn } from '@storybook/react';\r\nimport type { SearchFieldProps } from './SearchField';\r\n\r\nimport { SearchField } from './SearchField';\r\nimport { MOCK_DATA } from './mockData';\r\n\r\nconst codeString = `\r\n// Has all HTMLInputElement props available except \"className\" and \"style\" \r\n<SearchField \r\n id={string} \r\n queryLength?={number}\r\n placeholder?={string} \r\n isLoading?={boolean} \r\n isCompact?={boolean} \r\n onChange={function}\r\n children={ReactNode}\r\n resultCountRow?={ReactNode}\r\n>\r\n <SearchField.ResultOption \r\n onSelect={function} \r\n onEscape={function} \r\n value={string|number} \r\n key={string|number} \r\n ariaLabel={string}>\r\n {loop custom options here}\r\n </SearchField.ResultOption>\r\n</SearchField>`;\r\n\r\nconst MIN_QUERY_LENGTH = 4;\r\nconst MAX_RESULTS = 12;\r\nconst QUERY_SPEED = 1000; // ms\r\n\r\nenum ProgrammeType {\r\n DEGREE = 'degree-programme',\r\n CERTIFICATE = 'certificate-programme',\r\n ADMINISTRATIVE = 'administrative-programme',\r\n}\r\n\r\ninterface SearchResult {\r\n __typename: string;\r\n id: string;\r\n code: string;\r\n name: {\r\n EN: string;\r\n NL: string;\r\n };\r\n programmeType: string;\r\n programmeCode?: string;\r\n}\r\n\r\n// eslint-disable-next-line import/no-default-export\r\nexport default {\r\n title: 'Molecules/SearchField',\r\n component: SearchField,\r\n args: {},\r\n argTypes: {\r\n onSelect: {\r\n action: 'selected url',\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n onChange: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n children: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n id: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n resultCountRow: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n isCompact: {\r\n control: 'boolean',\r\n default: false,\r\n },\r\n },\r\n parameters: {\r\n inspectComponent: SearchField,\r\n codeString: codeString,\r\n },\r\n} as Meta<SearchFieldProps & { onSelect: (value: string | number) => void }>;\r\n\r\nconst Template: StoryFn<SearchFieldProps & { onSelect: (value: string | number) => void }> = (args) => {\r\n const [loading, setLoading] = useState(false);\r\n const [searchResults, setSearchResults] = useState<SearchResult[] | null>();\r\n const academicYear = '2026';\r\n const currentLanguage = 'NL';\r\n\r\n const createUrl = (\r\n academicYear: string,\r\n programmeType: keyof typeof ProgrammeType,\r\n programmeCode: string,\r\n code: string\r\n ) => `education-preparation/${ProgrammeType[programmeType]}/${academicYear}/${programmeCode}/${code}`;\r\n\r\n const searchInMockData = (query: string) => {\r\n if (query.length < MIN_QUERY_LENGTH) {\r\n setSearchResults(null);\r\n return;\r\n }\r\n setLoading(true);\r\n\r\n const lowerCaseQuery = query.toLowerCase();\r\n\r\n const mockResults = MOCK_DATA.filter(\r\n (item) => item.name.NL.toLowerCase().includes(lowerCaseQuery) || item.code.toLowerCase().includes(lowerCaseQuery)\r\n );\r\n\r\n setTimeout(() => {\r\n setSearchResults(mockResults);\r\n setLoading(false);\r\n }, QUERY_SPEED);\r\n };\r\n\r\n const getResultMessage = (results: SearchResult[]) => {\r\n if (results.length > MAX_RESULTS) {\r\n return `${results.length} resultaten gevonden, alleen de ${MAX_RESULTS} beste resultaten worden weergegeven.`;\r\n } else if (results.length > 0 && results.length <= MAX_RESULTS) {\r\n return `${results.length} resultaten gevonden.`;\r\n } else if (loading) {\r\n return 'Zoeken...';\r\n } else {\r\n return 'Geen resultaten gevonden.';\r\n }\r\n };\r\n\r\n const resultBar = (results: SearchResult[]) =>\r\n results ? (\r\n <div key={'result-count'} style={{ padding: '0.5rem 1rem', fontWeight: '600' }}>\r\n {getResultMessage(results)}\r\n </div>\r\n ) : null;\r\n\r\n const handleSelect = (value: string | number) => {\r\n setSearchResults(null);\r\n args.onSelect(value);\r\n };\r\n\r\n return (\r\n <div style={{ width: '20rem', display: 'flex', justifyContent: 'flex-end' }}>\r\n <SearchField\r\n placeholder=\"Zoek...\"\r\n id=\"search-field-1\"\r\n isLoading={loading}\r\n isCompact={args.isCompact}\r\n onChange={(event) => searchInMockData(event)}\r\n resultCountRow={resultBar(searchResults || [])}\r\n >\r\n {searchResults?.map((result) => (\r\n <SearchField.ResultOption\r\n key={result.id}\r\n ariaLabel={`${result.code}, ${result.name[currentLanguage]}`}\r\n value={createUrl(\r\n academicYear,\r\n result.programmeType as keyof typeof ProgrammeType,\r\n result.programmeCode || '',\r\n result.code\r\n )}\r\n onSelect={handleSelect}\r\n onEscape={() => setSearchResults(null)}\r\n >\r\n <div style={{ display: 'flex', gap: '1rem' }}>\r\n <span>{result.code}</span>\r\n <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\r\n {result.name[currentLanguage]}\r\n </span>\r\n </div>\r\n </SearchField.ResultOption>\r\n ))}\r\n </SearchField>\r\n </div>\r\n );\r\n};\r\n\r\nexport const InputExample = Template.bind({});\r\n"],"mappings":";;;;;AAQA,IAAM,IAAa,yhBAsBb,IAAmB,GACnB,IAAc,IACd,IAAc,KAEf,IAAL,yBAAA,GAAA;QACE,EAAA,SAAS,oBACT,EAAA,cAAc,yBACd,EAAA,iBAAiB;EAHd,KAAA,EAAA,CAIJ,EAeD,IAAe;CACb,OAAO;CACP,WAAW;CACX,MAAM,EAAE;CACR,UAAU;EACR,UAAU;GACR,QAAQ;GACR,OAAO,EACL,SAAS,IACV;GACF;EACD,UAAU,EACR,OAAO,EACL,SAAS,IACV,EACF;EACD,UAAU,EACR,OAAO,EACL,SAAS,IACV,EACF;EACD,IAAI,EACF,OAAO,EACL,SAAS,IACV,EACF;EACD,gBAAgB,EACd,OAAO,EACL,SAAS,IACV,EACF;EACD,WAAW;GACT,SAAS;GACT,SAAS;GACV;EACF;CACD,YAAY;EACV,kBAAkB;EACN;EACb;CACF,EA8FY,MA5FiF,MAAS;CACrG,IAAM,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAe,KAAoB,GAAiC,EAIrE,KACJ,GACA,GACA,GACA,MACG,yBAAyB,EAAc,GAAe,GAAG,EAAa,GAAG,EAAc,GAAG,KAEzF,KAAoB,MAAkB;
|
|
1
|
+
{"version":3,"file":"SearcheField.stories.js","names":[],"sources":["../../../src/components/SearchField/SearcheField.stories.tsx"],"sourcesContent":["import { useState } from 'react';\r\n\r\nimport type { Meta, StoryFn } from '@storybook/react';\r\nimport type { SearchFieldProps } from './SearchField';\r\n\r\nimport { SearchField } from './SearchField';\r\nimport { MOCK_DATA } from './mockData';\r\n\r\nconst codeString = `\r\n// Has all HTMLInputElement props available except \"className\" and \"style\" \r\n<SearchField \r\n id={string} \r\n queryLength?={number}\r\n placeholder?={string} \r\n isLoading?={boolean} \r\n isCompact?={boolean} \r\n onChange={function}\r\n children={ReactNode}\r\n resultCountRow?={ReactNode}\r\n>\r\n <SearchField.ResultOption \r\n onSelect={function} \r\n onEscape={function} \r\n value={string|number} \r\n key={string|number} \r\n ariaLabel={string}>\r\n {loop custom options here}\r\n </SearchField.ResultOption>\r\n</SearchField>`;\r\n\r\nconst MIN_QUERY_LENGTH = 4;\r\nconst MAX_RESULTS = 12;\r\nconst QUERY_SPEED = 1000; // ms\r\n\r\nenum ProgrammeType {\r\n DEGREE = 'degree-programme',\r\n CERTIFICATE = 'certificate-programme',\r\n ADMINISTRATIVE = 'administrative-programme',\r\n}\r\n\r\ninterface SearchResult {\r\n __typename: string;\r\n id: string;\r\n code: string;\r\n name: {\r\n EN: string;\r\n NL: string;\r\n };\r\n programmeType: string;\r\n programmeCode?: string;\r\n}\r\n\r\n// eslint-disable-next-line import/no-default-export\r\nexport default {\r\n title: 'Molecules/SearchField',\r\n component: SearchField,\r\n args: {},\r\n argTypes: {\r\n onSelect: {\r\n action: 'selected url',\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n onChange: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n children: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n id: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n resultCountRow: {\r\n table: {\r\n disable: true,\r\n },\r\n },\r\n isCompact: {\r\n control: 'boolean',\r\n default: false,\r\n },\r\n },\r\n parameters: {\r\n inspectComponent: SearchField,\r\n codeString: codeString,\r\n },\r\n} as Meta<SearchFieldProps & { onSelect: (value: string | number) => void }>;\r\n\r\nconst Template: StoryFn<SearchFieldProps & { onSelect: (value: string | number) => void }> = (args) => {\r\n const [loading, setLoading] = useState(false);\r\n const [searchResults, setSearchResults] = useState<SearchResult[] | null>();\r\n const academicYear = '2026';\r\n const currentLanguage = 'NL';\r\n\r\n const createUrl = (\r\n academicYear: string,\r\n programmeType: keyof typeof ProgrammeType,\r\n programmeCode: string,\r\n code: string\r\n ) => `education-preparation/${ProgrammeType[programmeType]}/${academicYear}/${programmeCode}/${code}`;\r\n\r\n const searchInMockData = (query: string) => {\r\n if (query.length < MIN_QUERY_LENGTH) {\r\n setSearchResults(null);\r\n return;\r\n }\r\n setLoading(true);\r\n\r\n const lowerCaseQuery = query.toLowerCase();\r\n\r\n const mockResults = MOCK_DATA.filter(\r\n (item) => item.name.NL.toLowerCase().includes(lowerCaseQuery) || item.code.toLowerCase().includes(lowerCaseQuery)\r\n );\r\n\r\n setTimeout(() => {\r\n setSearchResults(mockResults);\r\n setLoading(false);\r\n }, QUERY_SPEED);\r\n };\r\n\r\n const getResultMessage = (results: SearchResult[]) => {\r\n if (results.length > MAX_RESULTS) {\r\n return `${results.length} resultaten gevonden, alleen de ${MAX_RESULTS} beste resultaten worden weergegeven.`;\r\n } else if (results.length > 0 && results.length <= MAX_RESULTS) {\r\n return `${results.length} resultaten gevonden.`;\r\n } else if (loading) {\r\n return 'Zoeken...';\r\n } else {\r\n return 'Geen resultaten gevonden.';\r\n }\r\n };\r\n\r\n const resultBar = (results: SearchResult[]) =>\r\n results ? (\r\n <div key={'result-count'} style={{ padding: '0.5rem 1rem', fontWeight: '600' }}>\r\n {getResultMessage(results)}\r\n </div>\r\n ) : null;\r\n\r\n const handleSelect = (value: string | number) => {\r\n setSearchResults(null);\r\n args.onSelect(value);\r\n };\r\n\r\n return (\r\n <div style={{ width: '20rem', display: 'flex', justifyContent: 'flex-end' }}>\r\n <SearchField\r\n placeholder=\"Zoek...\"\r\n id=\"search-field-1\"\r\n isLoading={loading}\r\n isCompact={args.isCompact}\r\n onChange={(event) => searchInMockData(event)}\r\n resultCountRow={resultBar(searchResults || [])}\r\n >\r\n {searchResults?.map((result) => (\r\n <SearchField.ResultOption\r\n key={result.id}\r\n ariaLabel={`${result.code}, ${result.name[currentLanguage]}`}\r\n value={createUrl(\r\n academicYear,\r\n result.programmeType as keyof typeof ProgrammeType,\r\n result.programmeCode || '',\r\n result.code\r\n )}\r\n onSelect={handleSelect}\r\n onEscape={() => setSearchResults(null)}\r\n >\r\n <div style={{ display: 'flex', gap: '1rem' }}>\r\n <span>{result.code}</span>\r\n <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\r\n {result.name[currentLanguage]}\r\n </span>\r\n </div>\r\n </SearchField.ResultOption>\r\n ))}\r\n </SearchField>\r\n </div>\r\n );\r\n};\r\n\r\nexport const InputExample = Template.bind({});\r\n"],"mappings":";;;;;AAQA,IAAM,IAAa,yhBAsBb,IAAmB,GACnB,IAAc,IACd,IAAc,KAEf,IAAL,yBAAA,GAAA;QACE,EAAA,SAAS,oBACT,EAAA,cAAc,yBACd,EAAA,iBAAiB;EAHd,KAAA,EAAA,CAIJ,EAeD,IAAe;CACb,OAAO;CACP,WAAW;CACX,MAAM,EAAE;CACR,UAAU;EACR,UAAU;GACR,QAAQ;GACR,OAAO,EACL,SAAS,IACV;GACF;EACD,UAAU,EACR,OAAO,EACL,SAAS,IACV,EACF;EACD,UAAU,EACR,OAAO,EACL,SAAS,IACV,EACF;EACD,IAAI,EACF,OAAO,EACL,SAAS,IACV,EACF;EACD,gBAAgB,EACd,OAAO,EACL,SAAS,IACV,EACF;EACD,WAAW;GACT,SAAS;GACT,SAAS;GACV;EACF;CACD,YAAY;EACV,kBAAkB;EACN;EACb;CACF,EA8FY,MA5FiF,MAAS;CACrG,IAAM,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAe,KAAoB,GAAiC,EAIrE,KACJ,GACA,GACA,GACA,MACG,yBAAyB,EAAc,GAAe,GAAG,EAAa,GAAG,EAAc,GAAG,KAEzF,KAAoB,MAAkB;EAC1C,IAAI,EAAM,SAAS,GAAkB;GACnC,EAAiB,KAAK;GACtB;;EAEF,EAAW,GAAK;EAEhB,IAAM,IAAiB,EAAM,aAAa,EAEpC,IAAc,EAAU,QAC3B,MAAS,EAAK,KAAK,GAAG,aAAa,CAAC,SAAS,EAAe,IAAI,EAAK,KAAK,aAAa,CAAC,SAAS,EAAe,CAClH;EAED,iBAAiB;GAEf,AADA,EAAiB,EAAY,EAC7B,EAAW,GAAM;KAChB,EAAY;IAGX,KAAoB,MACpB,EAAQ,SAAS,IACZ,GAAG,EAAQ,OAAO,kCAAkC,EAAY,yCAC9D,EAAQ,SAAS,KAAK,EAAQ,UAAU,IAC1C,GAAG,EAAQ,OAAO,yBAChB,IACF,cAEA,6BAIL,KAAa,MACjB,IACE,kBAAC,OAAD;EAA0B,OAAO;GAAE,SAAS;GAAe,YAAY;GAAO;YAC3E,EAAiB,EAAQ;EACtB,EAFI,eAEJ,GACJ,MAEA,KAAgB,MAA2B;EAE/C,AADA,EAAiB,KAAK,EACtB,EAAK,SAAS,EAAM;;CAGtB,OACE,kBAAC,OAAD;EAAK,OAAO;GAAE,OAAO;GAAS,SAAS;GAAQ,gBAAgB;GAAY;YACzE,kBAAC,GAAD;GACE,aAAY;GACZ,IAAG;GACH,WAAW;GACX,WAAW,EAAK;GAChB,WAAW,MAAU,EAAiB,EAAM;GAC5C,gBAAgB,EAAU,KAAiB,EAAE,CAAC;aAE7C,GAAe,KAAK,MACnB,kBAAC,EAAY,cAAb;IAEE,WAAW,GAAG,EAAO,KAAK,IAAI,EAAO,KAAK;IAC1C,OAAO,EACL,QACA,EAAO,eACP,EAAO,iBAAiB,IACxB,EAAO,KACR;IACD,UAAU;IACV,gBAAgB,EAAiB,KAAK;cAEtC,kBAAC,OAAD;KAAK,OAAO;MAAE,SAAS;MAAQ,KAAK;MAAQ;eAA5C,CACE,kBAAC,QAAD,EAAA,UAAO,EAAO,MAAY,CAAA,EAC1B,kBAAC,QAAD;MAAM,OAAO;OAAE,YAAY;OAAU,UAAU;OAAU,cAAc;OAAY;gBAChF,EAAO,KAAK;MACR,CAAA,CACH;;IACmB,EAjBpB,EAAO,GAiBa,CAC3B;GACU,CAAA;EACV,CAAA;GAI2B,KAAK,EAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResultsOption.js","names":[],"sources":["../../../../src/components/SearchField/components/ResultsOption.tsx"],"sourcesContent":["import type { ReactNode, KeyboardEvent } from 'react';\r\n\r\nimport styles from 'components/SearchField/SearchField.module.css';\r\n\r\nexport interface ResultOptionProps {\r\n value: string | number;\r\n key: string | number;\r\n ariaLabel: string;\r\n children: ReactNode;\r\n onEscape: () => void;\r\n onReset?: () => void;\r\n onSelect: (value: string | number) => void;\r\n}\r\n\r\nexport const ResultOption = ({ value, key, ariaLabel, children, onSelect, onReset, onEscape }: ResultOptionProps) => {\r\n const handleSelect = (event: React.MouseEvent | React.KeyboardEvent) => {\r\n event.stopPropagation();\r\n onEscape();\r\n if (onReset) onReset();\r\n onSelect(value);\r\n };\r\n\r\n const onOptionKeyDown = (event: KeyboardEvent<HTMLLIElement>) => {\r\n switch (event.key) {\r\n case 'Escape':\r\n event.preventDefault();\r\n event.stopPropagation();\r\n onEscape();\r\n if (onReset) onReset();\r\n break;\r\n case 'Enter':\r\n event.preventDefault();\r\n handleSelect(event);\r\n break;\r\n case 'ArrowDown': {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n let nextElement = event.currentTarget.nextElementSibling;\r\n while (nextElement?.getAttribute('tabindex') === '-1') {\r\n nextElement = nextElement.nextElementSibling;\r\n }\r\n (nextElement as HTMLLIElement)?.focus();\r\n break;\r\n }\r\n case 'ArrowUp': {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n let previousElement = event.currentTarget.previousElementSibling;\r\n while (previousElement?.getAttribute('tabindex') === '-1') {\r\n previousElement = previousElement.previousElementSibling;\r\n }\r\n (previousElement as HTMLLIElement)?.focus();\r\n break;\r\n }\r\n case 'Tab':\r\n event.preventDefault();\r\n onEscape();\r\n break;\r\n default:\r\n event.preventDefault();\r\n break;\r\n }\r\n };\r\n\r\n return (\r\n <li\r\n className={styles['search-field__result-item']}\r\n key={key}\r\n onClick={handleSelect}\r\n onKeyDown={onOptionKeyDown}\r\n role=\"option\"\r\n aria-selected=\"false\"\r\n tabIndex={0}\r\n aria-label={ariaLabel}\r\n >\r\n {children}\r\n </li>\r\n );\r\n};\r\n"],"mappings":";;;AAcA,IAAa,KAAgB,EAAE,UAAO,QAAK,cAAW,aAAU,aAAU,YAAS,kBAAkC;CACnH,IAAM,KAAgB,MAAkD;
|
|
1
|
+
{"version":3,"file":"ResultsOption.js","names":[],"sources":["../../../../src/components/SearchField/components/ResultsOption.tsx"],"sourcesContent":["import type { ReactNode, KeyboardEvent } from 'react';\r\n\r\nimport styles from 'components/SearchField/SearchField.module.css';\r\n\r\nexport interface ResultOptionProps {\r\n value: string | number;\r\n key: string | number;\r\n ariaLabel: string;\r\n children: ReactNode;\r\n onEscape: () => void;\r\n onReset?: () => void;\r\n onSelect: (value: string | number) => void;\r\n}\r\n\r\nexport const ResultOption = ({ value, key, ariaLabel, children, onSelect, onReset, onEscape }: ResultOptionProps) => {\r\n const handleSelect = (event: React.MouseEvent | React.KeyboardEvent) => {\r\n event.stopPropagation();\r\n onEscape();\r\n if (onReset) onReset();\r\n onSelect(value);\r\n };\r\n\r\n const onOptionKeyDown = (event: KeyboardEvent<HTMLLIElement>) => {\r\n switch (event.key) {\r\n case 'Escape':\r\n event.preventDefault();\r\n event.stopPropagation();\r\n onEscape();\r\n if (onReset) onReset();\r\n break;\r\n case 'Enter':\r\n event.preventDefault();\r\n handleSelect(event);\r\n break;\r\n case 'ArrowDown': {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n let nextElement = event.currentTarget.nextElementSibling;\r\n while (nextElement?.getAttribute('tabindex') === '-1') {\r\n nextElement = nextElement.nextElementSibling;\r\n }\r\n (nextElement as HTMLLIElement)?.focus();\r\n break;\r\n }\r\n case 'ArrowUp': {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n let previousElement = event.currentTarget.previousElementSibling;\r\n while (previousElement?.getAttribute('tabindex') === '-1') {\r\n previousElement = previousElement.previousElementSibling;\r\n }\r\n (previousElement as HTMLLIElement)?.focus();\r\n break;\r\n }\r\n case 'Tab':\r\n event.preventDefault();\r\n onEscape();\r\n break;\r\n default:\r\n event.preventDefault();\r\n break;\r\n }\r\n };\r\n\r\n return (\r\n <li\r\n className={styles['search-field__result-item']}\r\n key={key}\r\n onClick={handleSelect}\r\n onKeyDown={onOptionKeyDown}\r\n role=\"option\"\r\n aria-selected=\"false\"\r\n tabIndex={0}\r\n aria-label={ariaLabel}\r\n >\r\n {children}\r\n </li>\r\n );\r\n};\r\n"],"mappings":";;;AAcA,IAAa,KAAgB,EAAE,UAAO,QAAK,cAAW,aAAU,aAAU,YAAS,kBAAkC;CACnH,IAAM,KAAgB,MAAkD;EAItE,AAHA,EAAM,iBAAiB,EACvB,GAAU,EACN,KAAS,GAAS,EACtB,EAAS,EAAM;;CA6CjB,OACE,kBAAC,MAAD;EACE,WAAW,EAAO;EAElB,SAAS;EACT,YA/CqB,MAAwC;GAC/D,QAAQ,EAAM,KAAd;IACE,KAAK;KAIH,AAHA,EAAM,gBAAgB,EACtB,EAAM,iBAAiB,EACvB,GAAU,EACN,KAAS,GAAS;KACtB;IACF,KAAK;KAEH,AADA,EAAM,gBAAgB,EACtB,EAAa,EAAM;KACnB;IACF,KAAK,aAAa;KAEhB,AADA,EAAM,gBAAgB,EACtB,EAAM,iBAAiB;KACvB,IAAI,IAAc,EAAM,cAAc;KACtC,OAAO,GAAa,aAAa,WAAW,KAAK,OAC/C,IAAc,EAAY;KAE5B,GAAgC,OAAO;KACvC;;IAEF,KAAK,WAAW;KAEd,AADA,EAAM,gBAAgB,EACtB,EAAM,iBAAiB;KACvB,IAAI,IAAkB,EAAM,cAAc;KAC1C,OAAO,GAAiB,aAAa,WAAW,KAAK,OACnD,IAAkB,EAAgB;KAEpC,GAAoC,OAAO;KAC3C;;IAEF,KAAK;KAEH,AADA,EAAM,gBAAgB,EACtB,GAAU;KACV;IACF;KACE,EAAM,gBAAgB;KACtB;;;EAUF,MAAK;EACL,iBAAc;EACd,UAAU;EACV,cAAY;EAEX;EACE,EATE,EASF"}
|