@intlayer/design-system 9.3.1 → 9.3.3
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/api/hooks/auth.mjs +2 -0
- package/dist/esm/api/hooks/auth.mjs.map +1 -1
- package/dist/esm/api/index.mjs +2 -2
- package/dist/esm/components/Browser/Browser.mjs +1 -1
- package/dist/esm/components/Browser/Browser.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/CopyButton/index.mjs +2 -1
- package/dist/esm/components/CopyButton/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs +4 -5
- package/dist/esm/components/DictionaryFieldEditor/ContentEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +6 -10
- 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/DictionaryCreationForm/useDictionaryFormSchema.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +6 -6
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +3 -7
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs +19 -12
- package/dist/esm/components/ExpandCollapse/ExpandCollapse.mjs.map +1 -1
- package/dist/esm/components/Form/FormBase.mjs.map +1 -1
- package/dist/esm/components/Form/elements/FormElementWrapper.mjs +1 -1
- package/dist/esm/components/Form/elements/FormElementWrapper.mjs.map +1 -1
- package/dist/esm/components/HTMLRender/HTMLRender.mjs +2 -2
- package/dist/esm/components/HTMLRender/HTMLRender.mjs.map +1 -1
- package/dist/esm/components/IDE/Code.mjs +3 -3
- package/dist/esm/components/IDE/Code.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockClient.mjs +16 -4
- package/dist/esm/components/IDE/CodeBlockClient.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs +14 -5
- package/dist/esm/components/IDE/CodeBlockHighlight.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockServer.mjs +5 -3
- package/dist/esm/components/IDE/CodeBlockServer.mjs.map +1 -1
- package/dist/esm/components/IDE/CodeBlockShiki.mjs +18 -18
- package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
- package/dist/esm/components/IDE/IDE.mjs +2 -5
- package/dist/esm/components/IDE/IDE.mjs.map +1 -1
- package/dist/esm/components/IDE/MarkDownRender.mjs +1 -2
- package/dist/esm/components/IDE/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/IDE/index.mjs +2 -2
- package/dist/esm/components/IDE/shikiThemes.mjs +18 -0
- package/dist/esm/components/IDE/shikiThemes.mjs.map +1 -0
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs +105 -4
- package/dist/esm/components/MarkDownRender/MarkDownIframe.mjs.map +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +16 -16
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxHeightSmoother/index.mjs.map +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs +1 -1
- package/dist/esm/components/MaxWidthSmoother/index.mjs.map +1 -1
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs +100 -15
- package/dist/esm/components/Pattern/GridDistortionPattern.mjs.map +1 -1
- package/dist/esm/components/Popover/dynamic.mjs +5 -4
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/Popover/index.mjs +2 -2
- package/dist/esm/components/Popover/static.mjs +47 -18
- package/dist/esm/components/Popover/static.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs +37 -16
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs +3 -2
- package/dist/esm/components/TextArea/AutoSizeTextArea.mjs.map +1 -1
- package/dist/esm/components/index.mjs +3 -3
- package/dist/esm/hooks/useDevice.mjs +16 -6
- package/dist/esm/hooks/useDevice.mjs.map +1 -1
- package/dist/esm/hooks/useItemSelector.mjs +107 -112
- package/dist/esm/hooks/useItemSelector.mjs.map +1 -1
- package/dist/esm/libs/auth.mjs +16 -3
- package/dist/esm/libs/auth.mjs.map +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs +1 -1
- package/dist/esm/structured-data/buildCreativeWorkJsonLd.mjs.map +1 -1
- package/dist/types/api/hooks/auth.d.ts.map +1 -1
- package/dist/types/components/Browser/Browser.d.ts +7 -1
- package/dist/types/components/Browser/Browser.d.ts.map +1 -1
- package/dist/types/components/CopyButton/index.d.ts +9 -3
- package/dist/types/components/CopyButton/index.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/ContentEditorView/TextEditor.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/useDictionaryFormSchema.d.ts +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts +0 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.d.ts +1 -1
- package/dist/types/components/ExpandCollapse/ExpandCollapse.d.ts.map +1 -1
- package/dist/types/components/Form/FormBase.d.ts +1 -1
- package/dist/types/components/Form/elements/FormElementWrapper.d.ts +1 -1
- package/dist/types/components/HTMLRender/HTMLRender.d.ts +1 -2
- package/dist/types/components/HTMLRender/HTMLRender.d.ts.map +1 -1
- package/dist/types/components/IDE/Code.d.ts +6 -1
- package/dist/types/components/IDE/Code.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockClient.d.ts +18 -2
- package/dist/types/components/IDE/CodeBlockClient.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts +3 -2
- package/dist/types/components/IDE/CodeBlockHighlight.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockServer.d.ts.map +1 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts +0 -1
- package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
- package/dist/types/components/IDE/IDE.d.ts +0 -1
- package/dist/types/components/IDE/IDE.d.ts.map +1 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts +0 -1
- package/dist/types/components/IDE/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/IDE/index.d.ts +2 -2
- package/dist/types/components/IDE/shikiThemes.d.ts +18 -0
- package/dist/types/components/IDE/shikiThemes.d.ts.map +1 -0
- package/dist/types/components/MarkDownRender/MarkDownIframe.d.ts.map +1 -1
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts +1 -2
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/Pattern/GridDistortionPattern.d.ts.map +1 -1
- package/dist/types/components/Popover/dynamic.d.ts.map +1 -1
- package/dist/types/components/Popover/index.d.ts +2 -2
- package/dist/types/components/Popover/static.d.ts +6 -1
- package/dist/types/components/Popover/static.d.ts.map +1 -1
- package/dist/types/components/Tab/Tab.d.ts +2 -2
- package/dist/types/components/Tab/Tab.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +3 -3
- package/dist/types/hooks/useItemSelector.d.ts.map +1 -1
- package/dist/types/libs/auth.d.ts +3 -0
- package/dist/types/libs/auth.d.ts.map +1 -1
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts +9 -3
- package/dist/types/structured-data/buildCreativeWorkJsonLd.d.ts.map +1 -1
- package/package.json +43 -38
- package/tailwind.css +27 -1
|
@@ -9,6 +9,14 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
import { cva } from "class-variance-authority";
|
|
10
10
|
|
|
11
11
|
//#region src/components/Tab/Tab.tsx
|
|
12
|
+
/**
|
|
13
|
+
* Builds an id fragment usable in `id` / `aria-controls`.
|
|
14
|
+
*
|
|
15
|
+
* Tab values come from markdown (`<Tab value="Extract command">`), so they can
|
|
16
|
+
* hold spaces — which makes `aria-controls` reference a list of ids rather than
|
|
17
|
+
* the one panel, and silently breaks the association for screen readers.
|
|
18
|
+
*/
|
|
19
|
+
const toIdFragment = (value) => value.trim().replace(/\s+/g, "-").replace(/[^\w-]/g, "");
|
|
12
20
|
const TabContext = createContext(void 0);
|
|
13
21
|
const tabContainerVariant = cva("relative w-full rounded-lg", {
|
|
14
22
|
variants: {
|
|
@@ -25,6 +33,18 @@ const tabContainerVariant = cva("relative w-full rounded-lg", {
|
|
|
25
33
|
defaultVariants: { variant: "default" }
|
|
26
34
|
});
|
|
27
35
|
/**
|
|
36
|
+
* Resolves the identifier of a tab.
|
|
37
|
+
*
|
|
38
|
+
* Both `label` and `value` are optional for markdown-authored tabs
|
|
39
|
+
* (`<Tab label="next-intl">`), so the identifier falls back to the label, then
|
|
40
|
+
* to the position, keeping every tab addressable instead of rendering ids from
|
|
41
|
+
* `undefined`.
|
|
42
|
+
*/
|
|
43
|
+
const resolveTabValue = ({ value, label }, index) => {
|
|
44
|
+
const identifier = value ?? label;
|
|
45
|
+
return typeof identifier === "string" ? identifier : String(index);
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
28
48
|
* TabItem component that represents a single tab
|
|
29
49
|
* Must be used as a child of the Tab component
|
|
30
50
|
*/
|
|
@@ -51,24 +71,27 @@ TabItem.displayName = "TabItem";
|
|
|
51
71
|
const TabComponent = ({ defaultTab, group, variant, children, className, headerClassName, fullHeight, ...props }) => {
|
|
52
72
|
const tabItems = Children.toArray(children).filter((child) => {
|
|
53
73
|
return isValidElement(child) && child.type === TabItem;
|
|
54
|
-
})
|
|
55
|
-
|
|
74
|
+
}).map((child, index) => ({
|
|
75
|
+
...child.props,
|
|
76
|
+
value: resolveTabValue(child.props, index)
|
|
77
|
+
}));
|
|
78
|
+
const firstTabValue = tabItems[0]?.value;
|
|
56
79
|
const { tabsValues, setTabsValues } = useTabContext();
|
|
57
80
|
const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? "");
|
|
58
81
|
const currentTabValue = (group && typeof tabsValues === "object" ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;
|
|
59
|
-
const activeTabIndex = tabItems.findIndex((tab) => tab.
|
|
82
|
+
const activeTabIndex = tabItems.findIndex((tab) => tab.value === currentTabValue);
|
|
60
83
|
const tabsCount = tabItems.length;
|
|
61
84
|
const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({
|
|
62
85
|
itemIndex: activeTabIndex,
|
|
63
86
|
itemCount: tabsCount,
|
|
64
87
|
onSwipeLeft: () => {
|
|
65
88
|
const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);
|
|
66
|
-
const nextValue = tabItems[targetIndex]?.
|
|
89
|
+
const nextValue = tabItems[targetIndex]?.value;
|
|
67
90
|
if (nextValue) handleSetActiveTab(nextValue);
|
|
68
91
|
},
|
|
69
92
|
onSwipeRight: () => {
|
|
70
93
|
const targetIndex = Math.max(0, activeTabIndex - 1);
|
|
71
|
-
const nextValue = tabItems[targetIndex]?.
|
|
94
|
+
const nextValue = tabItems[targetIndex]?.value;
|
|
72
95
|
if (nextValue) handleSetActiveTab(nextValue);
|
|
73
96
|
}
|
|
74
97
|
});
|
|
@@ -92,9 +115,9 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
|
|
|
92
115
|
className: cn("flex shrink-0 gap-3 p-3", headerClassName),
|
|
93
116
|
children: /* @__PURE__ */ jsx(TabSelector, {
|
|
94
117
|
selectedChoice: currentTabValue,
|
|
95
|
-
tabs: tabItems.map((
|
|
96
|
-
const { label, value, disabled } = child.props;
|
|
118
|
+
tabs: tabItems.map(({ label, value, disabled }) => {
|
|
97
119
|
const isActive = currentTabValue === value;
|
|
120
|
+
const idFragment = toIdFragment(value);
|
|
98
121
|
return /* @__PURE__ */ jsx("button", {
|
|
99
122
|
className: cn("cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none", !isActive && "text-neutral/70"),
|
|
100
123
|
"data-active": isActive,
|
|
@@ -102,8 +125,8 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
|
|
|
102
125
|
onClick: () => !disabled && handleSetActiveTab(value),
|
|
103
126
|
role: "tab",
|
|
104
127
|
"aria-selected": isActive,
|
|
105
|
-
"aria-controls": `tabpanel-${
|
|
106
|
-
id: `tab-${
|
|
128
|
+
"aria-controls": `tabpanel-${idFragment}`,
|
|
129
|
+
id: `tab-${idFragment}`,
|
|
107
130
|
type: "button",
|
|
108
131
|
children: label ?? value
|
|
109
132
|
}, value);
|
|
@@ -115,21 +138,19 @@ const TabComponent = ({ defaultTab, group, variant, children, className, headerC
|
|
|
115
138
|
className: cn("relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]", fullHeight && "min-h-0 flex-1"),
|
|
116
139
|
...containerProps,
|
|
117
140
|
children: /* @__PURE__ */ jsx("div", {
|
|
118
|
-
role: "tablist",
|
|
119
|
-
"aria-orientation": "horizontal",
|
|
120
141
|
className: cn("grid w-full min-w-0", fullHeight && "h-full", isDragging ? "transition-none" : "transition-transform duration-300 ease-in-out"),
|
|
121
142
|
style: {
|
|
122
143
|
gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,
|
|
123
144
|
transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`
|
|
124
145
|
},
|
|
125
|
-
children: tabItems.map(({
|
|
126
|
-
const { value, children, className: itemClassName } = props;
|
|
146
|
+
children: tabItems.map(({ value, children, className: itemClassName }, index) => {
|
|
127
147
|
const isActive = index === activeTabIndex;
|
|
148
|
+
const idFragment = toIdFragment(value);
|
|
128
149
|
return /* @__PURE__ */ jsx("div", {
|
|
129
150
|
role: "tabpanel",
|
|
130
|
-
"aria-labelledby": `tab-${
|
|
131
|
-
id: `tabpanel-${
|
|
132
|
-
|
|
151
|
+
"aria-labelledby": `tab-${idFragment}`,
|
|
152
|
+
id: `tabpanel-${idFragment}`,
|
|
153
|
+
inert: !isActive,
|
|
133
154
|
tabIndex: isActive ? 0 : -1,
|
|
134
155
|
"data-active": isActive,
|
|
135
156
|
className: cn("w-full min-w-0 p-3 opacity-100 transition-opacity duration-300 ease-in-out", fullHeight && "h-full overflow-y-auto", !isActive && "pointer-events-none opacity-0", itemClassName),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tab.mjs","names":[],"sources":["../../../../src/components/Tab/Tab.tsx"],"sourcesContent":["'use client';\n\nimport { useHorizontalSwipe } from '@hooks/useHorizontalSwipe';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Children,\n createContext,\n type HTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useState,\n} from 'react';\nimport { TabSelector } from '../TabSelector';\nimport { useTabContext } from './TabContext';\n\n// Context for managing tab state\ntype TabContextType = {\n activeTab: string;\n setActiveTab: (tab: string) => void;\n};\n\nconst TabContext = createContext<TabContextType | undefined>(undefined);\n\n// Tab container variants\nconst tabContainerVariant = cva('relative w-full rounded-lg', {\n variants: {\n background: {\n with: 'border border-neutral/20 bg-background/2 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur',\n without: '',\n },\n variant: {\n default: '',\n bordered: 'border-2',\n ghost: 'border-0 bg-transparent shadow-none',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\nexport type TabProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof tabContainerVariant> & {\n defaultTab?: string;\n group?: string;\n children: ReactNode;\n headerClassName?: string;\n fullHeight?: boolean;\n };\n\nexport type TabItemProps = HTMLAttributes<HTMLDivElement> & {\n label: string;\n value: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\n/**\n * TabItem component that represents a single tab\n * Must be used as a child of the Tab component\n */\nconst TabItem = ({ children, ...props }: TabItemProps) => (\n // This component is primarily used for its props by the parent Tab component\n // The actual rendering is handled by the Tab component\n <div {...props}>{children}</div>\n);\n\n// Add display name for better debugging\nTabItem.displayName = 'TabItem';\n\n/**\n * Tab container component that manages tab state and renders tab headers and content\n *\n * Example:\n * ```jsx\n * <Tab defaultTab=\"tab1\">\n * <Tab.Item label=\"First Tab\" value=\"tab1\">\n * Content for first tab\n * </Tab.Item>\n * <Tab.Item label=\"Second Tab\" value=\"tab2\">\n * Content for second tab\n * </Tab.Item>\n * </Tab>\n * ```\n */\nconst TabComponent = ({\n defaultTab,\n group,\n variant,\n children,\n className,\n headerClassName,\n fullHeight,\n ...props\n}: TabProps) => {\n // Extract TabItem children to get their props\n const tabItems = Children.toArray(children).filter((child) => {\n return isValidElement(child) && child.type === TabItem;\n }) as ReactElement<TabItemProps>[];\n\n const firstTabValue = tabItems[0]?.props?.value;\n const { tabsValues, setTabsValues } = useTabContext();\n const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? '');\n const hasGroup = group && typeof tabsValues === 'object';\n const currentTabValue =\n (hasGroup ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;\n const activeTabIndex = tabItems.findIndex(\n (tab) => tab.props.value === currentTabValue\n );\n\n const tabsCount = tabItems.length;\n\n const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({\n itemIndex: activeTabIndex,\n itemCount: tabsCount,\n onSwipeLeft: () => {\n const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);\n const nextValue = tabItems[targetIndex]?.props?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n onSwipeRight: () => {\n const targetIndex = Math.max(0, activeTabIndex - 1);\n const nextValue = tabItems[targetIndex]?.props?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n });\n\n const handleSetActiveTab = (tab: string) => {\n setActiveTab(tab);\n\n if (typeof setTabsValues === 'function') {\n setTabsValues((prev) => ({ ...prev, [group!]: tab }));\n }\n };\n\n const contextValue: TabContextType = {\n activeTab: activeTab ?? firstTabValue ?? '',\n setActiveTab: handleSetActiveTab,\n };\n\n return (\n <TabContext.Provider value={contextValue}>\n <div\n className={cn(\n tabContainerVariant({ variant }),\n fullHeight && 'flex h-full flex-col overflow-hidden',\n className\n )}\n {...props}\n >\n {/* Tab Headers */}\n <div className={cn('flex shrink-0 gap-3 p-3', headerClassName)}>\n <TabSelector\n selectedChoice={currentTabValue}\n tabs={tabItems.map((child) => {\n const { label, value, disabled } = child.props;\n const isActive = currentTabValue === value;\n\n return (\n <button\n key={value}\n className={cn(\n 'cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none',\n !isActive && 'text-neutral/70'\n )}\n data-active={isActive}\n disabled={disabled}\n onClick={() => !disabled && handleSetActiveTab(value)}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`tabpanel-${value}`}\n id={`tab-${value}`}\n type=\"button\"\n >\n {label ?? value}\n </button>\n );\n })}\n hoverable\n color=\"text\"\n />\n </div>\n {/* Tab Content */}\n {/* Clipper: no overflow; uses clip-path */}\n <div\n className={cn(\n 'relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]',\n fullHeight && 'min-h-0 flex-1'\n )}\n {...containerProps}\n >\n {/* Track */}\n <div\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n className={cn(\n 'grid w-full min-w-0',\n fullHeight && 'h-full',\n isDragging\n ? 'transition-none'\n : 'transition-transform duration-300 ease-in-out'\n )}\n style={{\n gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,\n transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`,\n }}\n >\n {tabItems.map(({ props }, index) => {\n const { value, children, className: itemClassName } = props;\n const isActive = index === activeTabIndex;\n\n return (\n <div\n key={value}\n role=\"tabpanel\"\n aria-labelledby={`tab-${value}`}\n id={`tabpanel-${value}`}\n aria-hidden={!isActive}\n tabIndex={isActive ? 0 : -1}\n data-active={isActive}\n className={cn(\n 'w-full min-w-0 p-3 opacity-100 transition-opacity duration-300 ease-in-out',\n fullHeight && 'h-full overflow-y-auto',\n !isActive && 'pointer-events-none opacity-0', // prevent offscreen interaction\n itemClassName\n )}\n >\n <div\n className={cn(\n 'flex w-full min-w-0 flex-col items-stretch gap-6',\n fullHeight && 'min-h-full'\n )}\n >\n {children}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n </TabContext.Provider>\n );\n};\n\n// Create the compound component\nexport const Tab = Object.assign(TabComponent, {\n Item: TabItem,\n});\n"],"mappings":";;;;;;;;;;;AAuBA,MAAM,aAAa,cAA0C,MAAS;AAGtE,MAAM,sBAAsB,IAAI,8BAA8B;CAC5D,UAAU;EACR,YAAY;GACV,MAAM;GACN,SAAS;EACX;EACA,SAAS;GACP,SAAS;GACT,UAAU;GACV,OAAO;EACT;CACF;CACA,iBAAiB,EACf,SAAS,UACX;AACF,CAAC;;;;;AAsBD,MAAM,WAAW,EAAE,UAAU,GAAG,YAG9B,oBAAC,OAAD;CAAK,GAAI;CAAQ;AAAc;AAIjC,QAAQ,cAAc;;;;;;;;;;;;;;;;AAiBtB,MAAM,gBAAgB,EACpB,YACA,OACA,SACA,UACA,WACA,iBACA,YACA,GAAG,YACW;CAEd,MAAM,WAAW,SAAS,QAAQ,QAAQ,CAAC,CAAC,QAAQ,UAAU;EAC5D,OAAO,eAAe,KAAK,KAAK,MAAM,SAAS;CACjD,CAAC;CAED,MAAM,gBAAgB,SAAS,EAAE,EAAE,OAAO;CAC1C,MAAM,EAAE,YAAY,kBAAkB,cAAc;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAS,cAAc,iBAAiB,EAAE;CAE5E,MAAM,mBADW,SAAS,OAAO,eAAe,WAElC,aAAa,SAAS,cAAc,cAAc;CAChE,MAAM,iBAAiB,SAAS,WAC7B,QAAQ,IAAI,MAAM,UAAU,eAC/B;CAEA,MAAM,YAAY,SAAS;CAE3B,MAAM,EAAE,gBAAgB,cAAc,eAAe,mBAAmB;EACtE,WAAW;EACX,WAAW;EACX,mBAAmB;GACjB,MAAM,cAAc,KAAK,IAAI,YAAY,GAAG,iBAAiB,CAAC;GAC9D,MAAM,YAAY,SAAS,YAAY,EAAE,OAAO;GAChD,IAAI,WAAW,mBAAmB,SAAS;EAC7C;EACA,oBAAoB;GAClB,MAAM,cAAc,KAAK,IAAI,GAAG,iBAAiB,CAAC;GAClD,MAAM,YAAY,SAAS,YAAY,EAAE,OAAO;GAChD,IAAI,WAAW,mBAAmB,SAAS;EAC7C;CACF,CAAC;CAED,MAAM,sBAAsB,QAAgB;EAC1C,aAAa,GAAG;EAEhB,IAAI,OAAO,kBAAkB,YAC3B,eAAe,UAAU;GAAE,GAAG;IAAO,QAAS;EAAI,EAAE;CAExD;CAEA,MAAM,eAA+B;EACnC,WAAW,aAAa,iBAAiB;EACzC,cAAc;CAChB;CAEA,OACE,oBAAC,WAAW,UAAZ;EAAqB,OAAO;YAC1B,qBAAC,OAAD;GACE,WAAW,GACT,oBAAoB,EAAE,QAAQ,CAAC,GAC/B,cAAc,wCACd,SACF;GACA,GAAI;aANN,CASE,oBAAC,OAAD;IAAK,WAAW,GAAG,2BAA2B,eAAe;cAC3D,oBAAC,aAAD;KACE,gBAAgB;KAChB,MAAM,SAAS,KAAK,UAAU;MAC5B,MAAM,EAAE,OAAO,OAAO,aAAa,MAAM;MACzC,MAAM,WAAW,oBAAoB;MAErC,OACE,oBAAC,UAAD;OAEE,WAAW,GACT,kHACA,CAAC,YAAY,iBACf;OACA,eAAa;OACH;OACV,eAAe,CAAC,YAAY,mBAAmB,KAAK;OACpD,MAAK;OACL,iBAAe;OACf,iBAAe,YAAY;OAC3B,IAAI,OAAO;OACX,MAAK;iBAEJ,SAAS;MACJ,GAfD,KAeC;KAEZ,CAAC;KACD;KACA,OAAM;IACP;GACE,IAGL,oBAAC,OAAD;IACE,WAAW,GACT,6FACA,cAAc,gBAChB;IACA,GAAI;cAGJ,oBAAC,OAAD;KACE,MAAK;KACL,oBAAiB;KACjB,WAAW,GACT,uBACA,cAAc,UACd,aACI,oBACA,+CACN;KACA,OAAO;MACL,qBAAqB,UAAU,SAAS,OAAO;MAC/C,WAAW,eAAe,iBAAiB,OAAO,aAAa,eAAe,GAAG;KACnF;eAEC,SAAS,KAAK,EAAE,SAAS,UAAU;MAClC,MAAM,EAAE,OAAO,UAAU,WAAW,kBAAkB;MACtD,MAAM,WAAW,UAAU;MAE3B,OACE,oBAAC,OAAD;OAEE,MAAK;OACL,mBAAiB,OAAO;OACxB,IAAI,YAAY;OAChB,eAAa,CAAC;OACd,UAAU,WAAW,IAAI;OACzB,eAAa;OACb,WAAW,GACT,8EACA,cAAc,0BACd,CAAC,YAAY,iCACb,aACF;iBAEA,oBAAC,OAAD;QACE,WAAW,GACT,oDACA,cAAc,YAChB;QAEC;OACE;MACF,GAtBE,KAsBF;KAET,CAAC;IACE;GACF,EACF;;CACc;AAEzB;AAGA,MAAa,MAAM,OAAO,OAAO,cAAc,EAC7C,MAAM,QACR,CAAC"}
|
|
1
|
+
{"version":3,"file":"Tab.mjs","names":[],"sources":["../../../../src/components/Tab/Tab.tsx"],"sourcesContent":["'use client';\n\nimport { useHorizontalSwipe } from '@hooks/useHorizontalSwipe';\nimport { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Children,\n createContext,\n type HTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useState,\n} from 'react';\nimport { TabSelector } from '../TabSelector';\nimport { useTabContext } from './TabContext';\n\n/**\n * Builds an id fragment usable in `id` / `aria-controls`.\n *\n * Tab values come from markdown (`<Tab value=\"Extract command\">`), so they can\n * hold spaces — which makes `aria-controls` reference a list of ids rather than\n * the one panel, and silently breaks the association for screen readers.\n */\nconst toIdFragment = (value: string) =>\n value\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/[^\\w-]/g, '');\n\n// Context for managing tab state\ntype TabContextType = {\n activeTab: string;\n setActiveTab: (tab: string) => void;\n};\n\nconst TabContext = createContext<TabContextType | undefined>(undefined);\n\n// Tab container variants\nconst tabContainerVariant = cva('relative w-full rounded-lg', {\n variants: {\n background: {\n with: 'border border-neutral/20 bg-background/2 shadow-[0_0_10px_-15px_rgba(0,0,0,0.3)] backdrop-blur',\n without: '',\n },\n variant: {\n default: '',\n bordered: 'border-2',\n ghost: 'border-0 bg-transparent shadow-none',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n});\n\nexport type TabProps = HTMLAttributes<HTMLDivElement> &\n VariantProps<typeof tabContainerVariant> & {\n defaultTab?: string;\n group?: string;\n children: ReactNode;\n headerClassName?: string;\n fullHeight?: boolean;\n };\n\nexport type TabItemProps = HTMLAttributes<HTMLDivElement> & {\n label?: string;\n value?: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\n/** A tab item whose identifier has been resolved to a usable string. */\ntype ResolvedTabItem = TabItemProps & { value: string };\n\n/**\n * Resolves the identifier of a tab.\n *\n * Both `label` and `value` are optional for markdown-authored tabs\n * (`<Tab label=\"next-intl\">`), so the identifier falls back to the label, then\n * to the position, keeping every tab addressable instead of rendering ids from\n * `undefined`.\n */\nconst resolveTabValue = (\n { value, label }: TabItemProps,\n index: number\n): string => {\n const identifier = value ?? label;\n\n return typeof identifier === 'string' ? identifier : String(index);\n};\n\n/**\n * TabItem component that represents a single tab\n * Must be used as a child of the Tab component\n */\nconst TabItem = ({ children, ...props }: TabItemProps) => (\n // This component is primarily used for its props by the parent Tab component\n // The actual rendering is handled by the Tab component\n <div {...props}>{children}</div>\n);\n\n// Add display name for better debugging\nTabItem.displayName = 'TabItem';\n\n/**\n * Tab container component that manages tab state and renders tab headers and content\n *\n * Example:\n * ```jsx\n * <Tab defaultTab=\"tab1\">\n * <Tab.Item label=\"First Tab\" value=\"tab1\">\n * Content for first tab\n * </Tab.Item>\n * <Tab.Item label=\"Second Tab\" value=\"tab2\">\n * Content for second tab\n * </Tab.Item>\n * </Tab>\n * ```\n */\nconst TabComponent = ({\n defaultTab,\n group,\n variant,\n children,\n className,\n headerClassName,\n fullHeight,\n ...props\n}: TabProps) => {\n // Extract TabItem children to get their props\n const tabItems: ResolvedTabItem[] = (\n Children.toArray(children).filter((child) => {\n return isValidElement(child) && child.type === TabItem;\n }) as ReactElement<TabItemProps>[]\n ).map((child, index) => ({\n ...child.props,\n value: resolveTabValue(child.props, index),\n }));\n\n const firstTabValue = tabItems[0]?.value;\n const { tabsValues, setTabsValues } = useTabContext();\n const [activeTab, setActiveTab] = useState(defaultTab ?? firstTabValue ?? '');\n const hasGroup = group && typeof tabsValues === 'object';\n const currentTabValue =\n (hasGroup ? tabsValues?.[group] : activeTab) ?? defaultTab ?? firstTabValue;\n const activeTabIndex = tabItems.findIndex(\n (tab) => tab.value === currentTabValue\n );\n\n const tabsCount = tabItems.length;\n\n const { containerProps, dragDeltaPct, isDragging } = useHorizontalSwipe({\n itemIndex: activeTabIndex,\n itemCount: tabsCount,\n onSwipeLeft: () => {\n const targetIndex = Math.min(tabsCount - 1, activeTabIndex + 1);\n const nextValue = tabItems[targetIndex]?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n onSwipeRight: () => {\n const targetIndex = Math.max(0, activeTabIndex - 1);\n const nextValue = tabItems[targetIndex]?.value;\n if (nextValue) handleSetActiveTab(nextValue);\n },\n });\n\n const handleSetActiveTab = (tab: string) => {\n setActiveTab(tab);\n\n if (typeof setTabsValues === 'function') {\n setTabsValues((prev) => ({ ...prev, [group!]: tab }));\n }\n };\n\n const contextValue: TabContextType = {\n activeTab: activeTab ?? firstTabValue ?? '',\n setActiveTab: handleSetActiveTab,\n };\n\n return (\n <TabContext.Provider value={contextValue}>\n <div\n className={cn(\n tabContainerVariant({ variant }),\n fullHeight && 'flex h-full flex-col overflow-hidden',\n className\n )}\n {...props}\n >\n {/* Tab Headers — `TabSelector` is the tablist; wrapping it in another\n one would nest two, leaving the outer without any `tab` child. */}\n <div className={cn('flex shrink-0 gap-3 p-3', headerClassName)}>\n <TabSelector\n selectedChoice={currentTabValue}\n tabs={tabItems.map(({ label, value, disabled }) => {\n const isActive = currentTabValue === value;\n const idFragment = toIdFragment(value);\n\n return (\n <button\n key={value}\n className={cn(\n 'cursor-pointer whitespace-nowrap rounded-md px-4 py-1 font-medium text-sm transition-colors focus:outline-none',\n !isActive && 'text-neutral/70'\n )}\n data-active={isActive}\n disabled={disabled}\n onClick={() => !disabled && handleSetActiveTab(value)}\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`tabpanel-${idFragment}`}\n id={`tab-${idFragment}`}\n type=\"button\"\n >\n {label ?? value}\n </button>\n );\n })}\n hoverable\n color=\"text\"\n />\n </div>\n {/* Tab Content */}\n {/* Clipper: no overflow; uses clip-path */}\n <div\n className={cn(\n 'relative w-full min-w-0 overflow-x-clip [-webkit-clip-path:inset(0)] [clip-path:inset(0)]',\n fullHeight && 'min-h-0 flex-1'\n )}\n {...containerProps}\n >\n {/* Track — presentational: it carries the sliding transform, while\n the panels it holds are the tablist's controlled regions. */}\n <div\n className={cn(\n 'grid w-full min-w-0',\n fullHeight && 'h-full',\n isDragging\n ? 'transition-none'\n : 'transition-transform duration-300 ease-in-out'\n )}\n style={{\n gridTemplateColumns: `repeat(${tabItems.length}, 100%)`,\n transform: `translateX(-${activeTabIndex * 100 - (isDragging ? dragDeltaPct : 0)}%)`,\n }}\n >\n {tabItems.map(\n ({ value, children, className: itemClassName }, index) => {\n const isActive = index === activeTabIndex;\n const idFragment = toIdFragment(value);\n\n return (\n <div\n key={value}\n role=\"tabpanel\"\n aria-labelledby={`tab-${idFragment}`}\n id={`tabpanel-${idFragment}`}\n // `inert` takes the whole subtree out of the tab order and\n // the accessibility tree at once. `aria-hidden` alone leaves\n // the buttons and links of an off-screen panel focusable,\n // which strands keyboard users on invisible controls.\n inert={!isActive}\n tabIndex={isActive ? 0 : -1}\n data-active={isActive}\n className={cn(\n 'w-full min-w-0 p-3 opacity-100 transition-opacity duration-300 ease-in-out',\n fullHeight && 'h-full overflow-y-auto',\n !isActive && 'pointer-events-none opacity-0', // prevent offscreen interaction\n itemClassName\n )}\n >\n <div\n className={cn(\n 'flex w-full min-w-0 flex-col items-stretch gap-6',\n fullHeight && 'min-h-full'\n )}\n >\n {children}\n </div>\n </div>\n );\n }\n )}\n </div>\n </div>\n </div>\n </TabContext.Provider>\n );\n};\n\n// Create the compound component\nexport const Tab = Object.assign(TabComponent, {\n Item: TabItem,\n});\n"],"mappings":"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"}
|
|
@@ -79,11 +79,13 @@ const AutoSizedTextArea = ({ className, autoSize = true, onChange, maxRows = 999
|
|
|
79
79
|
textAreaStyle.height = `${Math.max(Math.min(scrollHeight, maxHeight), 36)}px`;
|
|
80
80
|
};
|
|
81
81
|
useLayoutEffect(() => {
|
|
82
|
+
if (!Boolean(props.value ?? props.defaultValue)) return;
|
|
82
83
|
adjustHeight();
|
|
83
84
|
}, [
|
|
84
85
|
props.value,
|
|
85
86
|
props.defaultValue,
|
|
86
|
-
|
|
87
|
+
autoSize,
|
|
88
|
+
maxRows
|
|
87
89
|
]);
|
|
88
90
|
const handleChange = (e) => {
|
|
89
91
|
onChange?.(e);
|
|
@@ -91,7 +93,6 @@ const AutoSizedTextArea = ({ className, autoSize = true, onChange, maxRows = 999
|
|
|
91
93
|
};
|
|
92
94
|
const setRef = (el) => {
|
|
93
95
|
textAreaRef.current = el;
|
|
94
|
-
if (el) adjustHeight();
|
|
95
96
|
};
|
|
96
97
|
return /* @__PURE__ */ jsx(TextArea, {
|
|
97
98
|
ref: setRef,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoSizeTextArea.mjs","names":[],"sources":["../../../../src/components/TextArea/AutoSizeTextArea.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type ChangeEventHandler,\n type FC,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n} from 'react';\nimport { TextArea, type TextAreaProps } from './TextArea';\n\n/**\n * Props for the AutoSizedTextArea component.\n *\n * Extends TextAreaProps with auto-sizing functionality and row limitations.\n *\n * @example\n * ```tsx\n * // Auto-sizing textarea that grows with content\n * <AutoSizedTextArea\n * placeholder=\"Start typing and watch it grow...\"\n * autoSize={true}\n * maxRows={10}\n * />\n *\n * // Limited height with scroll when exceeded\n * <AutoSizedTextArea\n * value={longText}\n * onChange={handleChange}\n * autoSize={true}\n * maxRows={5}\n * className=\"max-h-30\"\n * />\n *\n * // Disable auto-sizing for fixed height\n * <AutoSizedTextArea\n * autoSize={false}\n * rows={3}\n * placeholder=\"Fixed height textarea\"\n * />\n * ```\n */\nexport type AutoSizedTextAreaProps = TextAreaProps & {\n /** Whether to automatically adjust height based on content */\n autoSize?: boolean;\n /** Maximum number of rows before scrolling is enabled */\n maxRows?: number;\n};\n\nconst LINE_HEIGHT = 24; // px\nconst LINE_PADDING = 12; // px\n\n/**\n * AutoSizedTextArea Component\n *\n * An enhanced textarea that automatically adjusts its height based on content,\n * providing a smooth user experience for variable-length text input.\n *\n * ## Features\n * - **Auto-Sizing**: Dynamically grows and shrinks based on content\n * - **Row Limits**: Configurable maximum rows before scrolling\n * - **Smooth Transitions**: Seamless height adjustments as user types\n * - **Scroll Management**: Automatic overflow handling when max height reached\n * - **Performance Optimized**: Efficient height calculations and updates\n *\n * ## Technical Details\n * - Line height: 24px with 12px padding\n * - Height calculation: `scrollHeight` vs `maxRows * lineHeight + padding`\n * - Resize: Disabled when auto-sizing is active for smooth experience\n * - Ref forwarding: Supports imperative access to textarea element\n *\n * ## Use Cases\n * - Chat message composition with dynamic sizing\n * - Comment forms that expand with content\n * - Note-taking interfaces with variable length\n * - Social media post creation\n * - Code snippet input with growth limits\n *\n * @example\n * ```tsx\n * // Chat-style auto-expanding textarea\n * const [message, setMessage] = useState('');\n *\n * <AutoSizedTextArea\n * value={message}\n * onChange={(e) => setMessage(e.target.value)}\n * placeholder=\"Type your message...\"\n * autoSize={true}\n * maxRows={8}\n * className=\"min-h-10\"\n * onKeyDown={(e) => {\n * if (e.key === 'Enter' && !e.shiftKey) {\n * e.preventDefault();\n * sendMessage(message);\n * setMessage('');\n * }\n * }}\n * />\n *\n * // Note-taking with generous height limits\n * <AutoSizedTextArea\n * defaultValue={note.content}\n * onChange={handleNoteChange}\n * placeholder=\"Write your notes here...\"\n * autoSize={true}\n * maxRows={20}\n * variant=\"default\"\n * />\n * ```\n */\nexport const AutoSizedTextArea: FC<AutoSizedTextAreaProps> = ({\n className,\n autoSize = true,\n onChange,\n maxRows = 999,\n ref,\n ...props\n}) => {\n const textAreaRef = useRef<HTMLTextAreaElement | null>(null);\n\n useImperativeHandle(ref, () => textAreaRef.current!);\n\n const adjustHeight = () => {\n const textAreaEl = textAreaRef.current;\n\n if (!textAreaEl || !autoSize) return;\n\n const textAreaStyle = textAreaEl.style;\n\n // Reset height to get accurate scrollHeight\n textAreaStyle.height = 'auto';\n const scrollHeight = textAreaEl.scrollHeight;\n const maxHeight = LINE_HEIGHT * maxRows + LINE_PADDING;\n const minHeight = LINE_HEIGHT + LINE_PADDING;\n\n // Set the new height\n textAreaStyle.height = `${Math.max(Math.min(scrollHeight, maxHeight), minHeight)}px`;\n };\n\n useLayoutEffect(() => {\n adjustHeight();\n }, [props.value, props.defaultValue,
|
|
1
|
+
{"version":3,"file":"AutoSizeTextArea.mjs","names":[],"sources":["../../../../src/components/TextArea/AutoSizeTextArea.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport {\n type ChangeEventHandler,\n type FC,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n} from 'react';\nimport { TextArea, type TextAreaProps } from './TextArea';\n\n/**\n * Props for the AutoSizedTextArea component.\n *\n * Extends TextAreaProps with auto-sizing functionality and row limitations.\n *\n * @example\n * ```tsx\n * // Auto-sizing textarea that grows with content\n * <AutoSizedTextArea\n * placeholder=\"Start typing and watch it grow...\"\n * autoSize={true}\n * maxRows={10}\n * />\n *\n * // Limited height with scroll when exceeded\n * <AutoSizedTextArea\n * value={longText}\n * onChange={handleChange}\n * autoSize={true}\n * maxRows={5}\n * className=\"max-h-30\"\n * />\n *\n * // Disable auto-sizing for fixed height\n * <AutoSizedTextArea\n * autoSize={false}\n * rows={3}\n * placeholder=\"Fixed height textarea\"\n * />\n * ```\n */\nexport type AutoSizedTextAreaProps = TextAreaProps & {\n /** Whether to automatically adjust height based on content */\n autoSize?: boolean;\n /** Maximum number of rows before scrolling is enabled */\n maxRows?: number;\n};\n\nconst LINE_HEIGHT = 24; // px\nconst LINE_PADDING = 12; // px\n\n/**\n * AutoSizedTextArea Component\n *\n * An enhanced textarea that automatically adjusts its height based on content,\n * providing a smooth user experience for variable-length text input.\n *\n * ## Features\n * - **Auto-Sizing**: Dynamically grows and shrinks based on content\n * - **Row Limits**: Configurable maximum rows before scrolling\n * - **Smooth Transitions**: Seamless height adjustments as user types\n * - **Scroll Management**: Automatic overflow handling when max height reached\n * - **Performance Optimized**: Efficient height calculations and updates\n *\n * ## Technical Details\n * - Line height: 24px with 12px padding\n * - Height calculation: `scrollHeight` vs `maxRows * lineHeight + padding`\n * - Resize: Disabled when auto-sizing is active for smooth experience\n * - Ref forwarding: Supports imperative access to textarea element\n *\n * ## Use Cases\n * - Chat message composition with dynamic sizing\n * - Comment forms that expand with content\n * - Note-taking interfaces with variable length\n * - Social media post creation\n * - Code snippet input with growth limits\n *\n * @example\n * ```tsx\n * // Chat-style auto-expanding textarea\n * const [message, setMessage] = useState('');\n *\n * <AutoSizedTextArea\n * value={message}\n * onChange={(e) => setMessage(e.target.value)}\n * placeholder=\"Type your message...\"\n * autoSize={true}\n * maxRows={8}\n * className=\"min-h-10\"\n * onKeyDown={(e) => {\n * if (e.key === 'Enter' && !e.shiftKey) {\n * e.preventDefault();\n * sendMessage(message);\n * setMessage('');\n * }\n * }}\n * />\n *\n * // Note-taking with generous height limits\n * <AutoSizedTextArea\n * defaultValue={note.content}\n * onChange={handleNoteChange}\n * placeholder=\"Write your notes here...\"\n * autoSize={true}\n * maxRows={20}\n * variant=\"default\"\n * />\n * ```\n */\nexport const AutoSizedTextArea: FC<AutoSizedTextAreaProps> = ({\n className,\n autoSize = true,\n onChange,\n maxRows = 999,\n ref,\n ...props\n}) => {\n const textAreaRef = useRef<HTMLTextAreaElement | null>(null);\n\n useImperativeHandle(ref, () => textAreaRef.current!);\n\n const adjustHeight = () => {\n const textAreaEl = textAreaRef.current;\n\n if (!textAreaEl || !autoSize) return;\n\n const textAreaStyle = textAreaEl.style;\n\n // Reset height to get accurate scrollHeight\n textAreaStyle.height = 'auto';\n const scrollHeight = textAreaEl.scrollHeight;\n const maxHeight = LINE_HEIGHT * maxRows + LINE_PADDING;\n const minHeight = LINE_HEIGHT + LINE_PADDING;\n\n // Set the new height\n textAreaStyle.height = `${Math.max(Math.min(scrollHeight, maxHeight), minHeight)}px`;\n };\n\n /*\n * `adjustHeight` writes a style and then reads `scrollHeight`, which forces\n * the browser to lay the page out there and then. Depending on the function\n * identity — recreated every render — repeated that reflow on every render of\n * every ancestor; the height only ever changes with the content, so that is\n * what this watches.\n *\n * An empty field is skipped entirely: its height is already the one line the\n * CSS gives it, so measuring only buys a reflow. That is the common case for\n * a chat composer sitting unopened on a large page.\n */\n useLayoutEffect(() => {\n const hasContent = Boolean(props.value ?? props.defaultValue);\n if (!hasContent) return;\n\n adjustHeight();\n }, [props.value, props.defaultValue, autoSize, maxRows]);\n\n const handleChange: ChangeEventHandler<HTMLTextAreaElement> = (e) => {\n onChange?.(e);\n adjustHeight();\n };\n\n const setRef = (el: HTMLTextAreaElement | null) => {\n textAreaRef.current = el;\n };\n\n return (\n <TextArea\n ref={setRef}\n onChange={handleChange}\n className={cn('overflow-y-auto', autoSize && 'resize-none', className)}\n {...props}\n />\n );\n};\n"],"mappings":";;;;;;;;AAkDA,MAAM,cAAc;AACpB,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DrB,MAAa,qBAAiD,EAC5D,WACA,WAAW,MACX,UACA,UAAU,KACV,KACA,GAAG,YACC;CACJ,MAAM,cAAc,OAAmC,IAAI;CAE3D,oBAAoB,WAAW,YAAY,OAAQ;CAEnD,MAAM,qBAAqB;EACzB,MAAM,aAAa,YAAY;EAE/B,IAAI,CAAC,cAAc,CAAC,UAAU;EAE9B,MAAM,gBAAgB,WAAW;EAGjC,cAAc,SAAS;EACvB,MAAM,eAAe,WAAW;EAChC,MAAM,YAAY,cAAc,UAAU;EAI1C,cAAc,SAAS,GAAG,KAAK,IAAI,KAAK,IAAI,cAAc,SAAS,GAAG,EAAS,EAAE;CACnF;CAaA,sBAAsB;EAEpB,IAAI,CADe,QAAQ,MAAM,SAAS,MAAM,YAClC,GAAG;EAEjB,aAAa;CACf,GAAG;EAAC,MAAM;EAAO,MAAM;EAAc;EAAU;CAAO,CAAC;CAEvD,MAAM,gBAAyD,MAAM;EACnE,WAAW,CAAC;EACZ,aAAa;CACf;CAEA,MAAM,UAAU,OAAmC;EACjD,YAAY,UAAU;CACxB;CAEA,OACE,oBAAC,UAAD;EACE,KAAK;EACL,UAAU;EACV,WAAW,GAAG,mBAAmB,YAAY,eAAe,SAAS;EACrE,GAAI;CACL;AAEL"}
|
|
@@ -20,7 +20,7 @@ import { Radio, radioVariants } from "./Input/Radio.mjs";
|
|
|
20
20
|
import { SearchInput } from "./Input/SearchInput.mjs";
|
|
21
21
|
import { Browser } from "./Browser/Browser.mjs";
|
|
22
22
|
import { KeyboardShortcut } from "./KeyboardShortcut/KeyboardShortcut.mjs";
|
|
23
|
-
import { Detail, PopoverStatic } from "./Popover/static.mjs";
|
|
23
|
+
import { Detail, PopoverStatic, usePopoverIds } from "./Popover/static.mjs";
|
|
24
24
|
import { Popover } from "./Popover/dynamic.mjs";
|
|
25
25
|
import { Carousel } from "./Carousel/index.mjs";
|
|
26
26
|
import { CollapsibleTable } from "./CollapsibleTable/CollapsibleTable.mjs";
|
|
@@ -105,7 +105,7 @@ import { Footer } from "./Footer/index.mjs";
|
|
|
105
105
|
import { HeightResizer } from "./HeightResizer/index.mjs";
|
|
106
106
|
import { HideShow } from "./HideShow/index.mjs";
|
|
107
107
|
import { HTMLRenderer, getIntlayerHTMLOptions } from "./HTMLRender/HTMLRender.mjs";
|
|
108
|
-
import { CodeBlock, CodeDefault } from "./IDE/CodeBlockClient.mjs";
|
|
108
|
+
import { CodeBlock, CodeDefault, CodeHighlighted, shikiWrapperClassName } from "./IDE/CodeBlockClient.mjs";
|
|
109
109
|
import { Code } from "./IDE/Code.mjs";
|
|
110
110
|
import { FileList } from "./IDE/FileList.mjs";
|
|
111
111
|
import { WithResizer } from "./WithResizer/index.mjs";
|
|
@@ -141,4 +141,4 @@ import { Terminal } from "./Terminal/Terminal.mjs";
|
|
|
141
141
|
import { DesktopThemeSwitcher } from "./ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs";
|
|
142
142
|
import { MobileThemeSwitcher } from "./ThemeSwitcherDropDown/MobileThemeSwitcher.mjs";
|
|
143
143
|
|
|
144
|
-
export { Accordion, AutoCompleteTextarea, AutoSizedTextArea, Avatar, Badge, BitbucketLogo, Breadcrumb, Browser, Burger, Button, Carousel, Checkbox, ClickOutsideDiv, Code, CodeBlock, CodeDefault, CollapsibleTable, Command, CommandRoot, Container, ContentEditableTextArea, ContentEditorInput, ContentEditorTextArea, ContentSelector, CopyButton, CopyToClipboard, DesktopNavbar, DesktopThemeSwitcher, Detail, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, FacebookLogo, FileList, Flag, flags_exports as Flags, Footer, Form, AutoSizedTextAreaElement as FormAutoSizedTextArea, Button as FormButton, CheckboxElement as FormCheckbox, FormControl, FormDescription, EditableFieldInputElement as FormEditableFieldInput, EditableFieldTextAreaElement as FormEditableFieldTextArea, FormElement, FormField, InputElement as FormInput, InputPasswordElement as FormInputPassword, FormItem, FormLabelLayout as FormLabel, FormMessage, MultiSelectElement as FormMultiSelect, OTPElement as FormOTP, SearchInputElement as FormSearchInput, SelectElement as FormSelect, SwitchSelectorElement as FormSwitchSelector, TextAreaElement as FormTextArea, GitHubLogo, GitLabLogo, GoogleLogo, GridDistortionPattern, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeightResizer, HideShow, Hr, IDE, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InstagramLogo, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, Label, LanguageBackground, LanguageSection, Link, LinkedInLogo, Loader, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProvider, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkDownIframe, MarkdownEditor, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileNavbar, MobileThemeSwitcher, Modal, MultiSelect, Navbar, NumberItemsSelector, OTPInput, OTPInputContext, Pagination, Popover, PopoverStatic, PressableSpan, ProductHuntLogo, Radio, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectLabel, SelectSeparator, ShowingResultsNumberItems, SmartTable, SocialNetworks, Spotlight, Step, Steps, SwitchSelector, Tab, TabSelector, Table, Tag, Td, TechLogo, techLogos_exports as TechLogos, Terminal, TextArea, TextEditor, TextEditorContainer, Th, TiktokLogo, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Tr, VersionSwitcher, VersionSwitcherProvider, VerticalSwitchSelector, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, choiceVariant, containerVariants, createEditorUploadFn, defaultChoices, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, indicatorVariant, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, radioVariants, reducer, switchSelectorVariant, toast, toastVariants, traceKeys, useContentEditable, useCopyToClipboard, useForm, useFormField, useLocaleSwitcherContent, usePasswordManagerBadge, usePrevious, useRightDrawer, useToast, useVersionSwitcher };
|
|
144
|
+
export { Accordion, AutoCompleteTextarea, AutoSizedTextArea, Avatar, Badge, BitbucketLogo, Breadcrumb, Browser, Burger, Button, Carousel, Checkbox, ClickOutsideDiv, Code, CodeBlock, CodeDefault, CodeHighlighted, CollapsibleTable, Command, CommandRoot, Container, ContentEditableTextArea, ContentEditorInput, ContentEditorTextArea, ContentSelector, CopyButton, CopyToClipboard, DesktopNavbar, DesktopThemeSwitcher, Detail, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, FacebookLogo, FileList, Flag, flags_exports as Flags, Footer, Form, AutoSizedTextAreaElement as FormAutoSizedTextArea, Button as FormButton, CheckboxElement as FormCheckbox, FormControl, FormDescription, EditableFieldInputElement as FormEditableFieldInput, EditableFieldTextAreaElement as FormEditableFieldTextArea, FormElement, FormField, InputElement as FormInput, InputPasswordElement as FormInputPassword, FormItem, FormLabelLayout as FormLabel, FormMessage, MultiSelectElement as FormMultiSelect, OTPElement as FormOTP, SearchInputElement as FormSearchInput, SelectElement as FormSelect, SwitchSelectorElement as FormSwitchSelector, TextAreaElement as FormTextArea, GitHubLogo, GitLabLogo, GoogleLogo, GridDistortionPattern, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeightResizer, HideShow, Hr, IDE, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InstagramLogo, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, Label, LanguageBackground, LanguageSection, Link, LinkedInLogo, Loader, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProvider, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkDownIframe, MarkdownEditor, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileNavbar, MobileThemeSwitcher, Modal, MultiSelect, Navbar, NumberItemsSelector, OTPInput, OTPInputContext, Pagination, Popover, PopoverStatic, PressableSpan, ProductHuntLogo, Radio, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectLabel, SelectSeparator, ShowingResultsNumberItems, SmartTable, SocialNetworks, Spotlight, Step, Steps, SwitchSelector, Tab, TabSelector, Table, Tag, Td, TechLogo, techLogos_exports as TechLogos, Terminal, TextArea, TextEditor, TextEditorContainer, Th, TiktokLogo, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, Tr, VersionSwitcher, VersionSwitcherProvider, VerticalSwitchSelector, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, choiceVariant, containerVariants, createEditorUploadFn, defaultChoices, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, indicatorVariant, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, radioVariants, reducer, shikiWrapperClassName, switchSelectorVariant, toast, toastVariants, traceKeys, useContentEditable, useCopyToClipboard, useForm, useFormField, useLocaleSwitcherContent, usePasswordManagerBadge, usePopoverIds, usePrevious, useRightDrawer, useToast, useVersionSwitcher };
|
|
@@ -64,12 +64,22 @@ const useDevice = (breakpoint = "md") => {
|
|
|
64
64
|
isMac: void 0
|
|
65
65
|
});
|
|
66
66
|
useEffect(() => {
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const breakpointValue = getBreakpointFromSize(breakpoint);
|
|
68
|
+
const mobileScreenQuery = window.matchMedia(`(max-width: ${breakpointValue}px)`);
|
|
69
|
+
const isMobileUserAgent = checkIsMobileUserAgent();
|
|
70
|
+
const isIOS = checkIsIOS();
|
|
71
|
+
const isMac = checkIsMac();
|
|
72
|
+
const applyMatches = (isMobileScreen) => setResult({
|
|
73
|
+
isMobileScreen,
|
|
74
|
+
isMobileUserAgent,
|
|
75
|
+
isMobile: isMobileScreen,
|
|
76
|
+
isIOS,
|
|
77
|
+
isMac
|
|
78
|
+
});
|
|
79
|
+
applyMatches(mobileScreenQuery.matches);
|
|
80
|
+
const handleChange = (event) => applyMatches(event.matches);
|
|
81
|
+
mobileScreenQuery.addEventListener("change", handleChange);
|
|
82
|
+
return () => mobileScreenQuery.removeEventListener("change", handleChange);
|
|
73
83
|
}, [breakpoint]);
|
|
74
84
|
return result;
|
|
75
85
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDevice.mjs","names":[],"sources":["../../../src/hooks/useDevice.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nexport type SizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport const getBreakpointFromSize = (breakpoint: SizeType | number) => {\n switch (breakpoint) {\n case 'xs':\n return 480;\n case 'sm':\n return 640;\n case 'md':\n return 768;\n case 'lg':\n return 1024;\n case 'xl':\n return 1280;\n case '2xl':\n return 1536;\n default:\n return breakpoint;\n }\n};\n\nexport const checkIsMobileUserAgent = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n if (typeof userAgent === 'undefined') return;\n\n return /android|bb\\d+|meego|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(?:hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(?:ob|in)i|palm(?: os)?|phone|p(?:ixi|re)\\/|plucker|pocket|psp|series(?:4|6)0|symbian|treo|up\\.(?:browser|link)|vodafone|wap|windows ce|windows phone|xda|xiino|zte-/i.test(\n userAgent\n );\n};\n\nexport const checkIsIphoneOrSafariDevice = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n /* 1 . is it Safari? (Chrome & co. also contain “Safari/…”, so exclude them) */\n const isSafari =\n /Safari\\/\\d/i.test(userAgent) && // has “Safari/xxx”\n !/Chrome|CriOS|FxiOS|Edg|OPR/i.test(userAgent); // …but not the other browsers\n\n /* 2. is it an Apple device? (macOS or iOS/iPadOS) */\n const isAppleDevice = /Macintosh|iP(?:hone|ad|od)/.test(userAgent);\n\n return isSafari && isAppleDevice; // true for mac-Safari & iOS-Safari\n};\n\nexport const checkIsIOS = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n if (typeof userAgent === 'undefined') return;\n\n // Check for iOS devices: iPhone, iPad, iPod\n return /iP(?:hone|ad|od)/.test(userAgent);\n};\n\nexport const checkIsMac = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n if (typeof userAgent === 'undefined') return;\n\n // Check for macOS/Mac devices\n return /Macintosh|MacIntel|Mac OS X/.test(userAgent);\n};\n\nexport const checkIsMobileScreen = (\n breakpoint: number\n): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n return (window?.innerWidth ?? 0) <= breakpoint;\n};\n\ntype UseDeviceState = {\n isMobileScreen: boolean | undefined;\n isMobileUserAgent: boolean | undefined;\n isMobile: boolean | undefined;\n isIOS: boolean | undefined;\n isMac: boolean | undefined;\n};\n\nexport const calculateIsMobile = (breakpoint: SizeType | number = 'md') => {\n const breakpointValue = getBreakpointFromSize(breakpoint);\n\n const isMobileUserAgent = checkIsMobileUserAgent();\n const isMobileScreen = checkIsMobileScreen(breakpointValue);\n const isMobile = isMobileScreen ?? isMobileUserAgent;\n const isIOS = checkIsIOS();\n const isMac = checkIsMac();\n\n return {\n isMobileScreen, // Is the screen width within a mobile breakpoint.\n isMobileUserAgent, // Is the user agent indicative of a mobile device.\n isMobile, // Combines both checks to determine if the device is mobile.\n isIOS, // Is the device running iOS (iPhone, iPad, iPod).\n isMac, // Is the device a Mac computer (macOS).\n };\n};\n\nexport const useDevice = (breakpoint: SizeType | number = 'md') => {\n const [result, setResult] = useState<UseDeviceState>({\n isMobileScreen: undefined,\n isMobileUserAgent: undefined,\n isMobile: undefined,\n isIOS: undefined,\n isMac: undefined,\n });\n\n useEffect(() => {\n
|
|
1
|
+
{"version":3,"file":"useDevice.mjs","names":[],"sources":["../../../src/hooks/useDevice.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nexport type SizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport const getBreakpointFromSize = (breakpoint: SizeType | number) => {\n switch (breakpoint) {\n case 'xs':\n return 480;\n case 'sm':\n return 640;\n case 'md':\n return 768;\n case 'lg':\n return 1024;\n case 'xl':\n return 1280;\n case '2xl':\n return 1536;\n default:\n return breakpoint;\n }\n};\n\nexport const checkIsMobileUserAgent = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n if (typeof userAgent === 'undefined') return;\n\n return /android|bb\\d+|meego|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(?:hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(?:ob|in)i|palm(?: os)?|phone|p(?:ixi|re)\\/|plucker|pocket|psp|series(?:4|6)0|symbian|treo|up\\.(?:browser|link)|vodafone|wap|windows ce|windows phone|xda|xiino|zte-/i.test(\n userAgent\n );\n};\n\nexport const checkIsIphoneOrSafariDevice = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n /* 1 . is it Safari? (Chrome & co. also contain “Safari/…”, so exclude them) */\n const isSafari =\n /Safari\\/\\d/i.test(userAgent) && // has “Safari/xxx”\n !/Chrome|CriOS|FxiOS|Edg|OPR/i.test(userAgent); // …but not the other browsers\n\n /* 2. is it an Apple device? (macOS or iOS/iPadOS) */\n const isAppleDevice = /Macintosh|iP(?:hone|ad|od)/.test(userAgent);\n\n return isSafari && isAppleDevice; // true for mac-Safari & iOS-Safari\n};\n\nexport const checkIsIOS = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n if (typeof userAgent === 'undefined') return;\n\n // Check for iOS devices: iPhone, iPad, iPod\n return /iP(?:hone|ad|od)/.test(userAgent);\n};\n\nexport const checkIsMac = (): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n const userAgent = window.navigator?.userAgent;\n\n if (typeof userAgent === 'undefined') return;\n\n // Check for macOS/Mac devices\n return /Macintosh|MacIntel|Mac OS X/.test(userAgent);\n};\n\nexport const checkIsMobileScreen = (\n breakpoint: number\n): boolean | undefined => {\n if (typeof window === 'undefined') return;\n\n return (window?.innerWidth ?? 0) <= breakpoint;\n};\n\ntype UseDeviceState = {\n isMobileScreen: boolean | undefined;\n isMobileUserAgent: boolean | undefined;\n isMobile: boolean | undefined;\n isIOS: boolean | undefined;\n isMac: boolean | undefined;\n};\n\nexport const calculateIsMobile = (breakpoint: SizeType | number = 'md') => {\n const breakpointValue = getBreakpointFromSize(breakpoint);\n\n const isMobileUserAgent = checkIsMobileUserAgent();\n const isMobileScreen = checkIsMobileScreen(breakpointValue);\n const isMobile = isMobileScreen ?? isMobileUserAgent;\n const isIOS = checkIsIOS();\n const isMac = checkIsMac();\n\n return {\n isMobileScreen, // Is the screen width within a mobile breakpoint.\n isMobileUserAgent, // Is the user agent indicative of a mobile device.\n isMobile, // Combines both checks to determine if the device is mobile.\n isIOS, // Is the device running iOS (iPhone, iPad, iPod).\n isMac, // Is the device a Mac computer (macOS).\n };\n};\n\nexport const useDevice = (breakpoint: SizeType | number = 'md') => {\n const [result, setResult] = useState<UseDeviceState>({\n isMobileScreen: undefined,\n isMobileUserAgent: undefined,\n isMobile: undefined,\n isIOS: undefined,\n isMac: undefined,\n });\n\n useEffect(() => {\n const breakpointValue = getBreakpointFromSize(breakpoint);\n const mobileScreenQuery = window.matchMedia(\n `(max-width: ${breakpointValue}px)`\n );\n\n const isMobileUserAgent = checkIsMobileUserAgent();\n const isIOS = checkIsIOS();\n const isMac = checkIsMac();\n\n const applyMatches = (isMobileScreen: boolean) =>\n setResult({\n isMobileScreen,\n isMobileUserAgent,\n isMobile: isMobileScreen,\n isIOS,\n isMac,\n });\n\n applyMatches(mobileScreenQuery.matches);\n\n const handleChange = (event: MediaQueryListEvent) =>\n applyMatches(event.matches);\n\n mobileScreenQuery.addEventListener('change', handleChange);\n\n return () => mobileScreenQuery.removeEventListener('change', handleChange);\n }, [breakpoint]);\n\n return result;\n};\n"],"mappings":";;;;;AAMA,MAAa,yBAAyB,eAAkC;CACtE,QAAQ,YAAR;EACE,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,SACE,OAAO;CACX;AACF;AAEA,MAAa,+BAAoD;CAC/D,IAAI,OAAO,WAAW,aAAa;CAEnC,MAAM,YAAY,OAAO,WAAW;CAEpC,IAAI,OAAO,cAAc,aAAa;CAEtC,OAAO,mVAAmV,KACxV,SACF;AACF;AAEA,MAAa,oCAAyD;CACpE,IAAI,OAAO,WAAW,aAAa;CAEnC,MAAM,YAAY,OAAO,WAAW;CAGpC,MAAM,WACJ,cAAc,KAAK,SAAS,KAC5B,CAAC,8BAA8B,KAAK,SAAS;CAG/C,MAAM,gBAAgB,6BAA6B,KAAK,SAAS;CAEjE,OAAO,YAAY;AACrB;AAEA,MAAa,mBAAwC;CACnD,IAAI,OAAO,WAAW,aAAa;CAEnC,MAAM,YAAY,OAAO,WAAW;CAEpC,IAAI,OAAO,cAAc,aAAa;CAGtC,OAAO,mBAAmB,KAAK,SAAS;AAC1C;AAEA,MAAa,mBAAwC;CACnD,IAAI,OAAO,WAAW,aAAa;CAEnC,MAAM,YAAY,OAAO,WAAW;CAEpC,IAAI,OAAO,cAAc,aAAa;CAGtC,OAAO,8BAA8B,KAAK,SAAS;AACrD;AAEA,MAAa,uBACX,eACwB;CACxB,IAAI,OAAO,WAAW,aAAa;CAEnC,QAAQ,QAAQ,cAAc,MAAM;AACtC;AAUA,MAAa,qBAAqB,aAAgC,SAAS;CACzE,MAAM,kBAAkB,sBAAsB,UAAU;CAExD,MAAM,oBAAoB,uBAAuB;CACjD,MAAM,iBAAiB,oBAAoB,eAAe;CAK1D,OAAO;EACL;EACA;EACA,UAPe,kBAAkB;EAQjC,OAPY,WAOR;EACJ,OAPY,WAOR;CACN;AACF;AAEA,MAAa,aAAa,aAAgC,SAAS;CACjE,MAAM,CAAC,QAAQ,aAAa,SAAyB;EACnD,gBAAgB;EAChB,mBAAmB;EACnB,UAAU;EACV,OAAO;EACP,OAAO;CACT,CAAC;CAED,gBAAgB;EACd,MAAM,kBAAkB,sBAAsB,UAAU;EACxD,MAAM,oBAAoB,OAAO,WAC/B,eAAe,gBAAgB,IACjC;EAEA,MAAM,oBAAoB,uBAAuB;EACjD,MAAM,QAAQ,WAAW;EACzB,MAAM,QAAQ,WAAW;EAEzB,MAAM,gBAAgB,mBACpB,UAAU;GACR;GACA;GACA,UAAU;GACV;GACA;EACF,CAAC;EAEH,aAAa,kBAAkB,OAAO;EAEtC,MAAM,gBAAgB,UACpB,aAAa,MAAM,OAAO;EAE5B,kBAAkB,iBAAiB,UAAU,YAAY;EAEzD,aAAa,kBAAkB,oBAAoB,UAAU,YAAY;CAC3E,GAAG,CAAC,UAAU,CAAC;CAEf,OAAO;AACT"}
|
|
@@ -1,157 +1,152 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/hooks/useItemSelector.ts
|
|
6
6
|
const selectorDefault = (option) => option?.getAttribute("aria-selected") === "true";
|
|
7
|
+
/** Attributes whose change moves the indicator to another option. */
|
|
8
|
+
const WATCHED_ATTRIBUTES = [
|
|
9
|
+
"aria-selected",
|
|
10
|
+
"data-active",
|
|
11
|
+
"data-indicator"
|
|
12
|
+
];
|
|
13
|
+
/** Delay before the indicator leaves the option the pointer just left. */
|
|
14
|
+
const HOVER_RELEASE_DELAY = 150;
|
|
7
15
|
const useItemSelector = (optionsRefs, { selector = selectorDefault, isHoverable = false, orientation = "horizontal" } = {
|
|
8
16
|
selector: selectorDefault,
|
|
9
17
|
isHoverable: false,
|
|
10
18
|
orientation: "horizontal"
|
|
11
19
|
}) => {
|
|
12
20
|
const [choiceIndicatorPosition, setChoiceIndicatorPosition] = useState(null);
|
|
13
|
-
const
|
|
21
|
+
const hoveredItemRef = useRef(null);
|
|
14
22
|
const hideTimeoutRef = useRef(null);
|
|
15
23
|
const lastPositionRef = useRef(null);
|
|
24
|
+
const measurementFrameRef = useRef(null);
|
|
25
|
+
const selectorRef = useRef(selector);
|
|
26
|
+
selectorRef.current = selector;
|
|
27
|
+
const orientationRef = useRef(orientation);
|
|
28
|
+
orientationRef.current = orientation;
|
|
16
29
|
const itemsLength = optionsRefs.current.length;
|
|
17
|
-
const calculatePosition = () => {
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
} else targetElement = optionsRefs.current.find(selector) ?? null;
|
|
30
|
+
const calculatePosition = useCallback(() => {
|
|
31
|
+
const targetElement = hoveredItemRef.current ?? optionsRefs.current.find(selectorRef.current) ?? null;
|
|
32
|
+
if (hoveredItemRef.current && hideTimeoutRef.current) {
|
|
33
|
+
clearTimeout(hideTimeoutRef.current);
|
|
34
|
+
hideTimeoutRef.current = null;
|
|
35
|
+
}
|
|
36
|
+
const isVertical = orientationRef.current === "vertical";
|
|
26
37
|
if (!targetElement) {
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const horizontalPrev = lastPositionRef.current;
|
|
40
|
-
const newPosition = {
|
|
41
|
-
left: horizontalPrev?.left ?? 0,
|
|
42
|
-
width: horizontalPrev?.width ?? 0,
|
|
43
|
-
opacity: 0
|
|
44
|
-
};
|
|
45
|
-
if (horizontalPrev?.opacity !== 0) {
|
|
46
|
-
setChoiceIndicatorPosition(newPosition);
|
|
47
|
-
lastPositionRef.current = newPosition;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
38
|
+
if (lastPositionRef.current?.opacity === 0) return;
|
|
39
|
+
const newPosition = isVertical ? {
|
|
40
|
+
top: lastPositionRef.current?.top ?? 0,
|
|
41
|
+
height: lastPositionRef.current?.height ?? 0,
|
|
42
|
+
opacity: 0
|
|
43
|
+
} : {
|
|
44
|
+
left: lastPositionRef.current?.left ?? 0,
|
|
45
|
+
width: lastPositionRef.current?.width ?? 0,
|
|
46
|
+
opacity: 0
|
|
47
|
+
};
|
|
48
|
+
setChoiceIndicatorPosition(newPosition);
|
|
49
|
+
lastPositionRef.current = newPosition;
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
-
if (
|
|
52
|
+
if (isVertical) {
|
|
53
53
|
const top = targetElement.offsetTop;
|
|
54
54
|
const height = targetElement.offsetHeight;
|
|
55
|
+
const previous = lastPositionRef.current;
|
|
56
|
+
if (previous?.top === top && previous.height === height && previous.opacity === 1) return;
|
|
55
57
|
const newPosition = {
|
|
56
58
|
top,
|
|
57
59
|
height,
|
|
58
60
|
opacity: 1
|
|
59
61
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
lastPositionRef.current = newPosition;
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
const left = targetElement.offsetLeft;
|
|
67
|
-
const width = targetElement.offsetWidth;
|
|
68
|
-
const newPosition = {
|
|
69
|
-
left,
|
|
70
|
-
width,
|
|
71
|
-
opacity: 1
|
|
72
|
-
};
|
|
73
|
-
const prev = lastPositionRef.current;
|
|
74
|
-
if (!prev || prev.left !== left || prev.width !== width || prev.opacity !== 1) {
|
|
75
|
-
setChoiceIndicatorPosition(newPosition);
|
|
76
|
-
lastPositionRef.current = newPosition;
|
|
77
|
-
}
|
|
62
|
+
setChoiceIndicatorPosition(newPosition);
|
|
63
|
+
lastPositionRef.current = newPosition;
|
|
64
|
+
return;
|
|
78
65
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const observer = new MutationObserver((mutations) => {
|
|
88
|
-
for (const mutation of mutations) if (mutation.type === "attributes" && (mutation.attributeName === "aria-selected" || mutation.attributeName === "data-active" || mutation.attributeName === "data-indicator")) {
|
|
89
|
-
calculatePosition();
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
observer.observe(option, {
|
|
94
|
-
attributes: true,
|
|
95
|
-
attributeFilter: [
|
|
96
|
-
"aria-selected",
|
|
97
|
-
"data-active",
|
|
98
|
-
"data-indicator"
|
|
99
|
-
]
|
|
100
|
-
});
|
|
101
|
-
mutationObservers.push(observer);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
const resizeObservers = [];
|
|
105
|
-
const observeSize = (element) => {
|
|
106
|
-
if (!element) return;
|
|
107
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
108
|
-
calculatePosition();
|
|
109
|
-
});
|
|
110
|
-
resizeObserver.observe(element);
|
|
111
|
-
resizeObservers.push(resizeObserver);
|
|
66
|
+
const left = targetElement.offsetLeft;
|
|
67
|
+
const width = targetElement.offsetWidth;
|
|
68
|
+
const previous = lastPositionRef.current;
|
|
69
|
+
if (previous?.left === left && previous.width === width && previous.opacity === 1) return;
|
|
70
|
+
const newPosition = {
|
|
71
|
+
left,
|
|
72
|
+
width,
|
|
73
|
+
opacity: 1
|
|
112
74
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
75
|
+
setChoiceIndicatorPosition(newPosition);
|
|
76
|
+
lastPositionRef.current = newPosition;
|
|
77
|
+
}, [optionsRefs]);
|
|
78
|
+
/**
|
|
79
|
+
* Defers the measurement to the next frame, and to a single one however many
|
|
80
|
+
* observers fire.
|
|
81
|
+
*
|
|
82
|
+
* Reading `offsetLeft` straight from a mutation, resize or scroll callback
|
|
83
|
+
* forces a synchronous reflow, and a documentation page mounts one selector
|
|
84
|
+
* per tab group and one per code block.
|
|
85
|
+
*/
|
|
86
|
+
const scheduleMeasurement = useCallback(() => {
|
|
87
|
+
if (measurementFrameRef.current !== null) return;
|
|
88
|
+
measurementFrameRef.current = requestAnimationFrame(() => {
|
|
89
|
+
measurementFrameRef.current = null;
|
|
90
|
+
calculatePosition();
|
|
91
|
+
});
|
|
92
|
+
}, [calculatePosition]);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
scheduleMeasurement();
|
|
95
|
+
window.addEventListener("resize", scheduleMeasurement, { passive: true });
|
|
96
|
+
window.addEventListener("DOMContentLoaded", scheduleMeasurement);
|
|
97
|
+
const options = optionsRefs.current.filter(Boolean);
|
|
98
|
+
const mutationObserver = new MutationObserver(scheduleMeasurement);
|
|
99
|
+
const resizeObserver = new ResizeObserver(scheduleMeasurement);
|
|
116
100
|
const handleMouseEnter = (event) => {
|
|
117
101
|
if (hideTimeoutRef.current) {
|
|
118
102
|
clearTimeout(hideTimeoutRef.current);
|
|
119
103
|
hideTimeoutRef.current = null;
|
|
120
104
|
}
|
|
121
|
-
|
|
105
|
+
hoveredItemRef.current = event.currentTarget;
|
|
106
|
+
scheduleMeasurement();
|
|
122
107
|
};
|
|
123
108
|
const handleMouseLeave = () => {
|
|
124
109
|
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
|
|
125
110
|
hideTimeoutRef.current = setTimeout(() => {
|
|
126
|
-
|
|
127
|
-
|
|
111
|
+
hoveredItemRef.current = null;
|
|
112
|
+
scheduleMeasurement();
|
|
113
|
+
}, HOVER_RELEASE_DELAY);
|
|
128
114
|
};
|
|
129
|
-
|
|
130
|
-
option
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return () => {
|
|
134
|
-
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
|
|
135
|
-
window.removeEventListener("resize", calculatePosition);
|
|
136
|
-
window.removeEventListener("DOMContentLoaded", calculatePosition);
|
|
137
|
-
mutationObservers.forEach((observer) => {
|
|
138
|
-
observer.disconnect();
|
|
139
|
-
});
|
|
140
|
-
resizeObservers.forEach((observer) => {
|
|
141
|
-
observer.disconnect();
|
|
142
|
-
});
|
|
143
|
-
optionsRefs.current.forEach((option) => {
|
|
144
|
-
option?.removeEventListener("mouseenter", handleMouseEnter);
|
|
145
|
-
option?.removeEventListener("mouseleave", handleMouseLeave);
|
|
115
|
+
for (const option of options) {
|
|
116
|
+
mutationObserver.observe(option, {
|
|
117
|
+
attributes: true,
|
|
118
|
+
attributeFilter: WATCHED_ATTRIBUTES
|
|
146
119
|
});
|
|
120
|
+
resizeObserver.observe(option);
|
|
121
|
+
if (isHoverable) {
|
|
122
|
+
option.addEventListener("mouseenter", handleMouseEnter, { passive: true });
|
|
123
|
+
option.addEventListener("mouseleave", handleMouseLeave, { passive: true });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return () => {
|
|
127
|
+
if (measurementFrameRef.current !== null) {
|
|
128
|
+
cancelAnimationFrame(measurementFrameRef.current);
|
|
129
|
+
measurementFrameRef.current = null;
|
|
130
|
+
}
|
|
131
|
+
if (hideTimeoutRef.current) {
|
|
132
|
+
clearTimeout(hideTimeoutRef.current);
|
|
133
|
+
hideTimeoutRef.current = null;
|
|
134
|
+
}
|
|
135
|
+
window.removeEventListener("resize", scheduleMeasurement);
|
|
136
|
+
window.removeEventListener("DOMContentLoaded", scheduleMeasurement);
|
|
137
|
+
mutationObserver.disconnect();
|
|
138
|
+
resizeObserver.disconnect();
|
|
139
|
+
if (isHoverable) for (const option of options) {
|
|
140
|
+
option.removeEventListener("mouseenter", handleMouseEnter);
|
|
141
|
+
option.removeEventListener("mouseleave", handleMouseLeave);
|
|
142
|
+
}
|
|
147
143
|
};
|
|
148
144
|
}, [
|
|
149
145
|
optionsRefs,
|
|
150
|
-
selector,
|
|
151
|
-
hoveredItem,
|
|
152
146
|
itemsLength,
|
|
147
|
+
isHoverable,
|
|
153
148
|
orientation,
|
|
154
|
-
|
|
149
|
+
scheduleMeasurement
|
|
155
150
|
]);
|
|
156
151
|
return {
|
|
157
152
|
choiceIndicatorPosition,
|