@intlayer/design-system 8.9.5 → 8.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +17 -9
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/SafeHtmlRenderer.mjs +20 -0
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/SafeHtmlRenderer.mjs.map +1 -0
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +83 -49
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +4 -4
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +57 -35
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +20 -8
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +3 -3
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +22 -52
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs.map +1 -1
- package/dist/esm/components/DropDown/index.mjs +2 -2
- package/dist/esm/components/DropDown/index.mjs.map +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +1 -1
- package/dist/esm/components/IDE/CodeFormatSelector.mjs +1 -1
- package/dist/esm/components/IDE/ContentDeclarationFormatSelector.mjs +1 -1
- package/dist/esm/components/IDE/PackageManagerSelector.mjs +1 -1
- package/dist/esm/components/LanguageBackground/index.mjs +4 -4
- package/dist/esm/components/LanguageBackground/index.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +2 -2
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/index.mjs +2 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +3 -3
- package/dist/esm/components/Tab/Tab.mjs +1 -1
- package/dist/esm/components/Table/SmartTable.mjs +1 -1
- package/dist/esm/components/index.mjs +13 -12
- package/dist/esm/hooks/index.mjs +11 -11
- package/dist/esm/hooks/reactQuery.mjs +17 -1
- package/dist/esm/hooks/reactQuery.mjs.map +1 -1
- package/dist/esm/hooks/useAuth/useOAuth2.mjs +1 -1
- package/dist/esm/hooks/useAuth/useSession.mjs +1 -1
- package/dist/types/components/Badge/index.d.ts +3 -3
- package/dist/types/components/Button/Button.d.ts +4 -4
- package/dist/types/components/CollapsibleTable/CollapsibleTable.d.ts +1 -1
- package/dist/types/components/Command/index.d.ts +2 -2
- package/dist/types/components/Container/index.d.ts +7 -7
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/SafeHtmlRenderer.d.ts +11 -0
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/SafeHtmlRenderer.d.ts.map +1 -0
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts +4 -3
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/StructureView/StructureView.d.ts.map +1 -1
- package/dist/types/components/DropDown/index.d.ts +6 -0
- package/dist/types/components/DropDown/index.d.ts.map +1 -1
- package/dist/types/components/Input/Checkbox.d.ts +3 -3
- package/dist/types/components/Input/Input.d.ts +1 -1
- package/dist/types/components/LanguageBackground/index.d.ts.map +1 -1
- package/dist/types/components/Link/Link.d.ts +4 -4
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts +1 -1
- package/dist/types/components/MarkDownRender/index.d.ts +2 -1
- package/dist/types/components/Pagination/Pagination.d.ts +1 -1
- package/dist/types/components/SwitchSelector/SwitchSelector.d.ts +1 -1
- package/dist/types/components/SwitchSelector/VerticalSwitchSelector.d.ts +1 -1
- package/dist/types/components/TabSelector/TabSelector.d.ts +1 -1
- package/dist/types/components/Tag/index.d.ts +3 -3
- package/dist/types/components/index.d.ts +2 -1
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/reactQuery.d.ts +3 -1
- package/dist/types/hooks/reactQuery.d.ts.map +1 -1
- package/package.json +22 -19
|
@@ -4,17 +4,17 @@ import { Container } from "../Container/index.mjs";
|
|
|
4
4
|
import { Button, ButtonColor, ButtonVariant } from "../Button/Button.mjs";
|
|
5
5
|
import { CopyToClipboard } from "../CopyToClipboard/index.mjs";
|
|
6
6
|
import { LocaleSwitcherContentProvider } from "../LocaleSwitcherContentDropDown/LocaleSwitcherContentContext.mjs";
|
|
7
|
-
import { Tab } from "../Tab/Tab.mjs";
|
|
8
7
|
import { ContentEditor } from "./ContentEditor.mjs";
|
|
8
|
+
import { TabSelector, TabSelectorColor } from "../TabSelector/TabSelector.mjs";
|
|
9
9
|
import { DictionaryDetailsForm } from "./DictionaryDetails/DictionaryDetailsForm.mjs";
|
|
10
10
|
import { JSONEditor } from "./JSONEditor.mjs";
|
|
11
11
|
import { SaveForm } from "./SaveForm/SaveForm.mjs";
|
|
12
12
|
import { StructureEditor } from "./StructureEditor.mjs";
|
|
13
|
-
import { useEffect } from "react";
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
14
14
|
import { ArrowLeft } from "lucide-react";
|
|
15
15
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
-
import { useConfiguration, useDictionariesRecordActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
|
|
17
16
|
import { useIntlayer } from "react-intlayer";
|
|
17
|
+
import { useConfiguration, useDictionariesRecordActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
|
|
18
18
|
|
|
19
19
|
//#region src/components/DictionaryFieldEditor/DictionaryFieldEditor.tsx
|
|
20
20
|
const DictionaryFieldEditor = ({ dictionary, onClickDictionaryList, isDarkMode, mode, onDelete, onSave, showReturnButton = true }) => {
|
|
@@ -22,6 +22,7 @@ const DictionaryFieldEditor = ({ dictionary, onClickDictionaryList, isDarkMode,
|
|
|
22
22
|
const { returnToDictionaryList } = useIntlayer("dictionary-field-editor");
|
|
23
23
|
const { focusedContent, setFocusedContent } = useFocusUnmergedDictionary();
|
|
24
24
|
const { setLocaleDictionary } = useDictionariesRecordActions();
|
|
25
|
+
const [activeTab, setActiveTab] = useState("content");
|
|
25
26
|
useEffect(() => {
|
|
26
27
|
setFocusedContent({
|
|
27
28
|
...focusedContent ?? {},
|
|
@@ -44,45 +45,66 @@ const DictionaryFieldEditor = ({ dictionary, onClickDictionaryList, isDarkMode,
|
|
|
44
45
|
label: returnToDictionaryList.label.value,
|
|
45
46
|
children: returnToDictionaryList.text
|
|
46
47
|
}),
|
|
47
|
-
/* @__PURE__ */
|
|
48
|
-
className: "mb-22 min-h-0 flex-1",
|
|
49
|
-
children: /* @__PURE__ */
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
/* @__PURE__ */ jsxs("div", {
|
|
49
|
+
className: "mb-22 flex min-h-0 flex-1 flex-col overflow-hidden",
|
|
50
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
51
|
+
className: "sticky top-0 z-10 flex shrink-0 gap-3 rounded-xl bg-background/20 p-3 pb-4",
|
|
52
|
+
children: /* @__PURE__ */ jsx(TabSelector, {
|
|
53
|
+
selectedChoice: activeTab,
|
|
54
|
+
tabs: [
|
|
55
|
+
...mode.includes("remote") ? [/* @__PURE__ */ jsx("button", {
|
|
56
|
+
className: "cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none",
|
|
57
|
+
"data-active": activeTab === "details",
|
|
58
|
+
onClick: () => setActiveTab("details"),
|
|
59
|
+
type: "button",
|
|
60
|
+
children: "Details"
|
|
61
|
+
}, "details")] : [],
|
|
62
|
+
/* @__PURE__ */ jsx("button", {
|
|
63
|
+
className: "cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none",
|
|
64
|
+
"data-active": activeTab === "structure",
|
|
65
|
+
onClick: () => setActiveTab("structure"),
|
|
66
|
+
type: "button",
|
|
67
|
+
children: "Structure"
|
|
68
|
+
}, "structure"),
|
|
69
|
+
/* @__PURE__ */ jsx("button", {
|
|
70
|
+
className: "cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none",
|
|
71
|
+
"data-active": activeTab === "content",
|
|
72
|
+
onClick: () => setActiveTab("content"),
|
|
73
|
+
type: "button",
|
|
74
|
+
children: "Content"
|
|
75
|
+
}, "content"),
|
|
76
|
+
/* @__PURE__ */ jsx("button", {
|
|
77
|
+
className: "cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none",
|
|
78
|
+
"data-active": activeTab === "json",
|
|
79
|
+
onClick: () => setActiveTab("json"),
|
|
80
|
+
type: "button",
|
|
81
|
+
children: "JSON"
|
|
82
|
+
}, "json")
|
|
83
|
+
],
|
|
84
|
+
hoverable: true,
|
|
85
|
+
color: "text"
|
|
86
|
+
})
|
|
87
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
88
|
+
className: "min-h-0 flex-1 overflow-y-auto p-6",
|
|
89
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
90
|
+
className: "flex w-full min-w-0 flex-col items-stretch gap-6",
|
|
91
|
+
children: [
|
|
92
|
+
mode.includes("remote") && activeTab === "details" && /* @__PURE__ */ jsx(DictionaryDetailsForm, {
|
|
59
93
|
dictionary,
|
|
60
94
|
mode
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
label: "Structure",
|
|
65
|
-
value: "structure",
|
|
66
|
-
children: /* @__PURE__ */ jsx(StructureEditor, { dictionary })
|
|
67
|
-
}),
|
|
68
|
-
/* @__PURE__ */ jsx(Tab.Item, {
|
|
69
|
-
label: "Content",
|
|
70
|
-
value: "content",
|
|
71
|
-
children: /* @__PURE__ */ jsx(ContentEditor, {
|
|
95
|
+
}),
|
|
96
|
+
activeTab === "structure" && /* @__PURE__ */ jsx(StructureEditor, { dictionary }),
|
|
97
|
+
activeTab === "content" && /* @__PURE__ */ jsx(ContentEditor, {
|
|
72
98
|
dictionary,
|
|
73
99
|
isDarkMode
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
/* @__PURE__ */ jsx(Tab.Item, {
|
|
77
|
-
label: "JSON",
|
|
78
|
-
value: "json",
|
|
79
|
-
children: /* @__PURE__ */ jsx(JSONEditor, {
|
|
100
|
+
}),
|
|
101
|
+
activeTab === "json" && /* @__PURE__ */ jsx(JSONEditor, {
|
|
80
102
|
dictionary,
|
|
81
103
|
isDarkMode
|
|
82
104
|
})
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
})]
|
|
86
108
|
}),
|
|
87
109
|
/* @__PURE__ */ jsx("div", {
|
|
88
110
|
className: "absolute bottom-3 z-20 w-full p-2",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DictionaryFieldEditor.mjs","names":[],"sources":["../../../../src/components/DictionaryFieldEditor/DictionaryFieldEditor.tsx"],"sourcesContent":["'use client';\n\nimport { Container } from '@components/Container';\nimport { CopyToClipboard } from '@components/CopyToClipboard';\nimport {\n useConfiguration,\n useDictionariesRecordActions,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { ArrowLeft } from 'lucide-react';\nimport { type FC, useEffect } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button, ButtonColor, ButtonVariant } from '../Button';\nimport { LocaleSwitcherContentProvider } from '../LocaleSwitcherContentDropDown';\nimport {
|
|
1
|
+
{"version":3,"file":"DictionaryFieldEditor.mjs","names":[],"sources":["../../../../src/components/DictionaryFieldEditor/DictionaryFieldEditor.tsx"],"sourcesContent":["'use client';\n\nimport { Container } from '@components/Container';\nimport { CopyToClipboard } from '@components/CopyToClipboard';\nimport {\n useConfiguration,\n useDictionariesRecordActions,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { Dictionary } from '@intlayer/types/dictionary';\nimport { ArrowLeft } from 'lucide-react';\nimport { type FC, useEffect, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { Button, ButtonColor, ButtonVariant } from '../Button';\nimport { LocaleSwitcherContentProvider } from '../LocaleSwitcherContentDropDown';\nimport { TabSelector, TabSelectorColor } from '../TabSelector';\nimport { ContentEditor } from './ContentEditor';\nimport { DictionaryDetailsForm } from './DictionaryDetails/DictionaryDetailsForm';\nimport { JSONEditor } from './JSONEditor';\nimport { SaveForm } from './SaveForm/SaveForm';\nimport { StructureEditor } from './StructureEditor';\n\ntype DictionaryFieldEditorProps = {\n dictionary: Dictionary;\n onClickDictionaryList?: () => void;\n onDelete?: () => void;\n onSave?: () => void;\n isDarkMode?: boolean;\n mode: ('local' | 'remote')[];\n showReturnButton?: boolean;\n};\n\nexport const DictionaryFieldEditor: FC<DictionaryFieldEditorProps> = ({\n dictionary,\n onClickDictionaryList,\n isDarkMode,\n mode,\n onDelete,\n onSave,\n showReturnButton = true,\n}) => {\n const config = useConfiguration();\n const { returnToDictionaryList } = useIntlayer('dictionary-field-editor');\n const { focusedContent, setFocusedContent } = useFocusUnmergedDictionary();\n const { setLocaleDictionary } = useDictionariesRecordActions();\n const [activeTab, setActiveTab] = useState<string>('content');\n\n useEffect(() => {\n setFocusedContent({\n ...(focusedContent ?? {}),\n dictionaryKey: dictionary.key,\n dictionaryLocalId: dictionary.localId,\n });\n setLocaleDictionary(dictionary);\n }, []);\n\n return (\n <LocaleSwitcherContentProvider\n availableLocales={config?.internationalization?.locales ?? []}\n >\n <div className=\"relative flex h-full min-h-0 w-full flex-1 flex-col md:overflow-hidden\">\n {showReturnButton && (\n <Button\n onClick={onClickDictionaryList}\n variant={ButtonVariant.HOVERABLE}\n className=\"z-10 mr-auto mb-6 ml-5 shrink-0\"\n color={ButtonColor.TEXT}\n Icon={ArrowLeft}\n label={returnToDictionaryList.label.value}\n >\n {returnToDictionaryList.text}\n </Button>\n )}\n\n <div className=\"mb-22 flex min-h-0 flex-1 flex-col overflow-hidden\">\n {/* Tab headers */}\n <div className=\"sticky top-0 z-10 flex shrink-0 gap-3 rounded-xl bg-background/20 p-3 pb-4\">\n <TabSelector\n selectedChoice={activeTab}\n tabs={[\n ...(mode.includes('remote')\n ? [\n <button\n key=\"details\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'details'}\n onClick={() => setActiveTab('details')}\n type=\"button\"\n >\n Details\n </button>,\n ]\n : []),\n <button\n key=\"structure\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'structure'}\n onClick={() => setActiveTab('structure')}\n type=\"button\"\n >\n Structure\n </button>,\n <button\n key=\"content\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'content'}\n onClick={() => setActiveTab('content')}\n type=\"button\"\n >\n Content\n </button>,\n <button\n key=\"json\"\n className=\"cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none\"\n data-active={activeTab === 'json'}\n onClick={() => setActiveTab('json')}\n type=\"button\"\n >\n JSON\n </button>,\n ]}\n hoverable\n color={TabSelectorColor.TEXT}\n />\n </div>\n {/* Tab content — only active panel is mounted */}\n <div className=\"min-h-0 flex-1 overflow-y-auto p-6\">\n <div className=\"flex w-full min-w-0 flex-col items-stretch gap-6\">\n {mode.includes('remote') && activeTab === 'details' && (\n <DictionaryDetailsForm dictionary={dictionary} mode={mode} />\n )}\n {activeTab === 'structure' && (\n <StructureEditor dictionary={dictionary} />\n )}\n {activeTab === 'content' && (\n <ContentEditor\n dictionary={dictionary}\n isDarkMode={isDarkMode}\n />\n )}\n {activeTab === 'json' && (\n <JSONEditor dictionary={dictionary} isDarkMode={isDarkMode} />\n )}\n </div>\n </div>\n </div>\n\n <div className=\"absolute bottom-3 z-20 w-full p-2\">\n <Container\n color=\"card\"\n roundedSize=\"2xl\"\n padding=\"sm\"\n className=\"w-full shrink-0 flex-row flex-wrap items-center justify-end gap-10 bg-background/20 md:bottom-0\"\n >\n <CopyToClipboard\n text={dictionary.id!}\n className=\"text-nowrap text-neutral text-sm\"\n size={9}\n >\n {dictionary.id}\n </CopyToClipboard>\n <SaveForm\n dictionary={dictionary}\n mode={mode}\n onDelete={() => {\n setFocusedContent(null);\n onDelete?.();\n }}\n onSave={onSave}\n />\n </Container>\n </div>\n </div>\n </LocaleSwitcherContentProvider>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,MAAa,yBAAyD,EACpE,YACA,uBACA,YACA,MACA,UACA,QACA,mBAAmB,WACf;CACJ,MAAM,SAAS,kBAAkB;CACjC,MAAM,EAAE,2BAA2B,YAAY,0BAA0B;CACzE,MAAM,EAAE,gBAAgB,sBAAsB,4BAA4B;CAC1E,MAAM,EAAE,wBAAwB,8BAA8B;CAC9D,MAAM,CAAC,WAAW,gBAAgB,SAAiB,UAAU;CAE7D,gBAAgB;EACd,kBAAkB;GAChB,GAAI,kBAAkB,EAAE;GACxB,eAAe,WAAW;GAC1B,mBAAmB,WAAW;GAC/B,CAAC;EACF,oBAAoB,WAAW;IAC9B,EAAE,CAAC;CAEN,OACE,oBAAC,+BAAD;EACE,kBAAkB,QAAQ,sBAAsB,WAAW,EAAE;YAE7D,qBAAC,OAAD;GAAK,WAAU;aAAf;IACG,oBACC,oBAAC,QAAD;KACE,SAAS;KACT;KACA,WAAU;KACV;KACA,MAAM;KACN,OAAO,uBAAuB,MAAM;eAEnC,uBAAuB;KACjB;IAGX,qBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,oBAAC,OAAD;MAAK,WAAU;gBACb,oBAAC,aAAD;OACE,gBAAgB;OAChB,MAAM;QACJ,GAAI,KAAK,SAAS,SAAS,GACvB,CACE,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,UAAU;SACtC,MAAK;mBACN;SAEQ,EAPH,UAOG,CACV,GACD,EAAE;QACN,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,YAAY;SACxC,MAAK;mBACN;SAEQ,EAPH,YAOG;QACT,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,UAAU;SACtC,MAAK;mBACN;SAEQ,EAPH,UAOG;QACT,oBAAC,UAAD;SAEE,WAAU;SACV,eAAa,cAAc;SAC3B,eAAe,aAAa,OAAO;SACnC,MAAK;mBACN;SAEQ,EAPH,OAOG;QACV;OACD;OACA;OACA;MACE,GAEN,oBAAC,OAAD;MAAK,WAAU;gBACb,qBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,KAAK,SAAS,SAAS,IAAI,cAAc,aACxC,oBAAC,uBAAD;SAAmC;SAAkB;SAAQ;QAE9D,cAAc,eACb,oBAAC,iBAAD,EAA6B,YAAc;QAE5C,cAAc,aACb,oBAAC,eAAD;SACc;SACA;SACZ;QAEH,cAAc,UACb,oBAAC,YAAD;SAAwB;SAAwB;SAAc;QAE5D;;MACF,EACF;;IAEN,oBAAC,OAAD;KAAK,WAAU;eACb,qBAAC,WAAD;MACE,OAAM;MACN,aAAY;MACZ,SAAQ;MACR,WAAU;gBAJZ,CAME,oBAAC,iBAAD;OACE,MAAM,WAAW;OACjB,WAAU;OACV,MAAM;iBAEL,WAAW;OACI,GAClB,oBAAC,UAAD;OACc;OACN;OACN,gBAAgB;QACd,kBAAkB,KAAK;QACvB,YAAY;;OAEN;OACR,EACQ;;KACR;IACF;;EACwB"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Button, ButtonColor, ButtonTextAlign, ButtonVariant } from "../../Button/Button.mjs";
|
|
2
2
|
import { Accordion } from "../../Accordion/Accordion.mjs";
|
|
3
3
|
import { getIsEditableSection } from "../getIsEditableSection.mjs";
|
|
4
|
-
import { ChevronRight, Plus } from "lucide-react";
|
|
5
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
4
|
import { internationalization } from "@intlayer/config/built";
|
|
7
|
-
import {
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { ChevronRight, Plus } from "lucide-react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
import { useIntlayer } from "react-intlayer";
|
|
9
|
+
import { useEditedContentActions, useEditorLocale, useFocusUnmergedDictionary } from "@intlayer/editor-react";
|
|
9
10
|
import { getContentNodeByKeyPath, getEmptyNode, getNodeType } from "@intlayer/core/dictionaryManipulator";
|
|
10
11
|
import * as NodeTypes from "@intlayer/types/nodeType";
|
|
11
12
|
import { isSameKeyPath } from "@intlayer/core/utils";
|
|
@@ -17,6 +18,17 @@ const traceKeys = [
|
|
|
17
18
|
"id",
|
|
18
19
|
"nodeType"
|
|
19
20
|
];
|
|
21
|
+
const GatedAccordion = ({ children, onToggle, ...props }) => {
|
|
22
|
+
const [hasOpened, setHasOpened] = useState(false);
|
|
23
|
+
return /* @__PURE__ */ jsx(Accordion, {
|
|
24
|
+
...props,
|
|
25
|
+
onToggle: (isOpen) => {
|
|
26
|
+
if (isOpen && !hasOpened) setHasOpened(true);
|
|
27
|
+
onToggle?.(isOpen);
|
|
28
|
+
},
|
|
29
|
+
children: hasOpened ? children : null
|
|
30
|
+
});
|
|
31
|
+
};
|
|
20
32
|
const NavigationViewNode = ({ section: sectionProp, keyPath, dictionary }) => {
|
|
21
33
|
const { locales } = internationalization;
|
|
22
34
|
const section = getContentNodeByKeyPath(sectionProp, keyPath, useEditorLocale());
|
|
@@ -26,7 +38,7 @@ const NavigationViewNode = ({ section: sectionProp, keyPath, dictionary }) => {
|
|
|
26
38
|
const nodeType = getNodeType(section);
|
|
27
39
|
const getIsSelected = (keyPath) => (focusedContent?.keyPath?.length ?? 0) > 0 && isSameKeyPath(keyPath, focusedContent?.keyPath ?? []);
|
|
28
40
|
const isEditableSubSection = getIsEditableSection(section);
|
|
29
|
-
if (!section) return /* @__PURE__ */ jsx(Fragment, {});
|
|
41
|
+
if (!section) return /* @__PURE__ */ jsx(Fragment$1, {});
|
|
30
42
|
if (isEditableSubSection) return /* @__PURE__ */ jsx(Button, {
|
|
31
43
|
label: goToField.label.value,
|
|
32
44
|
variant: "hoverable",
|
|
@@ -37,7 +49,7 @@ const NavigationViewNode = ({ section: sectionProp, keyPath, dictionary }) => {
|
|
|
37
49
|
children: camelCaseToSentence(keyPath[keyPath.length - 1].key)
|
|
38
50
|
});
|
|
39
51
|
if (typeof section === "object") {
|
|
40
|
-
if (nodeType === NodeTypes.REACT_NODE) return /* @__PURE__ */ jsx(Fragment, { children: "React Node" });
|
|
52
|
+
if (nodeType === NodeTypes.REACT_NODE) return /* @__PURE__ */ jsx(Fragment$1, { children: "React Node" });
|
|
41
53
|
if (nodeType === NodeTypes.TRANSLATION) return /* @__PURE__ */ jsx("div", {
|
|
42
54
|
className: "flex flex-col justify-between gap-2",
|
|
43
55
|
children: locales.map((translationKey) => {
|
|
@@ -81,7 +93,7 @@ const NavigationViewNode = ({ section: sectionProp, keyPath, dictionary }) => {
|
|
|
81
93
|
isActive: getIsSelected(childKeyPath),
|
|
82
94
|
children: ["Item ", index]
|
|
83
95
|
}, JSON.stringify(childKeyPath));
|
|
84
|
-
return /* @__PURE__ */ jsx(
|
|
96
|
+
return /* @__PURE__ */ jsx(GatedAccordion, {
|
|
85
97
|
label: `${goToField.label.value} ${index}`,
|
|
86
98
|
header: `Item ${index}`,
|
|
87
99
|
isActive: getIsSelected(childKeyPath),
|
|
@@ -139,7 +151,7 @@ const NavigationViewNode = ({ section: sectionProp, keyPath, dictionary }) => {
|
|
|
139
151
|
IconRight: ChevronRight,
|
|
140
152
|
children: camelCaseToSentence(key)
|
|
141
153
|
}, key);
|
|
142
|
-
return /* @__PURE__ */ jsx(
|
|
154
|
+
return /* @__PURE__ */ jsx(GatedAccordion, {
|
|
143
155
|
label: `${goToField.label.value} ${key}`,
|
|
144
156
|
isActive: getIsSelected(childKeyPath),
|
|
145
157
|
onClick: () => setFocusedContentKeyPath(childKeyPath),
|
|
@@ -159,7 +171,7 @@ const NavigationViewNode = ({ section: sectionProp, keyPath, dictionary }) => {
|
|
|
159
171
|
})
|
|
160
172
|
});
|
|
161
173
|
}
|
|
162
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
174
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
163
175
|
"Error loading section --",
|
|
164
176
|
nodeType,
|
|
165
177
|
"--",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NavigationViewNode.mjs","names":[],"sources":["../../../../../src/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.tsx"],"sourcesContent":["import { Accordion } from '@components/Accordion';\nimport {\n Button,\n ButtonColor,\n ButtonTextAlign,\n ButtonVariant,\n} from '@components/Button';\nimport { internationalization } from '@intlayer/config/built';\nimport { camelCaseToSentence } from '@intlayer/config/client';\nimport {\n getContentNodeByKeyPath,\n getEmptyNode,\n getNodeType,\n} from '@intlayer/core/dictionaryManipulator';\nimport { isSameKeyPath } from '@intlayer/core/utils';\nimport {\n useEditedContentActions,\n useEditorLocale,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport type { ContentNode, Dictionary } from 'intlayer';\nimport { ChevronRight, Plus } from 'lucide-react';\nimport type { FC } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { getIsEditableSection } from '../getIsEditableSection';\n\nexport const traceKeys: string[] = ['filePath', 'id', 'nodeType'];\n\nexport type NodeWrapperProps = {\n keyPath: KeyPath[];\n section: ContentNode;\n dictionary: Dictionary;\n};\n\nexport const NavigationViewNode: FC<NodeWrapperProps> = ({\n section: sectionProp,\n keyPath,\n dictionary,\n}) => {\n const { locales } = internationalization;\n\n const currentLocale = useEditorLocale();\n const section = getContentNodeByKeyPath(sectionProp, keyPath, currentLocale);\n const { addEditedContent } = useEditedContentActions();\n const { setFocusedContentKeyPath, focusedContent } =\n useFocusUnmergedDictionary();\n const { addNewElement, goToField } = useIntlayer('navigation-view');\n const nodeType = getNodeType(section);\n const getIsSelected = (keyPath: KeyPath[]) =>\n (focusedContent?.keyPath?.length ?? 0) > 0 &&\n isSameKeyPath(keyPath, focusedContent?.keyPath ?? []);\n const isEditableSubSection = getIsEditableSection(section);\n\n if (!section) return <></>;\n\n if (isEditableSubSection) {\n return (\n <Button\n label={goToField.label.value}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n className=\"w-full\"\n onClick={() => setFocusedContentKeyPath(keyPath)}\n IconRight={ChevronRight}\n >\n {camelCaseToSentence(keyPath[keyPath.length - 1].key as string)}\n </Button>\n );\n }\n\n if (typeof section === 'object') {\n if (nodeType === NodeTypes.REACT_NODE) {\n return <>React Node</>;\n }\n\n if (nodeType === NodeTypes.TRANSLATION) {\n return (\n <div className=\"flex flex-col justify-between gap-2\">\n {locales.map((translationKey) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: NodeTypes.TRANSLATION, key: translationKey },\n ];\n\n return (\n <NavigationViewNode\n key={translationKey}\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n );\n })}\n </div>\n );\n }\n\n if (\n nodeType === NodeTypes.ENUMERATION ||\n nodeType === NodeTypes.PLURAL ||\n nodeType === NodeTypes.CONDITION\n ) {\n return (\n <div className=\"flex flex-col justify-between gap-2\">\n {Object.keys(\n (section as any)[nodeType as unknown as keyof typeof section]\n ).map((key) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: nodeType, key },\n ];\n\n return (\n <NavigationViewNode\n key={key}\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n );\n })}\n </div>\n );\n }\n\n if (nodeType === NodeTypes.ARRAY) {\n return (\n <div className=\"flex flex-col justify-between gap-2\">\n {(section as unknown as ContentNode[]).map((subSection, index) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: NodeTypes.ARRAY, key: index },\n ];\n\n const isEditableSubSection = getIsEditableSection(subSection);\n\n if (isEditableSubSection) {\n return (\n <Button\n key={JSON.stringify(childKeyPath)}\n label={`${goToField.label.value} ${index}`}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n className=\"w-full\"\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n IconRight={ChevronRight}\n isActive={getIsSelected(childKeyPath)}\n >\n Item {index}\n </Button>\n );\n }\n\n return (\n <Accordion\n key={JSON.stringify(childKeyPath)}\n label={`${goToField.label.value} ${index}`}\n header={`Item ${index}`}\n isActive={getIsSelected(childKeyPath)}\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n >\n <div className=\"mt-2 flex w-full max-w-full\">\n <div className=\"flex-1 pl-10\">\n <NavigationViewNode\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n </div>\n </div>\n </Accordion>\n );\n })}\n\n <Button\n label={addNewElement.label.value}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.NEUTRAL}\n textAlign={ButtonTextAlign.LEFT}\n onClick={() => {\n const newKeyPath: KeyPath[] = [\n ...keyPath,\n {\n type: NodeTypes.ARRAY,\n key: (section as unknown as ContentNode[]).length,\n },\n ];\n const sectionArray = section as unknown as ContentNode[];\n const emptySectionEl =\n getEmptyNode(\n sectionArray[\n (sectionArray.length - 1) as keyof typeof sectionArray\n ] as ContentNode\n ) ?? '';\n addEditedContent(\n dictionary.localId as LocalDictionaryId,\n emptySectionEl,\n newKeyPath,\n false\n );\n setFocusedContentKeyPath(newKeyPath);\n }}\n Icon={Plus}\n >\n {addNewElement.text}\n </Button>\n </div>\n );\n }\n\n if (typeof section.nodeType === 'string') {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: section.nodeType } as KeyPath,\n ];\n\n return (\n <NavigationViewNode\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n );\n }\n\n const sectionArray = Object.keys(section);\n return (\n <div className=\"flex w-full max-w-full flex-col justify-between gap-2\">\n {sectionArray.map((key) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: NodeTypes.OBJECT, key },\n ];\n\n const subSection = getContentNodeByKeyPath(sectionProp, childKeyPath);\n const isEditableSubSection = getIsEditableSection(subSection);\n\n if (isEditableSubSection) {\n return (\n <Button\n label={`${goToField.label.value} ${key}`}\n key={key}\n isActive={getIsSelected(childKeyPath)}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n className=\"w-full\"\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n IconRight={ChevronRight}\n >\n {camelCaseToSentence(key)}\n </Button>\n );\n }\n\n return (\n <Accordion\n key={key}\n label={`${goToField.label.value} ${key}`}\n isActive={getIsSelected(childKeyPath)}\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n header={camelCaseToSentence(key)}\n >\n <div className=\"mt-2 flex w-full max-w-full\">\n <div className=\"flex-1 pl-10\">\n <NavigationViewNode\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n </div>\n </div>\n </Accordion>\n );\n })}\n </div>\n );\n }\n\n return (\n <>\n Error loading section --\n {nodeType}\n --\n {JSON.stringify(section)}\n --\n {JSON.stringify(keyPath)}\n </>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NavigationViewNode.mjs","names":[],"sources":["../../../../../src/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.tsx"],"sourcesContent":["import { Accordion, type AccordionProps } from '@components/Accordion';\nimport {\n Button,\n ButtonColor,\n ButtonTextAlign,\n ButtonVariant,\n} from '@components/Button';\nimport { internationalization } from '@intlayer/config/built';\nimport { camelCaseToSentence } from '@intlayer/config/client';\nimport {\n getContentNodeByKeyPath,\n getEmptyNode,\n getNodeType,\n} from '@intlayer/core/dictionaryManipulator';\nimport { isSameKeyPath } from '@intlayer/core/utils';\nimport {\n useEditedContentActions,\n useEditorLocale,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { LocalDictionaryId } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport type { ContentNode, Dictionary } from 'intlayer';\nimport { ChevronRight, Plus } from 'lucide-react';\nimport { type FC, type ReactNode, useState } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { getIsEditableSection } from '../getIsEditableSection';\n\nexport const traceKeys: string[] = ['filePath', 'id', 'nodeType'];\n\ntype GatedAccordionProps = AccordionProps & { children: ReactNode };\n\n// Renders children only after the accordion is first opened (mount-once pattern).\n// Prevents the entire recursive subtree from mounting on initial render.\nconst GatedAccordion: FC<GatedAccordionProps> = ({\n children,\n onToggle,\n ...props\n}) => {\n const [hasOpened, setHasOpened] = useState(false);\n return (\n <Accordion\n {...props}\n onToggle={(isOpen) => {\n if (isOpen && !hasOpened) setHasOpened(true);\n onToggle?.(isOpen);\n }}\n >\n {hasOpened ? children : null}\n </Accordion>\n );\n};\n\nexport type NodeWrapperProps = {\n keyPath: KeyPath[];\n section: ContentNode;\n dictionary: Dictionary;\n};\n\nexport const NavigationViewNode: FC<NodeWrapperProps> = ({\n section: sectionProp,\n keyPath,\n dictionary,\n}) => {\n const { locales } = internationalization;\n\n const currentLocale = useEditorLocale();\n const section = getContentNodeByKeyPath(sectionProp, keyPath, currentLocale);\n const { addEditedContent } = useEditedContentActions();\n const { setFocusedContentKeyPath, focusedContent } =\n useFocusUnmergedDictionary();\n const { addNewElement, goToField } = useIntlayer('navigation-view');\n const nodeType = getNodeType(section);\n const getIsSelected = (keyPath: KeyPath[]) =>\n (focusedContent?.keyPath?.length ?? 0) > 0 &&\n isSameKeyPath(keyPath, focusedContent?.keyPath ?? []);\n const isEditableSubSection = getIsEditableSection(section);\n\n if (!section) return <></>;\n\n if (isEditableSubSection) {\n return (\n <Button\n label={goToField.label.value}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n className=\"w-full\"\n onClick={() => setFocusedContentKeyPath(keyPath)}\n IconRight={ChevronRight}\n >\n {camelCaseToSentence(keyPath[keyPath.length - 1].key as string)}\n </Button>\n );\n }\n\n if (typeof section === 'object') {\n if (nodeType === NodeTypes.REACT_NODE) {\n return <>React Node</>;\n }\n\n if (nodeType === NodeTypes.TRANSLATION) {\n return (\n <div className=\"flex flex-col justify-between gap-2\">\n {locales.map((translationKey) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: NodeTypes.TRANSLATION, key: translationKey },\n ];\n\n return (\n <NavigationViewNode\n key={translationKey}\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n );\n })}\n </div>\n );\n }\n\n if (\n nodeType === NodeTypes.ENUMERATION ||\n nodeType === NodeTypes.PLURAL ||\n nodeType === NodeTypes.CONDITION\n ) {\n return (\n <div className=\"flex flex-col justify-between gap-2\">\n {Object.keys(\n (section as any)[nodeType as unknown as keyof typeof section]\n ).map((key) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: nodeType, key },\n ];\n\n return (\n <NavigationViewNode\n key={key}\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n );\n })}\n </div>\n );\n }\n\n if (nodeType === NodeTypes.ARRAY) {\n return (\n <div className=\"flex flex-col justify-between gap-2\">\n {(section as unknown as ContentNode[]).map((subSection, index) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: NodeTypes.ARRAY, key: index },\n ];\n\n const isEditableSubSection = getIsEditableSection(subSection);\n\n if (isEditableSubSection) {\n return (\n <Button\n key={JSON.stringify(childKeyPath)}\n label={`${goToField.label.value} ${index}`}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n className=\"w-full\"\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n IconRight={ChevronRight}\n isActive={getIsSelected(childKeyPath)}\n >\n Item {index}\n </Button>\n );\n }\n\n return (\n <GatedAccordion\n key={JSON.stringify(childKeyPath)}\n label={`${goToField.label.value} ${index}`}\n header={`Item ${index}`}\n isActive={getIsSelected(childKeyPath)}\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n >\n <div className=\"mt-2 flex w-full max-w-full\">\n <div className=\"flex-1 pl-10\">\n <NavigationViewNode\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n </div>\n </div>\n </GatedAccordion>\n );\n })}\n\n <Button\n label={addNewElement.label.value}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.NEUTRAL}\n textAlign={ButtonTextAlign.LEFT}\n onClick={() => {\n const newKeyPath: KeyPath[] = [\n ...keyPath,\n {\n type: NodeTypes.ARRAY,\n key: (section as unknown as ContentNode[]).length,\n },\n ];\n const sectionArray = section as unknown as ContentNode[];\n const emptySectionEl =\n getEmptyNode(\n sectionArray[\n (sectionArray.length - 1) as keyof typeof sectionArray\n ] as ContentNode\n ) ?? '';\n addEditedContent(\n dictionary.localId as LocalDictionaryId,\n emptySectionEl,\n newKeyPath,\n false\n );\n setFocusedContentKeyPath(newKeyPath);\n }}\n Icon={Plus}\n >\n {addNewElement.text}\n </Button>\n </div>\n );\n }\n\n if (typeof section.nodeType === 'string') {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: section.nodeType } as KeyPath,\n ];\n\n return (\n <NavigationViewNode\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n );\n }\n\n const sectionArray = Object.keys(section);\n return (\n <div className=\"flex w-full max-w-full flex-col justify-between gap-2\">\n {sectionArray.map((key) => {\n const childKeyPath: KeyPath[] = [\n ...keyPath,\n { type: NodeTypes.OBJECT, key },\n ];\n\n const subSection = getContentNodeByKeyPath(sectionProp, childKeyPath);\n const isEditableSubSection = getIsEditableSection(subSection);\n\n if (isEditableSubSection) {\n return (\n <Button\n label={`${goToField.label.value} ${key}`}\n key={key}\n isActive={getIsSelected(childKeyPath)}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n className=\"w-full\"\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n IconRight={ChevronRight}\n >\n {camelCaseToSentence(key)}\n </Button>\n );\n }\n\n return (\n <GatedAccordion\n key={key}\n label={`${goToField.label.value} ${key}`}\n isActive={getIsSelected(childKeyPath)}\n onClick={() => setFocusedContentKeyPath(childKeyPath)}\n header={camelCaseToSentence(key)}\n >\n <div className=\"mt-2 flex w-full max-w-full\">\n <div className=\"flex-1 pl-10\">\n <NavigationViewNode\n keyPath={childKeyPath}\n section={sectionProp}\n dictionary={dictionary}\n />\n </div>\n </div>\n </GatedAccordion>\n );\n })}\n </div>\n );\n }\n\n return (\n <>\n Error loading section --\n {nodeType}\n --\n {JSON.stringify(section)}\n --\n {JSON.stringify(keyPath)}\n </>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../../utils/cn.mjs";
|
|
4
|
+
import { ButtonColor, ButtonVariant } from "../../Button/Button.mjs";
|
|
4
5
|
import { useAuth } from "../../../hooks/useAuth/useAuth.mjs";
|
|
5
6
|
import { useDeleteDictionary, usePushDictionaries, useWriteDictionary } from "../../../hooks/reactQuery.mjs";
|
|
6
|
-
import { ButtonColor, ButtonVariant } from "../../Button/Button.mjs";
|
|
7
|
-
import { Modal, ModalSize } from "../../Modal/Modal.mjs";
|
|
8
7
|
import { Form } from "../../Form/Form.mjs";
|
|
8
|
+
import { Modal, ModalSize } from "../../Modal/Modal.mjs";
|
|
9
9
|
import { useState } from "react";
|
|
10
10
|
import { ArrowUpFromLine, Download, RotateCcw, Save, Trash } from "lucide-react";
|
|
11
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
-
import { useDictionariesRecordActions, useEditedContent } from "@intlayer/editor-react";
|
|
13
12
|
import { useIntlayer } from "react-intlayer";
|
|
13
|
+
import { useDictionariesRecordActions, useEditedContent } from "@intlayer/editor-react";
|
|
14
14
|
|
|
15
15
|
//#region src/components/DictionaryFieldEditor/SaveForm/SaveForm.tsx
|
|
16
16
|
const SaveForm = ({ dictionary, mode, className, onDelete, onSave, ...props }) => {
|
|
@@ -5,22 +5,20 @@ import { Button, ButtonColor, ButtonSize, ButtonVariant } from "../../Button/But
|
|
|
5
5
|
import { InputVariant } from "../../Input/Input.mjs";
|
|
6
6
|
import { EditableFieldInput } from "../../EditableField/EditableFieldInput.mjs";
|
|
7
7
|
import { NodeTypeSelector } from "../NodeTypeSelector.mjs";
|
|
8
|
-
import { memo, useMemo, useRef } from "react";
|
|
9
8
|
import { Plus, Trash } from "lucide-react";
|
|
10
|
-
import { Fragment
|
|
11
|
-
import { useConfiguration, useEditedContentActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
10
|
import { useIntlayer } from "react-intlayer";
|
|
11
|
+
import { useConfiguration, useEditedContentActions, useFocusUnmergedDictionary } from "@intlayer/editor-react";
|
|
13
12
|
import { getDefaultNode, getNodeChildren, getNodeType } from "@intlayer/core/dictionaryManipulator";
|
|
14
13
|
import * as NodeTypes from "@intlayer/types/nodeType";
|
|
15
14
|
import { isSameKeyPath } from "@intlayer/core/utils";
|
|
16
15
|
import { camelCaseToSentence } from "@intlayer/config/client";
|
|
17
|
-
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
18
16
|
|
|
19
17
|
//#region src/components/DictionaryFieldEditor/StructureView/StructureView.tsx
|
|
20
18
|
const NodeTypeView = ({ section, dictionaryLocalId, keyPath, onNodeTypeChange: onNodeTypeChangeProp }) => {
|
|
21
19
|
const locales = useConfiguration()?.internationalization?.locales ?? [];
|
|
22
|
-
const nodeType =
|
|
23
|
-
const children =
|
|
20
|
+
const nodeType = getNodeType(section);
|
|
21
|
+
const children = getNodeChildren(section);
|
|
24
22
|
const onNodeTypeChange = (content) => {
|
|
25
23
|
onNodeTypeChangeProp(getDefaultNode(nodeType, locales, content));
|
|
26
24
|
};
|
|
@@ -61,7 +59,7 @@ const NodeTypeView = ({ section, dictionaryLocalId, keyPath, onNodeTypeChange: o
|
|
|
61
59
|
})]
|
|
62
60
|
});
|
|
63
61
|
}
|
|
64
|
-
if (nodeType === NodeTypes.OBJECT) return /* @__PURE__ */ jsxs(Fragment
|
|
62
|
+
if (nodeType === NodeTypes.OBJECT) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(NodeTypeSelector, {
|
|
65
63
|
section,
|
|
66
64
|
onValueChange: (nodeType) => onNodeTypeChangeProp(getDefaultNode(nodeType, locales))
|
|
67
65
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -77,7 +75,7 @@ const NodeTypeView = ({ section, dictionaryLocalId, keyPath, onNodeTypeChange: o
|
|
|
77
75
|
onValueChange: (nodeType) => onNodeTypeChangeProp(getDefaultNode(nodeType, locales))
|
|
78
76
|
});
|
|
79
77
|
};
|
|
80
|
-
const NodeView =
|
|
78
|
+
const NodeView = ({ sectionKey, section, keyPath, dictionaryLocalId }) => {
|
|
81
79
|
const { focusedContent, setFocusedContentKeyPath } = useFocusUnmergedDictionary();
|
|
82
80
|
const { renameEditedContent, addEditedContent } = useEditedContentActions();
|
|
83
81
|
const { titleInput, deleteButton } = useIntlayer("structure-view");
|
|
@@ -142,63 +140,35 @@ const NodeView = memo(({ sectionKey, section, keyPath, dictionaryLocalId }) => {
|
|
|
142
140
|
})]
|
|
143
141
|
})
|
|
144
142
|
});
|
|
145
|
-
}
|
|
143
|
+
};
|
|
146
144
|
const ObjectView = ({ section, keyPath, dictionaryLocalId }) => {
|
|
147
145
|
const { addNodeButton } = useIntlayer("structure-view");
|
|
148
146
|
const { setFocusedContentKeyPath } = useFocusUnmergedDictionary();
|
|
149
147
|
const { addEditedContent } = useEditedContentActions();
|
|
150
|
-
const keys = useMemo(() => section && typeof section === "object" ? Object.keys(section) : [], [section]);
|
|
151
|
-
const parentRef = useRef(null);
|
|
152
|
-
const rowVirtualizer = useVirtualizer({
|
|
153
|
-
count: keys.length,
|
|
154
|
-
getScrollElement: () => parentRef.current,
|
|
155
|
-
estimateSize: () => 140,
|
|
156
|
-
overscan: 5
|
|
157
|
-
});
|
|
158
148
|
if (!section || typeof section !== "object") return /* @__PURE__ */ jsx("div", { children: "Not an object" });
|
|
159
149
|
return /* @__PURE__ */ jsxs("div", {
|
|
160
|
-
className: "flex flex-col gap-2",
|
|
161
|
-
children: [/* @__PURE__ */ jsx("
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"data-index": virtualRow.index,
|
|
174
|
-
ref: rowVirtualizer.measureElement,
|
|
175
|
-
style: {
|
|
176
|
-
position: "absolute",
|
|
177
|
-
top: 0,
|
|
178
|
-
left: 0,
|
|
179
|
-
width: "100%",
|
|
180
|
-
transform: `translateY(${virtualRow.start}px)`
|
|
181
|
-
},
|
|
182
|
-
className: "pb-4",
|
|
183
|
-
children: /* @__PURE__ */ jsx(NodeView, {
|
|
184
|
-
sectionKey: key,
|
|
185
|
-
section: section?.[key],
|
|
186
|
-
keyPath: [...keyPath, {
|
|
187
|
-
type: NodeTypes.OBJECT,
|
|
188
|
-
key
|
|
189
|
-
}],
|
|
190
|
-
dictionaryLocalId
|
|
191
|
-
})
|
|
192
|
-
}, virtualRow.key);
|
|
150
|
+
className: "flex flex-col gap-2 overflow-y-auto",
|
|
151
|
+
children: [/* @__PURE__ */ jsx("ul", {
|
|
152
|
+
className: "mr-auto flex flex-col gap-4",
|
|
153
|
+
children: Object.keys(section).map((key) => /* @__PURE__ */ jsx("li", {
|
|
154
|
+
className: "flex w-full",
|
|
155
|
+
children: /* @__PURE__ */ jsx(NodeView, {
|
|
156
|
+
sectionKey: key,
|
|
157
|
+
section: section?.[key],
|
|
158
|
+
keyPath: [...keyPath, {
|
|
159
|
+
type: NodeTypes.OBJECT,
|
|
160
|
+
key
|
|
161
|
+
}],
|
|
162
|
+
dictionaryLocalId
|
|
193
163
|
})
|
|
194
|
-
})
|
|
164
|
+
}, `${JSON.stringify(keyPath)}-object-${key}`))
|
|
195
165
|
}), /* @__PURE__ */ jsx(Button, {
|
|
196
166
|
label: addNodeButton.label.value,
|
|
197
167
|
variant: "hoverable",
|
|
198
168
|
size: "md",
|
|
199
169
|
color: "text",
|
|
200
170
|
Icon: Plus,
|
|
201
|
-
className: "
|
|
171
|
+
className: "flex-1",
|
|
202
172
|
onClick: () => {
|
|
203
173
|
const newKey = "newKey";
|
|
204
174
|
const newKeyPath = [...keyPath, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StructureView.mjs","names":[],"sources":["../../../../../src/components/DictionaryFieldEditor/StructureView/StructureView.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n ButtonColor,\n ButtonSize,\n ButtonVariant,\n} from '@components/Button';\nimport { Container } from '@components/Container';\nimport { EditableFieldInput } from '@components/EditableField';\nimport { InputVariant } from '@components/Input';\nimport { camelCaseToSentence } from '@intlayer/config/client';\nimport {\n getDefaultNode,\n getNodeChildren,\n getNodeType,\n} from '@intlayer/core/dictionaryManipulator';\nimport { isSameKeyPath } from '@intlayer/core/utils';\nimport {\n useConfiguration,\n useEditedContentActions,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { LocalDictionaryId, TypedNode } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport type { ContentNode } from 'intlayer';\nimport { Plus, Trash } from 'lucide-react';\nimport { type FC, memo, useMemo, useRef } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { NodeTypeSelector } from '../NodeTypeSelector';\n\ntype NodeTypeViewProps = {\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n onNodeTypeChange: (content?: ContentNode) => void;\n};\n\nconst NodeTypeView: FC<NodeTypeViewProps> = ({\n section,\n dictionaryLocalId,\n keyPath,\n onNodeTypeChange: onNodeTypeChangeProp,\n}) => {\n const locales = useConfiguration()?.internationalization?.locales ?? [];\n\n // Memoize node type and children calculations\n const nodeType = useMemo(() => getNodeType(section), [section]);\n const children = useMemo(() => getNodeChildren(section), [section]);\n\n const onNodeTypeChange = (content?: ContentNode) => {\n const transformedContent = getDefaultNode(\n nodeType,\n locales,\n content\n ) as ContentNode;\n\n onNodeTypeChangeProp(transformedContent);\n };\n\n if (\n nodeType === NodeTypes.TRANSLATION ||\n nodeType === NodeTypes.CONDITION ||\n nodeType === NodeTypes.GENDER ||\n nodeType === NodeTypes.ENUMERATION ||\n nodeType === NodeTypes.PLURAL\n ) {\n const firstKey = Object.keys(\n (section as unknown as TypedNode)[nodeType as keyof typeof section]\n )[0];\n const childrenKeyPath = [\n ...keyPath,\n { type: nodeType, key: firstKey },\n ] as KeyPath[];\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(\n getDefaultNode(nodeType, locales) as ContentNode\n )\n }\n />\n\n <NodeTypeView\n section={children}\n keyPath={childrenKeyPath}\n dictionaryLocalId={dictionaryLocalId}\n onNodeTypeChange={onNodeTypeChange}\n />\n </div>\n );\n }\n\n if (nodeType === NodeTypes.ARRAY) {\n const childrenKeyPath = [...keyPath, { type: nodeType, key: 0 } as KeyPath];\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(\n getDefaultNode(nodeType, locales) as ContentNode\n )\n }\n />\n\n <NodeTypeView\n section={children}\n keyPath={childrenKeyPath}\n dictionaryLocalId={dictionaryLocalId}\n onNodeTypeChange={onNodeTypeChange}\n />\n </div>\n );\n }\n\n if (nodeType === NodeTypes.OBJECT) {\n return (\n <>\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(\n getDefaultNode(nodeType, locales) as ContentNode\n )\n }\n />\n <div className=\"mt-6 ml-10\">\n <StructureView\n keyPath={keyPath}\n section={section}\n dictionaryLocalId={dictionaryLocalId}\n />\n </div>\n </>\n );\n }\n\n return (\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(getDefaultNode(nodeType, locales) as ContentNode)\n }\n />\n );\n};\n\ntype NodeWrapperProps = {\n sectionKey?: string;\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n};\n\nexport const NodeView: FC<NodeWrapperProps> = memo(\n ({ sectionKey, section, keyPath, dictionaryLocalId }) => {\n const { focusedContent, setFocusedContentKeyPath } =\n useFocusUnmergedDictionary();\n const { renameEditedContent, addEditedContent } = useEditedContentActions();\n\n const { titleInput, deleteButton } = useIntlayer('structure-view');\n\n const handleRenameNodeKey = (keyName: string) => {\n renameEditedContent(dictionaryLocalId, keyName, keyPath);\n const prevKeyPath: KeyPath[] = keyPath.slice(0, -1);\n const lastKeyPath: KeyPath = keyPath[keyPath.length - 1];\n const newKeyPath: KeyPath[] = [\n ...prevKeyPath,\n { ...lastKeyPath, key: keyName } as KeyPath,\n ];\n setFocusedContentKeyPath(newKeyPath);\n };\n\n return (\n <Container\n transparency=\"xl\"\n roundedSize=\"xl\"\n className=\"w-full min-w-80 gap-2 overflow-auto px-5 py-2\"\n border\n borderColor=\"text\"\n background=\"none\"\n aria-selected={isSameKeyPath(keyPath, focusedContent?.keyPath ?? [])}\n onClick={() => setFocusedContentKeyPath(keyPath)}\n >\n <div className=\"flex w-full flex-col items-start justify-between gap-3\">\n {typeof sectionKey === 'string' && (\n <div className=\"w-full\">\n <div className=\"flex w-full items-center justify-between gap-10\">\n <EditableFieldInput\n name=\"key\"\n aria-label=\"Key\"\n placeholder={titleInput.placeholder.value}\n defaultValue={sectionKey}\n onSave={(value) => handleRenameNodeKey(value)}\n variant={InputVariant.INVISIBLE}\n />\n <Button\n label={deleteButton.label.value}\n variant={ButtonVariant.HOVERABLE}\n size={ButtonSize.ICON_SM}\n color={ButtonColor.TEXT}\n className=\"translate-x-2\"\n Icon={Trash}\n onClick={() => {\n addEditedContent(dictionaryLocalId, undefined, keyPath);\n\n const parentKeyPath: KeyPath[] = keyPath.slice(0, -1);\n setFocusedContentKeyPath(parentKeyPath);\n }}\n />\n </div>\n\n <span className=\"ml-3 text-neutral text-sm\">\n ( {camelCaseToSentence(sectionKey)} )\n </span>\n </div>\n )}\n <NodeTypeView\n keyPath={keyPath}\n dictionaryLocalId={dictionaryLocalId}\n section={section}\n onNodeTypeChange={(content) => {\n addEditedContent(dictionaryLocalId, content, keyPath);\n }}\n />\n </div>\n </Container>\n );\n }\n);\n\ntype ObjectViewProps = {\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n};\n\nexport const ObjectView: FC<ObjectViewProps> = ({\n section,\n keyPath,\n dictionaryLocalId,\n}) => {\n const { addNodeButton } = useIntlayer('structure-view');\n const { setFocusedContentKeyPath } = useFocusUnmergedDictionary();\n const { addEditedContent } = useEditedContentActions();\n\n // 1. Memoize keys to avoid unnecessary virtualizer resets\n const keys = useMemo(\n () => (section && typeof section === 'object' ? Object.keys(section) : []),\n [section]\n );\n\n // 2. Setup virtualization for large objects\n const parentRef = useRef<HTMLDivElement>(null);\n\n const rowVirtualizer = useVirtualizer({\n count: keys.length,\n getScrollElement: () => parentRef.current,\n estimateSize: () => 140, // Estimated height for a NodeView\n overscan: 5,\n });\n\n if (!section || typeof section !== 'object') {\n return <div>Not an object</div>;\n }\n\n return (\n <div className=\"flex flex-col gap-2\">\n <div\n ref={parentRef}\n className=\"flex max-h-[800px] flex-col gap-4 overflow-y-auto pr-2\"\n >\n <div\n style={{\n height: `${rowVirtualizer.getTotalSize()}px`,\n width: '100%',\n position: 'relative',\n }}\n >\n {rowVirtualizer.getVirtualItems().map((virtualRow) => {\n const key = keys[virtualRow.index];\n return (\n <div\n key={virtualRow.key}\n data-index={virtualRow.index}\n ref={rowVirtualizer.measureElement}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n transform: `translateY(${virtualRow.start}px)`,\n }}\n className=\"pb-4\"\n >\n <NodeView\n sectionKey={key}\n section={section?.[key as keyof typeof section]}\n keyPath={[\n ...keyPath,\n { type: NodeTypes.OBJECT, key } as KeyPath,\n ]}\n dictionaryLocalId={dictionaryLocalId}\n />\n </div>\n );\n })}\n </div>\n </div>\n\n <Button\n label={addNodeButton.label.value}\n variant={ButtonVariant.HOVERABLE}\n size={ButtonSize.MD}\n color={ButtonColor.TEXT}\n Icon={Plus}\n className=\"mt-2 flex-1\"\n onClick={() => {\n const newKey = 'newKey';\n const newKeyPath = [\n ...keyPath,\n { type: NodeTypes.OBJECT, key: newKey },\n ] as KeyPath[];\n addEditedContent(dictionaryLocalId, '', newKeyPath);\n setFocusedContentKeyPath(newKeyPath);\n }}\n >\n {addNodeButton.text}\n </Button>\n </div>\n );\n};\n\ntype StructureViewProps = {\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n};\n\nexport const StructureView: FC<StructureViewProps> = ({\n section,\n keyPath,\n dictionaryLocalId,\n}) => {\n if (\n !section ||\n typeof section !== 'object' ||\n typeof section.nodeType === 'string'\n ) {\n return (\n <NodeView\n sectionKey={'content'}\n section={section}\n keyPath={keyPath}\n dictionaryLocalId={dictionaryLocalId}\n />\n );\n }\n\n return (\n <ObjectView\n section={section}\n keyPath={keyPath}\n dictionaryLocalId={dictionaryLocalId}\n />\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StructureView.mjs","names":[],"sources":["../../../../../src/components/DictionaryFieldEditor/StructureView/StructureView.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n ButtonColor,\n ButtonSize,\n ButtonVariant,\n} from '@components/Button';\nimport { Container } from '@components/Container';\nimport { EditableFieldInput } from '@components/EditableField';\nimport { InputVariant } from '@components/Input';\nimport { camelCaseToSentence } from '@intlayer/config/client';\nimport {\n getDefaultNode,\n getNodeChildren,\n getNodeType,\n} from '@intlayer/core/dictionaryManipulator';\nimport { isSameKeyPath } from '@intlayer/core/utils';\nimport {\n useConfiguration,\n useEditedContentActions,\n useFocusUnmergedDictionary,\n} from '@intlayer/editor-react';\nimport type { LocalDictionaryId, TypedNode } from '@intlayer/types/dictionary';\nimport type { KeyPath } from '@intlayer/types/keyPath';\nimport * as NodeTypes from '@intlayer/types/nodeType';\nimport type { ContentNode } from 'intlayer';\nimport { Plus, Trash } from 'lucide-react';\nimport type { FC } from 'react';\nimport { useIntlayer } from 'react-intlayer';\nimport { NodeTypeSelector } from '../NodeTypeSelector';\n\ntype NodeTypeViewProps = {\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n onNodeTypeChange: (content?: ContentNode) => void;\n};\n\nconst NodeTypeView: FC<NodeTypeViewProps> = ({\n section,\n dictionaryLocalId,\n keyPath,\n onNodeTypeChange: onNodeTypeChangeProp,\n}) => {\n const locales = useConfiguration()?.internationalization?.locales ?? [];\n const nodeType = getNodeType(section);\n const children = getNodeChildren(section);\n\n const onNodeTypeChange = (content?: ContentNode) => {\n const transformedContent = getDefaultNode(\n nodeType,\n locales,\n content\n ) as ContentNode;\n\n onNodeTypeChangeProp(transformedContent);\n };\n\n if (\n nodeType === NodeTypes.TRANSLATION ||\n nodeType === NodeTypes.CONDITION ||\n nodeType === NodeTypes.GENDER ||\n nodeType === NodeTypes.ENUMERATION ||\n nodeType === NodeTypes.PLURAL\n ) {\n const firstKey = Object.keys(\n (section as unknown as TypedNode)[nodeType as keyof typeof section]\n )[0];\n const childrenKeyPath = [\n ...keyPath,\n { type: nodeType, key: firstKey },\n ] as KeyPath[];\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(\n getDefaultNode(nodeType, locales) as ContentNode\n )\n }\n />\n\n <NodeTypeView\n section={children}\n keyPath={childrenKeyPath}\n dictionaryLocalId={dictionaryLocalId}\n onNodeTypeChange={onNodeTypeChange}\n />\n </div>\n );\n }\n\n if (nodeType === NodeTypes.ARRAY) {\n const childrenKeyPath = [...keyPath, { type: nodeType, key: 0 } as KeyPath];\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(\n getDefaultNode(nodeType, locales) as ContentNode\n )\n }\n />\n\n <NodeTypeView\n section={children}\n keyPath={childrenKeyPath}\n dictionaryLocalId={dictionaryLocalId}\n onNodeTypeChange={onNodeTypeChange}\n />\n </div>\n );\n }\n\n if (nodeType === NodeTypes.OBJECT) {\n return (\n <>\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(\n getDefaultNode(nodeType, locales) as ContentNode\n )\n }\n />\n <div className=\"mt-6 ml-10\">\n <StructureView\n keyPath={keyPath}\n section={section}\n dictionaryLocalId={dictionaryLocalId}\n />\n </div>\n </>\n );\n }\n\n return (\n <NodeTypeSelector\n section={section}\n onValueChange={(nodeType) =>\n onNodeTypeChangeProp(getDefaultNode(nodeType, locales) as ContentNode)\n }\n />\n );\n};\n\ntype NodeWrapperProps = {\n sectionKey?: string;\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n};\n\nexport const NodeView: FC<NodeWrapperProps> = ({\n sectionKey,\n section,\n keyPath,\n dictionaryLocalId,\n}) => {\n const { focusedContent, setFocusedContentKeyPath } =\n useFocusUnmergedDictionary();\n const { renameEditedContent, addEditedContent } = useEditedContentActions();\n\n const { titleInput, deleteButton } = useIntlayer('structure-view');\n\n const handleRenameNodeKey = (keyName: string) => {\n renameEditedContent(dictionaryLocalId, keyName, keyPath);\n const prevKeyPath: KeyPath[] = keyPath.slice(0, -1);\n const lastKeyPath: KeyPath = keyPath[keyPath.length - 1];\n const newKeyPath: KeyPath[] = [\n ...prevKeyPath,\n { ...lastKeyPath, key: keyName } as KeyPath,\n ];\n setFocusedContentKeyPath(newKeyPath);\n };\n\n return (\n <Container\n transparency=\"xl\"\n roundedSize=\"xl\"\n className=\"w-full min-w-80 gap-2 overflow-auto px-5 py-2\"\n border\n borderColor=\"text\"\n background=\"none\"\n aria-selected={isSameKeyPath(keyPath, focusedContent?.keyPath ?? [])}\n onClick={() => setFocusedContentKeyPath(keyPath)}\n >\n <div className=\"flex w-full flex-col items-start justify-between gap-3\">\n {typeof sectionKey === 'string' && (\n <div className=\"w-full\">\n <div className=\"flex w-full items-center justify-between gap-10\">\n <EditableFieldInput\n name=\"key\"\n aria-label=\"Key\"\n placeholder={titleInput.placeholder.value}\n defaultValue={sectionKey}\n onSave={(value) => handleRenameNodeKey(value)}\n variant={InputVariant.INVISIBLE}\n />\n <Button\n label={deleteButton.label.value}\n variant={ButtonVariant.HOVERABLE}\n size={ButtonSize.ICON_SM}\n color={ButtonColor.TEXT}\n className=\"translate-x-2\"\n Icon={Trash}\n onClick={() => {\n addEditedContent(dictionaryLocalId, undefined, keyPath);\n\n const parentKeyPath: KeyPath[] = keyPath.slice(0, -1);\n setFocusedContentKeyPath(parentKeyPath);\n }}\n />\n </div>\n\n <span className=\"ml-3 text-neutral text-sm\">\n ( {camelCaseToSentence(sectionKey)} )\n </span>\n </div>\n )}\n <NodeTypeView\n keyPath={keyPath}\n dictionaryLocalId={dictionaryLocalId}\n section={section}\n onNodeTypeChange={(content) => {\n addEditedContent(dictionaryLocalId, content, keyPath);\n }}\n />\n </div>\n </Container>\n );\n};\n\ntype ObjectViewProps = {\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n};\n\nexport const ObjectView: FC<ObjectViewProps> = ({\n section,\n keyPath,\n dictionaryLocalId,\n}) => {\n const { addNodeButton } = useIntlayer('structure-view');\n const { setFocusedContentKeyPath } = useFocusUnmergedDictionary();\n const { addEditedContent } = useEditedContentActions();\n\n if (!section || typeof section !== 'object') {\n return <div>Not an object</div>;\n }\n\n return (\n <div className=\"flex flex-col gap-2 overflow-y-auto\">\n <ul className=\"mr-auto flex flex-col gap-4\">\n {Object.keys(section).map((key) => (\n <li\n key={`${JSON.stringify(keyPath)}-object-${key}`}\n className=\"flex w-full\"\n >\n <NodeView\n sectionKey={key}\n section={section?.[key as keyof typeof section]}\n keyPath={[...keyPath, { type: NodeTypes.OBJECT, key }]}\n dictionaryLocalId={dictionaryLocalId}\n />\n </li>\n ))}\n </ul>\n <Button\n label={addNodeButton.label.value}\n variant={ButtonVariant.HOVERABLE}\n size={ButtonSize.MD}\n color={ButtonColor.TEXT}\n Icon={Plus}\n className=\"flex-1\"\n onClick={() => {\n const newKey = 'newKey';\n const newKeyPath = [\n ...keyPath,\n { type: NodeTypes.OBJECT, key: newKey },\n ] as KeyPath[];\n addEditedContent(dictionaryLocalId, '', newKeyPath);\n setFocusedContentKeyPath(newKeyPath);\n }}\n >\n {addNodeButton.text}\n </Button>\n </div>\n );\n};\n\ntype StructureViewProps = {\n dictionaryLocalId: LocalDictionaryId;\n keyPath: KeyPath[];\n section: ContentNode;\n};\n\nexport const StructureView: FC<StructureViewProps> = ({\n section,\n keyPath,\n dictionaryLocalId,\n}) => {\n if (\n !section ||\n typeof section !== 'object' ||\n typeof section.nodeType === 'string'\n ) {\n return (\n <NodeView\n sectionKey={'content'}\n section={section}\n keyPath={keyPath}\n dictionaryLocalId={dictionaryLocalId}\n />\n );\n }\n\n return (\n <ObjectView\n section={section}\n keyPath={keyPath}\n dictionaryLocalId={dictionaryLocalId}\n />\n );\n};\n"],"mappings":"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"}
|
|
@@ -157,7 +157,7 @@ let DropDownYAlign = /* @__PURE__ */ function(DropDownYAlign) {
|
|
|
157
157
|
* - Keyboard navigation support when isFocusable is enabled
|
|
158
158
|
* - Screen reader announcements for state changes
|
|
159
159
|
*/
|
|
160
|
-
const Panel = ({ children, isHidden = void 0, isOverable = false, isFocusable = false, align = "start", yAlign = "below", identifier, className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
160
|
+
const Panel = ({ children, isHidden = void 0, isOverable = false, isFocusable = false, align = "start", yAlign = "below", identifier, className, smootherClassName, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
161
161
|
className: cn("absolute z-100 min-w-full", align === "start" && "left-0", align === "end" && "right-0", yAlign === "below" && "top-[calc(100%+0.5rem)]", yAlign === "above" && "bottom-[calc(100%+0.5rem)]", className),
|
|
162
162
|
"aria-hidden": isHidden,
|
|
163
163
|
role: "region",
|
|
@@ -165,7 +165,7 @@ const Panel = ({ children, isHidden = void 0, isOverable = false, isFocusable =
|
|
|
165
165
|
id: `dropdown-panel-${identifier}`,
|
|
166
166
|
children: /* @__PURE__ */ jsx(MaxHeightSmoother, {
|
|
167
167
|
isHidden,
|
|
168
|
-
className: cn("overflow-x-visible", isHidden === false && "invisible", isHidden === true && "visible", isOverable && "group-hover/dropdown:visible group-hover/dropdown:grid-rows-[1fr]", isFocusable && "group-focus-within/dropdown:visible group-focus-within/dropdown:grid-rows-[1fr]"),
|
|
168
|
+
className: cn("overflow-x-visible", isHidden === false && "invisible", isHidden === true && "visible", isOverable && "group-hover/dropdown:visible group-hover/dropdown:grid-rows-[1fr]", isFocusable && "group-focus-within/dropdown:visible group-focus-within/dropdown:grid-rows-[1fr]", smootherClassName),
|
|
169
169
|
...props,
|
|
170
170
|
children
|
|
171
171
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/DropDown/index.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport type { FC, HTMLAttributes } from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the DropDown component\n */\nexport interface DropDownProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Unique identifier that links the trigger and panel for accessibility.\n * This is used to generate proper ARIA attributes.\n * @example \"user-menu\"\n * @example \"language-selector\"\n */\n identifier: string;\n}\n\nexport type DropDownType = FC<DropDownProps> & {\n Trigger: FC<TriggerProps>;\n Panel: FC<PanelProps>;\n};\n\n/**\n * DropDown Component\n *\n * A compound component that provides dropdown/popover functionality with flexible trigger mechanisms.\n * Supports hover, focus, and controlled visibility states with proper accessibility features.\n *\n * @example\n * ```tsx\n * // Basic hover dropdown\n * <DropDown identifier=\"menu\">\n * <DropDown.Trigger identifier=\"menu\">\n * Open Menu\n * </DropDown.Trigger>\n * <DropDown.Panel identifier=\"menu\" isOverable>\n * <div>Menu content</div>\n * </DropDown.Panel>\n * </DropDown>\n *\n * // Focus-based dropdown for accessibility\n * <DropDown identifier=\"accessible-menu\">\n * <DropDown.Trigger identifier=\"accessible-menu\">\n * Keyboard Accessible Menu\n * </DropDown.Trigger>\n * <DropDown.Panel identifier=\"accessible-menu\" isFocusable>\n * <div>Accessible content</div>\n * </DropDown.Panel>\n * </DropDown>\n *\n * // Controlled dropdown\n * <DropDown identifier=\"controlled\">\n * <DropDown.Trigger identifier=\"controlled\">\n * Controlled Menu\n * </DropDown.Trigger>\n * <DropDown.Panel identifier=\"controlled\" isHidden={!isOpen}>\n * <div>Controlled content</div>\n * </DropDown.Panel>\n * </DropDown>\n * ```\n *\n * @component\n * @accessibility\n * - Uses proper ARIA attributes (aria-haspopup, aria-labelledby, etc.)\n * - Supports keyboard navigation with focus management\n * - Screen reader compatible with proper role and labeling\n * - Maintains focus trap within dropdown when needed\n */\nexport const DropDown: DropDownType = ({\n children,\n className,\n identifier,\n ...props\n}) => (\n <div\n className={cn(`group/dropdown relative flex`, className)}\n aria-label={`DropDown ${identifier}`}\n id={`dropdown-container-${identifier}`}\n {...props}\n >\n {children}\n </div>\n);\n\n/**\n * Props for the DropDown.Trigger component\n */\nexport interface TriggerProps extends Partial<ButtonProps> {\n /**\n * Unique identifier that matches the parent DropDown identifier\n * @example \"user-menu\"\n */\n identifier: string;\n}\n\n/**\n * DropDown.Trigger Component\n *\n * The clickable/focusable element that controls the dropdown panel visibility.\n * Built on top of the Button component with enhanced dropdown-specific behaviors.\n *\n * @example\n * ```tsx\n * <DropDown.Trigger identifier=\"menu\">\n * <div>Click to open</div>\n * </DropDown.Trigger>\n * ```\n *\n * @component\n * @accessibility\n * - Automatically generates appropriate ARIA attributes\n * - Maintains proper focus management across browsers\n * - Works with keyboard navigation (Tab, Enter, Space)\n * - Announces dropdown state to screen readers\n *\n * @note Don't nest Button components inside the Trigger - it's already a button\n */\nconst Trigger: FC<TriggerProps> = ({\n children,\n identifier,\n className,\n label,\n ...props\n}) => (\n <Button\n className={cn([\n 'w-full cursor-pointer',\n 'group-focus-within/dropdown:bg-current/20 group-focus-within/dropdown:ring-4',\n className,\n ])}\n label={label ?? `Open panel ${identifier}`}\n aria-haspopup=\"true\"\n aria-controls={`dropdown-panel-${identifier}`}\n id={`dropdown-trigger-${identifier}`}\n onClick={(e) => {\n // Ensure focus behavior is consistent across all mobile browsers\n (e.currentTarget as HTMLButtonElement).focus();\n }}\n variant=\"none\"\n {...props}\n >\n {children}\n </Button>\n);\n\n/**\n * Horizontal alignment options for the dropdown panel relative to the trigger\n */\nexport enum DropDownAlign {\n /** Align panel to the start (left in LTR, right in RTL) of the trigger */\n START = 'start',\n /** Align panel to the end (right in LTR, left in RTL) of the trigger */\n END = 'end',\n}\n\n/**\n * Vertical alignment options for the dropdown panel relative to the trigger\n */\nexport enum DropDownYAlign {\n /** Position panel below the trigger (default) */\n BELOW = 'below',\n /** Position panel above the trigger */\n ABOVE = 'above',\n}\n\n/**\n * Props for the DropDown.Panel component\n */\nexport interface PanelProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Whether the panel should be visible when the trigger is focused.\n * Enables keyboard accessibility for the dropdown.\n * @default false\n */\n isFocusable?: boolean;\n\n /**\n * Controls panel visibility explicitly.\n * - `true`: Panel is hidden\n * - `false`: Panel is visible\n * - `undefined`: Panel visibility controlled by hover/focus states\n * @default undefined\n */\n isHidden?: boolean;\n\n /**\n * Whether the panel should be visible when hovering over the trigger.\n * Provides quick access via mouse interaction.\n * @default false\n */\n isOverable?: boolean;\n\n /**\n * Unique identifier that matches the parent DropDown identifier\n * @example \"user-menu\"\n */\n identifier: string;\n\n /**\n * Horizontal alignment of the panel relative to the trigger\n * @default DropDownAlign.START\n */\n align?: DropDownAlign | `${DropDownAlign}`;\n\n /**\n * Vertical alignment of the panel relative to the trigger\n * @default DropDownYAlign.BELOW\n */\n yAlign?: DropDownYAlign | `${DropDownYAlign}`;\n}\n\n/**\n * DropDown.Panel Component\n *\n * The content area that appears when the dropdown is triggered.\n * Supports multiple trigger methods (hover, focus, controlled) with smooth animations.\n *\n * @example\n * ```tsx\n * // Hover-triggered panel\n * <DropDown.Panel identifier=\"menu\" isOverable>\n * <div>Content appears on hover</div>\n * </DropDown.Panel>\n *\n * // Focus-triggered panel (accessible)\n * <DropDown.Panel identifier=\"menu\" isFocusable>\n * <div>Content appears on focus</div>\n * </DropDown.Panel>\n *\n * // Controlled panel\n * <DropDown.Panel identifier=\"menu\" isHidden={!isOpen}>\n * <div>Content visibility controlled externally</div>\n * </DropDown.Panel>\n *\n * // Right-aligned panel\n * <DropDown.Panel identifier=\"menu\" align={DropDownAlign.END} isOverable>\n * <div>Right-aligned content</div>\n * </DropDown.Panel>\n *\n * // Panel opening above the trigger\n * <DropDown.Panel identifier=\"menu\" yAlign={DropDownYAlign.ABOVE} isOverable>\n * <div>Content appears above</div>\n * </DropDown.Panel>\n * ```\n *\n * @component\n * @accessibility\n * - Proper ARIA attributes (role, aria-labelledby, aria-hidden)\n * - Smooth height transitions with MaxHeightSmoother\n * - Keyboard navigation support when isFocusable is enabled\n * - Screen reader announcements for state changes\n */\nconst Panel: FC<PanelProps> = ({\n children,\n isHidden = undefined,\n isOverable = false,\n isFocusable = false,\n align = DropDownAlign.START,\n yAlign = DropDownYAlign.BELOW,\n identifier,\n className,\n ...props\n}) => (\n <div\n className={cn(\n 'absolute z-100 min-w-full',\n /* Horizontal positioning */\n align === DropDownAlign.START && 'left-0',\n align === DropDownAlign.END && 'right-0',\n /* Vertical positioning */\n yAlign === DropDownYAlign.BELOW && 'top-[calc(100%+0.5rem)]',\n yAlign === DropDownYAlign.ABOVE && 'bottom-[calc(100%+0.5rem)]',\n className\n )}\n aria-hidden={isHidden}\n role=\"region\"\n aria-labelledby={`dropdown-trigger-${identifier}`}\n id={`dropdown-panel-${identifier}`}\n >\n <MaxHeightSmoother\n isHidden={isHidden}\n className={cn(\n 'overflow-x-visible',\n isHidden === false && 'invisible',\n isHidden === true && 'visible',\n isOverable &&\n 'group-hover/dropdown:visible group-hover/dropdown:grid-rows-[1fr]',\n isFocusable &&\n 'group-focus-within/dropdown:visible group-focus-within/dropdown:grid-rows-[1fr]'\n )}\n {...props}\n >\n {children}\n </MaxHeightSmoother>\n </div>\n);\n\nDropDown.Trigger = Trigger;\nDropDown.Panel = Panel;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEA,MAAa,YAA0B,EACrC,UACA,WACA,YACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GAAG,gCAAgC,UAAU;CACxD,cAAY,YAAY;CACxB,IAAI,sBAAsB;CAC1B,GAAI;CAEH;CACG;;;;;;;;;;;;;;;;;;;;;;;AAoCR,MAAM,WAA6B,EACjC,UACA,YACA,WACA,OACA,GAAG,YAEH,oBAAC,QAAD;CACE,WAAW,GAAG;EACZ;EACA;EACA;EACD,CAAC;CACF,OAAO,SAAS,cAAc;CAC9B,iBAAc;CACd,iBAAe,kBAAkB;CACjC,IAAI,oBAAoB;CACxB,UAAU,MAAM;EAEd,AAAC,EAAE,cAAoC,OAAO;;CAEhD,SAAQ;CACR,GAAI;CAEH;CACM;;;;AAMX,IAAY,gBAAL;;CAEL;;CAEA;;KACD;;;;AAKD,IAAY,iBAAL;;CAEL;;CAEA;;KACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFD,MAAM,SAAyB,EAC7B,UACA,WAAW,QACX,aAAa,OACb,cAAc,OACd,iBACA,kBACA,YACA,WACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GACT,6BAEA,qBAAiC,UACjC,mBAA+B,WAE/B,sBAAmC,2BACnC,sBAAmC,8BACnC,UACD;CACD,eAAa;CACb,MAAK;CACL,mBAAiB,oBAAoB;CACrC,IAAI,kBAAkB;WAEtB,oBAAC,mBAAD;EACY;EACV,WAAW,GACT,sBACA,aAAa,SAAS,aACtB,aAAa,QAAQ,WACrB,cACE,qEACF,eACE,kFACH;EACD,GAAI;EAEH;EACiB;CAChB;AAGR,SAAS,UAAU;AACnB,SAAS,QAAQ"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/DropDown/index.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport type { FC, HTMLAttributes } from 'react';\nimport { Button, type ButtonProps } from '../Button';\nimport { MaxHeightSmoother } from '../MaxHeightSmoother';\n\n/**\n * Props for the DropDown component\n */\nexport interface DropDownProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Unique identifier that links the trigger and panel for accessibility.\n * This is used to generate proper ARIA attributes.\n * @example \"user-menu\"\n * @example \"language-selector\"\n */\n identifier: string;\n}\n\nexport type DropDownType = FC<DropDownProps> & {\n Trigger: FC<TriggerProps>;\n Panel: FC<PanelProps>;\n};\n\n/**\n * DropDown Component\n *\n * A compound component that provides dropdown/popover functionality with flexible trigger mechanisms.\n * Supports hover, focus, and controlled visibility states with proper accessibility features.\n *\n * @example\n * ```tsx\n * // Basic hover dropdown\n * <DropDown identifier=\"menu\">\n * <DropDown.Trigger identifier=\"menu\">\n * Open Menu\n * </DropDown.Trigger>\n * <DropDown.Panel identifier=\"menu\" isOverable>\n * <div>Menu content</div>\n * </DropDown.Panel>\n * </DropDown>\n *\n * // Focus-based dropdown for accessibility\n * <DropDown identifier=\"accessible-menu\">\n * <DropDown.Trigger identifier=\"accessible-menu\">\n * Keyboard Accessible Menu\n * </DropDown.Trigger>\n * <DropDown.Panel identifier=\"accessible-menu\" isFocusable>\n * <div>Accessible content</div>\n * </DropDown.Panel>\n * </DropDown>\n *\n * // Controlled dropdown\n * <DropDown identifier=\"controlled\">\n * <DropDown.Trigger identifier=\"controlled\">\n * Controlled Menu\n * </DropDown.Trigger>\n * <DropDown.Panel identifier=\"controlled\" isHidden={!isOpen}>\n * <div>Controlled content</div>\n * </DropDown.Panel>\n * </DropDown>\n * ```\n *\n * @component\n * @accessibility\n * - Uses proper ARIA attributes (aria-haspopup, aria-labelledby, etc.)\n * - Supports keyboard navigation with focus management\n * - Screen reader compatible with proper role and labeling\n * - Maintains focus trap within dropdown when needed\n */\nexport const DropDown: DropDownType = ({\n children,\n className,\n identifier,\n ...props\n}) => (\n <div\n className={cn(`group/dropdown relative flex`, className)}\n aria-label={`DropDown ${identifier}`}\n id={`dropdown-container-${identifier}`}\n {...props}\n >\n {children}\n </div>\n);\n\n/**\n * Props for the DropDown.Trigger component\n */\nexport interface TriggerProps extends Partial<ButtonProps> {\n /**\n * Unique identifier that matches the parent DropDown identifier\n * @example \"user-menu\"\n */\n identifier: string;\n}\n\n/**\n * DropDown.Trigger Component\n *\n * The clickable/focusable element that controls the dropdown panel visibility.\n * Built on top of the Button component with enhanced dropdown-specific behaviors.\n *\n * @example\n * ```tsx\n * <DropDown.Trigger identifier=\"menu\">\n * <div>Click to open</div>\n * </DropDown.Trigger>\n * ```\n *\n * @component\n * @accessibility\n * - Automatically generates appropriate ARIA attributes\n * - Maintains proper focus management across browsers\n * - Works with keyboard navigation (Tab, Enter, Space)\n * - Announces dropdown state to screen readers\n *\n * @note Don't nest Button components inside the Trigger - it's already a button\n */\nconst Trigger: FC<TriggerProps> = ({\n children,\n identifier,\n className,\n label,\n ...props\n}) => (\n <Button\n className={cn([\n 'w-full cursor-pointer',\n 'group-focus-within/dropdown:bg-current/20 group-focus-within/dropdown:ring-4',\n className,\n ])}\n label={label ?? `Open panel ${identifier}`}\n aria-haspopup=\"true\"\n aria-controls={`dropdown-panel-${identifier}`}\n id={`dropdown-trigger-${identifier}`}\n onClick={(e) => {\n // Ensure focus behavior is consistent across all mobile browsers\n (e.currentTarget as HTMLButtonElement).focus();\n }}\n variant=\"none\"\n {...props}\n >\n {children}\n </Button>\n);\n\n/**\n * Horizontal alignment options for the dropdown panel relative to the trigger\n */\nexport enum DropDownAlign {\n /** Align panel to the start (left in LTR, right in RTL) of the trigger */\n START = 'start',\n /** Align panel to the end (right in LTR, left in RTL) of the trigger */\n END = 'end',\n}\n\n/**\n * Vertical alignment options for the dropdown panel relative to the trigger\n */\nexport enum DropDownYAlign {\n /** Position panel below the trigger (default) */\n BELOW = 'below',\n /** Position panel above the trigger */\n ABOVE = 'above',\n}\n\n/**\n * Props for the DropDown.Panel component\n */\nexport interface PanelProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Whether the panel should be visible when the trigger is focused.\n * Enables keyboard accessibility for the dropdown.\n * @default false\n */\n isFocusable?: boolean;\n\n /**\n * Controls panel visibility explicitly.\n * - `true`: Panel is hidden\n * - `false`: Panel is visible\n * - `undefined`: Panel visibility controlled by hover/focus states\n * @default undefined\n */\n isHidden?: boolean;\n\n /**\n * Whether the panel should be visible when hovering over the trigger.\n * Provides quick access via mouse interaction.\n * @default false\n */\n isOverable?: boolean;\n\n /**\n * Unique identifier that matches the parent DropDown identifier\n * @example \"user-menu\"\n */\n identifier: string;\n\n /**\n * Horizontal alignment of the panel relative to the trigger\n * @default DropDownAlign.START\n */\n align?: DropDownAlign | `${DropDownAlign}`;\n\n /**\n * Vertical alignment of the panel relative to the trigger\n * @default DropDownYAlign.BELOW\n */\n yAlign?: DropDownYAlign | `${DropDownYAlign}`;\n\n /**\n * Additional className applied directly to the MaxHeightSmoother container.\n * Useful for adding transition delays — e.g. `\"delay-0 group-hover/dropdown:delay-500\"`\n * gives an open delay while keeping the close instant.\n */\n smootherClassName?: string;\n}\n\n/**\n * DropDown.Panel Component\n *\n * The content area that appears when the dropdown is triggered.\n * Supports multiple trigger methods (hover, focus, controlled) with smooth animations.\n *\n * @example\n * ```tsx\n * // Hover-triggered panel\n * <DropDown.Panel identifier=\"menu\" isOverable>\n * <div>Content appears on hover</div>\n * </DropDown.Panel>\n *\n * // Focus-triggered panel (accessible)\n * <DropDown.Panel identifier=\"menu\" isFocusable>\n * <div>Content appears on focus</div>\n * </DropDown.Panel>\n *\n * // Controlled panel\n * <DropDown.Panel identifier=\"menu\" isHidden={!isOpen}>\n * <div>Content visibility controlled externally</div>\n * </DropDown.Panel>\n *\n * // Right-aligned panel\n * <DropDown.Panel identifier=\"menu\" align={DropDownAlign.END} isOverable>\n * <div>Right-aligned content</div>\n * </DropDown.Panel>\n *\n * // Panel opening above the trigger\n * <DropDown.Panel identifier=\"menu\" yAlign={DropDownYAlign.ABOVE} isOverable>\n * <div>Content appears above</div>\n * </DropDown.Panel>\n * ```\n *\n * @component\n * @accessibility\n * - Proper ARIA attributes (role, aria-labelledby, aria-hidden)\n * - Smooth height transitions with MaxHeightSmoother\n * - Keyboard navigation support when isFocusable is enabled\n * - Screen reader announcements for state changes\n */\nconst Panel: FC<PanelProps> = ({\n children,\n isHidden = undefined,\n isOverable = false,\n isFocusable = false,\n align = DropDownAlign.START,\n yAlign = DropDownYAlign.BELOW,\n identifier,\n className,\n smootherClassName,\n ...props\n}) => (\n <div\n className={cn(\n 'absolute z-100 min-w-full',\n /* Horizontal positioning */\n align === DropDownAlign.START && 'left-0',\n align === DropDownAlign.END && 'right-0',\n /* Vertical positioning */\n yAlign === DropDownYAlign.BELOW && 'top-[calc(100%+0.5rem)]',\n yAlign === DropDownYAlign.ABOVE && 'bottom-[calc(100%+0.5rem)]',\n className\n )}\n aria-hidden={isHidden}\n role=\"region\"\n aria-labelledby={`dropdown-trigger-${identifier}`}\n id={`dropdown-panel-${identifier}`}\n >\n <MaxHeightSmoother\n isHidden={isHidden}\n className={cn(\n 'overflow-x-visible',\n isHidden === false && 'invisible',\n isHidden === true && 'visible',\n isOverable &&\n 'group-hover/dropdown:visible group-hover/dropdown:grid-rows-[1fr]',\n isFocusable &&\n 'group-focus-within/dropdown:visible group-focus-within/dropdown:grid-rows-[1fr]',\n smootherClassName\n )}\n {...props}\n >\n {children}\n </MaxHeightSmoother>\n </div>\n);\n\nDropDown.Trigger = Trigger;\nDropDown.Panel = Panel;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEA,MAAa,YAA0B,EACrC,UACA,WACA,YACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GAAG,gCAAgC,UAAU;CACxD,cAAY,YAAY;CACxB,IAAI,sBAAsB;CAC1B,GAAI;CAEH;CACG;;;;;;;;;;;;;;;;;;;;;;;AAoCR,MAAM,WAA6B,EACjC,UACA,YACA,WACA,OACA,GAAG,YAEH,oBAAC,QAAD;CACE,WAAW,GAAG;EACZ;EACA;EACA;EACD,CAAC;CACF,OAAO,SAAS,cAAc;CAC9B,iBAAc;CACd,iBAAe,kBAAkB;CACjC,IAAI,oBAAoB;CACxB,UAAU,MAAM;EAEd,AAAC,EAAE,cAAoC,OAAO;;CAEhD,SAAQ;CACR,GAAI;CAEH;CACM;;;;AAMX,IAAY,gBAAL;;CAEL;;CAEA;;KACD;;;;AAKD,IAAY,iBAAL;;CAEL;;CAEA;;KACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGD,MAAM,SAAyB,EAC7B,UACA,WAAW,QACX,aAAa,OACb,cAAc,OACd,iBACA,kBACA,YACA,WACA,mBACA,GAAG,YAEH,oBAAC,OAAD;CACE,WAAW,GACT,6BAEA,qBAAiC,UACjC,mBAA+B,WAE/B,sBAAmC,2BACnC,sBAAmC,8BACnC,UACD;CACD,eAAa;CACb,MAAK;CACL,mBAAiB,oBAAoB;CACrC,IAAI,kBAAkB;WAEtB,oBAAC,mBAAD;EACY;EACV,WAAW,GACT,sBACA,aAAa,SAAS,aACtB,aAAa,QAAQ,WACrB,cACE,qEACF,eACE,mFACF,kBACD;EACD,GAAI;EAEH;EACiB;CAChB;AAGR,SAAS,UAAU;AACnB,SAAS,QAAQ"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
|
|
4
3
|
import { InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "../../Input/OTPInput.mjs";
|
|
4
|
+
import { useItemSelector } from "../../../hooks/useItemSelector.mjs";
|
|
5
5
|
import { useFormField } from "../FormField.mjs";
|
|
6
6
|
import { FormItemLayout } from "../layout/FormItemLayout.mjs";
|
|
7
7
|
import { Form } from "../Form.mjs";
|