@intlayer/design-system 9.4.1 → 9.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/esm/components/Avatar/index.mjs +1 -1
- package/dist/esm/components/Avatar/index.mjs.map +1 -1
- package/dist/esm/components/Badge/index.mjs +3 -3
- package/dist/esm/components/Badge/index.mjs.map +1 -1
- package/dist/esm/components/Breadcrumb/index.mjs +5 -5
- package/dist/esm/components/Breadcrumb/index.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.mjs +4 -4
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/esm/components/Button/Button.mjs +1 -0
- package/dist/esm/components/Button/Button.mjs.map +1 -1
- package/dist/esm/components/Carousel/index.mjs +5 -5
- package/dist/esm/components/Carousel/index.mjs.map +1 -1
- package/dist/esm/components/CollapsibleTable/CollapsibleTable.mjs +5 -5
- package/dist/esm/components/CollapsibleTable/CollapsibleTable.mjs.map +1 -1
- package/dist/esm/components/Container/index.mjs +2 -2
- package/dist/esm/components/Container/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryEditor/ItemLayout.mjs +1 -1
- package/dist/esm/components/DictionaryEditor/ItemLayout.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +5 -5
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +3 -3
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +3 -3
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/{ContentEditor.mjs → NodeEditor.mjs} +7 -7
- package/dist/esm/components/DictionaryFieldEditor/NodeEditor.mjs.map +1 -0
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/VersionSwitcherDropDown/VersionSwitcher.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/VersionSwitcherDropDown/VersionSwitcher.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/index.mjs +3 -3
- package/dist/esm/components/Footer/index.mjs +5 -3
- package/dist/esm/components/Footer/index.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +1 -1
- package/dist/esm/components/HeightResizer/index.mjs +1 -1
- package/dist/esm/components/HeightResizer/index.mjs.map +1 -1
- package/dist/esm/components/IDE/Code.mjs +1 -1
- package/dist/esm/components/IDE/Code.mjs.map +1 -1
- package/dist/esm/components/IDE/FileList.mjs +1 -1
- package/dist/esm/components/IDE/FileList.mjs.map +1 -1
- package/dist/esm/components/IDE/FileTree.mjs +1 -1
- package/dist/esm/components/IDE/FileTree.mjs.map +1 -1
- package/dist/esm/components/IDE/IDE.mjs +10 -7
- package/dist/esm/components/IDE/IDE.mjs.map +1 -1
- package/dist/esm/components/IDE/MarkDownRender.mjs +4 -3
- package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/Input/Checkbox.mjs +4 -4
- package/dist/esm/components/Input/Checkbox.mjs.map +1 -1
- package/dist/esm/components/Input/Input.mjs +2 -2
- package/dist/esm/components/Input/Input.mjs.map +1 -1
- package/dist/esm/components/Input/OTPInput.mjs +1 -1
- package/dist/esm/components/Input/OTPInput.mjs.map +1 -1
- package/dist/esm/components/KeyboardShortcut/KeyboardShortcut.mjs +2 -2
- package/dist/esm/components/KeyboardShortcut/KeyboardShortcut.mjs.map +1 -1
- package/dist/esm/components/LanguageBackground/LanguageSection.mjs +6 -8
- package/dist/esm/components/LanguageBackground/LanguageSection.mjs.map +1 -1
- package/dist/esm/components/LanguageBackground/index.mjs +2 -2
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +3 -3
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +9 -8
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/MarkdownEditor/MarkdownEditor.mjs +2 -2
- package/dist/esm/components/MarkdownEditor/MarkdownEditor.mjs.map +1 -1
- package/dist/esm/components/MarkdownEditor/generative/AISelector.mjs +3 -3
- package/dist/esm/components/MarkdownEditor/generative/AISelector.mjs.map +1 -1
- package/dist/esm/components/MarkdownEditor/generative/AISelectorCommands.mjs +2 -2
- package/dist/esm/components/MarkdownEditor/generative/AISelectorCommands.mjs.map +1 -1
- package/dist/esm/components/MarkdownEditor/generative/GenerativeMenuSwitch.mjs +2 -2
- package/dist/esm/components/MarkdownEditor/generative/GenerativeMenuSwitch.mjs.map +1 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -3
- package/dist/esm/components/Modal/Modal.mjs.map +1 -1
- package/dist/esm/components/Navbar/DesktopNavbar.mjs +3 -3
- package/dist/esm/components/Navbar/DesktopNavbar.mjs.map +1 -1
- package/dist/esm/components/Navbar/MobileNavbar.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs.map +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +1 -1
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs +2 -2
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
- package/dist/esm/components/Pattern/GridPattern.mjs +1 -1
- package/dist/esm/components/Pattern/GridPattern.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +5 -5
- package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
- package/dist/esm/components/Select/Multiselect.mjs +2 -2
- package/dist/esm/components/Select/Multiselect.mjs.map +1 -1
- package/dist/esm/components/Select/Select.mjs +2 -2
- package/dist/esm/components/Select/Select.mjs.map +1 -1
- package/dist/esm/components/Status/index.mjs +28 -0
- package/dist/esm/components/Status/index.mjs.map +1 -0
- package/dist/esm/components/Steps/index.mjs +3 -3
- package/dist/esm/components/Steps/index.mjs.map +1 -1
- package/dist/esm/components/SwitchSelector/SwitchSelector.mjs +12 -11
- package/dist/esm/components/SwitchSelector/SwitchSelector.mjs.map +1 -1
- package/dist/esm/components/SwitchSelector/VerticalSwitchSelector.mjs +6 -6
- package/dist/esm/components/SwitchSelector/VerticalSwitchSelector.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs +2 -2
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TabSelector/TabSelector.mjs +5 -5
- package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
- package/dist/esm/components/Table/SmartTable.mjs +2 -2
- package/dist/esm/components/Table/SmartTable.mjs.map +1 -1
- package/dist/esm/components/Table/Table.mjs +1 -1
- package/dist/esm/components/Table/Table.mjs.map +1 -1
- package/dist/esm/components/Table/TableElements.mjs +2 -2
- package/dist/esm/components/Table/TableElements.mjs.map +1 -1
- package/dist/esm/components/Tag/index.mjs +1 -1
- package/dist/esm/components/Tag/index.mjs.map +1 -1
- package/dist/esm/components/TechLogo/TechLogo.mjs +2 -0
- package/dist/esm/components/TechLogo/TechLogo.mjs.map +1 -1
- package/dist/esm/components/TechLogo/logos/HTMX.mjs +31 -0
- package/dist/esm/components/TechLogo/logos/HTMX.mjs.map +1 -0
- package/dist/esm/components/TechLogo/logos/Remix.mjs +22 -0
- package/dist/esm/components/TechLogo/logos/Remix.mjs.map +1 -0
- package/dist/esm/components/TechLogo/techLogos.mjs +5 -1
- package/dist/esm/components/TechLogo/techLogos.mjs.map +1 -1
- package/dist/esm/components/Terminal/Terminal.mjs +4 -4
- package/dist/esm/components/Terminal/Terminal.mjs.map +1 -1
- package/dist/esm/components/TextArea/TextArea.mjs.map +1 -1
- package/dist/esm/components/Toaster/Toast.mjs +4 -4
- package/dist/esm/components/Toaster/Toast.mjs.map +1 -1
- package/dist/esm/components/index.mjs +8 -7
- package/dist/esm/hooks/index.mjs +7 -7
- package/dist/esm/hooks/usePersistedStore.mjs +6 -18
- package/dist/esm/hooks/usePersistedStore.mjs.map +1 -1
- package/dist/esm/providers/ReactQueryProvider.mjs +10 -2
- package/dist/esm/providers/ReactQueryProvider.mjs.map +1 -1
- package/dist/esm/providers/ThemeProvider.mjs +27 -15
- package/dist/esm/providers/ThemeProvider.mjs.map +1 -1
- package/dist/esm/routes.mjs +3 -1
- package/dist/esm/routes.mjs.map +1 -1
- package/dist/esm/structured-data/buildItemListJsonLd.mjs +27 -0
- package/dist/esm/structured-data/buildItemListJsonLd.mjs.map +1 -0
- package/dist/esm/structured-data/index.mjs +2 -1
- package/dist/types/components/Badge/index.d.ts.map +1 -1
- package/dist/types/components/Button/Button.d.ts +2 -2
- package/dist/types/components/Button/Button.d.ts.map +1 -1
- package/dist/types/components/Container/index.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/NodeEditor.d.ts +10 -0
- package/dist/types/components/DictionaryFieldEditor/NodeEditor.d.ts.map +1 -0
- package/dist/types/components/DictionaryFieldEditor/index.d.ts +2 -2
- package/dist/types/components/Footer/index.d.ts.map +1 -1
- package/dist/types/components/Form/FormBase.d.ts +1 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts +1 -0
- package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/LanguageBackground/LanguageSection.d.ts +5 -1
- package/dist/types/components/LanguageBackground/LanguageSection.d.ts.map +1 -1
- package/dist/types/components/LanguageBackground/index.d.ts +2 -2
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/Status/index.d.ts +8 -0
- package/dist/types/components/Status/index.d.ts.map +1 -0
- package/dist/types/components/SwitchSelector/SwitchSelector.d.ts +3 -3
- package/dist/types/components/SwitchSelector/SwitchSelector.d.ts.map +1 -1
- package/dist/types/components/Table/TableElements.d.ts.map +1 -1
- package/dist/types/components/TechLogo/TechLogo.d.ts.map +1 -1
- package/dist/types/components/TechLogo/logos/HTMX.d.ts +6 -0
- package/dist/types/components/TechLogo/logos/HTMX.d.ts.map +1 -0
- package/dist/types/components/TechLogo/logos/Remix.d.ts +6 -0
- package/dist/types/components/TechLogo/logos/Remix.d.ts.map +1 -0
- package/dist/types/components/TechLogo/techLogos.d.ts +4 -2
- package/dist/types/components/TechLogo/types.d.ts +1 -1
- package/dist/types/components/TextArea/TextArea.d.ts +1 -1
- package/dist/types/components/index.d.ts +3 -2
- package/dist/types/providers/ThemeProvider.d.ts.map +1 -1
- package/dist/types/routes.d.ts +3 -1
- package/dist/types/routes.d.ts.map +1 -1
- package/dist/types/structured-data/buildItemListJsonLd.d.ts +36 -0
- package/dist/types/structured-data/buildItemListJsonLd.d.ts.map +1 -0
- package/dist/types/structured-data/index.d.ts +2 -1
- package/package.json +53 -53
- package/src/components/MarkdownEditor/markdown-editor.css +16 -16
- package/styles/globals.css +71 -0
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -10
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +0 -1
- package/tailwind.css +0 -269
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
|
|
3
4
|
import { Button } from "../Button/Button.mjs";
|
|
4
5
|
import { Container } from "../Container/index.mjs";
|
|
5
6
|
import { DropDown } from "../DropDown/index.mjs";
|
|
6
7
|
import { Input } from "../Input/Input.mjs";
|
|
7
8
|
import { SwitchSelector } from "../SwitchSelector/SwitchSelector.mjs";
|
|
8
|
-
import { usePersistedStore } from "../../hooks/usePersistedStore.mjs";
|
|
9
9
|
import { useLocaleSwitcherContent } from "./LocaleSwitcherContentContext.mjs";
|
|
10
10
|
import { useMemo, useRef, useState } from "react";
|
|
11
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -76,7 +76,7 @@ const LocaleSwitcherContent = ({ panelProps, isMultilingual = true }) => {
|
|
|
76
76
|
else setSelectedLocales([locale]);
|
|
77
77
|
};
|
|
78
78
|
return /* @__PURE__ */ jsx("div", {
|
|
79
|
-
className: "rounded-xl border border-text text-
|
|
79
|
+
className: "rounded-xl border border-text text-foreground transition-colors",
|
|
80
80
|
children: /* @__PURE__ */ jsxs(DropDown, {
|
|
81
81
|
identifier: DROPDOWN_IDENTIFIER,
|
|
82
82
|
children: [/* @__PURE__ */ jsx(DropDown.Trigger, {
|
|
@@ -87,7 +87,7 @@ const LocaleSwitcherContent = ({ panelProps, isMultilingual = true }) => {
|
|
|
87
87
|
color: "text",
|
|
88
88
|
variant: "hoverable",
|
|
89
89
|
children: /* @__PURE__ */ jsxs("div", {
|
|
90
|
-
className: "flex w-full items-center justify-between text-
|
|
90
|
+
className: "flex w-full items-center justify-between text-foreground",
|
|
91
91
|
children: [/* @__PURE__ */ jsx("div", {
|
|
92
92
|
className: "px-2 py-1",
|
|
93
93
|
children: /* @__PURE__ */ jsx(Globe, { size: 16 })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocaleSwitcherContent.mjs","names":[],"sources":["../../../../src/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.tsx"],"sourcesContent":["'use client';\n\nimport { usePersistedStore } from '@hooks/usePersistedStore';\nimport { getHTMLTextDir, getLocaleName } from '@intlayer/core/localization';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport Fuse, { type IFuseOptions } from 'fuse.js';\nimport { Check, Globe, MoveVertical } from 'lucide-react';\nimport { type FC, useMemo, useRef, useState } from 'react';\nimport { useIntlayer, useLocale } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { Container } from '../Container';\nimport { DropDown, type PanelProps } from '../DropDown';\nimport { Input } from '../Input';\nimport { SwitchSelector } from '../SwitchSelector';\nimport { useLocaleSwitcherContent } from './LocaleSwitcherContentContext';\n\nexport type LocaleSwitcherContentProps = {\n panelProps?: Omit<PanelProps, 'identifier'>;\n isMultilingual?: boolean;\n};\n\nconst DROPDOWN_IDENTIFIER = 'locale-switcher-content';\n\ntype MultilingualAvailableLocales = {\n locale: LocalesValues;\n englishName: string;\n currentLocaleName: string;\n ownLocaleName: string;\n};\n\nexport const LocaleSwitcherContent: FC<LocaleSwitcherContentProps> = ({\n panelProps,\n isMultilingual = true,\n}) => {\n const {\n switchTo,\n searchInput,\n localeSwitcherLabel,\n languageListLabel,\n seeAllLocalesSwitch,\n } = useIntlayer('locale-switcher-content');\n const inputRef = useRef<HTMLInputElement>(null);\n const { locale } = useLocale();\n const { availableLocales, selectedLocales, setSelectedLocales } =\n useLocaleSwitcherContent();\n\n // 1. Memoize the list construction so it doesn't rebuild every render\n const multilingualAvailableLocales: MultilingualAvailableLocales[] = useMemo(\n () =>\n availableLocales.map((localeEl) => {\n const englishName = getLocaleName(localeEl, ENGLISH);\n const currentLocaleName = getLocaleName(localeEl, locale);\n const ownLocaleName = getLocaleName(localeEl);\n return {\n locale: localeEl,\n englishName,\n currentLocaleName,\n ownLocaleName,\n };\n }),\n [availableLocales, locale]\n );\n\n // 2. State for Search Query only (Source of Truth)\n const [searchQuery, setSearchQuery] = useState('');\n\n const [seeAllLocales, setSeeAllLocales] = usePersistedStore(\n 'locale-content-selector-see-all-locales',\n false\n );\n\n // 3. Memoize Fuse instance\n const fuse = useMemo(() => {\n const fuseOptions: IFuseOptions<MultilingualAvailableLocales> = {\n keys: [\n { name: 'ownLocaleName', weight: 0.4 },\n { name: 'englishName', weight: 0.2 },\n { name: 'currentLocaleName', weight: 0.2 },\n { name: 'locale', weight: 0.2 },\n ],\n threshold: 0.02,\n };\n return new Fuse(multilingualAvailableLocales, fuseOptions);\n }, [multilingualAvailableLocales]);\n\n // 4. Derive results from Search Query\n const results = useMemo(() => {\n if (!searchQuery) {\n return multilingualAvailableLocales;\n }\n return fuse.search(searchQuery).map((result) => result.item);\n }, [searchQuery, multilingualAvailableLocales, fuse]);\n\n const handleClickLocale = (localeItem: LocalesValues) => {\n if (isMultilingual) {\n if (selectedLocales.includes(localeItem)) {\n if (selectedLocales.length > 1) {\n setSelectedLocales((prev) => prev.filter((el) => el !== localeItem));\n }\n } else {\n setSelectedLocales((prev) => [...prev, localeItem]);\n }\n } else {\n setSelectedLocales([localeItem]);\n }\n };\n\n const handleSeeAllLocales = (value: boolean) => {\n setSeeAllLocales(value);\n\n if (value) {\n setSelectedLocales(availableLocales);\n } else {\n setSelectedLocales([locale]);\n }\n };\n\n return (\n <div className=\"rounded-xl border border-text text-text transition-colors\">\n <DropDown identifier={DROPDOWN_IDENTIFIER}>\n <DropDown.Trigger\n identifier={DROPDOWN_IDENTIFIER}\n label={localeSwitcherLabel.value}\n className=\"p-0!\"\n roundedSize=\"3xl\"\n color=\"text\"\n variant=\"hoverable\"\n >\n <div className=\"flex w-full items-center justify-between text-text\">\n <div className=\"px-2 py-1\">\n <Globe size={16} />\n </div>\n <MoveVertical className=\"self-center\" size={16} />\n </div>\n </DropDown.Trigger>\n\n <DropDown.Panel\n identifier={DROPDOWN_IDENTIFIER}\n isOverable\n isFocusable\n align=\"end\"\n {...panelProps}\n >\n <Container\n className=\"max-h-[60vh] min-w-28\"\n separator=\"y\"\n role=\"listbox\"\n transparency=\"xs\"\n border\n roundedSize=\"3xl\"\n borderColor=\"text\"\n aria-label={languageListLabel.value}\n >\n {isMultilingual && (\n <div className=\"m-auto p-2\">\n <SwitchSelector\n defaultValue={seeAllLocales} // Ensure this uses the persisted state\n onChange={handleSeeAllLocales}\n color=\"text\"\n size=\"sm\"\n className=\"!w-60\"\n choices={[\n {\n content: seeAllLocalesSwitch.true.value,\n value: true,\n },\n {\n content: seeAllLocalesSwitch.false.value,\n value: false,\n },\n ]}\n />\n </div>\n )}\n\n {!(isMultilingual && seeAllLocales) && (\n <>\n <div className=\"p-3\">\n <Input\n type=\"search\"\n aria-label={searchInput.ariaLabel.value}\n placeholder={searchInput.placeholder.value}\n // Update search query state directly\n onChange={(e) => setSearchQuery(e.target.value)}\n ref={inputRef}\n />\n </div>\n <ol className=\"divide-y divide-dashed divide-text/20 overflow-y-auto p-1\">\n {results.map(\n ({\n locale: localeItem,\n currentLocaleName,\n ownLocaleName,\n }) => (\n <li className=\"px-1.5 py-1\" key={localeItem}>\n <Button\n onClick={() => handleClickLocale(localeItem)}\n label={`${switchTo} ${currentLocaleName}`}\n disabled={\n !(availableLocales ?? availableLocales).includes(\n localeItem\n )\n }\n isActive={selectedLocales.includes(localeItem)}\n variant=\"hoverable\"\n color=\"text\"\n isFullWidth\n textAlign=\"left\"\n size=\"sm\"\n >\n <div className=\"flex flex-row items-center justify-between gap-3 px-2 py-1\">\n {isMultilingual && (\n <div className=\"w-4\">\n {selectedLocales.includes(localeItem) && (\n <Check className=\"size-full\" />\n )}\n </div>\n )}\n <div className=\"flex flex-1 flex-row items-center justify-between gap-3 px-2 py-1\">\n <div className=\"flex flex-col text-nowrap\">\n <span\n dir={getHTMLTextDir(localeItem)}\n lang={localeItem}\n >\n {ownLocaleName}\n </span>\n <span className=\"text-neutral text-xs\">\n {currentLocaleName}\n </span>\n </div>\n <span className=\"text-neutral text-sm\">\n {localeItem.toUpperCase()}\n </span>\n </div>\n </div>\n </Button>\n </li>\n )\n )}\n </ol>\n </>\n )}\n </Container>\n </DropDown.Panel>\n </DropDown>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LocaleSwitcherContent.mjs","names":[],"sources":["../../../../src/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.tsx"],"sourcesContent":["'use client';\n\nimport { usePersistedStore } from '@hooks/usePersistedStore';\nimport { getHTMLTextDir, getLocaleName } from '@intlayer/core/localization';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport Fuse, { type IFuseOptions } from 'fuse.js';\nimport { Check, Globe, MoveVertical } from 'lucide-react';\nimport { type FC, useMemo, useRef, useState } from 'react';\nimport { useIntlayer, useLocale } from 'react-intlayer';\nimport { Button } from '../Button';\nimport { Container } from '../Container';\nimport { DropDown, type PanelProps } from '../DropDown';\nimport { Input } from '../Input';\nimport { SwitchSelector } from '../SwitchSelector';\nimport { useLocaleSwitcherContent } from './LocaleSwitcherContentContext';\n\nexport type LocaleSwitcherContentProps = {\n panelProps?: Omit<PanelProps, 'identifier'>;\n isMultilingual?: boolean;\n};\n\nconst DROPDOWN_IDENTIFIER = 'locale-switcher-content';\n\ntype MultilingualAvailableLocales = {\n locale: LocalesValues;\n englishName: string;\n currentLocaleName: string;\n ownLocaleName: string;\n};\n\nexport const LocaleSwitcherContent: FC<LocaleSwitcherContentProps> = ({\n panelProps,\n isMultilingual = true,\n}) => {\n const {\n switchTo,\n searchInput,\n localeSwitcherLabel,\n languageListLabel,\n seeAllLocalesSwitch,\n } = useIntlayer('locale-switcher-content');\n const inputRef = useRef<HTMLInputElement>(null);\n const { locale } = useLocale();\n const { availableLocales, selectedLocales, setSelectedLocales } =\n useLocaleSwitcherContent();\n\n // 1. Memoize the list construction so it doesn't rebuild every render\n const multilingualAvailableLocales: MultilingualAvailableLocales[] = useMemo(\n () =>\n availableLocales.map((localeEl) => {\n const englishName = getLocaleName(localeEl, ENGLISH);\n const currentLocaleName = getLocaleName(localeEl, locale);\n const ownLocaleName = getLocaleName(localeEl);\n return {\n locale: localeEl,\n englishName,\n currentLocaleName,\n ownLocaleName,\n };\n }),\n [availableLocales, locale]\n );\n\n // 2. State for Search Query only (Source of Truth)\n const [searchQuery, setSearchQuery] = useState('');\n\n const [seeAllLocales, setSeeAllLocales] = usePersistedStore(\n 'locale-content-selector-see-all-locales',\n false\n );\n\n // 3. Memoize Fuse instance\n const fuse = useMemo(() => {\n const fuseOptions: IFuseOptions<MultilingualAvailableLocales> = {\n keys: [\n { name: 'ownLocaleName', weight: 0.4 },\n { name: 'englishName', weight: 0.2 },\n { name: 'currentLocaleName', weight: 0.2 },\n { name: 'locale', weight: 0.2 },\n ],\n threshold: 0.02,\n };\n return new Fuse(multilingualAvailableLocales, fuseOptions);\n }, [multilingualAvailableLocales]);\n\n // 4. Derive results from Search Query\n const results = useMemo(() => {\n if (!searchQuery) {\n return multilingualAvailableLocales;\n }\n return fuse.search(searchQuery).map((result) => result.item);\n }, [searchQuery, multilingualAvailableLocales, fuse]);\n\n const handleClickLocale = (localeItem: LocalesValues) => {\n if (isMultilingual) {\n if (selectedLocales.includes(localeItem)) {\n if (selectedLocales.length > 1) {\n setSelectedLocales((prev) => prev.filter((el) => el !== localeItem));\n }\n } else {\n setSelectedLocales((prev) => [...prev, localeItem]);\n }\n } else {\n setSelectedLocales([localeItem]);\n }\n };\n\n const handleSeeAllLocales = (value: boolean) => {\n setSeeAllLocales(value);\n\n if (value) {\n setSelectedLocales(availableLocales);\n } else {\n setSelectedLocales([locale]);\n }\n };\n\n return (\n <div className=\"rounded-xl border border-text text-foreground transition-colors\">\n <DropDown identifier={DROPDOWN_IDENTIFIER}>\n <DropDown.Trigger\n identifier={DROPDOWN_IDENTIFIER}\n label={localeSwitcherLabel.value}\n className=\"p-0!\"\n roundedSize=\"3xl\"\n color=\"text\"\n variant=\"hoverable\"\n >\n <div className=\"flex w-full items-center justify-between text-foreground\">\n <div className=\"px-2 py-1\">\n <Globe size={16} />\n </div>\n <MoveVertical className=\"self-center\" size={16} />\n </div>\n </DropDown.Trigger>\n\n <DropDown.Panel\n identifier={DROPDOWN_IDENTIFIER}\n isOverable\n isFocusable\n align=\"end\"\n {...panelProps}\n >\n <Container\n className=\"max-h-[60vh] min-w-28\"\n separator=\"y\"\n role=\"listbox\"\n transparency=\"xs\"\n border\n roundedSize=\"3xl\"\n borderColor=\"text\"\n aria-label={languageListLabel.value}\n >\n {isMultilingual && (\n <div className=\"m-auto p-2\">\n <SwitchSelector\n defaultValue={seeAllLocales} // Ensure this uses the persisted state\n onChange={handleSeeAllLocales}\n color=\"text\"\n size=\"sm\"\n className=\"!w-60\"\n choices={[\n {\n content: seeAllLocalesSwitch.true.value,\n value: true,\n },\n {\n content: seeAllLocalesSwitch.false.value,\n value: false,\n },\n ]}\n />\n </div>\n )}\n\n {!(isMultilingual && seeAllLocales) && (\n <>\n <div className=\"p-3\">\n <Input\n type=\"search\"\n aria-label={searchInput.ariaLabel.value}\n placeholder={searchInput.placeholder.value}\n // Update search query state directly\n onChange={(e) => setSearchQuery(e.target.value)}\n ref={inputRef}\n />\n </div>\n <ol className=\"divide-y divide-dashed divide-text/20 overflow-y-auto p-1\">\n {results.map(\n ({\n locale: localeItem,\n currentLocaleName,\n ownLocaleName,\n }) => (\n <li className=\"px-1.5 py-1\" key={localeItem}>\n <Button\n onClick={() => handleClickLocale(localeItem)}\n label={`${switchTo} ${currentLocaleName}`}\n disabled={\n !(availableLocales ?? availableLocales).includes(\n localeItem\n )\n }\n isActive={selectedLocales.includes(localeItem)}\n variant=\"hoverable\"\n color=\"text\"\n isFullWidth\n textAlign=\"left\"\n size=\"sm\"\n >\n <div className=\"flex flex-row items-center justify-between gap-3 px-2 py-1\">\n {isMultilingual && (\n <div className=\"w-4\">\n {selectedLocales.includes(localeItem) && (\n <Check className=\"size-full\" />\n )}\n </div>\n )}\n <div className=\"flex flex-1 flex-row items-center justify-between gap-3 px-2 py-1\">\n <div className=\"flex flex-col text-nowrap\">\n <span\n dir={getHTMLTextDir(localeItem)}\n lang={localeItem}\n >\n {ownLocaleName}\n </span>\n <span className=\"text-neutral text-xs\">\n {currentLocaleName}\n </span>\n </div>\n <span className=\"text-neutral text-sm\">\n {localeItem.toUpperCase()}\n </span>\n </div>\n </div>\n </Button>\n </li>\n )\n )}\n </ol>\n </>\n )}\n </Container>\n </DropDown.Panel>\n </DropDown>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -158,7 +158,7 @@ const MarkDownIframe = (props) => {
|
|
|
158
158
|
rel: "noopener noreferrer",
|
|
159
159
|
label: "",
|
|
160
160
|
color: "neutral",
|
|
161
|
-
className: "inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-
|
|
161
|
+
className: "inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-muted-foreground text-xs underline-offset-2 hover:text-foreground hover:underline",
|
|
162
162
|
children: label
|
|
163
163
|
}) : /* @__PURE__ */ jsx("span", {
|
|
164
164
|
className: "text-neutral text-sm",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkDownIframe.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { Container } from '@components/Container';\nimport { Link } from '@components/Link';\nimport { Modal } from '@components/Modal';\nimport { cn } from '@utils/cn';\nimport { MoveDiagonal, Play } from 'lucide-react';\nimport {\n type ComponentProps,\n type FC,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nfunction embedLinkMeta(src: string | undefined): {\n href: string;\n label: string;\n} {\n if (!src) return { href: '', label: '' };\n if (/^https?:\\/\\//i.test(src)) {\n try {\n const url = new URL(src);\n return { href: url.href, label: url.host };\n } catch {\n return { href: src, label: src };\n }\n }\n return { href: src, label: src };\n}\n\n/**\n * Reads the video id out of a YouTube embed address.\n *\n * @returns The video id, or `null` when the address is not a YouTube embed.\n */\nconst getYoutubeVideoId = (src: string | undefined): string | null => {\n if (!src || !/^https?:\\/\\//i.test(src)) return null;\n\n try {\n const url = new URL(src);\n if (!/(^|\\.)youtube(-nocookie)?\\.com$/.test(url.hostname)) return null;\n\n const videoId = url.pathname.replace(/^\\/embed\\//, '').split('/')[0];\n\n return videoId || null;\n } catch {\n return null;\n }\n};\n\n/**\n * Tracks whether an element has come close enough to the viewport to be worth\n * loading.\n *\n * `loading=\"lazy\"` is not enough for embeds inside tabs or carousels: the\n * browser only defers frames that are far down the page, so a frame parked\n * off-screen by a transform — an inactive tab panel — still loads immediately.\n * A live-app embed costs upwards of a megabyte and seconds of main thread,\n * which is the whole page's performance budget spent on something nobody is\n * looking at.\n *\n * @returns A ref to attach to the placeholder, and whether it became visible.\n */\nconst useHasBecomeVisible = <ElementType extends HTMLElement>() => {\n const elementRef = useRef<ElementType>(null);\n const [hasBecomeVisible, setHasBecomeVisible] = useState(false);\n\n useEffect(() => {\n const element = elementRef.current;\n if (!element || hasBecomeVisible) return;\n\n if (typeof IntersectionObserver === 'undefined') {\n setHasBecomeVisible(true);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setHasBecomeVisible(true);\n observer.disconnect();\n }\n },\n { rootMargin: '200px' }\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n }, [hasBecomeVisible]);\n\n return { elementRef, hasBecomeVisible };\n};\n\ntype EmbeddedFrameProps = {\n src: string | undefined;\n title: string | undefined;\n className: string;\n frameProps: Omit<ComponentProps<'iframe'>, 'src' | 'title' | 'className'>;\n};\n\n/**\n * The embed itself: a poster for YouTube, deferred loading for anything else.\n *\n * A YouTube player pulls well over a megabyte of script before it can show a\n * single frame. Standing in for it with its own thumbnail until the reader\n * presses play keeps that cost on the readers who actually watch the video.\n */\nconst EmbeddedFrame: FC<EmbeddedFrameProps> = ({\n src,\n title,\n className,\n frameProps,\n}) => {\n const [isPlayRequested, setIsPlayRequested] = useState(false);\n const { elementRef, hasBecomeVisible } =\n useHasBecomeVisible<HTMLDivElement>();\n const youtubeVideoId = getYoutubeVideoId(src);\n\n if (youtubeVideoId && !isPlayRequested) {\n return (\n <button\n type=\"button\"\n aria-label={title ?? 'Play video'}\n onClick={() => setIsPlayRequested(true)}\n className={cn(\n 'group relative block cursor-pointer border-0 bg-card p-0',\n className\n )}\n >\n {/*\n * The poster stands in for the player, so on a page that opens with a\n * video it is the largest element painted — deferring it would defer\n * the page's LCP. It is the one image here worth fetching eagerly.\n */}\n <img\n src={`https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`}\n alt=\"\"\n fetchPriority=\"high\"\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"flex size-14 items-center justify-center rounded-full bg-text/70 text-text-opposite transition-transform group-hover:scale-110\">\n <Play className=\"size-6 translate-x-0.5 fill-current\" />\n </span>\n </span>\n </button>\n );\n }\n\n if (!hasBecomeVisible && !isPlayRequested) {\n // Same box as the frame it stands in for, so nothing shifts once it loads.\n return <div ref={elementRef} aria-hidden className={className} />;\n }\n\n const frameSrc = (() => {\n if (!isPlayRequested || !youtubeVideoId || !src) return src;\n\n const playbackSrc = new URL(src);\n playbackSrc.searchParams.set('autoplay', '1');\n\n return playbackSrc.href;\n })();\n\n return (\n <iframe\n {...frameProps}\n src={frameSrc}\n title={title}\n loading=\"lazy\"\n className={className}\n />\n );\n};\n\nexport const MarkDownIframe: FC<ComponentProps<'iframe'>> = (props) => {\n const { src, className, title, ...rest } = props;\n const [isModalOpen, setIsModalOpen] = useState(false);\n const { href, label } = embedLinkMeta(src);\n\n return (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <EmbeddedFrame\n src={src}\n title={title}\n frameProps={rest}\n className={cn(\n 'block max-h-[80vh] min-h-[12rem] w-full border-0',\n className\n )}\n />\n <div className=\"flex items-center justify-between gap-3 px-3 py-1\">\n {href ? (\n <Link\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n label=\"\"\n color=\"neutral\"\n className=\"inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-neutral text-xs underline-offset-2 hover:text-text hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <span className=\"text-neutral text-sm\">Embedded frame</span>\n )}\n <Button\n variant=\"hoverable\"\n size=\"icon-md\"\n onClick={() => setIsModalOpen(true)}\n label=\"Open embedded page in fullscreen\"\n Icon={MoveDiagonal}\n />\n </div>\n\n <Modal\n isOpen={isModalOpen}\n onClose={() => setIsModalOpen(false)}\n size=\"unset\"\n hasCloseButton\n isScrollable\n padding=\"sm\"\n >\n {isModalOpen && src ? (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <iframe\n {...rest}\n src={src}\n title={title ?? 'Embedded content'}\n allowFullScreen\n className=\"block min-h-[82vh] w-full border-0\"\n />\n </Container>\n ) : null}\n </Modal>\n </Container>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarkDownIframe.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownIframe.tsx"],"sourcesContent":["'use client';\n\nimport { Button } from '@components/Button';\nimport { Container } from '@components/Container';\nimport { Link } from '@components/Link';\nimport { Modal } from '@components/Modal';\nimport { cn } from '@utils/cn';\nimport { MoveDiagonal, Play } from 'lucide-react';\nimport {\n type ComponentProps,\n type FC,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nfunction embedLinkMeta(src: string | undefined): {\n href: string;\n label: string;\n} {\n if (!src) return { href: '', label: '' };\n if (/^https?:\\/\\//i.test(src)) {\n try {\n const url = new URL(src);\n return { href: url.href, label: url.host };\n } catch {\n return { href: src, label: src };\n }\n }\n return { href: src, label: src };\n}\n\n/**\n * Reads the video id out of a YouTube embed address.\n *\n * @returns The video id, or `null` when the address is not a YouTube embed.\n */\nconst getYoutubeVideoId = (src: string | undefined): string | null => {\n if (!src || !/^https?:\\/\\//i.test(src)) return null;\n\n try {\n const url = new URL(src);\n if (!/(^|\\.)youtube(-nocookie)?\\.com$/.test(url.hostname)) return null;\n\n const videoId = url.pathname.replace(/^\\/embed\\//, '').split('/')[0];\n\n return videoId || null;\n } catch {\n return null;\n }\n};\n\n/**\n * Tracks whether an element has come close enough to the viewport to be worth\n * loading.\n *\n * `loading=\"lazy\"` is not enough for embeds inside tabs or carousels: the\n * browser only defers frames that are far down the page, so a frame parked\n * off-screen by a transform — an inactive tab panel — still loads immediately.\n * A live-app embed costs upwards of a megabyte and seconds of main thread,\n * which is the whole page's performance budget spent on something nobody is\n * looking at.\n *\n * @returns A ref to attach to the placeholder, and whether it became visible.\n */\nconst useHasBecomeVisible = <ElementType extends HTMLElement>() => {\n const elementRef = useRef<ElementType>(null);\n const [hasBecomeVisible, setHasBecomeVisible] = useState(false);\n\n useEffect(() => {\n const element = elementRef.current;\n if (!element || hasBecomeVisible) return;\n\n if (typeof IntersectionObserver === 'undefined') {\n setHasBecomeVisible(true);\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setHasBecomeVisible(true);\n observer.disconnect();\n }\n },\n { rootMargin: '200px' }\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n }, [hasBecomeVisible]);\n\n return { elementRef, hasBecomeVisible };\n};\n\ntype EmbeddedFrameProps = {\n src: string | undefined;\n title: string | undefined;\n className: string;\n frameProps: Omit<ComponentProps<'iframe'>, 'src' | 'title' | 'className'>;\n};\n\n/**\n * The embed itself: a poster for YouTube, deferred loading for anything else.\n *\n * A YouTube player pulls well over a megabyte of script before it can show a\n * single frame. Standing in for it with its own thumbnail until the reader\n * presses play keeps that cost on the readers who actually watch the video.\n */\nconst EmbeddedFrame: FC<EmbeddedFrameProps> = ({\n src,\n title,\n className,\n frameProps,\n}) => {\n const [isPlayRequested, setIsPlayRequested] = useState(false);\n const { elementRef, hasBecomeVisible } =\n useHasBecomeVisible<HTMLDivElement>();\n const youtubeVideoId = getYoutubeVideoId(src);\n\n if (youtubeVideoId && !isPlayRequested) {\n return (\n <button\n type=\"button\"\n aria-label={title ?? 'Play video'}\n onClick={() => setIsPlayRequested(true)}\n className={cn(\n 'group relative block cursor-pointer border-0 bg-card p-0',\n className\n )}\n >\n {/*\n * The poster stands in for the player, so on a page that opens with a\n * video it is the largest element painted — deferring it would defer\n * the page's LCP. It is the one image here worth fetching eagerly.\n */}\n <img\n src={`https://i.ytimg.com/vi/${youtubeVideoId}/hqdefault.jpg`}\n alt=\"\"\n fetchPriority=\"high\"\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"flex size-14 items-center justify-center rounded-full bg-text/70 text-text-opposite transition-transform group-hover:scale-110\">\n <Play className=\"size-6 translate-x-0.5 fill-current\" />\n </span>\n </span>\n </button>\n );\n }\n\n if (!hasBecomeVisible && !isPlayRequested) {\n // Same box as the frame it stands in for, so nothing shifts once it loads.\n return <div ref={elementRef} aria-hidden className={className} />;\n }\n\n const frameSrc = (() => {\n if (!isPlayRequested || !youtubeVideoId || !src) return src;\n\n const playbackSrc = new URL(src);\n playbackSrc.searchParams.set('autoplay', '1');\n\n return playbackSrc.href;\n })();\n\n return (\n <iframe\n {...frameProps}\n src={frameSrc}\n title={title}\n loading=\"lazy\"\n className={className}\n />\n );\n};\n\nexport const MarkDownIframe: FC<ComponentProps<'iframe'>> = (props) => {\n const { src, className, title, ...rest } = props;\n const [isModalOpen, setIsModalOpen] = useState(false);\n const { href, label } = embedLinkMeta(src);\n\n return (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <EmbeddedFrame\n src={src}\n title={title}\n frameProps={rest}\n className={cn(\n 'block max-h-[80vh] min-h-[12rem] w-full border-0',\n className\n )}\n />\n <div className=\"flex items-center justify-between gap-3 px-3 py-1\">\n {href ? (\n <Link\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n label=\"\"\n color=\"neutral\"\n className=\"inline-flex min-w-0 max-w-[calc(100%-3rem)] items-center gap-2 text-muted-foreground text-xs underline-offset-2 hover:text-foreground hover:underline\"\n >\n {label}\n </Link>\n ) : (\n <span className=\"text-neutral text-sm\">Embedded frame</span>\n )}\n <Button\n variant=\"hoverable\"\n size=\"icon-md\"\n onClick={() => setIsModalOpen(true)}\n label=\"Open embedded page in fullscreen\"\n Icon={MoveDiagonal}\n />\n </div>\n\n <Modal\n isOpen={isModalOpen}\n onClose={() => setIsModalOpen(false)}\n size=\"unset\"\n hasCloseButton\n isScrollable\n padding=\"sm\"\n >\n {isModalOpen && src ? (\n <Container\n roundedSize=\"2xl\"\n border\n borderColor=\"neutral\"\n className=\"overflow-hidden p-0\"\n gap=\"none\"\n >\n <iframe\n {...rest}\n src={src}\n title={title ?? 'Embedded content'}\n allowFullScreen\n className=\"block min-h-[82vh] w-full border-0\"\n />\n </Container>\n ) : null}\n </Modal>\n </Container>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAgBA,SAAS,cAAc,KAGrB;CACA,IAAI,CAAC,KAAK,OAAO;EAAE,MAAM;EAAI,OAAO;CAAG;CACvC,IAAI,gBAAgB,KAAK,GAAG,GAC1B,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,GAAG;EACvB,OAAO;GAAE,MAAM,IAAI;GAAM,OAAO,IAAI;EAAK;CAC3C,QAAQ;EACN,OAAO;GAAE,MAAM;GAAK,OAAO;EAAI;CACjC;CAEF,OAAO;EAAE,MAAM;EAAK,OAAO;CAAI;AACjC;;;;;;AAOA,MAAM,qBAAqB,QAA2C;CACpE,IAAI,CAAC,OAAO,CAAC,gBAAgB,KAAK,GAAG,GAAG,OAAO;CAE/C,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,GAAG;EACvB,IAAI,CAAC,kCAAkC,KAAK,IAAI,QAAQ,GAAG,OAAO;EAIlE,OAFgB,IAAI,SAAS,QAAQ,cAAc,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAEhD;CACpB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;AAeA,MAAM,4BAA6D;CACjE,MAAM,aAAa,OAAoB,IAAI;CAC3C,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,KAAK;CAE9D,gBAAgB;EACd,MAAM,UAAU,WAAW;EAC3B,IAAI,CAAC,WAAW,kBAAkB;EAElC,IAAI,OAAO,yBAAyB,aAAa;GAC/C,oBAAoB,IAAI;GACxB;EACF;EAEA,MAAM,WAAW,IAAI,sBAClB,YAAY;GACX,IAAI,QAAQ,MAAM,UAAU,MAAM,cAAc,GAAG;IACjD,oBAAoB,IAAI;IACxB,SAAS,WAAW;GACtB;EACF,GACA,EAAE,YAAY,QAAQ,CACxB;EAEA,SAAS,QAAQ,OAAO;EAExB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,gBAAgB,CAAC;CAErB,OAAO;EAAE;EAAY;CAAiB;AACxC;;;;;;;;AAgBA,MAAM,iBAAyC,EAC7C,KACA,OACA,WACA,iBACI;CACJ,MAAM,CAAC,iBAAiB,sBAAsB,SAAS,KAAK;CAC5D,MAAM,EAAE,YAAY,qBAClB,oBAAoC;CACtC,MAAM,iBAAiB,kBAAkB,GAAG;CAE5C,IAAI,kBAAkB,CAAC,iBACrB,OACE,qBAAC,UAAD;EACE,MAAK;EACL,cAAY,SAAS;EACrB,eAAe,mBAAmB,IAAI;EACtC,WAAW,GACT,4DACA,SACF;YAPF,CAcE,oBAAC,OAAD;GACE,KAAK,0BAA0B,eAAe;GAC9C,KAAI;GACJ,eAAc;GACd,UAAS;GACT,WAAU;EACX,IACD,oBAAC,QAAD;GAAM,WAAU;aACd,oBAAC,QAAD;IAAM,WAAU;cACd,oBAAC,MAAD,EAAM,WAAU,sCAAuC;GACnD;EACF,EACA;;CAIZ,IAAI,CAAC,oBAAoB,CAAC,iBAExB,OAAO,oBAAC,OAAD;EAAK,KAAK;EAAY;EAAuB;CAAY;CAGlE,MAAM,kBAAkB;EACtB,IAAI,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,KAAK,OAAO;EAExD,MAAM,cAAc,IAAI,IAAI,GAAG;EAC/B,YAAY,aAAa,IAAI,YAAY,GAAG;EAE5C,OAAO,YAAY;CACrB,EAAC,CAAE;CAEH,OACE,oBAAC,UAAD;EACE,GAAI;EACJ,KAAK;EACE;EACP,SAAQ;EACG;CACZ;AAEL;AAEA,MAAa,kBAAgD,UAAU;CACrE,MAAM,EAAE,KAAK,WAAW,OAAO,GAAG,SAAS;CAC3C,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,EAAE,MAAM,UAAU,cAAc,GAAG;CAEzC,OACE,qBAAC,WAAD;EACE,aAAY;EACZ;EACA,aAAY;EACZ,WAAU;EACV,KAAI;YALN;GAOE,oBAAC,eAAD;IACO;IACE;IACP,YAAY;IACZ,WAAW,GACT,oDACA,SACF;GACD;GACD,qBAAC,OAAD;IAAK,WAAU;cAAf,CACG,OACC,oBAAC,MAAD;KACQ;KACN,QAAO;KACP,KAAI;KACJ,OAAM;KACN,OAAM;KACN,WAAU;eAET;IACG,KAEN,oBAAC,QAAD;KAAM,WAAU;eAAuB;IAAoB,IAE7D,oBAAC,QAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,eAAe,IAAI;KAClC,OAAM;KACN,MAAM;IACP,EACE;;GAEL,oBAAC,OAAD;IACE,QAAQ;IACR,eAAe,eAAe,KAAK;IACnC,MAAK;IACL;IACA;IACA,SAAQ;cAEP,eAAe,MACd,oBAAC,WAAD;KACE,aAAY;KACZ;KACA,aAAY;KACZ,WAAU;KACV,KAAI;eAEJ,oBAAC,UAAD;MACE,GAAI;MACC;MACL,OAAO,SAAS;MAChB;MACA,WAAU;KACX;IACQ,KACT;GACC;EACE;;AAEf"}
|
|
@@ -15,36 +15,36 @@ import { renderMarkdown } from "react-intlayer/markdown";
|
|
|
15
15
|
//#region src/components/MarkDownRender/MarkDownRender.tsx
|
|
16
16
|
const H1Renderer = (props) => /* @__PURE__ */ jsx(H1, {
|
|
17
17
|
isClickable: true,
|
|
18
|
-
className: "mb-16 mb-8 text-
|
|
18
|
+
className: "mb-16 mb-8 text-foreground",
|
|
19
19
|
...props
|
|
20
20
|
});
|
|
21
21
|
const H2Renderer = (props) => /* @__PURE__ */ jsx(H2, {
|
|
22
22
|
isClickable: true,
|
|
23
|
-
className: "mt-16 text-
|
|
23
|
+
className: "mt-16 text-foreground",
|
|
24
24
|
...props
|
|
25
25
|
});
|
|
26
26
|
const H3Renderer = (props) => /* @__PURE__ */ jsx(H3, {
|
|
27
27
|
isClickable: true,
|
|
28
|
-
className: "mt-5 text-
|
|
28
|
+
className: "mt-5 text-foreground",
|
|
29
29
|
...props
|
|
30
30
|
});
|
|
31
31
|
const H4Renderer = (props) => /* @__PURE__ */ jsx(H4, {
|
|
32
32
|
isClickable: true,
|
|
33
|
-
className: "mt-3 text-
|
|
33
|
+
className: "mt-3 text-foreground",
|
|
34
34
|
...props
|
|
35
35
|
});
|
|
36
36
|
const H5Renderer = (props) => /* @__PURE__ */ jsx(H5, {
|
|
37
37
|
isClickable: true,
|
|
38
|
-
className: "mt-3 text-
|
|
38
|
+
className: "mt-3 text-foreground",
|
|
39
39
|
...props
|
|
40
40
|
});
|
|
41
41
|
const H6Renderer = (props) => /* @__PURE__ */ jsx(H6, {
|
|
42
42
|
isClickable: true,
|
|
43
|
-
className: "mt-3 text-
|
|
43
|
+
className: "mt-3 text-foreground",
|
|
44
44
|
...props
|
|
45
45
|
});
|
|
46
46
|
const StrongRenderer = (props) => /* @__PURE__ */ jsx("strong", {
|
|
47
|
-
className: "text-
|
|
47
|
+
className: "text-foreground",
|
|
48
48
|
...props
|
|
49
49
|
});
|
|
50
50
|
const MemoizedCodeBlock = memo(({ className, children, highlightedHtml, lang, ...rest }) => {
|
|
@@ -62,6 +62,7 @@ const MemoizedCodeBlock = memo(({ className, children, highlightedHtml, lang, ..
|
|
|
62
62
|
language,
|
|
63
63
|
showHeader: true,
|
|
64
64
|
highlightedHtml,
|
|
65
|
+
className: "border",
|
|
65
66
|
children: content
|
|
66
67
|
});
|
|
67
68
|
}, (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.highlightedHtml === nextProps.highlightedHtml);
|
|
@@ -74,7 +75,7 @@ const MemoizedCodeBlock = memo(({ className, children, highlightedHtml, lang, ..
|
|
|
74
75
|
*/
|
|
75
76
|
const CodeRenderer = (props) => /* @__PURE__ */ jsx(MemoizedCodeBlock, { ...props });
|
|
76
77
|
const BlockquoteRenderer = ({ className, ...props }) => /* @__PURE__ */ jsx("blockquote", {
|
|
77
|
-
className: cn("mt-5 gap-3 border-card border-l-4 pl-5 text-
|
|
78
|
+
className: cn("mt-5 gap-3 border-card border-l-4 pl-5 text-muted-foreground [&_strong]:text-neutral", className),
|
|
78
79
|
...props
|
|
79
80
|
});
|
|
80
81
|
const UlRenderer = ({ className, ...props }) => /* @__PURE__ */ jsx("ul", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkDownRender.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownRender.tsx"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport type { ComponentProps, ComponentPropsWithoutRef, FC } from 'react';\nimport { memo } from 'react';\nimport {\n type MarkdownRenderer as MarkdownRendererIntlayer,\n type ParsedMarkdown,\n renderMarkdown,\n} from 'react-intlayer/markdown';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Code } from '../IDE/Code';\nimport { CodeProvider } from '../IDE/CodeContext';\nimport type { CodeLanguage } from '../IDE/shikiLanguages';\nimport { Link } from '../Link';\nimport { Tab } from '../Tab';\nimport { TabProvider } from '../Tab/TabContext';\nimport { Hr, SmartTable, Td, Th, Tr } from '../Table';\nimport { MarkDownIframe } from './MarkDownIframe';\n\nexport type { ParsedMarkdown };\n\n// Extracted, stable component renderers\nconst H1Renderer = (props: ComponentProps<'h1'>) => (\n <H1 isClickable className=\"mb-16 mb-8 text-text\" {...props} />\n);\nconst H2Renderer = (props: ComponentProps<'h2'>) => (\n <H2 isClickable className=\"mt-16 text-text\" {...props} />\n);\nconst H3Renderer = (props: ComponentProps<'h3'>) => (\n <H3 isClickable className=\"mt-5 text-text\" {...props} />\n);\nconst H4Renderer = (props: ComponentProps<'h4'>) => (\n <H4 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H5Renderer = (props: ComponentProps<'h5'>) => (\n <H5 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst H6Renderer = (props: ComponentProps<'h6'>) => (\n <H6 isClickable className=\"mt-3 text-text\" {...props} />\n);\nconst StrongRenderer = (props: ComponentProps<'strong'>) => (\n <strong className=\"text-text\" {...props} />\n);\n\nconst MemoizedCodeBlock = memo(\n ({\n className,\n children,\n highlightedHtml,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...rest\n }: ComponentProps<'code'> & { highlightedHtml?: string }) => {\n const content = String(children ?? '').replace(/\\n$/, '');\n const isBlock = !!className;\n\n if (!isBlock) {\n const decodedContent = content.replace(\n /&(?:amp;)?#(\\d+);/g,\n (_, code: string) => String.fromCharCode(parseInt(code, 10))\n );\n return (\n <code className=\"rounded-md border border-neutral/30 bg-card/60 box-decoration-clone px-1.5 py-0.5 font-mono text-[0.875em] leading-[inherit]\">\n {decodedContent}\n </code>\n );\n }\n\n const language = (className?.replace(/lang(?:uage)?-/, '') ||\n 'plaintext') as CodeLanguage;\n\n return (\n <Code\n {...rest}\n language={language}\n showHeader\n highlightedHtml={highlightedHtml}\n >\n {content}\n </Code>\n );\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.className === nextProps.className &&\n prevProps.highlightedHtml === nextProps.highlightedHtml\n);\n\n/**\n * Renders a fenced code block.\n *\n * `highlightedHtml` reaches this renderer through the code node's attributes:\n * a pipeline that highlights the markdown ahead of time writes it there, and\n * the block then paints without loading the highlighter in the browser.\n */\nconst CodeRenderer = (props: ComponentProps<'code'>) => (\n <MemoizedCodeBlock {...props} />\n);\n\nconst BlockquoteRenderer = ({\n className,\n ...props\n}: ComponentProps<'blockquote'>) => (\n <blockquote\n className={cn(\n 'mt-5 gap-3 border-card border-l-4 pl-5 text-neutral [&_strong]:text-neutral',\n className\n )}\n {...props}\n />\n);\n\nconst UlRenderer = ({ className, ...props }: ComponentProps<'ul'>) => (\n <ul\n className={cn(\n 'mt-5 flex list-disc flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst OlRenderer = ({ className, ...props }: ComponentProps<'ol'>) => (\n <ol\n className={cn(\n 'mt-5 flex list-decimal flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst ImgRenderer = ({\n className,\n alt,\n src,\n ...props\n}: ComponentProps<'img'>) => (\n <img\n {...props}\n alt={alt ?? ''}\n loading=\"lazy\"\n className={cn('max-h-[80vh] max-w-full rounded-md', className)}\n src={\n src?.includes('github.com')\n ? src\n ?.replace('github.com', 'raw.githubusercontent.com')\n .replace('/blob/', '/') // GitHub raw URLs do not use /blob/\n : src\n }\n />\n);\n\nconst createLinkRenderer = (locale?: LocalesValues) => {\n return (props: ComponentProps<'a'>) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined\n locale={locale}\n label=\"\"\n color=\"text\"\n {...(props as any)}\n />\n );\n};\n\nconst PreRenderer = (props: ComponentProps<'pre'>) => <>{props.children}</>;\nconst TableRenderer = (props: ComponentProps<typeof SmartTable>) => (\n <SmartTable isRollable displayModal {...props} />\n);\n\nconst TabsRenderer = (props: ComponentProps<typeof Tab>) => (\n <Tab\n {...props}\n className=\"rounded-xl border border-card\"\n headerClassName=\"sticky rounded-xl top-24 z-5 bg-background/70 backdrop-blur overflow-x-auto\"\n />\n);\nconst ColumnsRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex gap-4 max-md:flex-col', className)} {...props} />\n);\nconst ColumnRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex-1', className)} {...props} />\n);\n\nconst Iframe = (props: ComponentProps<'iframe'>) => (\n <MarkDownIframe {...props} />\n);\n\n// Static configuration object for static renderers\nconst staticMarkdownComponents = {\n h1: H1Renderer,\n h2: H2Renderer,\n h3: H3Renderer,\n h4: H4Renderer,\n h5: H5Renderer,\n h6: H6Renderer,\n strong: StrongRenderer,\n blockquote: BlockquoteRenderer,\n ul: UlRenderer,\n ol: OlRenderer,\n img: ImgRenderer,\n pre: PreRenderer,\n table: TableRenderer,\n th: Th,\n tr: Tr,\n td: Td,\n hr: Hr,\n Tabs: TabsRenderer,\n Tab: Tab.Item,\n Columns: ColumnsRenderer,\n Column: ColumnRenderer,\n iframe: Iframe,\n};\n\n// Factory function to create components with dynamic props\nconst createMarkdownComponents = (locale?: LocalesValues) => ({\n ...staticMarkdownComponents,\n code: CodeRenderer,\n a: createLinkRenderer(locale),\n});\n\n// Export static renderers for backward compatibility\nexport const baseMarkdownComponents = staticMarkdownComponents;\n\ntype MarkdownRendererProps = {\n children: string | ParsedMarkdown;\n locale?: LocalesValues;\n forceBlock?: boolean;\n preserveFrontmatter?: boolean;\n tagfilter?: boolean;\n components?: ComponentProps<typeof MarkdownRendererIntlayer>['components'];\n wrapper?: ComponentProps<typeof MarkdownRendererIntlayer>['wrapper'];\n};\n\nexport const getIntlayerMarkdownOptions = () => ({\n components: baseMarkdownComponents,\n});\n\nexport const MarkdownRenderer: FC<MarkdownRendererProps> = ({\n children,\n locale,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n components: componentsProp,\n wrapper,\n}) => {\n const markdownComponents = createMarkdownComponents(locale);\n\n const markdownContent = renderMarkdown(children, {\n components: {\n ...markdownComponents,\n ...componentsProp,\n },\n wrapper,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n });\n\n return (\n <CodeProvider>\n <TabProvider>{markdownContent}</TabProvider>\n </CodeProvider>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarkDownRender.mjs","names":[],"sources":["../../../../src/components/MarkDownRender/MarkDownRender.tsx"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { cn } from '@utils/cn';\nimport type { ComponentProps, ComponentPropsWithoutRef, FC } from 'react';\nimport { memo } from 'react';\nimport {\n type MarkdownRenderer as MarkdownRendererIntlayer,\n type ParsedMarkdown,\n renderMarkdown,\n} from 'react-intlayer/markdown';\nimport { H1, H2, H3, H4, H5, H6 } from '../Headers';\nimport { Code } from '../IDE/Code';\nimport { CodeProvider } from '../IDE/CodeContext';\nimport type { CodeLanguage } from '../IDE/shikiLanguages';\nimport { Link } from '../Link';\nimport { Tab } from '../Tab';\nimport { TabProvider } from '../Tab/TabContext';\nimport { Hr, SmartTable, Td, Th, Tr } from '../Table';\nimport { MarkDownIframe } from './MarkDownIframe';\n\nexport type { ParsedMarkdown };\n\n// Extracted, stable component renderers\nconst H1Renderer = (props: ComponentProps<'h1'>) => (\n <H1 isClickable className=\"mb-16 mb-8 text-foreground\" {...props} />\n);\nconst H2Renderer = (props: ComponentProps<'h2'>) => (\n <H2 isClickable className=\"mt-16 text-foreground\" {...props} />\n);\nconst H3Renderer = (props: ComponentProps<'h3'>) => (\n <H3 isClickable className=\"mt-5 text-foreground\" {...props} />\n);\nconst H4Renderer = (props: ComponentProps<'h4'>) => (\n <H4 isClickable className=\"mt-3 text-foreground\" {...props} />\n);\nconst H5Renderer = (props: ComponentProps<'h5'>) => (\n <H5 isClickable className=\"mt-3 text-foreground\" {...props} />\n);\nconst H6Renderer = (props: ComponentProps<'h6'>) => (\n <H6 isClickable className=\"mt-3 text-foreground\" {...props} />\n);\nconst StrongRenderer = (props: ComponentProps<'strong'>) => (\n <strong className=\"text-foreground\" {...props} />\n);\n\nconst MemoizedCodeBlock = memo(\n ({\n className,\n children,\n highlightedHtml,\n lang, // Destructure html lang prop to prevent passing invalid BCP-47 language tag to Code component\n ...rest\n }: ComponentProps<'code'> & { highlightedHtml?: string }) => {\n const content = String(children ?? '').replace(/\\n$/, '');\n const isBlock = !!className;\n\n if (!isBlock) {\n const decodedContent = content.replace(\n /&(?:amp;)?#(\\d+);/g,\n (_, code: string) => String.fromCharCode(parseInt(code, 10))\n );\n return (\n <code className=\"rounded-md border border-neutral/30 bg-card/60 box-decoration-clone px-1.5 py-0.5 font-mono text-[0.875em] leading-[inherit]\">\n {decodedContent}\n </code>\n );\n }\n\n const language = (className?.replace(/lang(?:uage)?-/, '') ||\n 'plaintext') as CodeLanguage;\n\n return (\n <Code\n {...rest}\n language={language}\n showHeader\n highlightedHtml={highlightedHtml}\n className=\"border\"\n >\n {content}\n </Code>\n );\n },\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children &&\n prevProps.className === nextProps.className &&\n prevProps.highlightedHtml === nextProps.highlightedHtml\n);\n\n/**\n * Renders a fenced code block.\n *\n * `highlightedHtml` reaches this renderer through the code node's attributes:\n * a pipeline that highlights the markdown ahead of time writes it there, and\n * the block then paints without loading the highlighter in the browser.\n */\nconst CodeRenderer = (props: ComponentProps<'code'>) => (\n <MemoizedCodeBlock {...props} />\n);\n\nconst BlockquoteRenderer = ({\n className,\n ...props\n}: ComponentProps<'blockquote'>) => (\n <blockquote\n className={cn(\n 'mt-5 gap-3 border-card border-l-4 pl-5 text-muted-foreground [&_strong]:text-neutral',\n className\n )}\n {...props}\n />\n);\n\nconst UlRenderer = ({ className, ...props }: ComponentProps<'ul'>) => (\n <ul\n className={cn(\n 'mt-5 flex list-disc flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst OlRenderer = ({ className, ...props }: ComponentProps<'ol'>) => (\n <ol\n className={cn(\n 'mt-5 flex list-decimal flex-col gap-3 pl-5 marker:text-neutral/80',\n className\n )}\n {...props}\n />\n);\n\nconst ImgRenderer = ({\n className,\n alt,\n src,\n ...props\n}: ComponentProps<'img'>) => (\n <img\n {...props}\n alt={alt ?? ''}\n loading=\"lazy\"\n className={cn('max-h-[80vh] max-w-full rounded-md', className)}\n src={\n src?.includes('github.com')\n ? src\n ?.replace('github.com', 'raw.githubusercontent.com')\n .replace('/blob/', '/') // GitHub raw URLs do not use /blob/\n : src\n }\n />\n);\n\nconst createLinkRenderer = (locale?: LocalesValues) => {\n return (props: ComponentProps<'a'>) => (\n <Link\n isExternalLink={props.href?.startsWith('http')}\n underlined\n locale={locale}\n label=\"\"\n color=\"text\"\n {...(props as any)}\n />\n );\n};\n\nconst PreRenderer = (props: ComponentProps<'pre'>) => <>{props.children}</>;\nconst TableRenderer = (props: ComponentProps<typeof SmartTable>) => (\n <SmartTable isRollable displayModal {...props} />\n);\n\nconst TabsRenderer = (props: ComponentProps<typeof Tab>) => (\n <Tab\n {...props}\n className=\"rounded-xl border border-card\"\n headerClassName=\"sticky rounded-xl top-24 z-5 bg-background/70 backdrop-blur overflow-x-auto\"\n />\n);\nconst ColumnsRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex gap-4 max-md:flex-col', className)} {...props} />\n);\nconst ColumnRenderer = ({\n className,\n ...props\n}: ComponentPropsWithoutRef<'div'>) => (\n <div className={cn('flex-1', className)} {...props} />\n);\n\nconst Iframe = (props: ComponentProps<'iframe'>) => (\n <MarkDownIframe {...props} />\n);\n\n// Static configuration object for static renderers\nconst staticMarkdownComponents = {\n h1: H1Renderer,\n h2: H2Renderer,\n h3: H3Renderer,\n h4: H4Renderer,\n h5: H5Renderer,\n h6: H6Renderer,\n strong: StrongRenderer,\n blockquote: BlockquoteRenderer,\n ul: UlRenderer,\n ol: OlRenderer,\n img: ImgRenderer,\n pre: PreRenderer,\n table: TableRenderer,\n th: Th,\n tr: Tr,\n td: Td,\n hr: Hr,\n Tabs: TabsRenderer,\n Tab: Tab.Item,\n Columns: ColumnsRenderer,\n Column: ColumnRenderer,\n iframe: Iframe,\n};\n\n// Factory function to create components with dynamic props\nconst createMarkdownComponents = (locale?: LocalesValues) => ({\n ...staticMarkdownComponents,\n code: CodeRenderer,\n a: createLinkRenderer(locale),\n});\n\n// Export static renderers for backward compatibility\nexport const baseMarkdownComponents = staticMarkdownComponents;\n\ntype MarkdownRendererProps = {\n children: string | ParsedMarkdown;\n locale?: LocalesValues;\n forceBlock?: boolean;\n preserveFrontmatter?: boolean;\n tagfilter?: boolean;\n components?: ComponentProps<typeof MarkdownRendererIntlayer>['components'];\n wrapper?: ComponentProps<typeof MarkdownRendererIntlayer>['wrapper'];\n};\n\nexport const getIntlayerMarkdownOptions = () => ({\n components: baseMarkdownComponents,\n});\n\nexport const MarkdownRenderer: FC<MarkdownRendererProps> = ({\n children,\n locale,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n components: componentsProp,\n wrapper,\n}) => {\n const markdownComponents = createMarkdownComponents(locale);\n\n const markdownContent = renderMarkdown(children, {\n components: {\n ...markdownComponents,\n ...componentsProp,\n },\n wrapper,\n forceBlock,\n preserveFrontmatter,\n tagfilter,\n });\n\n return (\n <CodeProvider>\n <TabProvider>{markdownContent}</TabProvider>\n </CodeProvider>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,MAAM,cAAc,UAClB,oBAAC,IAAD;CAAI;CAAY,WAAU;CAA6B,GAAI;AAAQ;AAErE,MAAM,cAAc,UAClB,oBAAC,IAAD;CAAI;CAAY,WAAU;CAAwB,GAAI;AAAQ;AAEhE,MAAM,cAAc,UAClB,oBAAC,IAAD;CAAI;CAAY,WAAU;CAAuB,GAAI;AAAQ;AAE/D,MAAM,cAAc,UAClB,oBAAC,IAAD;CAAI;CAAY,WAAU;CAAuB,GAAI;AAAQ;AAE/D,MAAM,cAAc,UAClB,oBAAC,IAAD;CAAI;CAAY,WAAU;CAAuB,GAAI;AAAQ;AAE/D,MAAM,cAAc,UAClB,oBAAC,IAAD;CAAI;CAAY,WAAU;CAAuB,GAAI;AAAQ;AAE/D,MAAM,kBAAkB,UACtB,oBAAC,UAAD;CAAQ,WAAU;CAAkB,GAAI;AAAQ;AAGlD,MAAM,oBAAoB,MACvB,EACC,WACA,UACA,iBACA,MACA,GAAG,WACwD;CAC3D,MAAM,UAAU,OAAO,YAAY,EAAE,CAAC,CAAC,QAAQ,OAAO,EAAE;CAGxD,IAAI,CAAC,CAFY,CAAC,WAEJ;EACZ,MAAM,iBAAiB,QAAQ,QAC7B,uBACC,GAAG,SAAiB,OAAO,aAAa,SAAS,MAAM,EAAE,CAAC,CAC7D;EACA,OACE,oBAAC,QAAD;GAAM,WAAU;aACb;EACG;CAEV;CAEA,MAAM,WAAY,WAAW,QAAQ,kBAAkB,EAAE,KACvD;CAEF,OACE,oBAAC,MAAD;EACE,GAAI;EACM;EACV;EACiB;EACjB,WAAU;YAET;CACG;AAEV,IACC,WAAW,cACV,UAAU,aAAa,UAAU,YACjC,UAAU,cAAc,UAAU,aAClC,UAAU,oBAAoB,UAAU,eAC5C;;;;;;;;AASA,MAAM,gBAAgB,UACpB,oBAAC,mBAAD,EAAmB,GAAI,MAAQ;AAGjC,MAAM,sBAAsB,EAC1B,WACA,GAAG,YAEH,oBAAC,cAAD;CACE,WAAW,GACT,wFACA,SACF;CACA,GAAI;AACL;AAGH,MAAM,cAAc,EAAE,WAAW,GAAG,YAClC,oBAAC,MAAD;CACE,WAAW,GACT,kEACA,SACF;CACA,GAAI;AACL;AAGH,MAAM,cAAc,EAAE,WAAW,GAAG,YAClC,oBAAC,MAAD;CACE,WAAW,GACT,qEACA,SACF;CACA,GAAI;AACL;AAGH,MAAM,eAAe,EACnB,WACA,KACA,KACA,GAAG,YAEH,oBAAC,OAAD;CACE,GAAI;CACJ,KAAK,OAAO;CACZ,SAAQ;CACR,WAAW,GAAG,sCAAsC,SAAS;CAC7D,KACE,KAAK,SAAS,YAAY,IACtB,KACI,QAAQ,cAAc,2BAA2B,CAAC,CACnD,QAAQ,UAAU,GAAG,IACxB;AAEP;AAGH,MAAM,sBAAsB,WAA2B;CACrD,QAAQ,UACN,oBAAC,MAAD;EACE,gBAAgB,MAAM,MAAM,WAAW,MAAM;EAC7C;EACQ;EACR,OAAM;EACN,OAAM;EACN,GAAK;CACN;AAEL;AAEA,MAAM,eAAe,UAAiC,4CAAG,MAAM,SAAW;AAC1E,MAAM,iBAAiB,UACrB,oBAAC,YAAD;CAAY;CAAW;CAAa,GAAI;AAAQ;AAGlD,MAAM,gBAAgB,UACpB,oBAAC,KAAD;CACE,GAAI;CACJ,WAAU;CACV,iBAAgB;AACjB;AAEH,MAAM,mBAAmB,EACvB,WACA,GAAG,YAEH,oBAAC,OAAD;CAAK,WAAW,GAAG,8BAA8B,SAAS;CAAG,GAAI;AAAQ;AAE3E,MAAM,kBAAkB,EACtB,WACA,GAAG,YAEH,oBAAC,OAAD;CAAK,WAAW,GAAG,UAAU,SAAS;CAAG,GAAI;AAAQ;AAGvD,MAAM,UAAU,UACd,oBAAC,gBAAD,EAAgB,GAAI,MAAQ;AAI9B,MAAM,2BAA2B;CAC/B,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,QAAQ;CACR,YAAY;CACZ,IAAI;CACJ,IAAI;CACJ,KAAK;CACL,KAAK;CACL,OAAO;CACP,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM;CACN,KAAK,IAAI;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;AACV;AAGA,MAAM,4BAA4B,YAA4B;CAC5D,GAAG;CACH,MAAM;CACN,GAAG,mBAAmB,MAAM;AAC9B;AAGA,MAAa,yBAAyB;AAYtC,MAAa,oCAAoC,EAC/C,YAAY,uBACd;AAEA,MAAa,oBAA+C,EAC1D,UACA,QACA,YACA,qBACA,WACA,YAAY,gBACZ,cACI;CACJ,MAAM,qBAAqB,yBAAyB,MAAM;CAE1D,MAAM,kBAAkB,eAAe,UAAU;EAC/C,YAAY;GACV,GAAG;GACH,GAAG;EACL;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,OACE,oBAAC,cAAD,YACE,oBAAC,aAAD,YAAc,gBAA6B,GAC/B;AAElB"}
|
|
@@ -75,7 +75,7 @@ const MarkdownEditor = ({ defaultValue, onChange, editable = true, enableAI = tr
|
|
|
75
75
|
onUpdate: handleUpdate,
|
|
76
76
|
slotAfter: /* @__PURE__ */ jsx(ImageResizer, {}),
|
|
77
77
|
children: [/* @__PURE__ */ jsxs(EditorCommand, {
|
|
78
|
-
className: "z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border
|
|
78
|
+
className: "z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border bg-card px-1 py-2 text-foreground shadow-xl transition-all",
|
|
79
79
|
children: [/* @__PURE__ */ jsx(EditorCommandEmpty, {
|
|
80
80
|
className: "px-2 text-muted-foreground",
|
|
81
81
|
children: content.noResults.value
|
|
@@ -100,7 +100,7 @@ const MarkdownEditor = ({ defaultValue, onChange, editable = true, enableAI = tr
|
|
|
100
100
|
children: toolbar
|
|
101
101
|
}) : /* @__PURE__ */ jsx(EditorBubble, {
|
|
102
102
|
options: { placement: "top" },
|
|
103
|
-
className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px]
|
|
103
|
+
className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl",
|
|
104
104
|
children: toolbar
|
|
105
105
|
})]
|
|
106
106
|
}) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownEditor.mjs","names":[],"sources":["../../../../src/components/MarkdownEditor/MarkdownEditor.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, useMemo, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { defaultExtensions } from './extensions';\nimport { GenerativeMenuSwitch } from './generative/GenerativeMenuSwitch';\nimport {\n type CreateEditorUploadFnOptions,\n createEditorUploadFn,\n} from './imageUpload';\nimport {\n EditorBubble,\n EditorCommand,\n EditorCommandEmpty,\n EditorCommandItem,\n EditorCommandList,\n EditorContent,\n type EditorInstance,\n EditorRoot,\n handleCommandNavigation,\n handleImageDrop,\n handleImagePaste,\n ImageResizer,\n} from './novel';\nimport { ColorSelector } from './selectors/ColorSelector';\nimport { LinkSelector } from './selectors/LinkSelector';\nimport { NodeSelector } from './selectors/NodeSelector';\nimport { TextButtons } from './selectors/TextButtons';\nimport { createSlashCommand } from './slashCommand';\n\nconst Divider = () => <div className=\"h-full w-px shrink-0 bg-border\" />;\n\nexport type MarkdownEditorProps = {\n /** Initial markdown value. */\n defaultValue?: string;\n /** Called with the serialized markdown on every change. */\n onChange?: (markdown: string) => void;\n /** Whether the document can be edited. Defaults to `true`. */\n editable?: boolean;\n /** Enables the AI bubble menu (\"Ask AI\"). Defaults to `true`. */\n enableAI?: boolean;\n /**\n * Custom image uploader. When omitted, images are embedded inline as base64\n * data URLs (no backend required).\n */\n uploadImage?: CreateEditorUploadFnOptions['uploadImage'];\n /** Maximum allowed image size in megabytes. Defaults to `20`. */\n maxImageSizeMb?: number;\n /** Class applied to the editor surface. */\n className?: string;\n};\n\n/**\n * Notion-style WYSIWYG markdown editor built on Novel / Tiptap.\n *\n * Features a slash command menu, a formatting bubble menu (node / link / color\n * / inline marks), image upload (drop / paste / slash), and an optional\n * AI assistant wired to the Intlayer AI backend.\n *\n * The value contract is markdown: pass `defaultValue` as a markdown string and\n * read changes from `onChange`.\n */\nexport const MarkdownEditor: FC<MarkdownEditorProps> = ({\n defaultValue,\n onChange,\n editable = true,\n enableAI = true,\n uploadImage,\n maxImageSizeMb,\n className,\n}) => {\n const [openAI, setOpenAI] = useState(false);\n const content = useIntlayer('markdown-editor');\n\n const uploadFn = useMemo(\n () => createEditorUploadFn({ uploadImage, maxSizeMb: maxImageSizeMb }),\n [uploadImage, maxImageSizeMb]\n );\n\n const { extensions, suggestionItems } = useMemo(() => {\n const { slashCommand, suggestionItems: items } = createSlashCommand(\n uploadFn,\n content\n );\n\n return {\n extensions: [...defaultExtensions, slashCommand],\n suggestionItems: items,\n };\n }, [uploadFn, content]);\n\n const handleUpdate = ({ editor }: { editor: EditorInstance }) => {\n onChange?.(editor.storage.markdown.getMarkdown() as string);\n };\n\n const toolbar = (\n <>\n <NodeSelector />\n <Divider />\n <LinkSelector />\n <Divider />\n <TextButtons />\n <Divider />\n <ColorSelector />\n </>\n );\n\n return (\n <div className=\"relative w-full\">\n <EditorRoot>\n <EditorContent\n immediatelyRender={false}\n editable={editable}\n initialContent={defaultValue ?? ''}\n extensions={extensions}\n className={cn('min-h-[350px] w-full max-w-full', className)}\n editorProps={{\n handleDOMEvents: {\n keydown: (_view, event) => handleCommandNavigation(event),\n },\n handlePaste: (view, event) =>\n handleImagePaste(view, event, uploadFn),\n handleDrop: (view, event, _slice, moved) =>\n handleImageDrop(view, event, moved, uploadFn),\n attributes: {\n // Content typography (headings, lists, spacing) is defined in\n // `markdown-editor.css`, scoped to `.ProseMirror` — this project\n // has no Tailwind `prose` plugin.\n class: 'max-w-full pl-6 focus:outline-none',\n },\n }}\n onUpdate={handleUpdate}\n slotAfter={<ImageResizer />}\n >\n <EditorCommand className=\"z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border
|
|
1
|
+
{"version":3,"file":"MarkdownEditor.mjs","names":[],"sources":["../../../../src/components/MarkdownEditor/MarkdownEditor.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { type FC, useMemo, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { defaultExtensions } from './extensions';\nimport { GenerativeMenuSwitch } from './generative/GenerativeMenuSwitch';\nimport {\n type CreateEditorUploadFnOptions,\n createEditorUploadFn,\n} from './imageUpload';\nimport {\n EditorBubble,\n EditorCommand,\n EditorCommandEmpty,\n EditorCommandItem,\n EditorCommandList,\n EditorContent,\n type EditorInstance,\n EditorRoot,\n handleCommandNavigation,\n handleImageDrop,\n handleImagePaste,\n ImageResizer,\n} from './novel';\nimport { ColorSelector } from './selectors/ColorSelector';\nimport { LinkSelector } from './selectors/LinkSelector';\nimport { NodeSelector } from './selectors/NodeSelector';\nimport { TextButtons } from './selectors/TextButtons';\nimport { createSlashCommand } from './slashCommand';\n\nconst Divider = () => <div className=\"h-full w-px shrink-0 bg-border\" />;\n\nexport type MarkdownEditorProps = {\n /** Initial markdown value. */\n defaultValue?: string;\n /** Called with the serialized markdown on every change. */\n onChange?: (markdown: string) => void;\n /** Whether the document can be edited. Defaults to `true`. */\n editable?: boolean;\n /** Enables the AI bubble menu (\"Ask AI\"). Defaults to `true`. */\n enableAI?: boolean;\n /**\n * Custom image uploader. When omitted, images are embedded inline as base64\n * data URLs (no backend required).\n */\n uploadImage?: CreateEditorUploadFnOptions['uploadImage'];\n /** Maximum allowed image size in megabytes. Defaults to `20`. */\n maxImageSizeMb?: number;\n /** Class applied to the editor surface. */\n className?: string;\n};\n\n/**\n * Notion-style WYSIWYG markdown editor built on Novel / Tiptap.\n *\n * Features a slash command menu, a formatting bubble menu (node / link / color\n * / inline marks), image upload (drop / paste / slash), and an optional\n * AI assistant wired to the Intlayer AI backend.\n *\n * The value contract is markdown: pass `defaultValue` as a markdown string and\n * read changes from `onChange`.\n */\nexport const MarkdownEditor: FC<MarkdownEditorProps> = ({\n defaultValue,\n onChange,\n editable = true,\n enableAI = true,\n uploadImage,\n maxImageSizeMb,\n className,\n}) => {\n const [openAI, setOpenAI] = useState(false);\n const content = useIntlayer('markdown-editor');\n\n const uploadFn = useMemo(\n () => createEditorUploadFn({ uploadImage, maxSizeMb: maxImageSizeMb }),\n [uploadImage, maxImageSizeMb]\n );\n\n const { extensions, suggestionItems } = useMemo(() => {\n const { slashCommand, suggestionItems: items } = createSlashCommand(\n uploadFn,\n content\n );\n\n return {\n extensions: [...defaultExtensions, slashCommand],\n suggestionItems: items,\n };\n }, [uploadFn, content]);\n\n const handleUpdate = ({ editor }: { editor: EditorInstance }) => {\n onChange?.(editor.storage.markdown.getMarkdown() as string);\n };\n\n const toolbar = (\n <>\n <NodeSelector />\n <Divider />\n <LinkSelector />\n <Divider />\n <TextButtons />\n <Divider />\n <ColorSelector />\n </>\n );\n\n return (\n <div className=\"relative w-full\">\n <EditorRoot>\n <EditorContent\n immediatelyRender={false}\n editable={editable}\n initialContent={defaultValue ?? ''}\n extensions={extensions}\n className={cn('min-h-[350px] w-full max-w-full', className)}\n editorProps={{\n handleDOMEvents: {\n keydown: (_view, event) => handleCommandNavigation(event),\n },\n handlePaste: (view, event) =>\n handleImagePaste(view, event, uploadFn),\n handleDrop: (view, event, _slice, moved) =>\n handleImageDrop(view, event, moved, uploadFn),\n attributes: {\n // Content typography (headings, lists, spacing) is defined in\n // `markdown-editor.css`, scoped to `.ProseMirror` — this project\n // has no Tailwind `prose` plugin.\n class: 'max-w-full pl-6 focus:outline-none',\n },\n }}\n onUpdate={handleUpdate}\n slotAfter={<ImageResizer />}\n >\n <EditorCommand className=\"z-50 h-auto max-h-[330px] overflow-y-auto rounded-lg border bg-card px-1 py-2 text-foreground shadow-xl transition-all\">\n <EditorCommandEmpty className=\"px-2 text-muted-foreground\">\n {content.noResults.value}\n </EditorCommandEmpty>\n <EditorCommandList>\n {suggestionItems.map((item) => (\n <EditorCommandItem\n value={item.title}\n onCommand={(val) => item.command?.(val)}\n className=\"flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-accent aria-selected:bg-accent\"\n key={item.title}\n >\n <div className=\"flex size-10 items-center justify-center rounded-md border border-card bg-background\">\n {item.icon}\n </div>\n <div>\n <p className=\"font-medium\">{item.title}</p>\n <p className=\"text-muted-foreground text-xs\">\n {item.description}\n </p>\n </div>\n </EditorCommandItem>\n ))}\n </EditorCommandList>\n </EditorCommand>\n\n {enableAI ? (\n <GenerativeMenuSwitch open={openAI} onOpenChange={setOpenAI}>\n {toolbar}\n </GenerativeMenuSwitch>\n ) : (\n <EditorBubble\n options={{ placement: 'top' }}\n className=\"flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl\"\n >\n {toolbar}\n </EditorBubble>\n )}\n </EditorContent>\n </EditorRoot>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,gBAAgB,oBAAC,OAAD,EAAK,WAAU,iCAAkC;;;;;;;;;;;AAgCvE,MAAa,kBAA2C,EACtD,cACA,UACA,WAAW,MACX,WAAW,MACX,aACA,gBACA,gBACI;CACJ,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,UAAU,YAAY,iBAAiB;CAE7C,MAAM,WAAW,cACT,qBAAqB;EAAE;EAAa,WAAW;CAAe,CAAC,GACrE,CAAC,aAAa,cAAc,CAC9B;CAEA,MAAM,EAAE,YAAY,oBAAoB,cAAc;EACpD,MAAM,EAAE,cAAc,iBAAiB,UAAU,mBAC/C,UACA,OACF;EAEA,OAAO;GACL,YAAY,CAAC,GAAG,mBAAmB,YAAY;GAC/C,iBAAiB;EACnB;CACF,GAAG,CAAC,UAAU,OAAO,CAAC;CAEtB,MAAM,gBAAgB,EAAE,aAAyC;EAC/D,WAAW,OAAO,QAAQ,SAAS,YAAY,CAAW;CAC5D;CAEA,MAAM,UACJ;EACE,oBAAC,cAAD,CAAe;EACf,oBAAC,SAAD,CAAU;EACV,oBAAC,cAAD,CAAe;EACf,oBAAC,SAAD,CAAU;EACV,oBAAC,aAAD,CAAc;EACd,oBAAC,SAAD,CAAU;EACV,oBAAC,eAAD,CAAgB;CAChB;CAGJ,OACE,oBAAC,OAAD;EAAK,WAAU;YACb,oBAAC,YAAD,YACE,qBAAC,eAAD;GACE,mBAAmB;GACT;GACV,gBAAgB,gBAAgB;GACpB;GACZ,WAAW,GAAG,mCAAmC,SAAS;GAC1D,aAAa;IACX,iBAAiB,EACf,UAAU,OAAO,UAAU,wBAAwB,KAAK,EAC1D;IACA,cAAc,MAAM,UAClB,iBAAiB,MAAM,OAAO,QAAQ;IACxC,aAAa,MAAM,OAAO,QAAQ,UAChC,gBAAgB,MAAM,OAAO,OAAO,QAAQ;IAC9C,YAAY,EAIV,OAAO,qCACT;GACF;GACA,UAAU;GACV,WAAW,oBAAC,cAAD,CAAe;aAtB5B,CAwBE,qBAAC,eAAD;IAAe,WAAU;cAAzB,CACE,oBAAC,oBAAD;KAAoB,WAAU;eAC3B,QAAQ,UAAU;IACD,IACpB,oBAAC,mBAAD,YACG,gBAAgB,KAAK,SACpB,qBAAC,mBAAD;KACE,OAAO,KAAK;KACZ,YAAY,QAAQ,KAAK,UAAU,GAAG;KACtC,WAAU;eAHZ,CAME,oBAAC,OAAD;MAAK,WAAU;gBACZ,KAAK;KACH,IACL,qBAAC,OAAD,aACE,oBAAC,KAAD;MAAG,WAAU;gBAAe,KAAK;KAAS,IAC1C,oBAAC,KAAD;MAAG,WAAU;gBACV,KAAK;KACL,EACA,IACY;OAXZ,KAAK,KAWO,CACpB,EACgB,EACN;OAEd,WACC,oBAAC,sBAAD;IAAsB,MAAM;IAAQ,cAAc;cAC/C;GACmB,KAEtB,oBAAC,cAAD;IACE,SAAS,EAAE,WAAW,MAAM;IAC5B,WAAU;cAET;GACW,EAEH;KACL;CACT;AAET"}
|
|
@@ -4,7 +4,7 @@ import { toast } from "../../Toaster/useToast.mjs";
|
|
|
4
4
|
import { Loader } from "../../Loader/index.mjs";
|
|
5
5
|
import { Button } from "../../Button/Button.mjs";
|
|
6
6
|
import { Command, CommandRoot } from "../../Command/index.mjs";
|
|
7
|
-
import { MarkdownRenderer
|
|
7
|
+
import { MarkdownRenderer } from "../../MarkDownRender/MarkDownRender.mjs";
|
|
8
8
|
import { useEditor } from "../novel/components/index.mjs";
|
|
9
9
|
import { addAIHighlight } from "../novel/extensions/ai-highlight.mjs";
|
|
10
10
|
import { AICompletionCommands } from "./AICompletionCommands.mjs";
|
|
@@ -45,12 +45,12 @@ const AISelector = ({ onOpenChange, sendButtonVariant = "default", sendButtonCol
|
|
|
45
45
|
className: "relative overflow-y-auto",
|
|
46
46
|
children: /* @__PURE__ */ jsx("div", {
|
|
47
47
|
className: "prose prose-sm dark:prose-invert p-2 px-4",
|
|
48
|
-
children: /* @__PURE__ */ jsx(MarkdownRenderer
|
|
48
|
+
children: /* @__PURE__ */ jsx(MarkdownRenderer, { children: completion })
|
|
49
49
|
})
|
|
50
50
|
})
|
|
51
51
|
}),
|
|
52
52
|
isLoading && /* @__PURE__ */ jsxs("div", {
|
|
53
|
-
className: "flex h-12 w-full items-center px-4 font-medium text-
|
|
53
|
+
className: "flex h-12 w-full items-center px-4 font-medium text-foreground text-sm",
|
|
54
54
|
children: [
|
|
55
55
|
/* @__PURE__ */ jsx(Sparkles, { className: "mr-2 size-4 shrink-0" }),
|
|
56
56
|
content.aiThinking,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AISelector.mjs","names":[
|
|
1
|
+
{"version":3,"file":"AISelector.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/AISelector.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n type ButtonColor,\n type ButtonVariant,\n} from '@components/Button';\nimport { Command, CommandRoot } from '@components/Command';\nimport { Loader } from '@components/Loader';\nimport { MarkdownRenderer } from '@components/MarkDownRender';\nimport { toast } from '@components/Toaster';\nimport { ArrowUp, Sparkles } from 'lucide-react';\nimport { useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { addAIHighlight, useEditor } from '../novel';\nimport { AICompletionCommands } from './AICompletionCommands';\nimport { AISelectorCommands } from './AISelectorCommands';\nimport { useAICompletion } from './useAICompletion';\n\nexport type AISelectorProps = {\n onOpenChange: (open: boolean) => void;\n /** Visual variant for the send button. @default \"default\" */\n sendButtonVariant?: ButtonVariant;\n /** Color theme for the send button. @default \"text\" */\n sendButtonColor?: ButtonColor;\n};\n\n/**\n * The AI command palette shown inside the bubble menu. Lets the user run a\n * preset transformation or a free-form prompt against the current selection,\n * preview the markdown result, then replace / insert / discard it.\n */\nexport const AISelector = ({\n onOpenChange,\n sendButtonVariant = 'default',\n sendButtonColor = 'text',\n}: AISelectorProps) => {\n const { editor } = useEditor();\n const [inputValue, setInputValue] = useState('');\n const content = useIntlayer('markdown-editor');\n\n const { completion, complete, isLoading, reset } = useAICompletion((error) =>\n toast({\n variant: 'error',\n title: content.aiError.value,\n description: error.message,\n })\n );\n\n if (!editor) return null;\n\n const hasCompletion = completion.length > 0;\n\n const getSelectionMarkdown = () => {\n const slice = editor.state.selection.content();\n return editor.storage.markdown.serializer.serialize(\n slice.content\n ) as string;\n };\n\n return (\n <CommandRoot className=\"w-[350px]\">\n {hasCompletion && (\n <div className=\"flex max-h-[400px]\">\n <div className=\"relative overflow-y-auto\">\n <div className=\"prose prose-sm dark:prose-invert p-2 px-4\">\n <MarkdownRenderer>{completion}</MarkdownRenderer>\n </div>\n </div>\n </div>\n )}\n\n {isLoading && (\n <div className=\"flex h-12 w-full items-center px-4 font-medium text-foreground text-sm\">\n <Sparkles className=\"mr-2 size-4 shrink-0\" />\n {content.aiThinking}\n <div className=\"mt-1 ml-2\">\n <Loader />\n </div>\n </div>\n )}\n\n {!isLoading && (\n <>\n <div className=\"flex items-center gap-2 px-3 py-3\">\n <Command.Input\n value={inputValue}\n onValueChange={setInputValue}\n autoFocus\n placeholder={\n hasCompletion ? content.tellAI.value : content.askAIToEdit.value\n }\n onFocus={() => addAIHighlight(editor)}\n />\n <Button\n label={content.send.value}\n size=\"icon-sm\"\n roundedSize=\"full\"\n variant={sendButtonVariant}\n color={sendButtonColor}\n className=\"size-7 shrink-0\"\n Icon={ArrowUp}\n onClick={() => {\n const text = completion || getSelectionMarkdown();\n complete(text, {\n body: { option: 'zap', command: inputValue },\n }).then(() => setInputValue(''));\n }}\n />\n </div>\n {hasCompletion ? (\n <AICompletionCommands\n completion={completion}\n onDiscard={() => {\n editor.chain().unsetHighlight().focus().run();\n reset();\n onOpenChange(false);\n }}\n />\n ) : (\n <AISelectorCommands\n onSelect={(value, option) =>\n complete(value, { body: { option } })\n }\n />\n )}\n </>\n )}\n </CommandRoot>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,cAAc,EACzB,cACA,oBAAoB,WACpB,kBAAkB,aACG;CACrB,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,CAAC,YAAY,iBAAiB,SAAS,EAAE;CAC/C,MAAM,UAAU,YAAY,iBAAiB;CAE7C,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU,iBAAiB,UAClE,MAAM;EACJ,SAAS;EACT,OAAO,QAAQ,QAAQ;EACvB,aAAa,MAAM;CACrB,CAAC,CACH;CAEA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,gBAAgB,WAAW,SAAS;CAE1C,MAAM,6BAA6B;EACjC,MAAM,QAAQ,OAAO,MAAM,UAAU,QAAQ;EAC7C,OAAO,OAAO,QAAQ,SAAS,WAAW,UACxC,MAAM,OACR;CACF;CAEA,OACE,qBAAC,aAAD;EAAa,WAAU;YAAvB;GACG,iBACC,oBAAC,OAAD;IAAK,WAAU;cACb,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,kBAAD,YAAmB,WAA6B;KAC7C;IACF;GACF;GAGN,aACC,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,UAAD,EAAU,WAAU,uBAAwB;KAC3C,QAAQ;KACT,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,QAAD,CAAS;KACN;IACF;;GAGN,CAAC,aACA,8CACE,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,QAAQ,OAAT;KACE,OAAO;KACP,eAAe;KACf;KACA,aACE,gBAAgB,QAAQ,OAAO,QAAQ,QAAQ,YAAY;KAE7D,eAAe,eAAe,MAAM;IACrC,IACD,oBAAC,QAAD;KACE,OAAO,QAAQ,KAAK;KACpB,MAAK;KACL,aAAY;KACZ,SAAS;KACT,OAAO;KACP,WAAU;KACV,MAAM;KACN,eAAe;MACb,MAAM,OAAO,cAAc,qBAAqB;MAChD,SAAS,MAAM,EACb,MAAM;OAAE,QAAQ;OAAO,SAAS;MAAW,EAC7C,CAAC,CAAC,CAAC,WAAW,cAAc,EAAE,CAAC;KACjC;IACD,EACE;OACJ,gBACC,oBAAC,sBAAD;IACc;IACZ,iBAAiB;KACf,OAAO,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI;KAC5C,MAAM;KACN,aAAa,KAAK;IACpB;GACD,KAED,oBAAC,oBAAD,EACE,WAAW,OAAO,WAChB,SAAS,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAEvC,EAEH;EAEO;;AAEjB"}
|
|
@@ -57,7 +57,7 @@ const AISelectorCommands = ({ onSelect }) => {
|
|
|
57
57
|
const slice = editor.state.selection.content();
|
|
58
58
|
onSelect(editor.storage.markdown.serializer.serialize(slice.content), value);
|
|
59
59
|
},
|
|
60
|
-
children: [/* @__PURE__ */ jsx(option.icon, { className: "size-4 text-
|
|
60
|
+
children: [/* @__PURE__ */ jsx(option.icon, { className: "size-4 text-foreground" }), getOptionLabel(option.value)]
|
|
61
61
|
}, option.value))
|
|
62
62
|
}),
|
|
63
63
|
/* @__PURE__ */ jsx(Command.Separator, {}),
|
|
@@ -70,7 +70,7 @@ const AISelectorCommands = ({ onSelect }) => {
|
|
|
70
70
|
const pos = editor.state.selection.from;
|
|
71
71
|
onSelect(getPrevText(editor, pos), "continue");
|
|
72
72
|
},
|
|
73
|
-
children: [/* @__PURE__ */ jsx(StepForward, { className: "size-4 text-
|
|
73
|
+
children: [/* @__PURE__ */ jsx(StepForward, { className: "size-4 text-foreground" }), content.continueWriting]
|
|
74
74
|
})
|
|
75
75
|
})
|
|
76
76
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AISelectorCommands.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/AISelectorCommands.tsx"],"sourcesContent":["'use client';\n\nimport { Command } from '@components/Command';\nimport {\n ArrowDownWideNarrow,\n CheckCheck,\n RefreshCcwDot,\n StepForward,\n WrapText,\n} from 'lucide-react';\nimport { useIntlayer } from 'react-intlayer';\nimport { getPrevText, useEditor } from '../novel';\nimport type { AICompletionOption } from './useAICompletion';\n\nconst options: {\n value: Exclude<AICompletionOption, 'zap'>;\n label: string;\n icon: typeof RefreshCcwDot;\n}[] = [\n { value: 'improve', label: 'Improve writing', icon: RefreshCcwDot },\n { value: 'fix', label: 'Fix grammar', icon: CheckCheck },\n { value: 'shorter', label: 'Make shorter', icon: ArrowDownWideNarrow },\n { value: 'longer', label: 'Make longer', icon: WrapText },\n];\n\nexport type AISelectorCommandsProps = {\n onSelect: (value: string, option: AICompletionOption) => void;\n};\n\n/**\n * The default command list shown in the AI bubble before a completion exists:\n * transforms applied to the current selection plus \"continue writing\".\n */\nexport const AISelectorCommands = ({ onSelect }: AISelectorCommandsProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n if (!editor) return null;\n\n const getOptionLabel = (value: string) => {\n switch (value) {\n case 'improve':\n return content.improveWriting.value;\n case 'fix':\n return content.fixGrammar.value;\n case 'shorter':\n return content.makeShorter.value;\n case 'longer':\n return content.makeLonger.value;\n default:\n return value;\n }\n };\n\n return (\n <>\n <Command.Group heading={content.editHeading.value}>\n {options.map((option) => (\n <Command.Item\n key={option.value}\n value={option.value}\n className=\"flex gap-2 px-4\"\n onSelect={(value) => {\n const slice = editor.state.selection.content();\n const text = editor.storage.markdown.serializer.serialize(\n slice.content\n );\n onSelect(text, value as AICompletionOption);\n }}\n >\n <option.icon className=\"size-4 text-
|
|
1
|
+
{"version":3,"file":"AISelectorCommands.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/AISelectorCommands.tsx"],"sourcesContent":["'use client';\n\nimport { Command } from '@components/Command';\nimport {\n ArrowDownWideNarrow,\n CheckCheck,\n RefreshCcwDot,\n StepForward,\n WrapText,\n} from 'lucide-react';\nimport { useIntlayer } from 'react-intlayer';\nimport { getPrevText, useEditor } from '../novel';\nimport type { AICompletionOption } from './useAICompletion';\n\nconst options: {\n value: Exclude<AICompletionOption, 'zap'>;\n label: string;\n icon: typeof RefreshCcwDot;\n}[] = [\n { value: 'improve', label: 'Improve writing', icon: RefreshCcwDot },\n { value: 'fix', label: 'Fix grammar', icon: CheckCheck },\n { value: 'shorter', label: 'Make shorter', icon: ArrowDownWideNarrow },\n { value: 'longer', label: 'Make longer', icon: WrapText },\n];\n\nexport type AISelectorCommandsProps = {\n onSelect: (value: string, option: AICompletionOption) => void;\n};\n\n/**\n * The default command list shown in the AI bubble before a completion exists:\n * transforms applied to the current selection plus \"continue writing\".\n */\nexport const AISelectorCommands = ({ onSelect }: AISelectorCommandsProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n if (!editor) return null;\n\n const getOptionLabel = (value: string) => {\n switch (value) {\n case 'improve':\n return content.improveWriting.value;\n case 'fix':\n return content.fixGrammar.value;\n case 'shorter':\n return content.makeShorter.value;\n case 'longer':\n return content.makeLonger.value;\n default:\n return value;\n }\n };\n\n return (\n <>\n <Command.Group heading={content.editHeading.value}>\n {options.map((option) => (\n <Command.Item\n key={option.value}\n value={option.value}\n className=\"flex gap-2 px-4\"\n onSelect={(value) => {\n const slice = editor.state.selection.content();\n const text = editor.storage.markdown.serializer.serialize(\n slice.content\n );\n onSelect(text, value as AICompletionOption);\n }}\n >\n <option.icon className=\"size-4 text-foreground\" />\n {getOptionLabel(option.value)}\n </Command.Item>\n ))}\n </Command.Group>\n <Command.Separator />\n <Command.Group heading={content.moreHeading.value}>\n <Command.Item\n value=\"continue\"\n className=\"gap-2 px-4\"\n onSelect={() => {\n const pos = editor.state.selection.from;\n const text = getPrevText(editor, pos);\n onSelect(text, 'continue');\n }}\n >\n <StepForward className=\"size-4 text-foreground\" />\n {content.continueWriting}\n </Command.Item>\n </Command.Group>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;AAcA,MAAM,UAIA;CACJ;EAAE,OAAO;EAAW,OAAO;EAAmB,MAAM;CAAc;CAClE;EAAE,OAAO;EAAO,OAAO;EAAe,MAAM;CAAW;CACvD;EAAE,OAAO;EAAW,OAAO;EAAgB,MAAM;CAAoB;CACrE;EAAE,OAAO;EAAU,OAAO;EAAe,MAAM;CAAS;AAC1D;;;;;AAUA,MAAa,sBAAsB,EAAE,eAAwC;CAC3E,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,YAAY,iBAAiB;CAE7C,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,kBAAkB,UAAkB;EACxC,QAAQ,OAAR;GACE,KAAK,WACH,OAAO,QAAQ,eAAe;GAChC,KAAK,OACH,OAAO,QAAQ,WAAW;GAC5B,KAAK,WACH,OAAO,QAAQ,YAAY;GAC7B,KAAK,UACH,OAAO,QAAQ,WAAW;GAC5B,SACE,OAAO;EACX;CACF;CAEA,OACE;EACE,oBAAC,QAAQ,OAAT;GAAe,SAAS,QAAQ,YAAY;aACzC,QAAQ,KAAK,WACZ,qBAAC,QAAQ,MAAT;IAEE,OAAO,OAAO;IACd,WAAU;IACV,WAAW,UAAU;KACnB,MAAM,QAAQ,OAAO,MAAM,UAAU,QAAQ;KAI7C,SAHa,OAAO,QAAQ,SAAS,WAAW,UAC9C,MAAM,OAEI,GAAG,KAA2B;IAC5C;cAVF,CAYE,oBAAC,OAAO,MAAR,EAAa,WAAU,yBAA0B,IAChD,eAAe,OAAO,KAAK,CAChB;MAbP,OAAO,KAaA,CACf;EACY;EACf,oBAAC,QAAQ,WAAT,CAAoB;EACpB,oBAAC,QAAQ,OAAT;GAAe,SAAS,QAAQ,YAAY;aAC1C,qBAAC,QAAQ,MAAT;IACE,OAAM;IACN,WAAU;IACV,gBAAgB;KACd,MAAM,MAAM,OAAO,MAAM,UAAU;KAEnC,SADa,YAAY,QAAQ,GACrB,GAAG,UAAU;IAC3B;cAPF,CASE,oBAAC,aAAD,EAAa,WAAU,yBAA0B,IAChD,QAAQ,eACG;;EACD;CACf;AAEN"}
|
|
@@ -23,7 +23,7 @@ const GenerativeMenuSwitch = ({ children, open, onOpenChange, sendButtonVariant,
|
|
|
23
23
|
}, [open, editor]);
|
|
24
24
|
return /* @__PURE__ */ jsxs(EditorBubble, {
|
|
25
25
|
options: { placement: open ? "bottom-start" : "top" },
|
|
26
|
-
className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px]
|
|
26
|
+
className: "flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl",
|
|
27
27
|
children: [open && /* @__PURE__ */ jsx(AISelector, {
|
|
28
28
|
onOpenChange,
|
|
29
29
|
sendButtonVariant,
|
|
@@ -34,7 +34,7 @@ const GenerativeMenuSwitch = ({ children, open, onOpenChange, sendButtonVariant,
|
|
|
34
34
|
color: "text",
|
|
35
35
|
size: "sm",
|
|
36
36
|
roundedSize: "none",
|
|
37
|
-
className: "gap-1 text-
|
|
37
|
+
className: "gap-1 text-foreground",
|
|
38
38
|
Icon: Sparkles,
|
|
39
39
|
onClick: () => onOpenChange(true),
|
|
40
40
|
children: content.askAI
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenerativeMenuSwitch.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/GenerativeMenuSwitch.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n type ButtonColor,\n type ButtonVariant,\n} from '@components/Button';\nimport { Sparkles } from 'lucide-react';\nimport { Fragment, type ReactNode, useEffect } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { EditorBubble, removeAIHighlight, useEditor } from '../novel';\nimport { AISelector } from './AISelector';\n\nexport type GenerativeMenuSwitchProps = {\n children: ReactNode;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Visual variant forwarded to the AI send button. @default \"default\" */\n sendButtonVariant?: ButtonVariant;\n /** Color theme forwarded to the AI send button. @default \"text\" */\n sendButtonColor?: ButtonColor;\n};\n\n/**\n * Bubble menu that toggles between the regular formatting toolbar (its\n * `children`) and the AI command palette.\n */\nexport const GenerativeMenuSwitch = ({\n children,\n open,\n onOpenChange,\n sendButtonVariant,\n sendButtonColor,\n}: GenerativeMenuSwitchProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n useEffect(() => {\n if (!open && editor) removeAIHighlight(editor);\n }, [open, editor]);\n\n return (\n <EditorBubble\n options={{ placement: open ? 'bottom-start' : 'top' }}\n className=\"flex w-fit max-w-[90vw] rounded-lg border-[1.3px]
|
|
1
|
+
{"version":3,"file":"GenerativeMenuSwitch.mjs","names":[],"sources":["../../../../../src/components/MarkdownEditor/generative/GenerativeMenuSwitch.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n type ButtonColor,\n type ButtonVariant,\n} from '@components/Button';\nimport { Sparkles } from 'lucide-react';\nimport { Fragment, type ReactNode, useEffect } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { EditorBubble, removeAIHighlight, useEditor } from '../novel';\nimport { AISelector } from './AISelector';\n\nexport type GenerativeMenuSwitchProps = {\n children: ReactNode;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** Visual variant forwarded to the AI send button. @default \"default\" */\n sendButtonVariant?: ButtonVariant;\n /** Color theme forwarded to the AI send button. @default \"text\" */\n sendButtonColor?: ButtonColor;\n};\n\n/**\n * Bubble menu that toggles between the regular formatting toolbar (its\n * `children`) and the AI command palette.\n */\nexport const GenerativeMenuSwitch = ({\n children,\n open,\n onOpenChange,\n sendButtonVariant,\n sendButtonColor,\n}: GenerativeMenuSwitchProps) => {\n const { editor } = useEditor();\n const content = useIntlayer('markdown-editor');\n\n useEffect(() => {\n if (!open && editor) removeAIHighlight(editor);\n }, [open, editor]);\n\n return (\n <EditorBubble\n options={{ placement: open ? 'bottom-start' : 'top' }}\n className=\"flex w-fit max-w-[90vw] rounded-lg border-[1.3px] bg-card/95 text-foreground shadow-xl backdrop-blur [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl\"\n >\n {open && (\n <AISelector\n onOpenChange={onOpenChange}\n sendButtonVariant={sendButtonVariant}\n sendButtonColor={sendButtonColor}\n />\n )}\n {!open && (\n <Fragment>\n <Button\n label={content.askAI.value}\n variant=\"hoverable\"\n color=\"text\"\n size=\"sm\"\n roundedSize=\"none\"\n className=\"gap-1 text-foreground\"\n Icon={Sparkles}\n onClick={() => onOpenChange(true)}\n >\n {content.askAI}\n </Button>\n {children}\n </Fragment>\n )}\n </EditorBubble>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,MAAa,wBAAwB,EACnC,UACA,MACA,cACA,mBACA,sBAC+B;CAC/B,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,UAAU,YAAY,iBAAiB;CAE7C,gBAAgB;EACd,IAAI,CAAC,QAAQ,QAAQ,kBAAkB,MAAM;CAC/C,GAAG,CAAC,MAAM,MAAM,CAAC;CAEjB,OACE,qBAAC,cAAD;EACE,SAAS,EAAE,WAAW,OAAO,iBAAiB,MAAM;EACpD,WAAU;YAFZ,CAIG,QACC,oBAAC,YAAD;GACgB;GACK;GACF;EAClB,IAEF,CAAC,QACA,qBAAC,UAAD,aACE,oBAAC,QAAD;GACE,OAAO,QAAQ,MAAM;GACrB,SAAQ;GACR,OAAM;GACN,MAAK;GACL,aAAY;GACZ,WAAU;GACV,MAAM;GACN,eAAe,aAAa,IAAI;aAE/B,QAAQ;EACH,IACP,QACO,IAEA;;AAElB"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../utils/cn.mjs";
|
|
4
|
-
import { Button } from "../Button/Button.mjs";
|
|
5
|
-
import { Container } from "../Container/index.mjs";
|
|
6
4
|
import { useGetElementOrWindow } from "../../hooks/useGetElementOrWindow.mjs";
|
|
7
5
|
import { useScrollBlockage } from "../../hooks/useScrollBlockage/index.mjs";
|
|
6
|
+
import { Button } from "../Button/Button.mjs";
|
|
7
|
+
import { Container } from "../Container/index.mjs";
|
|
8
8
|
import { H3 } from "../Headers/index.mjs";
|
|
9
9
|
import { useEffect } from "react";
|
|
10
10
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -155,7 +155,6 @@ const Modal = ({ children, isOpen, container, onClose, hasCloseButton = false, t
|
|
|
155
155
|
}),
|
|
156
156
|
role: "dialog",
|
|
157
157
|
"aria-modal": true,
|
|
158
|
-
roundedSize: "4xl",
|
|
159
158
|
padding: "none",
|
|
160
159
|
...props,
|
|
161
160
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.mjs","names":["m"],"sources":["../../../../src/components/Modal/Modal.tsx"],"sourcesContent":["'use client';\n\nimport { useGetElementOrWindow, useScrollBlockage } from '@hooks/index';\nimport { cn } from '@utils/cn';\nimport { cva } from 'class-variance-authority';\nimport { motion as m } from 'framer-motion';\nimport { X } from 'lucide-react';\nimport { type FC, type ReactNode, useEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Button } from '../Button';\nimport { Container, type ContainerProps } from '../Container';\nimport { H3 } from '../Headers';\n\n/**\n * Enumeration of available modal sizes\n */\nexport type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'unset';\n\ntype ModalProps = {\n children: ReactNode;\n isOpen: boolean;\n onClose?: () => void;\n container?: HTMLElement;\n disableScroll?: boolean;\n hasCloseButton?: boolean;\n title?: ReactNode;\n size?: ModalSize | `${ModalSize}`;\n /**\n * Defines if the modal content area is scrollable.\n */\n isScrollable?: boolean | 'x' | 'y';\n} & Partial<\n Pick<\n ContainerProps,\n | 'className'\n | 'transparency'\n | 'border'\n | 'background'\n | 'roundedSize'\n | 'borderColor'\n | 'padding'\n | 'separator'\n | 'gap'\n >\n>;\n\nconst modalVariants = cva(\n 'flex cursor-default flex-col overflow-hidden shadow-sm',\n {\n variants: {\n size: {\n sm: 'h-auto max-h-[30vh] w-[95vw] max-w-xl',\n md: 'h-auto max-h-[50vh] w-[95vw] max-w-xl',\n lg: 'h-auto max-h-[70vh] w-[95vw] max-w-4xl',\n xl: 'h-auto max-h-[95vh] w-[95vw] max-w-6xl',\n unset: 'h-auto max-h-[95vh] w-[95vw]',\n },\n },\n defaultVariants: {\n size: 'unset',\n },\n }\n);\n\n// Mapped from Container/index.tsx to apply internally\nconst contentPaddingVariants = {\n none: 'p-0',\n sm: 'px-2 py-4',\n md: 'px-4 py-6',\n lg: 'px-6 py-8',\n xl: 'px-8 py-10',\n '2xl': 'px-10 py-12',\n};\n\nconst MotionModal = m.create(Container);\n\n/**\n * Modal Component\n *\n * A highly customizable modal dialog component with portal rendering, Framer Motion animations,\n * and comprehensive accessibility features. Supports multiple size variants and scroll management.\n *\n * Features:\n * - Portal rendering to any container element (defaults to document.body)\n * - Smooth animations with Framer Motion\n * - Size variants: SM, MD, LG, XL, UNSET with responsive sizing\n * - Optional title and close button\n * - Background scroll prevention\n * - Click-outside-to-close functionality\n * - Full accessibility support with ARIA attributes\n * - Keyboard navigation support (ESC to close)\n * - Extensible styling with Container props\n *\n * @example\n * Basic usage:\n * ```jsx\n * <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>\n * <p>Modal content goes here</p>\n * </Modal>\n * ```\n *\n * @example\n * With title and close button:\n * ```jsx\n * <Modal\n * isOpen={isOpen}\n * onClose={onClose}\n * title=\"Confirm Action\"\n * hasCloseButton\n * size=\"lg\"\n * >\n * <div>\n * <p>Are you sure you want to continue?</p>\n * <Button onClick={onConfirm}>Confirm</Button>\n * </div>\n * </Modal>\n * ```\n *\n * @example\n * Custom container and styling:\n * ```jsx\n * <Modal\n * isOpen={isOpen}\n * onClose={onClose}\n * container={customContainer}\n * background=\"card\"\n * padding=\"lg\"\n * border=\"default\"\n * >\n * Content with custom styling\n * </Modal>\n * ```\n *\n * Accessibility Notes:\n * - Modal receives focus when opened\n * - Background content is hidden from screen readers when modal is open\n * - ESC key closes modal (handled by browser for role=\"dialog\")\n * - Click outside modal closes it\n * - Close button has descriptive label for screen readers\n *\n * @param props - Modal component props\n * @returns JSX element rendered via createPortal\n */\nexport const Modal: FC<ModalProps> = ({\n children,\n isOpen,\n container,\n onClose,\n hasCloseButton = false,\n title,\n size = 'md',\n className,\n isScrollable = false, // Enable the scroll of the content\n disableScroll = true, // Disable the scroll of the background\n padding = 'none', // Extract padding here\n ...props\n}) => {\n const containerElement = useGetElementOrWindow(container);\n\n useScrollBlockage({ key: 'modal', disableScroll: isOpen && disableScroll });\n\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && isOpen && onClose) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleEscape);\n return () => {\n document.removeEventListener('keydown', handleEscape);\n };\n }, [isOpen, onClose]);\n\n if (!containerElement) return <></>;\n\n const hasTitle = Boolean(title);\n\n // Determine the class for the inner content based on the padding prop\n const contentPaddingClass =\n contentPaddingVariants[\n (padding as keyof typeof contentPaddingVariants) || 'none'\n ];\n\n return createPortal(\n <m.div\n className=\"invisible fixed top-0 left-0 z-50 flex size-full cursor-pointer items-center justify-center overflow-hidden bg-background/40 backdrop-blur\"\n animate={isOpen ? 'visible' : 'invisible'}\n variants={{\n visible: {\n opacity: 1,\n visibility: 'visible',\n transition: { duration: 0.1, when: 'beforeChildren' },\n },\n invisible: {\n opacity: 0,\n visibility: 'hidden',\n transition: { duration: 0.1, when: 'afterChildren' },\n },\n }}\n onClick={(e) => {\n e.stopPropagation();\n onClose?.();\n }}\n aria-hidden={!isOpen}\n >\n <MotionModal\n onClick={(e) => e.stopPropagation()}\n initial={{ scale: isOpen ? 0.5 : 1 }}\n animate={{ scale: isOpen ? 1 : 0.5 }}\n transition={{ duration: 0.3 }}\n className={modalVariants({ size, className })}\n role=\"dialog\"\n aria-modal\n
|
|
1
|
+
{"version":3,"file":"Modal.mjs","names":["m"],"sources":["../../../../src/components/Modal/Modal.tsx"],"sourcesContent":["'use client';\n\nimport { useGetElementOrWindow, useScrollBlockage } from '@hooks/index';\nimport { cn } from '@utils/cn';\nimport { cva } from 'class-variance-authority';\nimport { motion as m } from 'framer-motion';\nimport { X } from 'lucide-react';\nimport { type FC, type ReactNode, useEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Button } from '../Button';\nimport { Container, type ContainerProps } from '../Container';\nimport { H3 } from '../Headers';\n\n/**\n * Enumeration of available modal sizes\n */\nexport type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'unset';\n\ntype ModalProps = {\n children: ReactNode;\n isOpen: boolean;\n onClose?: () => void;\n container?: HTMLElement;\n disableScroll?: boolean;\n hasCloseButton?: boolean;\n title?: ReactNode;\n size?: ModalSize | `${ModalSize}`;\n /**\n * Defines if the modal content area is scrollable.\n */\n isScrollable?: boolean | 'x' | 'y';\n} & Partial<\n Pick<\n ContainerProps,\n | 'className'\n | 'transparency'\n | 'border'\n | 'background'\n | 'roundedSize'\n | 'borderColor'\n | 'padding'\n | 'separator'\n | 'gap'\n >\n>;\n\nconst modalVariants = cva(\n 'flex cursor-default flex-col overflow-hidden shadow-sm',\n {\n variants: {\n size: {\n sm: 'h-auto max-h-[30vh] w-[95vw] max-w-xl',\n md: 'h-auto max-h-[50vh] w-[95vw] max-w-xl',\n lg: 'h-auto max-h-[70vh] w-[95vw] max-w-4xl',\n xl: 'h-auto max-h-[95vh] w-[95vw] max-w-6xl',\n unset: 'h-auto max-h-[95vh] w-[95vw]',\n },\n },\n defaultVariants: {\n size: 'unset',\n },\n }\n);\n\n// Mapped from Container/index.tsx to apply internally\nconst contentPaddingVariants = {\n none: 'p-0',\n sm: 'px-2 py-4',\n md: 'px-4 py-6',\n lg: 'px-6 py-8',\n xl: 'px-8 py-10',\n '2xl': 'px-10 py-12',\n};\n\nconst MotionModal = m.create(Container);\n\n/**\n * Modal Component\n *\n * A highly customizable modal dialog component with portal rendering, Framer Motion animations,\n * and comprehensive accessibility features. Supports multiple size variants and scroll management.\n *\n * Features:\n * - Portal rendering to any container element (defaults to document.body)\n * - Smooth animations with Framer Motion\n * - Size variants: SM, MD, LG, XL, UNSET with responsive sizing\n * - Optional title and close button\n * - Background scroll prevention\n * - Click-outside-to-close functionality\n * - Full accessibility support with ARIA attributes\n * - Keyboard navigation support (ESC to close)\n * - Extensible styling with Container props\n *\n * @example\n * Basic usage:\n * ```jsx\n * <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>\n * <p>Modal content goes here</p>\n * </Modal>\n * ```\n *\n * @example\n * With title and close button:\n * ```jsx\n * <Modal\n * isOpen={isOpen}\n * onClose={onClose}\n * title=\"Confirm Action\"\n * hasCloseButton\n * size=\"lg\"\n * >\n * <div>\n * <p>Are you sure you want to continue?</p>\n * <Button onClick={onConfirm}>Confirm</Button>\n * </div>\n * </Modal>\n * ```\n *\n * @example\n * Custom container and styling:\n * ```jsx\n * <Modal\n * isOpen={isOpen}\n * onClose={onClose}\n * container={customContainer}\n * background=\"card\"\n * padding=\"lg\"\n * border=\"default\"\n * >\n * Content with custom styling\n * </Modal>\n * ```\n *\n * Accessibility Notes:\n * - Modal receives focus when opened\n * - Background content is hidden from screen readers when modal is open\n * - ESC key closes modal (handled by browser for role=\"dialog\")\n * - Click outside modal closes it\n * - Close button has descriptive label for screen readers\n *\n * @param props - Modal component props\n * @returns JSX element rendered via createPortal\n */\nexport const Modal: FC<ModalProps> = ({\n children,\n isOpen,\n container,\n onClose,\n hasCloseButton = false,\n title,\n size = 'md',\n className,\n isScrollable = false, // Enable the scroll of the content\n disableScroll = true, // Disable the scroll of the background\n padding = 'none', // Extract padding here\n ...props\n}) => {\n const containerElement = useGetElementOrWindow(container);\n\n useScrollBlockage({ key: 'modal', disableScroll: isOpen && disableScroll });\n\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape' && isOpen && onClose) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleEscape);\n return () => {\n document.removeEventListener('keydown', handleEscape);\n };\n }, [isOpen, onClose]);\n\n if (!containerElement) return <></>;\n\n const hasTitle = Boolean(title);\n\n // Determine the class for the inner content based on the padding prop\n const contentPaddingClass =\n contentPaddingVariants[\n (padding as keyof typeof contentPaddingVariants) || 'none'\n ];\n\n return createPortal(\n <m.div\n className=\"invisible fixed top-0 left-0 z-50 flex size-full cursor-pointer items-center justify-center overflow-hidden bg-background/40 backdrop-blur\"\n animate={isOpen ? 'visible' : 'invisible'}\n variants={{\n visible: {\n opacity: 1,\n visibility: 'visible',\n transition: { duration: 0.1, when: 'beforeChildren' },\n },\n invisible: {\n opacity: 0,\n visibility: 'hidden',\n transition: { duration: 0.1, when: 'afterChildren' },\n },\n }}\n onClick={(e) => {\n e.stopPropagation();\n onClose?.();\n }}\n aria-hidden={!isOpen}\n >\n <MotionModal\n onClick={(e) => e.stopPropagation()}\n initial={{ scale: isOpen ? 0.5 : 1 }}\n animate={{ scale: isOpen ? 1 : 0.5 }}\n transition={{ duration: 0.3 }}\n className={modalVariants({ size, className })}\n role=\"dialog\"\n aria-modal\n // Force the outer container to have no padding so scrollbars hit the edge\n padding=\"none\"\n {...props}\n >\n {/* HEADER SECTION */}\n <div\n className={cn(\n 'relative flex-none px-4 pt-4',\n hasCloseButton && hasTitle\n ? `flex items-start`\n : hasCloseButton\n ? `flex justify-end`\n : hasTitle\n ? `items-center`\n : `hidden`\n )}\n >\n {hasTitle && (\n <H3 className=\"mb-2 ml-1 flex items-center justify-center font-bold text-lg\">\n {title}\n </H3>\n )}\n {hasCloseButton && (\n <Button\n variant=\"hoverable\"\n color=\"text\"\n label=\"Close modal\"\n className=\"ml-auto\"\n onClick={(e) => {\n e.stopPropagation();\n onClose?.();\n }}\n Icon={X}\n size=\"icon-md\"\n />\n )}\n </div>\n\n {/* SCROLLABLE WRAPPER - Full width, no padding */}\n <div\n className={cn(\n 'flex min-h-0 w-full flex-1 flex-col',\n // Scrollbars will now appear at the very edge of this div (the modal edge)\n isScrollable === true && 'overflow-auto',\n isScrollable === 'y' && 'overflow-y-auto overflow-x-hidden',\n isScrollable === 'x' && 'overflow-x-auto overflow-y-hidden',\n !isScrollable && 'overflow-visible'\n )}\n >\n {/* CONTENT PADDING WRAPPER */}\n {/* We apply the padding class here, effectively putting content inside the scroll area */}\n <div\n className={cn(\n 'flex size-full flex-1 flex-col',\n contentPaddingClass\n )}\n >\n {children}\n </div>\n </div>\n </MotionModal>\n </m.div>,\n containerElement\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,MAAM,gBAAgB,IACpB,0DACA;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,OAAO;CACT,EACF;CACA,iBAAiB,EACf,MAAM,QACR;AACF,CACF;AAGA,MAAM,yBAAyB;CAC7B,MAAM;CACN,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;AACT;AAEA,MAAM,cAAcA,OAAE,OAAO,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEtC,MAAa,SAAyB,EACpC,UACA,QACA,WACA,SACA,iBAAiB,OACjB,OACA,OAAO,MACP,WACA,eAAe,OACf,gBAAgB,MAChB,UAAU,QACV,GAAG,YACC;CACJ,MAAM,mBAAmB,sBAAsB,SAAS;CAExD,kBAAkB;EAAE,KAAK;EAAS,eAAe,UAAU;CAAc,CAAC;CAE1E,gBAAgB;EACd,MAAM,gBAAgB,UAAyB;GAC7C,IAAI,MAAM,QAAQ,YAAY,UAAU,SACtC,QAAQ;EAEZ;EACA,SAAS,iBAAiB,WAAW,YAAY;EACjD,aAAa;GACX,SAAS,oBAAoB,WAAW,YAAY;EACtD;CACF,GAAG,CAAC,QAAQ,OAAO,CAAC;CAEpB,IAAI,CAAC,kBAAkB,OAAO,iCAAI;CAElC,MAAM,WAAW,QAAQ,KAAK;CAG9B,MAAM,sBACJ,uBACG,WAAmD;CAGxD,OAAO,aACL,oBAACA,OAAE,KAAH;EACE,WAAU;EACV,SAAS,SAAS,YAAY;EAC9B,UAAU;GACR,SAAS;IACP,SAAS;IACT,YAAY;IACZ,YAAY;KAAE,UAAU;KAAK,MAAM;IAAiB;GACtD;GACA,WAAW;IACT,SAAS;IACT,YAAY;IACZ,YAAY;KAAE,UAAU;KAAK,MAAM;IAAgB;GACrD;EACF;EACA,UAAU,MAAM;GACd,EAAE,gBAAgB;GAClB,UAAU;EACZ;EACA,eAAa,CAAC;YAEd,qBAAC,aAAD;GACE,UAAU,MAAM,EAAE,gBAAgB;GAClC,SAAS,EAAE,OAAO,SAAS,KAAM,EAAE;GACnC,SAAS,EAAE,OAAO,SAAS,IAAI,GAAI;GACnC,YAAY,EAAE,UAAU,GAAI;GAC5B,WAAW,cAAc;IAAE;IAAM;GAAU,CAAC;GAC5C,MAAK;GACL;GAEA,SAAQ;GACR,GAAI;aAVN,CAaE,qBAAC,OAAD;IACE,WAAW,GACT,gCACA,kBAAkB,WACd,qBACA,iBACE,qBACA,WACE,iBACA,QACV;cAVF,CAYG,YACC,oBAAC,IAAD;KAAI,WAAU;eACX;IACC,IAEL,kBACC,oBAAC,QAAD;KACE,SAAQ;KACR,OAAM;KACN,OAAM;KACN,WAAU;KACV,UAAU,MAAM;MACd,EAAE,gBAAgB;MAClB,UAAU;KACZ;KACA,MAAM;KACN,MAAK;IACN,EAEA;OAGL,oBAAC,OAAD;IACE,WAAW,GACT,uCAEA,iBAAiB,QAAQ,iBACzB,iBAAiB,OAAO,qCACxB,iBAAiB,OAAO,qCACxB,CAAC,gBAAgB,kBACnB;cAIA,oBAAC,OAAD;KACE,WAAW,GACT,kCACA,mBACF;KAEC;IACE;GACF,EACM;;CACR,IACP,gBACF;AACF"}
|
|
@@ -75,15 +75,15 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
75
75
|
* @returns Horizontal desktop navigation JSX element
|
|
76
76
|
*/
|
|
77
77
|
const DesktopNavbar = ({ logo, sections, rightItems, selectedChoice }) => /* @__PURE__ */ jsxs("nav", {
|
|
78
|
-
className: "sticky top-0 z-50 flex w-full items-center
|
|
78
|
+
className: "sticky top-0 z-50 flex w-full items-center border-neutral border-b px-4 py-3 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur-2xl",
|
|
79
79
|
children: [
|
|
80
80
|
logo,
|
|
81
81
|
/* @__PURE__ */ jsx(TabSelector, {
|
|
82
82
|
selectedChoice,
|
|
83
|
-
className: "ml-[2vw] h-auto gap-3 overflow-x-auto text-
|
|
83
|
+
className: "ml-[2vw] h-auto gap-3 overflow-x-auto text-muted-foreground tracking-wide lg:ml-[5vw] lg:gap-3 xl:ml-[10vw] xl:gap-6",
|
|
84
84
|
tabs: sections,
|
|
85
85
|
hoverable: true,
|
|
86
|
-
color: "
|
|
86
|
+
color: "light"
|
|
87
87
|
}),
|
|
88
88
|
/* @__PURE__ */ jsx("div", {
|
|
89
89
|
className: "mr-4 flex items-center justify-end gap-2 md:gap-4",
|