@intlayer/design-system 8.7.3 → 8.7.4
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/Accordion/Accordion.mjs +2 -2
- package/dist/esm/components/Accordion/Accordion.mjs.map +1 -1
- package/dist/esm/components/Avatar/image.mjs +4 -14
- package/dist/esm/components/Avatar/image.mjs.map +1 -1
- package/dist/esm/components/Badge/index.mjs +21 -21
- package/dist/esm/components/Badge/index.mjs.map +1 -1
- package/dist/esm/components/Breadcrumb/index.mjs +13 -13
- package/dist/esm/components/Breadcrumb/index.mjs.map +1 -1
- package/dist/esm/components/Browser/Browser.content.mjs +0 -20
- package/dist/esm/components/Browser/Browser.content.mjs.map +1 -1
- package/dist/esm/components/Button/Button.mjs +60 -60
- package/dist/esm/components/Button/Button.mjs.map +1 -1
- package/dist/esm/components/Carousel/index.mjs +6 -6
- package/dist/esm/components/Carousel/index.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditor.mjs +1 -1
- package/dist/esm/components/ContentEditor/ContentEditor.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorInput.mjs +7 -7
- package/dist/esm/components/ContentEditor/ContentEditorInput.mjs.map +1 -1
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +6 -6
- package/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs.map +1 -1
- package/dist/esm/components/CopyButton/index.mjs +3 -3
- package/dist/esm/components/CopyButton/index.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +28 -38
- 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/DictionaryCreationForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.mjs +0 -42
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +3 -3
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +10 -10
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +11 -11
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +7 -7
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs.map +1 -1
- package/dist/esm/components/DictionaryFieldEditor/VersionSwitcherDropDown/VersionSwitcher.mjs +4 -4
- package/dist/esm/components/DictionaryFieldEditor/VersionSwitcherDropDown/VersionSwitcher.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/EditableField/EditableFieldLayout.mjs +9 -9
- package/dist/esm/components/EditableField/EditableFieldLayout.mjs.map +1 -1
- package/dist/esm/components/IDE/CopyCode.mjs +1 -1
- package/dist/esm/components/IDE/CopyCode.mjs.map +1 -1
- package/dist/esm/components/Link/Link.mjs +70 -70
- package/dist/esm/components/Link/Link.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +6 -6
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherDropDown/LocaleSwitcher.mjs +3 -3
- package/dist/esm/components/LocaleSwitcherDropDown/LocaleSwitcher.mjs.map +1 -1
- package/dist/esm/components/Modal/Modal.mjs +4 -4
- package/dist/esm/components/Modal/Modal.mjs.map +1 -1
- package/dist/esm/components/Navbar/DesktopNavbar.mjs +1 -1
- package/dist/esm/components/Navbar/DesktopNavbar.mjs.map +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +14 -12
- package/dist/esm/components/Pagination/Pagination.mjs.map +1 -1
- package/dist/esm/components/Popover/dynamic.mjs +9 -9
- package/dist/esm/components/Popover/dynamic.mjs.map +1 -1
- package/dist/esm/components/Popover/static.mjs +1 -1
- package/dist/esm/components/Popover/static.mjs.map +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +5 -5
- package/dist/esm/components/RightDrawer/RightDrawer.mjs.map +1 -1
- package/dist/esm/components/Select/Multiselect.mjs +1 -1
- package/dist/esm/components/Select/Multiselect.mjs.map +1 -1
- package/dist/esm/components/Select/Select.mjs +1 -1
- package/dist/esm/components/Select/Select.mjs.map +1 -1
- package/dist/esm/components/SwitchSelector/index.mjs +20 -20
- package/dist/esm/components/SwitchSelector/index.mjs.map +1 -1
- package/dist/esm/components/Tab/Tab.mjs +1 -1
- package/dist/esm/components/Tab/Tab.mjs.map +1 -1
- package/dist/esm/components/TabSelector/TabSelector.mjs +1 -1
- package/dist/esm/components/TabSelector/TabSelector.mjs.map +1 -1
- package/dist/esm/components/Table/ExpandButton.mjs +1 -1
- package/dist/esm/components/Table/ExpandButton.mjs.map +1 -1
- package/dist/esm/components/Table/SmartTable.mjs +1 -1
- package/dist/esm/components/Table/SmartTable.mjs.map +1 -1
- package/dist/esm/components/Tag/index.mjs +38 -38
- package/dist/esm/components/Tag/index.mjs.map +1 -1
- package/dist/esm/components/TechLogo/TechLogo.mjs +36 -36
- package/dist/esm/components/TechLogo/TechLogo.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs +12 -12
- package/dist/esm/components/ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs +9 -9
- package/dist/esm/components/ThemeSwitcherDropDown/MobileThemeSwitcher.mjs.map +1 -1
- package/dist/esm/components/Toaster/useToast.mjs +9 -26
- package/dist/esm/components/Toaster/useToast.mjs.map +1 -1
- package/dist/esm/hooks/useAuth/useOAuth2.mjs +1 -1
- package/dist/esm/hooks/useAuth/useSession.mjs +1 -1
- package/dist/esm/libs/auth.mjs +1 -1
- package/dist/types/components/Badge/index.d.ts +1 -1
- package/dist/types/components/Browser/Browser.content.d.ts +0 -20
- package/dist/types/components/Browser/Browser.content.d.ts.map +1 -1
- package/dist/types/components/Button/Button.d.ts +2 -2
- package/dist/types/components/CollapsibleTable/CollapsibleTable.d.ts +2 -2
- package/dist/types/components/Command/index.d.ts +2 -2
- package/dist/types/components/Container/index.d.ts +6 -6
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.d.ts +0 -42
- package/dist/types/components/DictionaryFieldEditor/DictionaryCreationForm/dictionaryCreationForm.content.d.ts.map +1 -1
- package/dist/types/components/DictionaryFieldEditor/DictionaryDetails/useDictionaryDetailsSchema.d.ts +1 -1
- package/dist/types/components/Input/Checkbox.d.ts +1 -1
- package/dist/types/components/Link/Link.d.ts +1 -1
- package/dist/types/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/Tag/index.d.ts +1 -1
- package/dist/types/components/Toaster/Toast.d.ts +1 -1
- package/package.json +20 -20
|
@@ -21,16 +21,16 @@ let ButtonSize = /* @__PURE__ */ function(ButtonSize) {
|
|
|
21
21
|
}({});
|
|
22
22
|
const buttonIconVariants = cva("flex-none shrink-0", {
|
|
23
23
|
variants: { size: {
|
|
24
|
-
[
|
|
25
|
-
[
|
|
26
|
-
[
|
|
27
|
-
[
|
|
28
|
-
[
|
|
29
|
-
[
|
|
30
|
-
[
|
|
31
|
-
[
|
|
24
|
+
[`sm`]: "size-3",
|
|
25
|
+
[`md`]: "size-4",
|
|
26
|
+
[`lg`]: "size-5",
|
|
27
|
+
[`xl`]: "size-6",
|
|
28
|
+
[`icon-sm`]: "size-3",
|
|
29
|
+
[`icon-md`]: "size-4",
|
|
30
|
+
[`icon-lg`]: "size-4",
|
|
31
|
+
[`icon-xl`]: "size-5"
|
|
32
32
|
} },
|
|
33
|
-
defaultVariants: { size:
|
|
33
|
+
defaultVariants: { size: "md" }
|
|
34
34
|
});
|
|
35
35
|
/**
|
|
36
36
|
* Button visual style variants
|
|
@@ -80,65 +80,65 @@ let ButtonTextAlign = /* @__PURE__ */ function(ButtonTextAlign) {
|
|
|
80
80
|
const buttonVariants = cva("relative inline-flex cursor-pointer items-center justify-center font-medium ring-0 transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50", {
|
|
81
81
|
variants: {
|
|
82
82
|
size: {
|
|
83
|
-
[
|
|
84
|
-
[
|
|
85
|
-
[
|
|
86
|
-
[
|
|
87
|
-
[
|
|
88
|
-
[
|
|
89
|
-
[
|
|
90
|
-
[
|
|
83
|
+
[`sm`]: "min-h-7 px-3 text-xs max-md:py-1",
|
|
84
|
+
[`md`]: "min-h-8 px-6 text-sm max-md:py-2",
|
|
85
|
+
[`lg`]: "min-h-10 px-8 text-lg max-md:py-3",
|
|
86
|
+
[`xl`]: "min-h-11 px-10 text-xl max-md:py-4",
|
|
87
|
+
[`icon-sm`]: "p-1.5",
|
|
88
|
+
[`icon-md`]: "p-1.5",
|
|
89
|
+
[`icon-lg`]: "p-2",
|
|
90
|
+
[`icon-xl`]: "p-3"
|
|
91
91
|
},
|
|
92
92
|
color: {
|
|
93
|
-
[
|
|
94
|
-
[
|
|
95
|
-
[
|
|
96
|
-
[
|
|
97
|
-
[
|
|
98
|
-
[
|
|
99
|
-
[
|
|
100
|
-
[
|
|
101
|
-
[
|
|
102
|
-
[
|
|
103
|
-
[
|
|
104
|
-
[
|
|
105
|
-
[
|
|
93
|
+
[`primary`]: "hover-primary-500/20 text-primary ring-primary-500/20 *:text-text-light",
|
|
94
|
+
[`secondary`]: "hover-secondary-500/20 text-secondary ring-secondary-500/20 *:text-text-light",
|
|
95
|
+
[`destructive`]: "hover-destructive-500/20 text-destructive ring-destructive-500/20 *:text-text-light",
|
|
96
|
+
[`neutral`]: "text-neutral ring-neutral-500/5 *:text-text-light",
|
|
97
|
+
[`card`]: "hover-card-500/20 text-card ring-card-500/20 *:text-text-light",
|
|
98
|
+
[`light`]: "hover-white-500/20 text-white ring-white/20 *:text-text-light",
|
|
99
|
+
[`dark`]: "text-neutral-800 ring-text-light/50 *:text-text-light",
|
|
100
|
+
[`text`]: "text-text ring-text/20 *:text-text-opposite",
|
|
101
|
+
[`current`]: "hover-current-500/10 text-current ring-current/10 *:text-text-light",
|
|
102
|
+
[`text-inverse`]: "text-text-opposite ring-text-opposite/20 *:text-text",
|
|
103
|
+
[`error`]: "hover-error-500/20 text-error ring-error/20 *:text-text-light",
|
|
104
|
+
[`success`]: "hover-success-500/20 text-success ring-success/20 *:text-text-light",
|
|
105
|
+
[`custom`]: ""
|
|
106
106
|
},
|
|
107
107
|
roundedSize: {
|
|
108
|
-
[`${
|
|
109
|
-
[`${
|
|
110
|
-
[`${
|
|
111
|
-
[`${
|
|
112
|
-
[`${
|
|
113
|
-
[`${
|
|
114
|
-
[`${
|
|
115
|
-
[`${
|
|
116
|
-
[`${
|
|
117
|
-
[`${
|
|
108
|
+
[`${"none"}`]: "rounded-none",
|
|
109
|
+
[`${"sm"}`]: "rounded-lg [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl",
|
|
110
|
+
[`${"md"}`]: "rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl",
|
|
111
|
+
[`${"lg"}`]: "rounded-2xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-3xl",
|
|
112
|
+
[`${"xl"}`]: "rounded-3xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-4xl",
|
|
113
|
+
[`${"2xl"}`]: "rounded-4xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[2.5rem]",
|
|
114
|
+
[`${"3xl"}`]: "rounded-[2.5rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[3rem]",
|
|
115
|
+
[`${"4xl"}`]: "rounded-[3rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[4rem]",
|
|
116
|
+
[`${"5xl"}`]: "rounded-[4rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[5rem]",
|
|
117
|
+
[`${"full"}`]: "rounded-full"
|
|
118
118
|
},
|
|
119
119
|
variant: {
|
|
120
|
-
[
|
|
120
|
+
[`default`]: [
|
|
121
121
|
"bg-current",
|
|
122
122
|
"hover:bg-current/90",
|
|
123
123
|
"hover:ring-5",
|
|
124
124
|
"aria-selected:ring-5"
|
|
125
125
|
],
|
|
126
|
-
[
|
|
126
|
+
[`outline`]: [
|
|
127
127
|
"rounded-2xl border-[1.3px] border-current bg-current/0 *:text-current!",
|
|
128
128
|
"hover:bg-current/20 focus-visible:bg-current/20",
|
|
129
129
|
"hover:ring-5 focus-visible:ring-5",
|
|
130
130
|
"aria-selected:ring-5"
|
|
131
131
|
],
|
|
132
|
-
[
|
|
133
|
-
[
|
|
134
|
-
[
|
|
135
|
-
[
|
|
136
|
-
[
|
|
132
|
+
[`none`]: "border-none bg-current/0 text-inherit hover:bg-current/0",
|
|
133
|
+
[`link`]: "h-auto justify-start border-inherit bg-transparent px-1 underline-offset-4 *:text-current! hover:bg-transparent hover:underline",
|
|
134
|
+
[`invisible-link`]: "h-auto justify-start border-inherit bg-transparent px-1 underline-offset-4 *:text-current! hover:bg-transparent",
|
|
135
|
+
[`hoverable`]: "rounded-lg border-none bg-current/0 transition *:text-current! hover:bg-current/20 aria-[current]:bg-current/5",
|
|
136
|
+
[`fade`]: [
|
|
137
137
|
"rounded-lg border-none bg-current/10 ring-current/5 transition *:text-current! hover:bg-current/20 aria-[current]:bg-current/5",
|
|
138
138
|
"hover:ring-5 focus-visible:ring-5",
|
|
139
139
|
"aria-selected:ring-5"
|
|
140
140
|
],
|
|
141
|
-
[
|
|
141
|
+
[`input`]: [
|
|
142
142
|
"text-text",
|
|
143
143
|
"w-full select-text resize-none rounded-2xl text-base shadow-none outline-none supports-[corner-shape:squircle]:rounded-4xl",
|
|
144
144
|
"transition-shadow duration-100 md:text-sm",
|
|
@@ -157,9 +157,9 @@ const buttonVariants = cva("relative inline-flex cursor-pointer items-center jus
|
|
|
157
157
|
]
|
|
158
158
|
},
|
|
159
159
|
textAlign: {
|
|
160
|
-
[
|
|
161
|
-
[
|
|
162
|
-
[
|
|
160
|
+
[`left`]: "justify-start text-left",
|
|
161
|
+
[`center`]: "justify-center text-center",
|
|
162
|
+
[`right`]: "justify-end text-right"
|
|
163
163
|
},
|
|
164
164
|
isFullWidth: {
|
|
165
165
|
true: "w-full",
|
|
@@ -167,11 +167,11 @@ const buttonVariants = cva("relative inline-flex cursor-pointer items-center jus
|
|
|
167
167
|
}
|
|
168
168
|
},
|
|
169
169
|
defaultVariants: {
|
|
170
|
-
variant:
|
|
171
|
-
size:
|
|
172
|
-
color:
|
|
173
|
-
roundedSize: `${
|
|
174
|
-
textAlign:
|
|
170
|
+
variant: `default`,
|
|
171
|
+
size: `md`,
|
|
172
|
+
color: `custom`,
|
|
173
|
+
roundedSize: `${"md"}`,
|
|
174
|
+
textAlign: `center`,
|
|
175
175
|
isFullWidth: false
|
|
176
176
|
}
|
|
177
177
|
});
|
|
@@ -214,7 +214,7 @@ const buttonVariants = cva("relative inline-flex cursor-pointer items-center jus
|
|
|
214
214
|
* ```
|
|
215
215
|
*/
|
|
216
216
|
const Button = ({ variant, size, color, children, Icon, IconRight, iconClassName, isLoading = false, isActive, isSelected, isFullWidth, roundedSize, textAlign, disabled, label, className, type = "button", "aria-describedby": ariaDescribedBy, "aria-expanded": ariaExpanded, "aria-haspopup": ariaHasPopup, "aria-pressed": ariaPressed, ...props }) => {
|
|
217
|
-
const isLink = variant ===
|
|
217
|
+
const isLink = variant === `link` || variant === `invisible-link`;
|
|
218
218
|
const isIconOnly = !children && (Icon || IconRight);
|
|
219
219
|
const accessibilityProps = {
|
|
220
220
|
"aria-label": isIconOnly ? label ?? void 0 : void 0,
|
|
@@ -228,7 +228,7 @@ const Button = ({ variant, size, color, children, Icon, IconRight, iconClassName
|
|
|
228
228
|
"aria-disabled": disabled || isLoading,
|
|
229
229
|
"aria-selected": isSelected
|
|
230
230
|
};
|
|
231
|
-
const isSquareButton = size ===
|
|
231
|
+
const isSquareButton = size === "icon-sm" || size === "icon-md" || size === "icon-lg" || size === "icon-xl";
|
|
232
232
|
return /* @__PURE__ */ jsxs("button", {
|
|
233
233
|
disabled: isLoading || disabled,
|
|
234
234
|
role: isLink ? "link" : "button",
|
|
@@ -239,7 +239,7 @@ const Button = ({ variant, size, color, children, Icon, IconRight, iconClassName
|
|
|
239
239
|
color,
|
|
240
240
|
isFullWidth,
|
|
241
241
|
roundedSize,
|
|
242
|
-
textAlign: textAlign ?? (IconRight ?
|
|
242
|
+
textAlign: textAlign ?? (IconRight ? "left" : "center"),
|
|
243
243
|
className
|
|
244
244
|
}),
|
|
245
245
|
...accessibilityProps,
|
|
@@ -253,7 +253,7 @@ const Button = ({ variant, size, color, children, Icon, IconRight, iconClassName
|
|
|
253
253
|
"aria-hidden": "true"
|
|
254
254
|
}),
|
|
255
255
|
/* @__PURE__ */ jsx("div", {
|
|
256
|
-
className: cn("flex items-center justify-center transition-[width] duration-300", isLoading && size ===
|
|
256
|
+
className: cn("flex items-center justify-center transition-[width] duration-300", isLoading && size === "sm" && "w-3", isLoading && size === "md" && "w-4", isLoading && size === "lg" && "w-5", isLoading && size === "xl" && "w-6"),
|
|
257
257
|
children: /* @__PURE__ */ jsx(Loader, {
|
|
258
258
|
className: buttonIconVariants({
|
|
259
259
|
size,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.mjs","names":["ButtonRoundedSize"],"sources":["../../../../src/components/Button/Button.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type { LucideIcon } from 'lucide-react';\nimport type { ButtonHTMLAttributes, DetailedHTMLProps, FC } from 'react';\nimport { ContainerRoundedSize as ButtonRoundedSize } from '../Container';\nimport { Loader } from '../Loader';\n\n/**\n * Button size variants for different use cases\n */\nexport enum ButtonSize {\n SM = 'sm',\n MD = 'md',\n LG = 'lg',\n XL = 'xl',\n ICON_SM = 'icon-sm',\n ICON_MD = 'icon-md',\n ICON_LG = 'icon-lg',\n ICON_XL = 'icon-xl',\n}\n\nconst buttonIconVariants = cva('flex-none shrink-0', {\n variants: {\n size: {\n [`${ButtonSize.SM}`]: 'size-3',\n [`${ButtonSize.MD}`]: 'size-4',\n [`${ButtonSize.LG}`]: 'size-5',\n [`${ButtonSize.XL}`]: 'size-6',\n [`${ButtonSize.ICON_SM}`]: 'size-3',\n [`${ButtonSize.ICON_MD}`]: 'size-4',\n [`${ButtonSize.ICON_LG}`]: 'size-4',\n [`${ButtonSize.ICON_XL}`]: 'size-5',\n },\n },\n defaultVariants: {\n size: ButtonSize.MD,\n },\n});\n\n/**\n * Button visual style variants\n */\nexport enum ButtonVariant {\n DEFAULT = 'default',\n NONE = 'none',\n OUTLINE = 'outline',\n LINK = 'link',\n INVISIBLE_LINK = 'invisible-link',\n HOVERABLE = 'hoverable',\n FADE = 'fade',\n INPUT = 'input',\n}\n\n/**\n * Button color themes that work with the design system\n */\nexport enum ButtonColor {\n PRIMARY = 'primary',\n SECONDARY = 'secondary',\n DESTRUCTIVE = 'destructive',\n NEUTRAL = 'neutral',\n LIGHT = 'light',\n DARK = 'dark',\n TEXT = 'text',\n CARD = 'card',\n TEXT_INVERSE = 'text-inverse',\n CURRENT = 'current',\n ERROR = 'error',\n SUCCESS = 'success',\n CUSTOM = 'custom',\n}\n\n/**\n * Text alignment options for button content\n */\nexport enum ButtonTextAlign {\n LEFT = 'left',\n CENTER = 'center',\n RIGHT = 'right',\n}\n\n/**\n * Enhanced button variants with improved accessibility and focus states\n */\nexport const buttonVariants = cva(\n 'relative inline-flex cursor-pointer items-center justify-center font-medium ring-0 transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n size: {\n [`${ButtonSize.SM}`]: 'min-h-7 px-3 text-xs max-md:py-1',\n [`${ButtonSize.MD}`]: 'min-h-8 px-6 text-sm max-md:py-2',\n [`${ButtonSize.LG}`]: 'min-h-10 px-8 text-lg max-md:py-3',\n [`${ButtonSize.XL}`]: 'min-h-11 px-10 text-xl max-md:py-4',\n [`${ButtonSize.ICON_SM}`]: 'p-1.5',\n [`${ButtonSize.ICON_MD}`]: 'p-1.5',\n [`${ButtonSize.ICON_LG}`]: 'p-2',\n [`${ButtonSize.ICON_XL}`]: 'p-3',\n },\n color: {\n [`${ButtonColor.PRIMARY}`]:\n 'hover-primary-500/20 text-primary ring-primary-500/20 *:text-text-light',\n [`${ButtonColor.SECONDARY}`]:\n 'hover-secondary-500/20 text-secondary ring-secondary-500/20 *:text-text-light',\n [`${ButtonColor.DESTRUCTIVE}`]:\n 'hover-destructive-500/20 text-destructive ring-destructive-500/20 *:text-text-light',\n [`${ButtonColor.NEUTRAL}`]:\n 'text-neutral ring-neutral-500/5 *:text-text-light',\n [`${ButtonColor.CARD}`]:\n 'hover-card-500/20 text-card ring-card-500/20 *:text-text-light',\n [`${ButtonColor.LIGHT}`]:\n 'hover-white-500/20 text-white ring-white/20 *:text-text-light',\n [`${ButtonColor.DARK}`]:\n 'text-neutral-800 ring-text-light/50 *:text-text-light',\n [`${ButtonColor.TEXT}`]: 'text-text ring-text/20 *:text-text-opposite',\n [`${ButtonColor.CURRENT}`]:\n 'hover-current-500/10 text-current ring-current/10 *:text-text-light',\n [`${ButtonColor.TEXT_INVERSE}`]:\n 'text-text-opposite ring-text-opposite/20 *:text-text',\n [`${ButtonColor.ERROR}`]:\n 'hover-error-500/20 text-error ring-error/20 *:text-text-light',\n [`${ButtonColor.SUCCESS}`]:\n 'hover-success-500/20 text-success ring-success/20 *:text-text-light',\n [`${ButtonColor.CUSTOM}`]: '',\n },\n roundedSize: {\n [`${ButtonRoundedSize.NONE}`]: 'rounded-none',\n [`${ButtonRoundedSize.SM}`]:\n 'rounded-lg [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl',\n [`${ButtonRoundedSize.MD}`]:\n 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n [`${ButtonRoundedSize.LG}`]:\n 'rounded-2xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-3xl',\n [`${ButtonRoundedSize.XL}`]:\n 'rounded-3xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-4xl',\n [`${ButtonRoundedSize['2xl']}`]:\n 'rounded-4xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[2.5rem]',\n [`${ButtonRoundedSize['3xl']}`]:\n 'rounded-[2.5rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[3rem]',\n [`${ButtonRoundedSize['4xl']}`]:\n 'rounded-[3rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[4rem]',\n [`${ButtonRoundedSize['5xl']}`]:\n 'rounded-[4rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[5rem]',\n [`${ButtonRoundedSize.FULL}`]: 'rounded-full',\n },\n variant: {\n [`${ButtonVariant.DEFAULT}`]: [\n 'bg-current',\n 'hover:bg-current/90',\n 'hover:ring-5',\n 'aria-selected:ring-5',\n ],\n\n [`${ButtonVariant.OUTLINE}`]: [\n 'rounded-2xl border-[1.3px] border-current bg-current/0 *:text-current!',\n 'hover:bg-current/20 focus-visible:bg-current/20',\n 'hover:ring-5 focus-visible:ring-5',\n 'aria-selected:ring-5',\n ],\n\n [`${ButtonVariant.NONE}`]:\n 'border-none bg-current/0 text-inherit hover:bg-current/0',\n\n [`${ButtonVariant.LINK}`]:\n 'h-auto justify-start border-inherit bg-transparent px-1 underline-offset-4 *:text-current! hover:bg-transparent hover:underline',\n\n [`${ButtonVariant.INVISIBLE_LINK}`]:\n 'h-auto justify-start border-inherit bg-transparent px-1 underline-offset-4 *:text-current! hover:bg-transparent',\n\n [`${ButtonVariant.HOVERABLE}`]:\n 'rounded-lg border-none bg-current/0 transition *:text-current! hover:bg-current/20 aria-[current]:bg-current/5',\n\n [`${ButtonVariant.FADE}`]: [\n 'rounded-lg border-none bg-current/10 ring-current/5 transition *:text-current! hover:bg-current/20 aria-[current]:bg-current/5',\n 'hover:ring-5 focus-visible:ring-5',\n 'aria-selected:ring-5',\n ],\n [`${ButtonVariant.INPUT}`]: [\n // base styles\n 'text-text',\n 'w-full select-text resize-none rounded-2xl text-base shadow-none outline-none supports-[corner-shape:squircle]:rounded-4xl',\n 'transition-shadow duration-100 md:text-sm',\n 'ring-0', // base ring\n 'disabled:opacity-50',\n\n 'text-text',\n 'bg-neutral-50 dark:bg-neutral-950',\n 'ring-neutral-100 dark:ring-neutral-700',\n\n // Hover ring (similar spirit to your input)\n 'hover:ring-3', // width\n 'aria-selected:ring-4',\n 'focus-visible:ring-3',\n 'disabled:ring-0',\n\n // Focus ring + animation\n 'focus-visible:outline-none',\n\n // Remove any weird box-shadow\n '[box-shadow:none] focus:[box-shadow:none]',\n\n // aria-invalid border color\n 'aria-invalid:border-error',\n ],\n },\n\n textAlign: {\n [`${ButtonTextAlign.LEFT}`]: 'justify-start text-left',\n [`${ButtonTextAlign.CENTER}`]: 'justify-center text-center',\n [`${ButtonTextAlign.RIGHT}`]: 'justify-end text-right',\n },\n\n isFullWidth: {\n true: 'w-full',\n false: '',\n },\n },\n defaultVariants: {\n variant: `${ButtonVariant.DEFAULT}`,\n size: `${ButtonSize.MD}`,\n color: `${ButtonColor.CUSTOM}`,\n roundedSize: `${ButtonRoundedSize.MD}`,\n textAlign: `${ButtonTextAlign.CENTER}`,\n isFullWidth: false,\n },\n }\n);\n\n/**\n * Enhanced Button component props with comprehensive type safety and accessibility features\n */\nexport type ButtonProps = DetailedHTMLProps<\n ButtonHTMLAttributes<HTMLButtonElement>,\n HTMLButtonElement\n> &\n VariantProps<typeof buttonVariants> & {\n /**\n * Accessible label for screen readers and assistive technologies.\n * This is required for accessibility compliance.\n */\n label: string | null;\n\n /**\n * Optional icon to display on the left side of the button\n */\n Icon?: FC | LucideIcon;\n\n /**\n * Optional icon to display on the right side of the button\n */\n IconRight?: FC | LucideIcon;\n\n /**\n * Additional CSS classes for icon styling\n */\n iconClassName?: string;\n\n /**\n * Shows loading spinner and disables button interaction when true\n */\n isLoading?: boolean;\n\n /**\n * Marks the button as active (useful for navigation or toggle states)\n */\n isActive?: boolean;\n\n /**\n * Marks the button as selected\n */\n isSelected?: boolean;\n\n /**\n * Makes the button span the full width of its container\n */\n isFullWidth?: boolean;\n\n /**\n * Additional description for complex buttons (optional)\n */\n 'aria-describedby'?: string;\n\n /**\n * Expanded state for collapsible sections (optional)\n */\n 'aria-expanded'?: boolean;\n\n /**\n * Controls whether the button has popup/menu (optional)\n */\n 'aria-haspopup'?:\n | boolean\n | 'true'\n | 'false'\n | 'menu'\n | 'listbox'\n | 'tree'\n | 'grid'\n | 'dialog';\n\n /**\n * Indicates if button controls are currently pressed (for toggle buttons)\n */\n 'aria-pressed'?: boolean;\n };\n\n/**\n * Button Component - A comprehensive, accessible button component\n *\n * Features:\n * - Full accessibility compliance with ARIA attributes\n * - Multiple variants and sizes for different use cases\n * - Icon support (left and right positioning)\n * - Loading states with spinner\n * - Keyboard navigation support\n * - Focus management with visible indicators\n * - Responsive design adaptations\n *\n * @example\n * ```tsx\n * // Basic button\n * <Button label=\"Click me\">Click me</Button>\n *\n * // Button with icon and loading state\n * <Button\n * label=\"Save document\"\n * Icon={SaveIcon}\n * isLoading={saving}\n * disabled={!hasChanges}\n * >\n * Save\n * </Button>\n *\n * // Destructive action button\n * <Button\n * variant={`${ButtonVariant.OUTLINE}`}\n * color={ButtonColor.DESTRUCTIVE}\n * label=\"Delete item permanently\"\n * aria-describedby=\"delete-warning\"\n * >\n * Delete\n * </Button>\n * ```\n */\nexport const Button: FC<ButtonProps> = ({\n variant,\n size,\n color,\n children,\n Icon,\n IconRight,\n iconClassName,\n isLoading = false,\n isActive,\n isSelected,\n isFullWidth,\n roundedSize,\n textAlign,\n disabled,\n label,\n className,\n type = 'button',\n 'aria-describedby': ariaDescribedBy,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHasPopup,\n 'aria-pressed': ariaPressed,\n ...props\n}) => {\n const isLink =\n variant === `${ButtonVariant.LINK}` ||\n variant === `${ButtonVariant.INVISIBLE_LINK}`;\n const isIconOnly = !children && (Icon || IconRight);\n\n const accessibilityProps = {\n 'aria-label': isIconOnly ? (label ?? undefined) : undefined,\n 'aria-labelledby': !isIconOnly ? undefined : undefined,\n 'aria-describedby': ariaDescribedBy,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHasPopup,\n 'aria-pressed': isActive !== undefined ? isActive : ariaPressed,\n 'aria-busy': isLoading,\n 'aria-current': (isActive ? 'page' : undefined) as 'page' | undefined,\n 'aria-disabled': disabled || isLoading,\n 'aria-selected': isSelected,\n };\n\n const isSquareButton =\n size === ButtonSize.ICON_SM ||\n size === ButtonSize.ICON_MD ||\n size === ButtonSize.ICON_LG ||\n size === ButtonSize.ICON_XL;\n\n return (\n <button\n disabled={isLoading || disabled}\n role={isLink ? 'link' : 'button'}\n type={type}\n className={buttonVariants({\n variant,\n size,\n color,\n isFullWidth,\n roundedSize,\n textAlign:\n textAlign ??\n (IconRight ? ButtonTextAlign.LEFT : ButtonTextAlign.CENTER),\n className,\n })}\n {...accessibilityProps}\n {...props}\n >\n {Icon && !isLoading && (\n <Icon\n className={buttonIconVariants({\n size,\n className: cn(!isSquareButton && 'mr-3', iconClassName),\n })}\n aria-hidden=\"true\"\n />\n )}\n\n <div\n className={cn(\n 'flex items-center justify-center transition-[width] duration-300',\n isLoading && size === ButtonSize.SM && 'w-3',\n isLoading && size === ButtonSize.MD && 'w-4',\n isLoading && size === ButtonSize.LG && 'w-5',\n isLoading && size === ButtonSize.XL && 'w-6'\n )}\n >\n <Loader\n className={buttonIconVariants({\n size,\n className: cn(!isSquareButton && 'mr-3', iconClassName),\n })}\n isLoading={isLoading}\n aria-hidden=\"true\"\n data-testid=\"loader\"\n />\n </div>\n\n {children && (\n <span className=\"flex-1 truncate whitespace-nowrap\">{children}</span>\n )}\n\n {!children && isIconOnly && <span className=\"sr-only\">{label}</span>}\n\n {IconRight && (\n <IconRight\n className={buttonIconVariants({\n size,\n className: cn(!isSquareButton && 'ml-3', iconClassName),\n })}\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n};\n"],"mappings":";;;;;;;;;;AAUA,IAAY,aAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;KACD;AAED,MAAM,qBAAqB,IAAI,sBAAsB;CACnD,UAAU,EACR,MAAM;GACH,GAAG,WAAW,OAAO;GACrB,GAAG,WAAW,OAAO;GACrB,GAAG,WAAW,OAAO;GACrB,GAAG,WAAW,OAAO;GACrB,GAAG,WAAW,YAAY;GAC1B,GAAG,WAAW,YAAY;GAC1B,GAAG,WAAW,YAAY;GAC1B,GAAG,WAAW,YAAY;EAC5B,EACF;CACD,iBAAiB,EACf,MAAM,WAAW,IAClB;CACF,CAAC;;;;AAKF,IAAY,gBAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;KACD;;;;AAKD,IAAY,cAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;KACD;;;;AAKD,IAAY,kBAAL;AACL;AACA;AACA;;KACD;;;;AAKD,MAAa,iBAAiB,IAC5B,8LACA;CACE,UAAU;EACR,MAAM;IACH,GAAG,WAAW,OAAO;IACrB,GAAG,WAAW,OAAO;IACrB,GAAG,WAAW,OAAO;IACrB,GAAG,WAAW,OAAO;IACrB,GAAG,WAAW,YAAY;IAC1B,GAAG,WAAW,YAAY;IAC1B,GAAG,WAAW,YAAY;IAC1B,GAAG,WAAW,YAAY;GAC5B;EACD,OAAO;IACJ,GAAG,YAAY,YACd;IACD,GAAG,YAAY,cACd;IACD,GAAG,YAAY,gBACd;IACD,GAAG,YAAY,YACd;IACD,GAAG,YAAY,SACd;IACD,GAAG,YAAY,UACd;IACD,GAAG,YAAY,SACd;IACD,GAAG,YAAY,SAAS;IACxB,GAAG,YAAY,YACd;IACD,GAAG,YAAY,iBACd;IACD,GAAG,YAAY,UACd;IACD,GAAG,YAAY,YACd;IACD,GAAG,YAAY,WAAW;GAC5B;EACD,aAAa;IACV,GAAGA,qBAAkB,SAAS;IAC9B,GAAGA,qBAAkB,OACpB;IACD,GAAGA,qBAAkB,OACpB;IACD,GAAGA,qBAAkB,OACpB;IACD,GAAGA,qBAAkB,OACpB;IACD,GAAGA,qBAAkB,WACpB;IACD,GAAGA,qBAAkB,WACpB;IACD,GAAGA,qBAAkB,WACpB;IACD,GAAGA,qBAAkB,WACpB;IACD,GAAGA,qBAAkB,SAAS;GAChC;EACD,SAAS;IACN,GAAG,cAAc,YAAY;IAC5B;IACA;IACA;IACA;IACD;IAEA,GAAG,cAAc,YAAY;IAC5B;IACA;IACA;IACA;IACD;IAEA,GAAG,cAAc,SAChB;IAED,GAAG,cAAc,SAChB;IAED,GAAG,cAAc,mBAChB;IAED,GAAG,cAAc,cAChB;IAED,GAAG,cAAc,SAAS;IACzB;IACA;IACA;IACD;IACA,GAAG,cAAc,UAAU;IAE1B;IACA;IACA;IACA;IACA;IAEA;IACA;IACA;IAGA;IACA;IACA;IACA;IAGA;IAGA;IAGA;IACD;GACF;EAED,WAAW;IACR,GAAG,gBAAgB,SAAS;IAC5B,GAAG,gBAAgB,WAAW;IAC9B,GAAG,gBAAgB,UAAU;GAC/B;EAED,aAAa;GACX,MAAM;GACN,OAAO;GACR;EACF;CACD,iBAAiB;EACf,SAAS,GAAG,cAAc;EAC1B,MAAM,GAAG,WAAW;EACpB,OAAO,GAAG,YAAY;EACtB,aAAa,GAAGA,qBAAkB;EAClC,WAAW,GAAG,gBAAgB;EAC9B,aAAa;EACd;CACF,CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsHD,MAAa,UAA2B,EACtC,SACA,MACA,OACA,UACA,MACA,WACA,eACA,YAAY,OACZ,UACA,YACA,aACA,aACA,WACA,UACA,OACA,WACA,OAAO,UACP,oBAAoB,iBACpB,iBAAiB,cACjB,iBAAiB,cACjB,gBAAgB,aAChB,GAAG,YACC;CACJ,MAAM,SACJ,YAAY,GAAG,cAAc,UAC7B,YAAY,GAAG,cAAc;CAC/B,MAAM,aAAa,CAAC,aAAa,QAAQ;CAEzC,MAAM,qBAAqB;EACzB,cAAc,aAAc,SAAS,SAAa;EAClD,mBAAmB,CAAC,aAAa,SAAY;EAC7C,oBAAoB;EACpB,iBAAiB;EACjB,iBAAiB;EACjB,gBAAgB,aAAa,SAAY,WAAW;EACpD,aAAa;EACb,gBAAiB,WAAW,SAAS;EACrC,iBAAiB,YAAY;EAC7B,iBAAiB;EAClB;CAED,MAAM,iBACJ,SAAS,WAAW,WACpB,SAAS,WAAW,WACpB,SAAS,WAAW,WACpB,SAAS,WAAW;AAEtB,QACE,qBAAC,UAAD;EACE,UAAU,aAAa;EACvB,MAAM,SAAS,SAAS;EAClB;EACN,WAAW,eAAe;GACxB;GACA;GACA;GACA;GACA;GACA,WACE,cACC,YAAY,gBAAgB,OAAO,gBAAgB;GACtD;GACD,CAAC;EACF,GAAI;EACJ,GAAI;YAhBN;GAkBG,QAAQ,CAAC,aACR,oBAAC,MAAD;IACE,WAAW,mBAAmB;KAC5B;KACA,WAAW,GAAG,CAAC,kBAAkB,QAAQ,cAAc;KACxD,CAAC;IACF,eAAY;IACZ;GAGJ,oBAAC,OAAD;IACE,WAAW,GACT,oEACA,aAAa,SAAS,WAAW,MAAM,OACvC,aAAa,SAAS,WAAW,MAAM,OACvC,aAAa,SAAS,WAAW,MAAM,OACvC,aAAa,SAAS,WAAW,MAAM,MACxC;cAED,oBAAC,QAAD;KACE,WAAW,mBAAmB;MAC5B;MACA,WAAW,GAAG,CAAC,kBAAkB,QAAQ,cAAc;MACxD,CAAC;KACS;KACX,eAAY;KACZ,eAAY;KACZ;IACE;GAEL,YACC,oBAAC,QAAD;IAAM,WAAU;IAAqC;IAAgB;GAGtE,CAAC,YAAY,cAAc,oBAAC,QAAD;IAAM,WAAU;cAAW;IAAa;GAEnE,aACC,oBAAC,WAAD;IACE,WAAW,mBAAmB;KAC5B;KACA,WAAW,GAAG,CAAC,kBAAkB,QAAQ,cAAc;KACxD,CAAC;IACF,eAAY;IACZ;GAEG"}
|
|
1
|
+
{"version":3,"file":"Button.mjs","names":[],"sources":["../../../../src/components/Button/Button.tsx"],"sourcesContent":["import { cn } from '@utils/cn';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type { LucideIcon } from 'lucide-react';\nimport type { ButtonHTMLAttributes, DetailedHTMLProps, FC } from 'react';\nimport { ContainerRoundedSize as ButtonRoundedSize } from '../Container';\nimport { Loader } from '../Loader';\n\n/**\n * Button size variants for different use cases\n */\nexport enum ButtonSize {\n SM = 'sm',\n MD = 'md',\n LG = 'lg',\n XL = 'xl',\n ICON_SM = 'icon-sm',\n ICON_MD = 'icon-md',\n ICON_LG = 'icon-lg',\n ICON_XL = 'icon-xl',\n}\n\nconst buttonIconVariants = cva('flex-none shrink-0', {\n variants: {\n size: {\n [`${ButtonSize.SM}`]: 'size-3',\n [`${ButtonSize.MD}`]: 'size-4',\n [`${ButtonSize.LG}`]: 'size-5',\n [`${ButtonSize.XL}`]: 'size-6',\n [`${ButtonSize.ICON_SM}`]: 'size-3',\n [`${ButtonSize.ICON_MD}`]: 'size-4',\n [`${ButtonSize.ICON_LG}`]: 'size-4',\n [`${ButtonSize.ICON_XL}`]: 'size-5',\n },\n },\n defaultVariants: {\n size: ButtonSize.MD,\n },\n});\n\n/**\n * Button visual style variants\n */\nexport enum ButtonVariant {\n DEFAULT = 'default',\n NONE = 'none',\n OUTLINE = 'outline',\n LINK = 'link',\n INVISIBLE_LINK = 'invisible-link',\n HOVERABLE = 'hoverable',\n FADE = 'fade',\n INPUT = 'input',\n}\n\n/**\n * Button color themes that work with the design system\n */\nexport enum ButtonColor {\n PRIMARY = 'primary',\n SECONDARY = 'secondary',\n DESTRUCTIVE = 'destructive',\n NEUTRAL = 'neutral',\n LIGHT = 'light',\n DARK = 'dark',\n TEXT = 'text',\n CARD = 'card',\n TEXT_INVERSE = 'text-inverse',\n CURRENT = 'current',\n ERROR = 'error',\n SUCCESS = 'success',\n CUSTOM = 'custom',\n}\n\n/**\n * Text alignment options for button content\n */\nexport enum ButtonTextAlign {\n LEFT = 'left',\n CENTER = 'center',\n RIGHT = 'right',\n}\n\n/**\n * Enhanced button variants with improved accessibility and focus states\n */\nexport const buttonVariants = cva(\n 'relative inline-flex cursor-pointer items-center justify-center font-medium ring-0 transition-all duration-300 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',\n {\n variants: {\n size: {\n [`${ButtonSize.SM}`]: 'min-h-7 px-3 text-xs max-md:py-1',\n [`${ButtonSize.MD}`]: 'min-h-8 px-6 text-sm max-md:py-2',\n [`${ButtonSize.LG}`]: 'min-h-10 px-8 text-lg max-md:py-3',\n [`${ButtonSize.XL}`]: 'min-h-11 px-10 text-xl max-md:py-4',\n [`${ButtonSize.ICON_SM}`]: 'p-1.5',\n [`${ButtonSize.ICON_MD}`]: 'p-1.5',\n [`${ButtonSize.ICON_LG}`]: 'p-2',\n [`${ButtonSize.ICON_XL}`]: 'p-3',\n },\n color: {\n [`${ButtonColor.PRIMARY}`]:\n 'hover-primary-500/20 text-primary ring-primary-500/20 *:text-text-light',\n [`${ButtonColor.SECONDARY}`]:\n 'hover-secondary-500/20 text-secondary ring-secondary-500/20 *:text-text-light',\n [`${ButtonColor.DESTRUCTIVE}`]:\n 'hover-destructive-500/20 text-destructive ring-destructive-500/20 *:text-text-light',\n [`${ButtonColor.NEUTRAL}`]:\n 'text-neutral ring-neutral-500/5 *:text-text-light',\n [`${ButtonColor.CARD}`]:\n 'hover-card-500/20 text-card ring-card-500/20 *:text-text-light',\n [`${ButtonColor.LIGHT}`]:\n 'hover-white-500/20 text-white ring-white/20 *:text-text-light',\n [`${ButtonColor.DARK}`]:\n 'text-neutral-800 ring-text-light/50 *:text-text-light',\n [`${ButtonColor.TEXT}`]: 'text-text ring-text/20 *:text-text-opposite',\n [`${ButtonColor.CURRENT}`]:\n 'hover-current-500/10 text-current ring-current/10 *:text-text-light',\n [`${ButtonColor.TEXT_INVERSE}`]:\n 'text-text-opposite ring-text-opposite/20 *:text-text',\n [`${ButtonColor.ERROR}`]:\n 'hover-error-500/20 text-error ring-error/20 *:text-text-light',\n [`${ButtonColor.SUCCESS}`]:\n 'hover-success-500/20 text-success ring-success/20 *:text-text-light',\n [`${ButtonColor.CUSTOM}`]: '',\n },\n roundedSize: {\n [`${ButtonRoundedSize.NONE}`]: 'rounded-none',\n [`${ButtonRoundedSize.SM}`]:\n 'rounded-lg [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-xl',\n [`${ButtonRoundedSize.MD}`]:\n 'rounded-xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-2xl',\n [`${ButtonRoundedSize.LG}`]:\n 'rounded-2xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-3xl',\n [`${ButtonRoundedSize.XL}`]:\n 'rounded-3xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-4xl',\n [`${ButtonRoundedSize['2xl']}`]:\n 'rounded-4xl [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[2.5rem]',\n [`${ButtonRoundedSize['3xl']}`]:\n 'rounded-[2.5rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[3rem]',\n [`${ButtonRoundedSize['4xl']}`]:\n 'rounded-[3rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[4rem]',\n [`${ButtonRoundedSize['5xl']}`]:\n 'rounded-[4rem] [corner-shape:squircle] supports-[corner-shape:squircle]:rounded-[5rem]',\n [`${ButtonRoundedSize.FULL}`]: 'rounded-full',\n },\n variant: {\n [`${ButtonVariant.DEFAULT}`]: [\n 'bg-current',\n 'hover:bg-current/90',\n 'hover:ring-5',\n 'aria-selected:ring-5',\n ],\n\n [`${ButtonVariant.OUTLINE}`]: [\n 'rounded-2xl border-[1.3px] border-current bg-current/0 *:text-current!',\n 'hover:bg-current/20 focus-visible:bg-current/20',\n 'hover:ring-5 focus-visible:ring-5',\n 'aria-selected:ring-5',\n ],\n\n [`${ButtonVariant.NONE}`]:\n 'border-none bg-current/0 text-inherit hover:bg-current/0',\n\n [`${ButtonVariant.LINK}`]:\n 'h-auto justify-start border-inherit bg-transparent px-1 underline-offset-4 *:text-current! hover:bg-transparent hover:underline',\n\n [`${ButtonVariant.INVISIBLE_LINK}`]:\n 'h-auto justify-start border-inherit bg-transparent px-1 underline-offset-4 *:text-current! hover:bg-transparent',\n\n [`${ButtonVariant.HOVERABLE}`]:\n 'rounded-lg border-none bg-current/0 transition *:text-current! hover:bg-current/20 aria-[current]:bg-current/5',\n\n [`${ButtonVariant.FADE}`]: [\n 'rounded-lg border-none bg-current/10 ring-current/5 transition *:text-current! hover:bg-current/20 aria-[current]:bg-current/5',\n 'hover:ring-5 focus-visible:ring-5',\n 'aria-selected:ring-5',\n ],\n [`${ButtonVariant.INPUT}`]: [\n // base styles\n 'text-text',\n 'w-full select-text resize-none rounded-2xl text-base shadow-none outline-none supports-[corner-shape:squircle]:rounded-4xl',\n 'transition-shadow duration-100 md:text-sm',\n 'ring-0', // base ring\n 'disabled:opacity-50',\n\n 'text-text',\n 'bg-neutral-50 dark:bg-neutral-950',\n 'ring-neutral-100 dark:ring-neutral-700',\n\n // Hover ring (similar spirit to your input)\n 'hover:ring-3', // width\n 'aria-selected:ring-4',\n 'focus-visible:ring-3',\n 'disabled:ring-0',\n\n // Focus ring + animation\n 'focus-visible:outline-none',\n\n // Remove any weird box-shadow\n '[box-shadow:none] focus:[box-shadow:none]',\n\n // aria-invalid border color\n 'aria-invalid:border-error',\n ],\n },\n\n textAlign: {\n [`${ButtonTextAlign.LEFT}`]: 'justify-start text-left',\n [`${ButtonTextAlign.CENTER}`]: 'justify-center text-center',\n [`${ButtonTextAlign.RIGHT}`]: 'justify-end text-right',\n },\n\n isFullWidth: {\n true: 'w-full',\n false: '',\n },\n },\n defaultVariants: {\n variant: `${ButtonVariant.DEFAULT}`,\n size: `${ButtonSize.MD}`,\n color: `${ButtonColor.CUSTOM}`,\n roundedSize: `${ButtonRoundedSize.MD}`,\n textAlign: `${ButtonTextAlign.CENTER}`,\n isFullWidth: false,\n },\n }\n);\n\n/**\n * Enhanced Button component props with comprehensive type safety and accessibility features\n */\nexport type ButtonProps = DetailedHTMLProps<\n ButtonHTMLAttributes<HTMLButtonElement>,\n HTMLButtonElement\n> &\n VariantProps<typeof buttonVariants> & {\n /**\n * Accessible label for screen readers and assistive technologies.\n * This is required for accessibility compliance.\n */\n label: string | null;\n\n /**\n * Optional icon to display on the left side of the button\n */\n Icon?: FC | LucideIcon;\n\n /**\n * Optional icon to display on the right side of the button\n */\n IconRight?: FC | LucideIcon;\n\n /**\n * Additional CSS classes for icon styling\n */\n iconClassName?: string;\n\n /**\n * Shows loading spinner and disables button interaction when true\n */\n isLoading?: boolean;\n\n /**\n * Marks the button as active (useful for navigation or toggle states)\n */\n isActive?: boolean;\n\n /**\n * Marks the button as selected\n */\n isSelected?: boolean;\n\n /**\n * Makes the button span the full width of its container\n */\n isFullWidth?: boolean;\n\n /**\n * Additional description for complex buttons (optional)\n */\n 'aria-describedby'?: string;\n\n /**\n * Expanded state for collapsible sections (optional)\n */\n 'aria-expanded'?: boolean;\n\n /**\n * Controls whether the button has popup/menu (optional)\n */\n 'aria-haspopup'?:\n | boolean\n | 'true'\n | 'false'\n | 'menu'\n | 'listbox'\n | 'tree'\n | 'grid'\n | 'dialog';\n\n /**\n * Indicates if button controls are currently pressed (for toggle buttons)\n */\n 'aria-pressed'?: boolean;\n };\n\n/**\n * Button Component - A comprehensive, accessible button component\n *\n * Features:\n * - Full accessibility compliance with ARIA attributes\n * - Multiple variants and sizes for different use cases\n * - Icon support (left and right positioning)\n * - Loading states with spinner\n * - Keyboard navigation support\n * - Focus management with visible indicators\n * - Responsive design adaptations\n *\n * @example\n * ```tsx\n * // Basic button\n * <Button label=\"Click me\">Click me</Button>\n *\n * // Button with icon and loading state\n * <Button\n * label=\"Save document\"\n * Icon={SaveIcon}\n * isLoading={saving}\n * disabled={!hasChanges}\n * >\n * Save\n * </Button>\n *\n * // Destructive action button\n * <Button\n * variant={`${ButtonVariant.OUTLINE}`}\n * color={ButtonColor.DESTRUCTIVE}\n * label=\"Delete item permanently\"\n * aria-describedby=\"delete-warning\"\n * >\n * Delete\n * </Button>\n * ```\n */\nexport const Button: FC<ButtonProps> = ({\n variant,\n size,\n color,\n children,\n Icon,\n IconRight,\n iconClassName,\n isLoading = false,\n isActive,\n isSelected,\n isFullWidth,\n roundedSize,\n textAlign,\n disabled,\n label,\n className,\n type = 'button',\n 'aria-describedby': ariaDescribedBy,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHasPopup,\n 'aria-pressed': ariaPressed,\n ...props\n}) => {\n const isLink =\n variant === `${ButtonVariant.LINK}` ||\n variant === `${ButtonVariant.INVISIBLE_LINK}`;\n const isIconOnly = !children && (Icon || IconRight);\n\n const accessibilityProps = {\n 'aria-label': isIconOnly ? (label ?? undefined) : undefined,\n 'aria-labelledby': !isIconOnly ? undefined : undefined,\n 'aria-describedby': ariaDescribedBy,\n 'aria-expanded': ariaExpanded,\n 'aria-haspopup': ariaHasPopup,\n 'aria-pressed': isActive !== undefined ? isActive : ariaPressed,\n 'aria-busy': isLoading,\n 'aria-current': (isActive ? 'page' : undefined) as 'page' | undefined,\n 'aria-disabled': disabled || isLoading,\n 'aria-selected': isSelected,\n };\n\n const isSquareButton =\n size === ButtonSize.ICON_SM ||\n size === ButtonSize.ICON_MD ||\n size === ButtonSize.ICON_LG ||\n size === ButtonSize.ICON_XL;\n\n return (\n <button\n disabled={isLoading || disabled}\n role={isLink ? 'link' : 'button'}\n type={type}\n className={buttonVariants({\n variant,\n size,\n color,\n isFullWidth,\n roundedSize,\n textAlign:\n textAlign ??\n (IconRight ? ButtonTextAlign.LEFT : ButtonTextAlign.CENTER),\n className,\n })}\n {...accessibilityProps}\n {...props}\n >\n {Icon && !isLoading && (\n <Icon\n className={buttonIconVariants({\n size,\n className: cn(!isSquareButton && 'mr-3', iconClassName),\n })}\n aria-hidden=\"true\"\n />\n )}\n\n <div\n className={cn(\n 'flex items-center justify-center transition-[width] duration-300',\n isLoading && size === ButtonSize.SM && 'w-3',\n isLoading && size === ButtonSize.MD && 'w-4',\n isLoading && size === ButtonSize.LG && 'w-5',\n isLoading && size === ButtonSize.XL && 'w-6'\n )}\n >\n <Loader\n className={buttonIconVariants({\n size,\n className: cn(!isSquareButton && 'mr-3', iconClassName),\n })}\n isLoading={isLoading}\n aria-hidden=\"true\"\n data-testid=\"loader\"\n />\n </div>\n\n {children && (\n <span className=\"flex-1 truncate whitespace-nowrap\">{children}</span>\n )}\n\n {!children && isIconOnly && <span className=\"sr-only\">{label}</span>}\n\n {IconRight && (\n <IconRight\n className={buttonIconVariants({\n size,\n className: cn(!isSquareButton && 'ml-3', iconClassName),\n })}\n aria-hidden=\"true\"\n />\n )}\n </button>\n );\n};\n"],"mappings":";;;;;;;;;;AAUA,IAAY,aAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;KACD;AAED,MAAM,qBAAqB,IAAI,sBAAsB;CACnD,UAAU,EACR,MAAM;GACH,OAAqB;GACrB,OAAqB;GACrB,OAAqB;GACrB,OAAqB;GACrB,YAA0B;GAC1B,YAA0B;GAC1B,YAA0B;GAC1B,YAA0B;EAC5B,EACF;CACD,iBAAiB,EACf,YACD;CACF,CAAC;;;;AAKF,IAAY,gBAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;KACD;;;;AAKD,IAAY,cAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;KACD;;;;AAKD,IAAY,kBAAL;AACL;AACA;AACA;;KACD;;;;AAKD,MAAa,iBAAiB,IAC5B,8LACA;CACE,UAAU;EACR,MAAM;IACH,OAAqB;IACrB,OAAqB;IACrB,OAAqB;IACrB,OAAqB;IACrB,YAA0B;IAC1B,YAA0B;IAC1B,YAA0B;IAC1B,YAA0B;GAC5B;EACD,OAAO;IACJ,YACC;IACD,cACC;IACD,gBACC;IACD,YACC;IACD,SACC;IACD,UACC;IACD,SACC;IACD,SAAwB;IACxB,YACC;IACD,iBACC;IACD,UACC;IACD,YACC;IACD,WAA0B;GAC5B;EACD,aAAa;IACV,cAA8B;IAC9B,YACC;IACD,YACC;IACD,YACC;IACD,YACC;IACD,aACC;IACD,aACC;IACD,aACC;IACD,aACC;IACD,cAA8B;GAChC;EACD,SAAS;IACN,YAA6B;IAC5B;IACA;IACA;IACA;IACD;IAEA,YAA6B;IAC5B;IACA;IACA;IACA;IACD;IAEA,SACC;IAED,SACC;IAED,mBACC;IAED,cACC;IAED,SAA0B;IACzB;IACA;IACA;IACD;IACA,UAA2B;IAE1B;IACA;IACA;IACA;IACA;IAEA;IACA;IACA;IAGA;IACA;IACA;IACA;IAGA;IAGA;IAGA;IACD;GACF;EAED,WAAW;IACR,SAA4B;IAC5B,WAA8B;IAC9B,UAA6B;GAC/B;EAED,aAAa;GACX,MAAM;GACN,OAAO;GACR;EACF;CACD,iBAAiB;EACf,SAAS;EACT,MAAM;EACN,OAAO;EACP,aAAa;EACb,WAAW;EACX,aAAa;EACd;CACF,CACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsHD,MAAa,UAA2B,EACtC,SACA,MACA,OACA,UACA,MACA,WACA,eACA,YAAY,OACZ,UACA,YACA,aACA,aACA,WACA,UACA,OACA,WACA,OAAO,UACP,oBAAoB,iBACpB,iBAAiB,cACjB,iBAAiB,cACjB,gBAAgB,aAChB,GAAG,YACC;CACJ,MAAM,SACJ,YAAY,UACZ,YAAY;CACd,MAAM,aAAa,CAAC,aAAa,QAAQ;CAEzC,MAAM,qBAAqB;EACzB,cAAc,aAAc,SAAS,SAAa;EAClD,mBAAmB,CAAC,aAAa,SAAY;EAC7C,oBAAoB;EACpB,iBAAiB;EACjB,iBAAiB;EACjB,gBAAgB,aAAa,SAAY,WAAW;EACpD,aAAa;EACb,gBAAiB,WAAW,SAAS;EACrC,iBAAiB,YAAY;EAC7B,iBAAiB;EAClB;CAED,MAAM,iBACJ,sBACA,sBACA,sBACA;AAEF,QACE,qBAAC,UAAD;EACE,UAAU,aAAa;EACvB,MAAM,SAAS,SAAS;EAClB;EACN,WAAW,eAAe;GACxB;GACA;GACA;GACA;GACA;GACA,WACE,cACC;GACH;GACD,CAAC;EACF,GAAI;EACJ,GAAI;YAhBN;GAkBG,QAAQ,CAAC,aACR,oBAAC,MAAD;IACE,WAAW,mBAAmB;KAC5B;KACA,WAAW,GAAG,CAAC,kBAAkB,QAAQ,cAAc;KACxD,CAAC;IACF,eAAY;IACZ;GAGJ,oBAAC,OAAD;IACE,WAAW,GACT,oEACA,aAAa,iBAA0B,OACvC,aAAa,iBAA0B,OACvC,aAAa,iBAA0B,OACvC,aAAa,iBAA0B,MACxC;cAED,oBAAC,QAAD;KACE,WAAW,mBAAmB;MAC5B;MACA,WAAW,GAAG,CAAC,kBAAkB,QAAQ,cAAc;MACxD,CAAC;KACS;KACX,eAAY;KACZ,eAAY;KACZ;IACE;GAEL,YACC,oBAAC,QAAD;IAAM,WAAU;IAAqC;IAAgB;GAGtE,CAAC,YAAY,cAAc,oBAAC,QAAD;IAAM,WAAU;cAAW;IAAa;GAEnE,aACC,oBAAC,WAAD;IACE,WAAW,mBAAmB;KAC5B;KACA,WAAW,GAAG,CAAC,kBAAkB,QAAQ,cAAc;KACxD,CAAC;IACF,eAAY;IACZ;GAEG"}
|
|
@@ -55,8 +55,8 @@ const CarouselIndicators = ({ className, disableKeyboardShortcuts = false, ...pr
|
|
|
55
55
|
/* @__PURE__ */ jsxs(Popover, {
|
|
56
56
|
identifier: "carousel-prev",
|
|
57
57
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
58
|
-
variant:
|
|
59
|
-
color:
|
|
58
|
+
variant: "hoverable",
|
|
59
|
+
color: "neutral",
|
|
60
60
|
label: previousSlide.value,
|
|
61
61
|
roundedSize: "full",
|
|
62
62
|
onClick: (e) => {
|
|
@@ -64,7 +64,7 @@ const CarouselIndicators = ({ className, disableKeyboardShortcuts = false, ...pr
|
|
|
64
64
|
handlePrev();
|
|
65
65
|
},
|
|
66
66
|
Icon: ChevronLeft,
|
|
67
|
-
size:
|
|
67
|
+
size: "icon-md",
|
|
68
68
|
disabled: selectedIndex === 0
|
|
69
69
|
}), /* @__PURE__ */ jsx(Popover.Detail, {
|
|
70
70
|
identifier: "carousel-prev",
|
|
@@ -97,8 +97,8 @@ const CarouselIndicators = ({ className, disableKeyboardShortcuts = false, ...pr
|
|
|
97
97
|
/* @__PURE__ */ jsxs(Popover, {
|
|
98
98
|
identifier: "carousel-next",
|
|
99
99
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
100
|
-
variant:
|
|
101
|
-
color:
|
|
100
|
+
variant: "hoverable",
|
|
101
|
+
color: "neutral",
|
|
102
102
|
roundedSize: "full",
|
|
103
103
|
label: nextSlide.value,
|
|
104
104
|
onClick: (e) => {
|
|
@@ -106,7 +106,7 @@ const CarouselIndicators = ({ className, disableKeyboardShortcuts = false, ...pr
|
|
|
106
106
|
handleNext();
|
|
107
107
|
},
|
|
108
108
|
Icon: ChevronRight,
|
|
109
|
-
size:
|
|
109
|
+
size: "icon-md",
|
|
110
110
|
disabled: selectedIndex === totalItems - 1
|
|
111
111
|
}), /* @__PURE__ */ jsx(Popover.Detail, {
|
|
112
112
|
identifier: "carousel-next",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/Carousel/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n ButtonColor,\n ButtonSize,\n ButtonVariant,\n} from '@components/Button';\nimport { KeyboardShortcut } from '@components/KeyboardShortcut';\nimport { Popover } from '@components/Popover';\nimport { cn } from '@utils/cn';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport {\n Children,\n createContext,\n type FC,\n type HTMLAttributes,\n isValidElement,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n type TouchEventHandler,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\n\n// ------------------------------------------------------------------\n// Configuration\n// ------------------------------------------------------------------\nconst SWIPE_THRESHOLD_DIVISOR = 5;\nconst TRANSITION_DELAY_MS = 50;\n\n// ------------------------------------------------------------------\n// Context Definition\n// ------------------------------------------------------------------\ntype CarouselContextValue = {\n selectedIndex: number;\n setSelectedIndex: (index: number) => void;\n totalItems: number;\n handlePrev: () => void;\n handleNext: () => void;\n};\n\nconst CarouselContext = createContext<CarouselContextValue | null>(null);\n\nconst useCarousel = () => {\n const context = useContext(CarouselContext);\n if (!context) {\n throw new Error('useCarousel must be used within a Carousel');\n }\n return context;\n};\n\n// ------------------------------------------------------------------\n// Helper Functions\n// ------------------------------------------------------------------\nconst getCardStyle = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 'opacity-100 z-40';\n case 1:\n return 'opacity-75 z-30 cursor-pointer';\n case 2:\n return 'opacity-50 z-20 pointer-events-none';\n default:\n return 'opacity-0 z-10 pointer-events-none';\n }\n};\n\nconst getCardScale = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 1;\n case 1:\n return 0.9;\n case 2:\n return 0.8;\n default:\n return 0.7;\n }\n};\n\n// This allows the calculation to work on SSR without hydration mismatch.\n// Your original logic: (3 * screenWidth) / 10 === 30% of viewport width\nconst getCardPositionX = (\n index: number,\n displayedIndex: number,\n containerWidth: number\n) => {\n const diff = index - displayedIndex;\n const gapPercentage = containerWidth < 600 ? 0.15 : 0.3; // Dropped to 15% for a tighter cluster\n const step = Math.min(containerWidth * gapPercentage, 300);\n\n // The 'px' here is mandatory\n return `${diff * step}px`;\n};\n\n// ------------------------------------------------------------------\n// Sub-Component: Item\n// ------------------------------------------------------------------\ntype CarouselItemProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n};\n\nconst CarouselItem: FC<CarouselItemProps> = ({\n children,\n className,\n ...props\n}) => {\n return (\n <div className={cn('h-full w-full', className)} {...props}>\n {children}\n </div>\n );\n};\n\n// ------------------------------------------------------------------\n// Sub-Component: Indicators (Controller)\n// ------------------------------------------------------------------\ntype CarouselIndicatorsProps = HTMLAttributes<HTMLDivElement> & {\n disableKeyboardShortcuts?: boolean;\n};\n\nconst CarouselIndicators: FC<CarouselIndicatorsProps> = ({\n className,\n disableKeyboardShortcuts = false,\n ...props\n}) => {\n const {\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n } = useCarousel();\n const { goToSlide, previousSlide, nextSlide } = useIntlayer('carousel');\n\n if (totalItems <= 1) return null;\n\n return (\n <div\n className={cn(\n 'absolute bottom-4 left-1/2 z-50 flex -translate-x-1/2 flex-row items-center gap-2',\n className\n )}\n {...props}\n >\n <Popover identifier=\"carousel-prev\">\n <Button\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.NEUTRAL}\n label={previousSlide.value}\n roundedSize=\"full\"\n onClick={(e) => {\n e.stopPropagation();\n handlePrev();\n }}\n Icon={ChevronLeft}\n size={ButtonSize.ICON_MD}\n disabled={selectedIndex === 0}\n />\n\n <Popover.Detail identifier=\"carousel-prev\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {previousSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowLeft\"\n disabled={disableKeyboardShortcuts}\n size=\"sm\"\n onTriggered={handlePrev}\n />\n </div>\n </Popover.Detail>\n </Popover>\n\n {Array.from({ length: totalItems }).map((_, index) => {\n const isActive = index === selectedIndex;\n return (\n <button\n key={index}\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n aria-label={goToSlide({ index: index + 1 }).value}\n className={cn(\n 'h-2.5 w-2.5 rounded-full transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2',\n isActive ? 'scale-110 bg-text' : 'bg-text/20 hover:bg-text/40'\n )}\n />\n );\n })}\n\n <Popover identifier=\"carousel-next\">\n <Button\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.NEUTRAL}\n roundedSize=\"full\"\n label={nextSlide.value}\n onClick={(e) => {\n e.stopPropagation();\n handleNext();\n }}\n Icon={ChevronRight}\n size={ButtonSize.ICON_MD}\n disabled={selectedIndex === totalItems - 1}\n />\n\n <Popover.Detail identifier=\"carousel-next\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {nextSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowRight\"\n size=\"sm\"\n onTriggered={handleNext}\n disabled={disableKeyboardShortcuts}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n );\n};\n\n// ------------------------------------------------------------------\n// Main Component: Carousel Root\n// ------------------------------------------------------------------\ntype CarouselProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n initialIndex?: number;\n onIndexChange?: (index: number) => void;\n};\n\nconst partitionCarouselChildren = (\n children: ReactNode[]\n): [ReactElement[], ReactNode[]] => {\n const slides: ReactElement[] = [];\n const others: ReactNode[] = [];\n\n children.forEach((child) => {\n if (isValidElement(child) && child.type === CarouselItem) {\n slides.push(child);\n } else {\n others.push(child);\n }\n });\n\n return [slides, others];\n};\n\nconst CarouselRoot: FC<CarouselProps> = ({\n children,\n className,\n initialIndex = 0,\n onIndexChange,\n ...props\n}) => {\n const allChildren = Children.toArray(children);\n const [slides, others] = partitionCarouselChildren(allChildren);\n const totalItems = slides.length;\n\n // State Management\n const [selectedIndex, setSelectedIndex] = useState<number>(initialIndex);\n const [displayedIndex, setDisplayedIndex] = useState<number>(initialIndex);\n const [containerHeight, setContainerHeight] = useState<number>(0);\n const [containerWidth, setContainerWidth] = useState<number>(0);\n\n useEffect(() => {\n const calculateDimensions = () => {\n if (!containerRef.current) return;\n\n // Track Width\n const width = containerRef.current.clientWidth;\n setContainerWidth(width);\n\n // Track Height (existing logic)\n const heights = itemsRef.current.map((item) => item?.offsetHeight || 0);\n const maxHeight = Math.max(0, ...heights);\n if (maxHeight > 0) {\n setContainerHeight(maxHeight + 40);\n }\n };\n\n calculateDimensions();\n\n const observer = new ResizeObserver(() => {\n calculateDimensions();\n });\n\n if (containerRef.current) observer.observe(containerRef.current);\n itemsRef.current.forEach((item) => {\n if (item) observer.observe(item);\n });\n\n return () => observer.disconnect();\n }, [totalItems]);\n\n // Drag State\n const [startX, setStartX] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n // Refs\n const containerRef = useRef<HTMLDivElement>(null);\n const itemsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n // Navigation Logic\n const handleSwitchItem = (diff: number) => {\n if (containerWidth === 0) return;\n\n // Use container width for the threshold\n const swipeStep = containerWidth / SWIPE_THRESHOLD_DIVISOR;\n const numSwipe = Math.round(diff / swipeStep);\n\n if (Math.abs(numSwipe) >= 1) {\n const newIndex = displayedIndex - numSwipe;\n const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1));\n\n if (clampedIndex !== selectedIndex) {\n setSelectedIndex(clampedIndex);\n setStartX((prev) => prev + diff);\n }\n }\n };\n\n const handleNext = () => {\n setSelectedIndex((prev) => Math.min(prev + 1, totalItems - 1));\n };\n\n const handlePrev = () => {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n };\n\n // Input Handlers\n const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.clientX);\n };\n const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.clientX - startX);\n };\n const handleDragEnd = () => setIsDragging(false);\n const handleTouchStart: TouchEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.touches[0].clientX);\n };\n const handleTouchMove: TouchEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.touches[0].clientX - startX);\n };\n\n // Effects\n useEffect(() => {\n if (selectedIndex) onIndexChange?.(selectedIndex);\n }, [selectedIndex, onIndexChange]);\n\n useEffect(() => {\n let interval: NodeJS.Timeout;\n\n if (selectedIndex !== displayedIndex) {\n interval = setInterval(() => {\n setDisplayedIndex((prev) => {\n if (prev === selectedIndex) {\n clearInterval(interval);\n return prev;\n }\n return prev < selectedIndex ? prev + 1 : prev - 1;\n });\n }, TRANSITION_DELAY_MS);\n }\n return () => clearInterval(interval);\n }, [selectedIndex, displayedIndex]);\n\n // Calculate height based on the MAX height of ALL items\n useEffect(() => {\n const calculateMaxHeight = () => {\n const heights = itemsRef.current.map((item) => item?.offsetHeight || 0);\n const maxHeight = Math.max(0, ...heights);\n\n if (maxHeight > 0) {\n setContainerHeight(maxHeight + 40);\n }\n };\n\n calculateMaxHeight();\n\n const observer = new ResizeObserver(() => {\n calculateMaxHeight();\n });\n\n itemsRef.current.forEach((item) => {\n if (item) observer.observe(item);\n });\n\n return () => observer.disconnect();\n }, [totalItems]); // Removed isMounted dependency\n\n return (\n <CarouselContext.Provider\n value={{\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n }}\n >\n <div\n ref={containerRef}\n className={cn(\n 'relative flex w-full cursor-grab select-none items-center overflow-hidden outline-none transition-[height] duration-300 ease-in-out focus:outline-none focus:outline-none focus:ring-0 active:cursor-grabbing',\n 'max-w-[1400px]',\n className\n )}\n style={{\n height: containerHeight > 0 ? containerHeight : 'auto',\n minHeight: '400px',\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleDragEnd}\n onMouseLeave={handleDragEnd}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleDragEnd}\n role=\"region\"\n aria-label=\"Carousel\"\n {...props}\n >\n {slides.map((child, index) => {\n return (\n <div\n key={index}\n role=\"button\"\n tabIndex={0}\n ref={(el) => {\n itemsRef.current[index] = el;\n }}\n // FIX 2: Removed isMounted checks and invisible classes.\n // CSS units allow correct SSR rendering.\n className={cn(\n 'absolute left-1/2 -translate-x-1/2 transition-all duration-300 ease-in-out',\n 'outline-none ring-0 focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',\n getCardStyle(index, displayedIndex)\n )}\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') setSelectedIndex(index);\n }}\n style={{\n transform: `\n translateX(${getCardPositionX(\n index,\n displayedIndex,\n containerWidth\n )})\n scale(${getCardScale(index, displayedIndex)})\n `,\n }}\n >\n {child}\n </div>\n );\n })}\n\n {others}\n </div>\n </CarouselContext.Provider>\n );\n};\n\nexport const Carousel = Object.assign(CarouselRoot, {\n Item: CarouselItem,\n Indicators: CarouselIndicators,\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/components/Carousel/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n Button,\n ButtonColor,\n ButtonSize,\n ButtonVariant,\n} from '@components/Button';\nimport { KeyboardShortcut } from '@components/KeyboardShortcut';\nimport { Popover } from '@components/Popover';\nimport { cn } from '@utils/cn';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport {\n Children,\n createContext,\n type FC,\n type HTMLAttributes,\n isValidElement,\n type MouseEventHandler,\n type ReactElement,\n type ReactNode,\n type TouchEventHandler,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { useIntlayer } from 'react-intlayer';\n\n// ------------------------------------------------------------------\n// Configuration\n// ------------------------------------------------------------------\nconst SWIPE_THRESHOLD_DIVISOR = 5;\nconst TRANSITION_DELAY_MS = 50;\n\n// ------------------------------------------------------------------\n// Context Definition\n// ------------------------------------------------------------------\ntype CarouselContextValue = {\n selectedIndex: number;\n setSelectedIndex: (index: number) => void;\n totalItems: number;\n handlePrev: () => void;\n handleNext: () => void;\n};\n\nconst CarouselContext = createContext<CarouselContextValue | null>(null);\n\nconst useCarousel = () => {\n const context = useContext(CarouselContext);\n if (!context) {\n throw new Error('useCarousel must be used within a Carousel');\n }\n return context;\n};\n\n// ------------------------------------------------------------------\n// Helper Functions\n// ------------------------------------------------------------------\nconst getCardStyle = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 'opacity-100 z-40';\n case 1:\n return 'opacity-75 z-30 cursor-pointer';\n case 2:\n return 'opacity-50 z-20 pointer-events-none';\n default:\n return 'opacity-0 z-10 pointer-events-none';\n }\n};\n\nconst getCardScale = (index: number, displayedIndex: number) => {\n const diff = Math.abs(index - displayedIndex);\n switch (diff) {\n case 0:\n return 1;\n case 1:\n return 0.9;\n case 2:\n return 0.8;\n default:\n return 0.7;\n }\n};\n\n// This allows the calculation to work on SSR without hydration mismatch.\n// Your original logic: (3 * screenWidth) / 10 === 30% of viewport width\nconst getCardPositionX = (\n index: number,\n displayedIndex: number,\n containerWidth: number\n) => {\n const diff = index - displayedIndex;\n const gapPercentage = containerWidth < 600 ? 0.15 : 0.3; // Dropped to 15% for a tighter cluster\n const step = Math.min(containerWidth * gapPercentage, 300);\n\n // The 'px' here is mandatory\n return `${diff * step}px`;\n};\n\n// ------------------------------------------------------------------\n// Sub-Component: Item\n// ------------------------------------------------------------------\ntype CarouselItemProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n};\n\nconst CarouselItem: FC<CarouselItemProps> = ({\n children,\n className,\n ...props\n}) => {\n return (\n <div className={cn('h-full w-full', className)} {...props}>\n {children}\n </div>\n );\n};\n\n// ------------------------------------------------------------------\n// Sub-Component: Indicators (Controller)\n// ------------------------------------------------------------------\ntype CarouselIndicatorsProps = HTMLAttributes<HTMLDivElement> & {\n disableKeyboardShortcuts?: boolean;\n};\n\nconst CarouselIndicators: FC<CarouselIndicatorsProps> = ({\n className,\n disableKeyboardShortcuts = false,\n ...props\n}) => {\n const {\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n } = useCarousel();\n const { goToSlide, previousSlide, nextSlide } = useIntlayer('carousel');\n\n if (totalItems <= 1) return null;\n\n return (\n <div\n className={cn(\n 'absolute bottom-4 left-1/2 z-50 flex -translate-x-1/2 flex-row items-center gap-2',\n className\n )}\n {...props}\n >\n <Popover identifier=\"carousel-prev\">\n <Button\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.NEUTRAL}\n label={previousSlide.value}\n roundedSize=\"full\"\n onClick={(e) => {\n e.stopPropagation();\n handlePrev();\n }}\n Icon={ChevronLeft}\n size={ButtonSize.ICON_MD}\n disabled={selectedIndex === 0}\n />\n\n <Popover.Detail identifier=\"carousel-prev\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {previousSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowLeft\"\n disabled={disableKeyboardShortcuts}\n size=\"sm\"\n onTriggered={handlePrev}\n />\n </div>\n </Popover.Detail>\n </Popover>\n\n {Array.from({ length: totalItems }).map((_, index) => {\n const isActive = index === selectedIndex;\n return (\n <button\n key={index}\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n aria-label={goToSlide({ index: index + 1 }).value}\n className={cn(\n 'h-2.5 w-2.5 rounded-full transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2',\n isActive ? 'scale-110 bg-text' : 'bg-text/20 hover:bg-text/40'\n )}\n />\n );\n })}\n\n <Popover identifier=\"carousel-next\">\n <Button\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.NEUTRAL}\n roundedSize=\"full\"\n label={nextSlide.value}\n onClick={(e) => {\n e.stopPropagation();\n handleNext();\n }}\n Icon={ChevronRight}\n size={ButtonSize.ICON_MD}\n disabled={selectedIndex === totalItems - 1}\n />\n\n <Popover.Detail identifier=\"carousel-next\">\n <div className=\"flex items-center gap-2 p-2\">\n <span className=\"whitespace-nowrap text-neutral text-xs\">\n {nextSlide.value}\n </span>\n <KeyboardShortcut\n shortcut=\"ArrowRight\"\n size=\"sm\"\n onTriggered={handleNext}\n disabled={disableKeyboardShortcuts}\n />\n </div>\n </Popover.Detail>\n </Popover>\n </div>\n );\n};\n\n// ------------------------------------------------------------------\n// Main Component: Carousel Root\n// ------------------------------------------------------------------\ntype CarouselProps = HTMLAttributes<HTMLDivElement> & {\n children: ReactNode;\n initialIndex?: number;\n onIndexChange?: (index: number) => void;\n};\n\nconst partitionCarouselChildren = (\n children: ReactNode[]\n): [ReactElement[], ReactNode[]] => {\n const slides: ReactElement[] = [];\n const others: ReactNode[] = [];\n\n children.forEach((child) => {\n if (isValidElement(child) && child.type === CarouselItem) {\n slides.push(child);\n } else {\n others.push(child);\n }\n });\n\n return [slides, others];\n};\n\nconst CarouselRoot: FC<CarouselProps> = ({\n children,\n className,\n initialIndex = 0,\n onIndexChange,\n ...props\n}) => {\n const allChildren = Children.toArray(children);\n const [slides, others] = partitionCarouselChildren(allChildren);\n const totalItems = slides.length;\n\n // State Management\n const [selectedIndex, setSelectedIndex] = useState<number>(initialIndex);\n const [displayedIndex, setDisplayedIndex] = useState<number>(initialIndex);\n const [containerHeight, setContainerHeight] = useState<number>(0);\n const [containerWidth, setContainerWidth] = useState<number>(0);\n\n useEffect(() => {\n const calculateDimensions = () => {\n if (!containerRef.current) return;\n\n // Track Width\n const width = containerRef.current.clientWidth;\n setContainerWidth(width);\n\n // Track Height (existing logic)\n const heights = itemsRef.current.map((item) => item?.offsetHeight || 0);\n const maxHeight = Math.max(0, ...heights);\n if (maxHeight > 0) {\n setContainerHeight(maxHeight + 40);\n }\n };\n\n calculateDimensions();\n\n const observer = new ResizeObserver(() => {\n calculateDimensions();\n });\n\n if (containerRef.current) observer.observe(containerRef.current);\n itemsRef.current.forEach((item) => {\n if (item) observer.observe(item);\n });\n\n return () => observer.disconnect();\n }, [totalItems]);\n\n // Drag State\n const [startX, setStartX] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n\n // Refs\n const containerRef = useRef<HTMLDivElement>(null);\n const itemsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n // Navigation Logic\n const handleSwitchItem = (diff: number) => {\n if (containerWidth === 0) return;\n\n // Use container width for the threshold\n const swipeStep = containerWidth / SWIPE_THRESHOLD_DIVISOR;\n const numSwipe = Math.round(diff / swipeStep);\n\n if (Math.abs(numSwipe) >= 1) {\n const newIndex = displayedIndex - numSwipe;\n const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1));\n\n if (clampedIndex !== selectedIndex) {\n setSelectedIndex(clampedIndex);\n setStartX((prev) => prev + diff);\n }\n }\n };\n\n const handleNext = () => {\n setSelectedIndex((prev) => Math.min(prev + 1, totalItems - 1));\n };\n\n const handlePrev = () => {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n };\n\n // Input Handlers\n const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.clientX);\n };\n const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.clientX - startX);\n };\n const handleDragEnd = () => setIsDragging(false);\n const handleTouchStart: TouchEventHandler<HTMLDivElement> = (e) => {\n setIsDragging(true);\n setStartX(e.touches[0].clientX);\n };\n const handleTouchMove: TouchEventHandler<HTMLDivElement> = (e) => {\n if (isDragging) handleSwitchItem(e.touches[0].clientX - startX);\n };\n\n // Effects\n useEffect(() => {\n if (selectedIndex) onIndexChange?.(selectedIndex);\n }, [selectedIndex, onIndexChange]);\n\n useEffect(() => {\n let interval: NodeJS.Timeout;\n\n if (selectedIndex !== displayedIndex) {\n interval = setInterval(() => {\n setDisplayedIndex((prev) => {\n if (prev === selectedIndex) {\n clearInterval(interval);\n return prev;\n }\n return prev < selectedIndex ? prev + 1 : prev - 1;\n });\n }, TRANSITION_DELAY_MS);\n }\n return () => clearInterval(interval);\n }, [selectedIndex, displayedIndex]);\n\n // Calculate height based on the MAX height of ALL items\n useEffect(() => {\n const calculateMaxHeight = () => {\n const heights = itemsRef.current.map((item) => item?.offsetHeight || 0);\n const maxHeight = Math.max(0, ...heights);\n\n if (maxHeight > 0) {\n setContainerHeight(maxHeight + 40);\n }\n };\n\n calculateMaxHeight();\n\n const observer = new ResizeObserver(() => {\n calculateMaxHeight();\n });\n\n itemsRef.current.forEach((item) => {\n if (item) observer.observe(item);\n });\n\n return () => observer.disconnect();\n }, [totalItems]); // Removed isMounted dependency\n\n return (\n <CarouselContext.Provider\n value={{\n selectedIndex,\n setSelectedIndex,\n totalItems,\n handlePrev,\n handleNext,\n }}\n >\n <div\n ref={containerRef}\n className={cn(\n 'relative flex w-full cursor-grab select-none items-center overflow-hidden outline-none transition-[height] duration-300 ease-in-out focus:outline-none focus:outline-none focus:ring-0 active:cursor-grabbing',\n 'max-w-[1400px]',\n className\n )}\n style={{\n height: containerHeight > 0 ? containerHeight : 'auto',\n minHeight: '400px',\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleDragEnd}\n onMouseLeave={handleDragEnd}\n onTouchStart={handleTouchStart}\n onTouchMove={handleTouchMove}\n onTouchEnd={handleDragEnd}\n role=\"region\"\n aria-label=\"Carousel\"\n {...props}\n >\n {slides.map((child, index) => {\n return (\n <div\n key={index}\n role=\"button\"\n tabIndex={0}\n ref={(el) => {\n itemsRef.current[index] = el;\n }}\n // FIX 2: Removed isMounted checks and invisible classes.\n // CSS units allow correct SSR rendering.\n className={cn(\n 'absolute left-1/2 -translate-x-1/2 transition-all duration-300 ease-in-out',\n 'outline-none ring-0 focus:outline-none focus:ring-0 focus:ring-offset-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0',\n getCardStyle(index, displayedIndex)\n )}\n onClick={(e) => {\n e.stopPropagation();\n setSelectedIndex(index);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') setSelectedIndex(index);\n }}\n style={{\n transform: `\n translateX(${getCardPositionX(\n index,\n displayedIndex,\n containerWidth\n )})\n scale(${getCardScale(index, displayedIndex)})\n `,\n }}\n >\n {child}\n </div>\n );\n })}\n\n {others}\n </div>\n </CarouselContext.Provider>\n );\n};\n\nexport const Carousel = Object.assign(CarouselRoot, {\n Item: CarouselItem,\n Indicators: CarouselIndicators,\n});\n"],"mappings":";;;;;;;;;;;;AAgCA,MAAM,0BAA0B;AAChC,MAAM,sBAAsB;AAa5B,MAAM,kBAAkB,cAA2C,KAAK;AAExE,MAAM,oBAAoB;CACxB,MAAM,UAAU,WAAW,gBAAgB;AAC3C,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,6CAA6C;AAE/D,QAAO;;AAMT,MAAM,gBAAgB,OAAe,mBAA2B;AAE9D,SADa,KAAK,IAAI,QAAQ,eAAe,EAC7C;EACE,KAAK,EACH,QAAO;EACT,KAAK,EACH,QAAO;EACT,KAAK,EACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAM,gBAAgB,OAAe,mBAA2B;AAE9D,SADa,KAAK,IAAI,QAAQ,eAAe,EAC7C;EACE,KAAK,EACH,QAAO;EACT,KAAK,EACH,QAAO;EACT,KAAK,EACH,QAAO;EACT,QACE,QAAO;;;AAMb,MAAM,oBACJ,OACA,gBACA,mBACG;AAMH,QAAO,IALM,QAAQ,kBAER,KAAK,IAAI,kBADA,iBAAiB,MAAM,MAAO,KACE,IAAI,CAGpC;;AAUxB,MAAM,gBAAuC,EAC3C,UACA,WACA,GAAG,YACC;AACJ,QACE,oBAAC,OAAD;EAAK,WAAW,GAAG,iBAAiB,UAAU;EAAE,GAAI;EACjD;EACG;;AAWV,MAAM,sBAAmD,EACvD,WACA,2BAA2B,OAC3B,GAAG,YACC;CACJ,MAAM,EACJ,eACA,kBACA,YACA,YACA,eACE,aAAa;CACjB,MAAM,EAAE,WAAW,eAAe,cAAc,YAAY,WAAW;AAEvE,KAAI,cAAc,EAAG,QAAO;AAE5B,QACE,qBAAC,OAAD;EACE,WAAW,GACT,qFACA,UACD;EACD,GAAI;YALN;GAOE,qBAAC,SAAD;IAAS,YAAW;cAApB,CACE,oBAAC,QAAD;KACE;KACA;KACA,OAAO,cAAc;KACrB,aAAY;KACZ,UAAU,MAAM;AACd,QAAE,iBAAiB;AACnB,kBAAY;;KAEd,MAAM;KACN;KACA,UAAU,kBAAkB;KAC5B,GAEF,oBAAC,QAAQ,QAAT;KAAgB,YAAW;eACzB,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD;OAAM,WAAU;iBACb,cAAc;OACV,GACP,oBAAC,kBAAD;OACE,UAAS;OACT,UAAU;OACV,MAAK;OACL,aAAa;OACb,EACE;;KACS,EACT;;GAET,MAAM,KAAK,EAAE,QAAQ,YAAY,CAAC,CAAC,KAAK,GAAG,UAAU;IACpD,MAAM,WAAW,UAAU;AAC3B,WACE,oBAAC,UAAD;KAEE,MAAK;KACL,UAAU,MAAM;AACd,QAAE,iBAAiB;AACnB,uBAAiB,MAAM;;KAEzB,cAAY,UAAU,EAAE,OAAO,QAAQ,GAAG,CAAC,CAAC;KAC5C,WAAW,GACT,mIACA,WAAW,sBAAsB,8BAClC;KACD,EAXK,MAWL;KAEJ;GAEF,qBAAC,SAAD;IAAS,YAAW;cAApB,CACE,oBAAC,QAAD;KACE;KACA;KACA,aAAY;KACZ,OAAO,UAAU;KACjB,UAAU,MAAM;AACd,QAAE,iBAAiB;AACnB,kBAAY;;KAEd,MAAM;KACN;KACA,UAAU,kBAAkB,aAAa;KACzC,GAEF,oBAAC,QAAQ,QAAT;KAAgB,YAAW;eACzB,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD;OAAM,WAAU;iBACb,UAAU;OACN,GACP,oBAAC,kBAAD;OACE,UAAS;OACT,MAAK;OACL,aAAa;OACb,UAAU;OACV,EACE;;KACS,EACT;;GACN;;;AAaV,MAAM,6BACJ,aACkC;CAClC,MAAM,SAAyB,EAAE;CACjC,MAAM,SAAsB,EAAE;AAE9B,UAAS,SAAS,UAAU;AAC1B,MAAI,eAAe,MAAM,IAAI,MAAM,SAAS,aAC1C,QAAO,KAAK,MAAM;MAElB,QAAO,KAAK,MAAM;GAEpB;AAEF,QAAO,CAAC,QAAQ,OAAO;;AAGzB,MAAM,gBAAmC,EACvC,UACA,WACA,eAAe,GACf,eACA,GAAG,YACC;CAEJ,MAAM,CAAC,QAAQ,UAAU,0BADL,SAAS,QAAQ,SAAS,CACiB;CAC/D,MAAM,aAAa,OAAO;CAG1B,MAAM,CAAC,eAAe,oBAAoB,SAAiB,aAAa;CACxE,MAAM,CAAC,gBAAgB,qBAAqB,SAAiB,aAAa;CAC1E,MAAM,CAAC,iBAAiB,sBAAsB,SAAiB,EAAE;CACjE,MAAM,CAAC,gBAAgB,qBAAqB,SAAiB,EAAE;AAE/D,iBAAgB;EACd,MAAM,4BAA4B;AAChC,OAAI,CAAC,aAAa,QAAS;GAG3B,MAAM,QAAQ,aAAa,QAAQ;AACnC,qBAAkB,MAAM;GAGxB,MAAM,UAAU,SAAS,QAAQ,KAAK,SAAS,MAAM,gBAAgB,EAAE;GACvE,MAAM,YAAY,KAAK,IAAI,GAAG,GAAG,QAAQ;AACzC,OAAI,YAAY,EACd,oBAAmB,YAAY,GAAG;;AAItC,uBAAqB;EAErB,MAAM,WAAW,IAAI,qBAAqB;AACxC,wBAAqB;IACrB;AAEF,MAAI,aAAa,QAAS,UAAS,QAAQ,aAAa,QAAQ;AAChE,WAAS,QAAQ,SAAS,SAAS;AACjC,OAAI,KAAM,UAAS,QAAQ,KAAK;IAChC;AAEF,eAAa,SAAS,YAAY;IACjC,CAAC,WAAW,CAAC;CAGhB,MAAM,CAAC,QAAQ,aAAa,SAAS,EAAE;CACvC,MAAM,CAAC,YAAY,iBAAiB,SAAS,MAAM;CAGnD,MAAM,eAAe,OAAuB,KAAK;CACjD,MAAM,WAAW,OAAkC,EAAE,CAAC;CAGtD,MAAM,oBAAoB,SAAiB;AACzC,MAAI,mBAAmB,EAAG;EAG1B,MAAM,YAAY,iBAAiB;EACnC,MAAM,WAAW,KAAK,MAAM,OAAO,UAAU;AAE7C,MAAI,KAAK,IAAI,SAAS,IAAI,GAAG;GAC3B,MAAM,WAAW,iBAAiB;GAClC,MAAM,eAAe,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,aAAa,EAAE,CAAC;AAEpE,OAAI,iBAAiB,eAAe;AAClC,qBAAiB,aAAa;AAC9B,eAAW,SAAS,OAAO,KAAK;;;;CAKtC,MAAM,mBAAmB;AACvB,oBAAkB,SAAS,KAAK,IAAI,OAAO,GAAG,aAAa,EAAE,CAAC;;CAGhE,MAAM,mBAAmB;AACvB,oBAAkB,SAAS,KAAK,IAAI,OAAO,GAAG,EAAE,CAAC;;CAInD,MAAM,mBAAsD,MAAM;AAChE,gBAAc,KAAK;AACnB,YAAU,EAAE,QAAQ;;CAEtB,MAAM,mBAAsD,MAAM;AAChE,MAAI,WAAY,kBAAiB,EAAE,UAAU,OAAO;;CAEtD,MAAM,sBAAsB,cAAc,MAAM;CAChD,MAAM,oBAAuD,MAAM;AACjE,gBAAc,KAAK;AACnB,YAAU,EAAE,QAAQ,GAAG,QAAQ;;CAEjC,MAAM,mBAAsD,MAAM;AAChE,MAAI,WAAY,kBAAiB,EAAE,QAAQ,GAAG,UAAU,OAAO;;AAIjE,iBAAgB;AACd,MAAI,cAAe,iBAAgB,cAAc;IAChD,CAAC,eAAe,cAAc,CAAC;AAElC,iBAAgB;EACd,IAAI;AAEJ,MAAI,kBAAkB,eACpB,YAAW,kBAAkB;AAC3B,sBAAmB,SAAS;AAC1B,QAAI,SAAS,eAAe;AAC1B,mBAAc,SAAS;AACvB,YAAO;;AAET,WAAO,OAAO,gBAAgB,OAAO,IAAI,OAAO;KAChD;KACD,oBAAoB;AAEzB,eAAa,cAAc,SAAS;IACnC,CAAC,eAAe,eAAe,CAAC;AAGnC,iBAAgB;EACd,MAAM,2BAA2B;GAC/B,MAAM,UAAU,SAAS,QAAQ,KAAK,SAAS,MAAM,gBAAgB,EAAE;GACvE,MAAM,YAAY,KAAK,IAAI,GAAG,GAAG,QAAQ;AAEzC,OAAI,YAAY,EACd,oBAAmB,YAAY,GAAG;;AAItC,sBAAoB;EAEpB,MAAM,WAAW,IAAI,qBAAqB;AACxC,uBAAoB;IACpB;AAEF,WAAS,QAAQ,SAAS,SAAS;AACjC,OAAI,KAAM,UAAS,QAAQ,KAAK;IAChC;AAEF,eAAa,SAAS,YAAY;IACjC,CAAC,WAAW,CAAC;AAEhB,QACE,oBAAC,gBAAgB,UAAjB;EACE,OAAO;GACL;GACA;GACA;GACA;GACA;GACD;YAED,qBAAC,OAAD;GACE,KAAK;GACL,WAAW,GACT,iNACA,kBACA,UACD;GACD,OAAO;IACL,QAAQ,kBAAkB,IAAI,kBAAkB;IAChD,WAAW;IACZ;GACD,aAAa;GACb,aAAa;GACb,WAAW;GACX,cAAc;GACd,cAAc;GACd,aAAa;GACb,YAAY;GACZ,MAAK;GACL,cAAW;GACX,GAAI;aApBN,CAsBG,OAAO,KAAK,OAAO,UAAU;AAC5B,WACE,oBAAC,OAAD;KAEE,MAAK;KACL,UAAU;KACV,MAAM,OAAO;AACX,eAAS,QAAQ,SAAS;;KAI5B,WAAW,GACT,8EACA,uJACA,aAAa,OAAO,eAAe,CACpC;KACD,UAAU,MAAM;AACd,QAAE,iBAAiB;AACnB,uBAAiB,MAAM;;KAEzB,YAAY,MAAM;AAChB,UAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,IAAK,kBAAiB,MAAM;;KAEjE,OAAO,EACL,WAAW;+BACI,iBACX,OACA,gBACA,eACD,CAAC;0BACM,aAAa,OAAO,eAAe,CAAC;mBAE/C;eAEA;KACG,EAhCC,MAgCD;KAER,EAED,OACG;;EACmB;;AAI/B,MAAa,WAAW,OAAO,OAAO,cAAc;CAClD,MAAM;CACN,YAAY;CACb,CAAC"}
|
|
@@ -62,7 +62,7 @@ const ContentEditor = ({ children, onContentChange, isEditing, ...props }) => {
|
|
|
62
62
|
className: cn("break-word m-3 inline w-full bg-transparent outline-hidden", isEditing ? "cursor-text" : "cursor-pointer"),
|
|
63
63
|
onChange: handleOnContentChange,
|
|
64
64
|
onKeyDown: handleKeyDown,
|
|
65
|
-
variant:
|
|
65
|
+
variant: "invisible",
|
|
66
66
|
defaultValue: children,
|
|
67
67
|
"aria-label": "Editable content",
|
|
68
68
|
"aria-describedby": isEdited ? "content-editor-actions" : void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentEditor.mjs","names":[],"sources":["../../../../src/components/ContentEditor/ContentEditor.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { Check, X } from 'lucide-react';\nimport { type ChangeEventHandler, type FC, useState } from 'react';\nimport { InputVariant } from '../Input';\nimport {\n AutoSizedTextArea,\n type AutoSizedTextAreaProps,\n} from '../TextArea/AutoSizeTextArea';\n\n/** Props for the ContentEditor component */\nexport type ContentEditorProps = {\n /** The current content to display and edit */\n children: string;\n /** Callback function called when content is saved/validated */\n onContentChange: (content: string) => void;\n /** Whether the editor is currently in editing mode */\n isEditing?: boolean;\n} & AutoSizedTextAreaProps;\n\n/**\n * ContentEditor Component\n *\n * An inline editing component that allows users to edit text content with\n * validation and cancellation options. Built on top of AutoSizedTextArea\n * for flexible text editing experiences.\n *\n * ## Features\n * - **Inline Editing**: Edit content directly in place with visual feedback\n * - **Auto-sizing**: Textarea automatically adjusts to content size\n * - **Validation Controls**: Check and X buttons appear when content is modified\n * - **Keyboard Support**: Full keyboard navigation and accessibility\n * - **State Management**: Handles editing states and content validation\n *\n * ## Accessibility\n * - Proper ARIA labels for all interactive elements\n * - Keyboard navigation support (Tab, Enter, Escape)\n * - Screen reader announcements for state changes\n * - Focus management between edit and display modes\n *\n * @param children - The current content to display and edit\n * @param onContentChange - Callback when content is saved\n * @param isEditing - Whether editor is in editing mode\n * @param props - Additional AutoSizedTextArea props\n */\nexport const ContentEditor: FC<ContentEditorProps> = ({\n children,\n onContentChange,\n isEditing,\n ...props\n}) => {\n const [newValue, setNewValue] = useState<string>(children);\n const [resetIncrementor, setResetIncrementor] = useState<number>(0); // To reset the div on cancel\n const isEdited: boolean = newValue !== children;\n\n const handleCancel = () => {\n setNewValue(children);\n setResetIncrementor((prev) => prev + 1);\n };\n\n const handleValid = () => {\n onContentChange(newValue);\n };\n\n const handleOnContentChange: ChangeEventHandler<HTMLTextAreaElement> = (e) =>\n setNewValue(e.target.value ?? '');\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleValid();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n handleCancel();\n }\n };\n\n return (\n <div\n className=\"flex flex-row items-center justify-between gap-2\"\n role=\"group\"\n aria-label=\"Content editor\"\n >\n <AutoSizedTextArea\n className={cn(\n 'break-word m-3 inline w-full bg-transparent outline-hidden',\n isEditing ? 'cursor-text' : 'cursor-pointer'\n )}\n onChange={handleOnContentChange}\n onKeyDown={handleKeyDown}\n key={resetIncrementor}\n variant={InputVariant.INVISIBLE}\n defaultValue={children}\n aria-label=\"Editable content\"\n aria-describedby={isEdited ? 'content-editor-actions' : undefined}\n {...props}\n />\n {isEdited && (\n <div\n id=\"content-editor-actions\"\n className=\"flex flex-row items-center justify-between gap-2\"\n role=\"group\"\n aria-label=\"Edit actions\"\n >\n <Check\n className=\"cursor-pointer text-green-600 hover:scale-110\"\n size={16}\n onClick={handleValid}\n role=\"button\"\n tabIndex={0}\n aria-label=\"Save changes (Ctrl+Enter)\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleValid();\n }\n }}\n />\n <X\n className=\"cursor-pointer text-red-600 hover:scale-110\"\n size={16}\n onClick={handleCancel}\n role=\"button\"\n tabIndex={0}\n aria-label=\"Cancel changes (Escape)\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleCancel();\n }\n }}\n />\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,iBAAyC,EACpD,UACA,iBACA,WACA,GAAG,YACC;CACJ,MAAM,CAAC,UAAU,eAAe,SAAiB,SAAS;CAC1D,MAAM,CAAC,kBAAkB,uBAAuB,SAAiB,EAAE;CACnE,MAAM,WAAoB,aAAa;CAEvC,MAAM,qBAAqB;AACzB,cAAY,SAAS;AACrB,uBAAqB,SAAS,OAAO,EAAE;;CAGzC,MAAM,oBAAoB;AACxB,kBAAgB,SAAS;;CAG3B,MAAM,yBAAkE,MACtE,YAAY,EAAE,OAAO,SAAS,GAAG;CAEnC,MAAM,iBAAiB,MAAgD;AACrE,MAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,UAAU;AACjD,KAAE,gBAAgB;AAClB,gBAAa;aACJ,EAAE,QAAQ,UAAU;AAC7B,KAAE,gBAAgB;AAClB,iBAAc;;;AAIlB,QACE,qBAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,cAAW;YAHb,CAKE,oBAAC,mBAAD;GACE,WAAW,GACT,8DACA,YAAY,gBAAgB,iBAC7B;GACD,UAAU;GACV,WAAW;GAEX
|
|
1
|
+
{"version":3,"file":"ContentEditor.mjs","names":[],"sources":["../../../../src/components/ContentEditor/ContentEditor.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport { Check, X } from 'lucide-react';\nimport { type ChangeEventHandler, type FC, useState } from 'react';\nimport { InputVariant } from '../Input';\nimport {\n AutoSizedTextArea,\n type AutoSizedTextAreaProps,\n} from '../TextArea/AutoSizeTextArea';\n\n/** Props for the ContentEditor component */\nexport type ContentEditorProps = {\n /** The current content to display and edit */\n children: string;\n /** Callback function called when content is saved/validated */\n onContentChange: (content: string) => void;\n /** Whether the editor is currently in editing mode */\n isEditing?: boolean;\n} & AutoSizedTextAreaProps;\n\n/**\n * ContentEditor Component\n *\n * An inline editing component that allows users to edit text content with\n * validation and cancellation options. Built on top of AutoSizedTextArea\n * for flexible text editing experiences.\n *\n * ## Features\n * - **Inline Editing**: Edit content directly in place with visual feedback\n * - **Auto-sizing**: Textarea automatically adjusts to content size\n * - **Validation Controls**: Check and X buttons appear when content is modified\n * - **Keyboard Support**: Full keyboard navigation and accessibility\n * - **State Management**: Handles editing states and content validation\n *\n * ## Accessibility\n * - Proper ARIA labels for all interactive elements\n * - Keyboard navigation support (Tab, Enter, Escape)\n * - Screen reader announcements for state changes\n * - Focus management between edit and display modes\n *\n * @param children - The current content to display and edit\n * @param onContentChange - Callback when content is saved\n * @param isEditing - Whether editor is in editing mode\n * @param props - Additional AutoSizedTextArea props\n */\nexport const ContentEditor: FC<ContentEditorProps> = ({\n children,\n onContentChange,\n isEditing,\n ...props\n}) => {\n const [newValue, setNewValue] = useState<string>(children);\n const [resetIncrementor, setResetIncrementor] = useState<number>(0); // To reset the div on cancel\n const isEdited: boolean = newValue !== children;\n\n const handleCancel = () => {\n setNewValue(children);\n setResetIncrementor((prev) => prev + 1);\n };\n\n const handleValid = () => {\n onContentChange(newValue);\n };\n\n const handleOnContentChange: ChangeEventHandler<HTMLTextAreaElement> = (e) =>\n setNewValue(e.target.value ?? '');\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n handleValid();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n handleCancel();\n }\n };\n\n return (\n <div\n className=\"flex flex-row items-center justify-between gap-2\"\n role=\"group\"\n aria-label=\"Content editor\"\n >\n <AutoSizedTextArea\n className={cn(\n 'break-word m-3 inline w-full bg-transparent outline-hidden',\n isEditing ? 'cursor-text' : 'cursor-pointer'\n )}\n onChange={handleOnContentChange}\n onKeyDown={handleKeyDown}\n key={resetIncrementor}\n variant={InputVariant.INVISIBLE}\n defaultValue={children}\n aria-label=\"Editable content\"\n aria-describedby={isEdited ? 'content-editor-actions' : undefined}\n {...props}\n />\n {isEdited && (\n <div\n id=\"content-editor-actions\"\n className=\"flex flex-row items-center justify-between gap-2\"\n role=\"group\"\n aria-label=\"Edit actions\"\n >\n <Check\n className=\"cursor-pointer text-green-600 hover:scale-110\"\n size={16}\n onClick={handleValid}\n role=\"button\"\n tabIndex={0}\n aria-label=\"Save changes (Ctrl+Enter)\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleValid();\n }\n }}\n />\n <X\n className=\"cursor-pointer text-red-600 hover:scale-110\"\n size={16}\n onClick={handleCancel}\n role=\"button\"\n tabIndex={0}\n aria-label=\"Cancel changes (Escape)\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleCancel();\n }\n }}\n />\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,iBAAyC,EACpD,UACA,iBACA,WACA,GAAG,YACC;CACJ,MAAM,CAAC,UAAU,eAAe,SAAiB,SAAS;CAC1D,MAAM,CAAC,kBAAkB,uBAAuB,SAAiB,EAAE;CACnE,MAAM,WAAoB,aAAa;CAEvC,MAAM,qBAAqB;AACzB,cAAY,SAAS;AACrB,uBAAqB,SAAS,OAAO,EAAE;;CAGzC,MAAM,oBAAoB;AACxB,kBAAgB,SAAS;;CAG3B,MAAM,yBAAkE,MACtE,YAAY,EAAE,OAAO,SAAS,GAAG;CAEnC,MAAM,iBAAiB,MAAgD;AACrE,MAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,UAAU;AACjD,KAAE,gBAAgB;AAClB,gBAAa;aACJ,EAAE,QAAQ,UAAU;AAC7B,KAAE,gBAAgB;AAClB,iBAAc;;;AAIlB,QACE,qBAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,cAAW;YAHb,CAKE,oBAAC,mBAAD;GACE,WAAW,GACT,8DACA,YAAY,gBAAgB,iBAC7B;GACD,UAAU;GACV,WAAW;GAEX;GACA,cAAc;GACd,cAAW;GACX,oBAAkB,WAAW,2BAA2B;GACxD,GAAI;GACJ,EANK,iBAML,EACD,YACC,qBAAC,OAAD;GACE,IAAG;GACH,WAAU;GACV,MAAK;GACL,cAAW;aAJb,CAME,oBAAC,OAAD;IACE,WAAU;IACV,MAAM;IACN,SAAS;IACT,MAAK;IACL,UAAU;IACV,cAAW;IACX,YAAY,MAAM;AAChB,SAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,QAAE,gBAAgB;AAClB,mBAAa;;;IAGjB,GACF,oBAAC,GAAD;IACE,WAAU;IACV,MAAM;IACN,SAAS;IACT,MAAK;IACL,UAAU;IACV,cAAW;IACX,YAAY,MAAM;AAChB,SAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,QAAE,gBAAgB;AAClB,oBAAc;;;IAGlB,EACE;KAEJ"}
|
|
@@ -72,7 +72,7 @@ const ContentEditorInput = ({ children, onContentChange, disabled, validate, add
|
|
|
72
72
|
"aria-label": "Editable input value",
|
|
73
73
|
"aria-describedby": isEdited || additionalButtons ? "content-editor-input-actions" : void 0,
|
|
74
74
|
"aria-invalid": !isValid,
|
|
75
|
-
variant:
|
|
75
|
+
variant: "invisible",
|
|
76
76
|
className: "size-full",
|
|
77
77
|
defaultValue: children,
|
|
78
78
|
disabled,
|
|
@@ -85,9 +85,9 @@ const ContentEditorInput = ({ children, onContentChange, disabled, validate, add
|
|
|
85
85
|
children: [isEdited && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
86
86
|
Icon: Check,
|
|
87
87
|
label: `Save changes${!isValid ? " (invalid content)" : ""}`,
|
|
88
|
-
variant:
|
|
89
|
-
color:
|
|
90
|
-
size:
|
|
88
|
+
variant: "hoverable",
|
|
89
|
+
color: "text",
|
|
90
|
+
size: "icon-sm",
|
|
91
91
|
className: "cursor-pointer hover:scale-110",
|
|
92
92
|
disabled: disabled || !isValid,
|
|
93
93
|
onClick: handleValid,
|
|
@@ -95,9 +95,9 @@ const ContentEditorInput = ({ children, onContentChange, disabled, validate, add
|
|
|
95
95
|
}), /* @__PURE__ */ jsx(Button, {
|
|
96
96
|
Icon: X,
|
|
97
97
|
label: "Cancel changes",
|
|
98
|
-
variant:
|
|
99
|
-
size:
|
|
100
|
-
color:
|
|
98
|
+
variant: "hoverable",
|
|
99
|
+
size: "icon-sm",
|
|
100
|
+
color: "text",
|
|
101
101
|
className: "cursor-pointer hover:scale-110",
|
|
102
102
|
onClick: handleCancel,
|
|
103
103
|
disabled
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentEditorInput.mjs","names":[],"sources":["../../../../src/components/ContentEditor/ContentEditorInput.tsx"],"sourcesContent":["'use client';\n\nimport { Check, X } from 'lucide-react';\nimport {\n type ChangeEventHandler,\n type FC,\n type ReactNode,\n useEffect,\n useState,\n} from 'react';\nimport { Button, ButtonColor, ButtonSize, ButtonVariant } from '../Button';\nimport { Input, type InputProps, InputVariant } from '../Input';\n\n/** Props for the ContentEditorInput component */\nexport type ContentEditorInputProps = {\n /** The current content to display and edit */\n children: InputProps['value'];\n /** Callback function called when content is saved/validated */\n onContentChange: (content: InputProps['value']) => void;\n /** Whether the editor is disabled */\n disabled?: boolean;\n /** Optional validation function to check content validity */\n validate?: (content: InputProps['value']) => boolean;\n /** Additional buttons to display alongside edit actions */\n additionalButtons?: ReactNode;\n} & Omit<InputProps, 'children'>;\n\n/**\n * ContentEditorInput Component\n *\n * An inline editing component for single-line text input with validation,\n * cancel/save functionality, and support for additional action buttons.\n *\n * ## Features\n * - **Inline Input Editing**: Edit single-line content with immediate feedback\n * - **Validation Support**: Optional content validation with visual feedback\n * - **Action Buttons**: Built-in save/cancel with support for additional buttons\n * - **Keyboard Shortcuts**: Enter to save, Escape to cancel\n * - **Accessibility**: Full ARIA support and keyboard navigation\n * - **State Management**: Handles editing states and validation\n *\n * ## Accessibility\n * - Proper ARIA labels and descriptions for all controls\n * - Keyboard navigation (Tab, Enter, Escape)\n * - Screen reader support for validation states\n * - Focus management and visual indicators\n *\n * @param children - Current input value\n * @param onContentChange - Callback when content is saved\n * @param disabled - Whether the editor is disabled\n * @param validate - Optional validation function\n * @param additionalButtons - Extra buttons to display\n * @param props - Additional Input component props\n */\nexport const ContentEditorInput: FC<ContentEditorInputProps> = ({\n children,\n onContentChange,\n disabled,\n validate,\n additionalButtons,\n ...props\n}) => {\n const [newValue, setNewValue] = useState<InputProps['value']>(children);\n const [resetIncrementor, setResetIncrementor] = useState<number>(0); // To reset the div on cancel\n const isEdited: boolean = newValue !== children;\n\n const handleCancel = () => {\n setNewValue(children);\n setResetIncrementor((prev) => prev + 1);\n };\n\n const handleValid = () => {\n onContentChange(newValue);\n };\n\n const handleOnContentChange: ChangeEventHandler<HTMLInputElement> = (e) => {\n setNewValue(e.currentTarget.value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' && !disabled && isValid) {\n e.preventDefault();\n handleValid();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n handleCancel();\n }\n };\n\n useEffect(() => {\n setNewValue(children);\n // Force input to reset when children changes externally\n setResetIncrementor((prev) => prev + 1);\n }, [children]);\n\n const isValid = validate?.(newValue) ?? true;\n\n return (\n <div\n className=\"flex size-full flex-col items-center justify-between gap-2\"\n key={String(children)}\n role=\"group\"\n aria-label=\"Content editor input\"\n >\n <Input\n onChange={handleOnContentChange}\n onKeyDown={handleKeyDown}\n key={resetIncrementor}\n aria-label=\"Editable input value\"\n aria-describedby={\n isEdited || additionalButtons\n ? 'content-editor-input-actions'\n : undefined\n }\n aria-invalid={!isValid}\n variant={InputVariant.INVISIBLE}\n className=\"size-full\"\n defaultValue={children}\n disabled={disabled}\n {...props}\n />\n {(isEdited || additionalButtons) && (\n <div\n id=\"content-editor-input-actions\"\n className=\"flex w-full items-center justify-end gap-2\"\n role=\"group\"\n aria-label=\"Edit actions\"\n >\n {isEdited && (\n <>\n <Button\n Icon={Check}\n label={`Save changes${!isValid ? ' (invalid content)' : ''}`}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n size={ButtonSize.ICON_SM}\n className=\"cursor-pointer hover:scale-110\"\n disabled={disabled || !isValid}\n onClick={handleValid}\n aria-describedby={!isValid ? 'validation-error' : undefined}\n />\n <Button\n Icon={X}\n label=\"Cancel changes\"\n variant={ButtonVariant.HOVERABLE}\n size={ButtonSize.ICON_SM}\n color={ButtonColor.TEXT}\n className=\"cursor-pointer hover:scale-110\"\n onClick={handleCancel}\n disabled={disabled}\n />\n </>\n )}\n {additionalButtons}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAa,sBAAmD,EAC9D,UACA,iBACA,UACA,UACA,mBACA,GAAG,YACC;CACJ,MAAM,CAAC,UAAU,eAAe,SAA8B,SAAS;CACvE,MAAM,CAAC,kBAAkB,uBAAuB,SAAiB,EAAE;CACnE,MAAM,WAAoB,aAAa;CAEvC,MAAM,qBAAqB;AACzB,cAAY,SAAS;AACrB,uBAAqB,SAAS,OAAO,EAAE;;CAGzC,MAAM,oBAAoB;AACxB,kBAAgB,SAAS;;CAG3B,MAAM,yBAA+D,MAAM;AACzE,cAAY,EAAE,cAAc,MAAM;;CAGpC,MAAM,iBAAiB,MAA6C;AAClE,MAAI,EAAE,QAAQ,WAAW,CAAC,YAAY,SAAS;AAC7C,KAAE,gBAAgB;AAClB,gBAAa;aACJ,EAAE,QAAQ,UAAU;AAC7B,KAAE,gBAAgB;AAClB,iBAAc;;;AAIlB,iBAAgB;AACd,cAAY,SAAS;AAErB,uBAAqB,SAAS,OAAO,EAAE;IACtC,CAAC,SAAS,CAAC;CAEd,MAAM,UAAU,WAAW,SAAS,IAAI;AAExC,QACE,qBAAC,OAAD;EACE,WAAU;EAEV,MAAK;EACL,cAAW;YAJb,CAME,oBAAC,OAAD;GACE,UAAU;GACV,WAAW;GAEX,cAAW;GACX,oBACE,YAAY,oBACR,iCACA;GAEN,gBAAc,CAAC;GACf
|
|
1
|
+
{"version":3,"file":"ContentEditorInput.mjs","names":[],"sources":["../../../../src/components/ContentEditor/ContentEditorInput.tsx"],"sourcesContent":["'use client';\n\nimport { Check, X } from 'lucide-react';\nimport {\n type ChangeEventHandler,\n type FC,\n type ReactNode,\n useEffect,\n useState,\n} from 'react';\nimport { Button, ButtonColor, ButtonSize, ButtonVariant } from '../Button';\nimport { Input, type InputProps, InputVariant } from '../Input';\n\n/** Props for the ContentEditorInput component */\nexport type ContentEditorInputProps = {\n /** The current content to display and edit */\n children: InputProps['value'];\n /** Callback function called when content is saved/validated */\n onContentChange: (content: InputProps['value']) => void;\n /** Whether the editor is disabled */\n disabled?: boolean;\n /** Optional validation function to check content validity */\n validate?: (content: InputProps['value']) => boolean;\n /** Additional buttons to display alongside edit actions */\n additionalButtons?: ReactNode;\n} & Omit<InputProps, 'children'>;\n\n/**\n * ContentEditorInput Component\n *\n * An inline editing component for single-line text input with validation,\n * cancel/save functionality, and support for additional action buttons.\n *\n * ## Features\n * - **Inline Input Editing**: Edit single-line content with immediate feedback\n * - **Validation Support**: Optional content validation with visual feedback\n * - **Action Buttons**: Built-in save/cancel with support for additional buttons\n * - **Keyboard Shortcuts**: Enter to save, Escape to cancel\n * - **Accessibility**: Full ARIA support and keyboard navigation\n * - **State Management**: Handles editing states and validation\n *\n * ## Accessibility\n * - Proper ARIA labels and descriptions for all controls\n * - Keyboard navigation (Tab, Enter, Escape)\n * - Screen reader support for validation states\n * - Focus management and visual indicators\n *\n * @param children - Current input value\n * @param onContentChange - Callback when content is saved\n * @param disabled - Whether the editor is disabled\n * @param validate - Optional validation function\n * @param additionalButtons - Extra buttons to display\n * @param props - Additional Input component props\n */\nexport const ContentEditorInput: FC<ContentEditorInputProps> = ({\n children,\n onContentChange,\n disabled,\n validate,\n additionalButtons,\n ...props\n}) => {\n const [newValue, setNewValue] = useState<InputProps['value']>(children);\n const [resetIncrementor, setResetIncrementor] = useState<number>(0); // To reset the div on cancel\n const isEdited: boolean = newValue !== children;\n\n const handleCancel = () => {\n setNewValue(children);\n setResetIncrementor((prev) => prev + 1);\n };\n\n const handleValid = () => {\n onContentChange(newValue);\n };\n\n const handleOnContentChange: ChangeEventHandler<HTMLInputElement> = (e) => {\n setNewValue(e.currentTarget.value);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' && !disabled && isValid) {\n e.preventDefault();\n handleValid();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n handleCancel();\n }\n };\n\n useEffect(() => {\n setNewValue(children);\n // Force input to reset when children changes externally\n setResetIncrementor((prev) => prev + 1);\n }, [children]);\n\n const isValid = validate?.(newValue) ?? true;\n\n return (\n <div\n className=\"flex size-full flex-col items-center justify-between gap-2\"\n key={String(children)}\n role=\"group\"\n aria-label=\"Content editor input\"\n >\n <Input\n onChange={handleOnContentChange}\n onKeyDown={handleKeyDown}\n key={resetIncrementor}\n aria-label=\"Editable input value\"\n aria-describedby={\n isEdited || additionalButtons\n ? 'content-editor-input-actions'\n : undefined\n }\n aria-invalid={!isValid}\n variant={InputVariant.INVISIBLE}\n className=\"size-full\"\n defaultValue={children}\n disabled={disabled}\n {...props}\n />\n {(isEdited || additionalButtons) && (\n <div\n id=\"content-editor-input-actions\"\n className=\"flex w-full items-center justify-end gap-2\"\n role=\"group\"\n aria-label=\"Edit actions\"\n >\n {isEdited && (\n <>\n <Button\n Icon={Check}\n label={`Save changes${!isValid ? ' (invalid content)' : ''}`}\n variant={ButtonVariant.HOVERABLE}\n color={ButtonColor.TEXT}\n size={ButtonSize.ICON_SM}\n className=\"cursor-pointer hover:scale-110\"\n disabled={disabled || !isValid}\n onClick={handleValid}\n aria-describedby={!isValid ? 'validation-error' : undefined}\n />\n <Button\n Icon={X}\n label=\"Cancel changes\"\n variant={ButtonVariant.HOVERABLE}\n size={ButtonSize.ICON_SM}\n color={ButtonColor.TEXT}\n className=\"cursor-pointer hover:scale-110\"\n onClick={handleCancel}\n disabled={disabled}\n />\n </>\n )}\n {additionalButtons}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAa,sBAAmD,EAC9D,UACA,iBACA,UACA,UACA,mBACA,GAAG,YACC;CACJ,MAAM,CAAC,UAAU,eAAe,SAA8B,SAAS;CACvE,MAAM,CAAC,kBAAkB,uBAAuB,SAAiB,EAAE;CACnE,MAAM,WAAoB,aAAa;CAEvC,MAAM,qBAAqB;AACzB,cAAY,SAAS;AACrB,uBAAqB,SAAS,OAAO,EAAE;;CAGzC,MAAM,oBAAoB;AACxB,kBAAgB,SAAS;;CAG3B,MAAM,yBAA+D,MAAM;AACzE,cAAY,EAAE,cAAc,MAAM;;CAGpC,MAAM,iBAAiB,MAA6C;AAClE,MAAI,EAAE,QAAQ,WAAW,CAAC,YAAY,SAAS;AAC7C,KAAE,gBAAgB;AAClB,gBAAa;aACJ,EAAE,QAAQ,UAAU;AAC7B,KAAE,gBAAgB;AAClB,iBAAc;;;AAIlB,iBAAgB;AACd,cAAY,SAAS;AAErB,uBAAqB,SAAS,OAAO,EAAE;IACtC,CAAC,SAAS,CAAC;CAEd,MAAM,UAAU,WAAW,SAAS,IAAI;AAExC,QACE,qBAAC,OAAD;EACE,WAAU;EAEV,MAAK;EACL,cAAW;YAJb,CAME,oBAAC,OAAD;GACE,UAAU;GACV,WAAW;GAEX,cAAW;GACX,oBACE,YAAY,oBACR,iCACA;GAEN,gBAAc,CAAC;GACf;GACA,WAAU;GACV,cAAc;GACJ;GACV,GAAI;GACJ,EAbK,iBAaL,GACA,YAAY,sBACZ,qBAAC,OAAD;GACE,IAAG;GACH,WAAU;GACV,MAAK;GACL,cAAW;aAJb,CAMG,YACC,8CACE,oBAAC,QAAD;IACE,MAAM;IACN,OAAO,eAAe,CAAC,UAAU,uBAAuB;IACxD;IACA;IACA;IACA,WAAU;IACV,UAAU,YAAY,CAAC;IACvB,SAAS;IACT,oBAAkB,CAAC,UAAU,qBAAqB;IAClD,GACF,oBAAC,QAAD;IACE,MAAM;IACN,OAAM;IACN;IACA;IACA;IACA,WAAU;IACV,SAAS;IACC;IACV,EACD,KAEJ,kBACG;KAEJ;IAxDC,OAAO,SAAS,CAwDjB"}
|
|
@@ -88,9 +88,9 @@ const ContentEditorTextArea = ({ children, onContentChange, disabled, validate,
|
|
|
88
88
|
children: [isEdited && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
89
89
|
Icon: Check,
|
|
90
90
|
label: `Save changes${!isValid ? " (invalid content)" : ""}`,
|
|
91
|
-
variant:
|
|
92
|
-
color:
|
|
93
|
-
size:
|
|
91
|
+
variant: "hoverable",
|
|
92
|
+
color: "text",
|
|
93
|
+
size: "icon-sm",
|
|
94
94
|
className: "cursor-pointer hover:scale-110",
|
|
95
95
|
disabled: disabled || !isValid,
|
|
96
96
|
onClick: handleValid,
|
|
@@ -98,9 +98,9 @@ const ContentEditorTextArea = ({ children, onContentChange, disabled, validate,
|
|
|
98
98
|
}), /* @__PURE__ */ jsx(Button, {
|
|
99
99
|
Icon: X,
|
|
100
100
|
label: "Cancel changes",
|
|
101
|
-
variant:
|
|
102
|
-
size:
|
|
103
|
-
color:
|
|
101
|
+
variant: "hoverable",
|
|
102
|
+
size: "icon-sm",
|
|
103
|
+
color: "text",
|
|
104
104
|
className: "cursor-pointer hover:scale-110",
|
|
105
105
|
onClick: handleCancel,
|
|
106
106
|
disabled
|