@flikk/ui 1.0.0-beta.25 → 1.0.0-beta.26
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/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
- package/dist/components/ai/PromptInput/PromptInput.js +1 -0
- package/dist/components/ai/PromptInput/VoiceRecorder.js +1 -0
- package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +1 -1
- package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
- package/dist/components/charts/BarChart/BarChart.js +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.js +1 -1
- package/dist/components/charts/shared/BarRenderer/BarRenderer.js +2 -2
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +1 -1
- package/dist/components/core/Button/Button.js +1 -1
- package/dist/components/core/Button/Button.theme.js +4 -4
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
- package/dist/components/core/ContextMenu/ContextMenu.theme.js +1 -1
- package/dist/components/core/Drawer/Drawer.theme.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.theme.js +2 -2
- package/dist/components/core/Empty/Empty.js +1 -1
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.d.ts +14 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.js +28 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.d.ts +6 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.js +8 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.types.d.ts +32 -0
- package/dist/components/core/ImagePlaceholder/index.d.ts +2 -0
- package/dist/components/core/Message/Message.theme.js +1 -1
- package/dist/components/core/Modal/Modal.theme.js +1 -1
- package/dist/components/core/NavItem/NavItem.d.ts +1 -1
- package/dist/components/core/NavItem/NavItem.js +171 -68
- package/dist/components/core/NavItem/NavItem.theme.js +20 -12
- package/dist/components/core/NavItem/NavItem.types.d.ts +17 -39
- package/dist/components/core/Pagination/Pagination.theme.js +3 -3
- package/dist/components/core/Popover/Popover.theme.js +1 -1
- package/dist/components/core/Progress/Progress.theme.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
- package/dist/components/core/Segmented/Segmented.js +8 -6
- package/dist/components/core/Segmented/Segmented.theme.js +3 -3
- package/dist/components/core/Sidebar/Sidebar.d.ts +1 -1
- package/dist/components/core/Sidebar/Sidebar.js +42 -13
- package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +33 -4
- package/dist/components/core/Sidebar/SidebarContent.js +6 -6
- package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
- package/dist/components/core/Sidebar/SidebarContext.js +42 -19
- package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
- package/dist/components/core/Sidebar/SidebarHeader.js +4 -9
- package/dist/components/core/Sidebar/SidebarLogo.js +3 -7
- package/dist/components/core/Sidebar/SidebarNav.js +4 -3
- package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
- package/dist/components/core/Sidebar/SidebarToggle.js +6 -1
- package/dist/components/core/Sidebar/SidebarUserProfile.js +33 -17
- package/dist/components/core/Sidebar/index.d.ts +2 -3
- package/dist/components/core/Skeleton/Skeleton.theme.js +1 -1
- package/dist/components/core/Spinner/Spinner.theme.js +1 -1
- package/dist/components/core/Tabs/Tabs.js +6 -1
- package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
- package/dist/components/core/Tabs/TabsBody.js +5 -3
- package/dist/components/core/Tag/Tag.theme.js +1 -1
- package/dist/components/core/index.d.ts +1 -0
- package/dist/components/core/index.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
- package/dist/components/data-display/KPI/KPI.js +13 -3
- package/dist/components/data-display/KPI/KPI.theme.js +3 -3
- package/dist/components/data-display/Metric/Metric.theme.js +3 -3
- package/dist/components/data-display/Table/Table.js +4 -3
- package/dist/components/data-display/Table/Table.theme.js +11 -1
- package/dist/components/data-display/Table/Table.types.d.ts +15 -0
- package/dist/components/data-display/Table/TableColumnManager.js +1 -1
- package/dist/components/data-display/Table/index.d.ts +2 -2
- package/dist/components/effects/Aurora/Aurora.js +79 -40
- package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +3 -3
- package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +13 -4
- package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +13 -4
- package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +14 -4
- package/dist/components/effects/MorphingText/MorphingText.js +40 -7
- package/dist/components/effects/StripePattern/StripePattern.d.ts +18 -0
- package/dist/components/effects/StripePattern/StripePattern.js +54 -0
- package/dist/components/effects/StripePattern/StripePattern.types.d.ts +53 -0
- package/dist/components/effects/StripePattern/index.d.ts +2 -0
- package/dist/components/effects/index.d.ts +2 -0
- package/dist/components/effects/index.js +1 -0
- package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
- package/dist/components/forms/CronInput/CronInput.theme.js +16 -16
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
- package/dist/components/forms/FileUpload/FileUpload.js +1 -1
- package/dist/components/forms/FileUpload/FileUploadProgress.js +27 -20
- package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
- package/dist/components/forms/Input/Input.theme.js +9 -11
- package/dist/components/forms/Mention/Mention.theme.js +3 -3
- package/dist/components/forms/Radio/Radio.theme.js +1 -1
- package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +18 -0
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +1231 -236
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +53 -1
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +99 -25
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +42 -3
- package/dist/components/forms/RichTextEditor/index.d.ts +2 -2
- package/dist/components/forms/Select/Select.js +1 -1
- package/dist/components/forms/Slider/Slider.theme.js +1 -1
- package/dist/components/forms/Switch/Switch.theme.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
- package/dist/components/forms/TimePicker/TimePickerContent.js +1 -0
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
- package/dist/components/forms/forms.theme.js +12 -12
- package/dist/components/forms/index.js +1 -0
- package/dist/components/layout/FormLayout/FormLayout.js +6 -4
- package/dist/components/layout/FormLayout/FormLayout.types.d.ts +3 -3
- package/dist/components/layout/FormLayout/FormLayoutSection.js +6 -1
- package/dist/components/layout/PageLayout/PageLayout.d.ts +2 -2
- package/dist/components/layout/PageLayout/PageLayout.js +23 -5
- package/dist/components/layout/PageLayout/PageLayout.theme.d.ts +2 -1
- package/dist/components/layout/PageLayout/PageLayout.theme.js +35 -6
- package/dist/components/layout/PageLayout/PageLayout.types.d.ts +45 -0
- package/dist/components/layout/PageLayout/PageLayoutContent.js +6 -4
- package/dist/components/layout/PageLayout/PageLayoutHeader.js +3 -3
- package/dist/components/layout/PageLayout/PageLayoutSidebar.js +5 -3
- package/dist/components/layout/PageLayout/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/styles/theme.css +127 -92
- package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
- package/dist/components/core/Sidebar/SidebarSubmenu.js +0 -12
|
@@ -4,10 +4,9 @@ export { SidebarContent } from "./SidebarContent";
|
|
|
4
4
|
export { SidebarFooter } from "./SidebarFooter";
|
|
5
5
|
export { SidebarNav } from "./SidebarNav";
|
|
6
6
|
export { SidebarNavGroup } from "./SidebarNavGroup";
|
|
7
|
-
export { SidebarSubmenu } from "./SidebarSubmenu";
|
|
8
7
|
export { SidebarToggle } from "./SidebarToggle";
|
|
9
8
|
export { SidebarMobileTrigger } from "./SidebarMobileTrigger";
|
|
10
9
|
export { SidebarUserProfile } from "./SidebarUserProfile";
|
|
11
10
|
export { SidebarLogo } from "./SidebarLogo";
|
|
12
|
-
export { useSidebarContext } from "./SidebarContext";
|
|
13
|
-
export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarMobileTriggerProps, SidebarUserProfileProps, SidebarUserStatus, SidebarSide, SidebarVariant, SidebarContextValue, SidebarTheme, SidebarThemeOverrides, SidebarLogoProps, } from "./Sidebar.types";
|
|
11
|
+
export { SidebarProvider, useSidebarContext } from "./SidebarContext";
|
|
12
|
+
export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarMobileTriggerProps, SidebarUserProfileProps, SidebarUserStatus, SidebarSide, SidebarVariant, SidebarDensity, SidebarContextValue, SidebarProviderProps, SidebarTheme, SidebarThemeOverrides, SidebarLogoProps, } from "./Sidebar.types";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const skeletonTheme = {
|
|
6
6
|
// Base styles with background color and animation
|
|
7
|
-
baseStyle: "bg-[var(--color-
|
|
7
|
+
baseStyle: "bg-[var(--color-surface-sunken)] overflow-hidden",
|
|
8
8
|
// Shape variants
|
|
9
9
|
variants: {
|
|
10
10
|
text: "h-4 rounded",
|
|
@@ -15,7 +15,7 @@ const spinnerTheme = {
|
|
|
15
15
|
// Color variants
|
|
16
16
|
colors: {
|
|
17
17
|
...semanticSpinnerColors,
|
|
18
|
-
dark: "border-[var(--color-
|
|
18
|
+
dark: "border-[var(--color-surface-sunken)] border-t-transparent",
|
|
19
19
|
},
|
|
20
20
|
// Animation class with drop shadow
|
|
21
21
|
animation: "animate-[spin_1s_linear_infinite] rounded-full",
|
|
@@ -14,6 +14,9 @@ import { TabsBody } from './TabsBody.js';
|
|
|
14
14
|
*/
|
|
15
15
|
const TabsComponent = React__default.forwardRef(({ value, defaultValue, onValueChange, orientation = "horizontal", size = "md", disabled = false, children, className, darkMode = false, ...props }, ref) => {
|
|
16
16
|
const [currentValue, setCurrentValue] = useState(value || defaultValue || "");
|
|
17
|
+
// Track whether the user has switched tabs yet, so content only animates
|
|
18
|
+
// on an actual tab change and not on mount / page load.
|
|
19
|
+
const [hasSwitched, setHasSwitched] = useState(false);
|
|
17
20
|
// Unique instance prefix to scope all DOM IDs to this Tabs instance
|
|
18
21
|
const instanceId = useId();
|
|
19
22
|
// Track trigger elements for underline positioning
|
|
@@ -42,6 +45,7 @@ const TabsComponent = React__default.forwardRef(({ value, defaultValue, onValueC
|
|
|
42
45
|
// Safe value change handler with error handling
|
|
43
46
|
const handleValueChange = useCallback((newValue) => {
|
|
44
47
|
try {
|
|
48
|
+
setHasSwitched(true);
|
|
45
49
|
if (!isControlled) {
|
|
46
50
|
setCurrentValue(newValue);
|
|
47
51
|
}
|
|
@@ -71,7 +75,8 @@ const TabsComponent = React__default.forwardRef(({ value, defaultValue, onValueC
|
|
|
71
75
|
registerTrigger,
|
|
72
76
|
unregisterTrigger,
|
|
73
77
|
instanceId,
|
|
74
|
-
|
|
78
|
+
hasSwitched,
|
|
79
|
+
}), [activeValue, handleValueChange, orientation, size, disabled, registerTrigger, unregisterTrigger, instanceId, hasSwitched]);
|
|
75
80
|
// Memoize theme classes for performance
|
|
76
81
|
const themeClasses = useMemo(() => ({
|
|
77
82
|
baseStyle: tabsTheme.baseStyle,
|
|
@@ -141,4 +141,6 @@ export interface TabsContextValue {
|
|
|
141
141
|
unregisterTrigger: (value: string) => void;
|
|
142
142
|
/** Unique prefix scoping DOM IDs to this Tabs instance */
|
|
143
143
|
instanceId: string;
|
|
144
|
+
/** Whether the user has switched tabs yet (false until the first change). Used to suppress content animation on mount. */
|
|
145
|
+
hasSwitched: boolean;
|
|
144
146
|
}
|
|
@@ -10,7 +10,7 @@ import { getTabsBodyVariants } from './Tabs.animations.js';
|
|
|
10
10
|
* TabsBody component displays content panels for tabs with directional fade animations
|
|
11
11
|
*/
|
|
12
12
|
const TabsBody = React__default.forwardRef(({ value, forceMount = false, children, className, ...props }, ref) => {
|
|
13
|
-
const { value: selectedValue, orientation, instanceId } = useTabsContext();
|
|
13
|
+
const { value: selectedValue, orientation, instanceId, hasSwitched } = useTabsContext();
|
|
14
14
|
const shouldReduceMotion = useReducedMotion();
|
|
15
15
|
// Check if we're in Storybook and should disable animations
|
|
16
16
|
const isInStorybook = typeof window !== 'undefined' && window.__STORYBOOK_DISABLE_ANIMATIONS__;
|
|
@@ -32,9 +32,11 @@ const TabsBody = React__default.forwardRef(({ value, forceMount = false, childre
|
|
|
32
32
|
if (!shouldRender) {
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
|
-
// Determine animation props (user props will override due to spread order)
|
|
35
|
+
// Determine animation props (user props will override due to spread order).
|
|
36
|
+
// Until the user switches tabs, the panel snaps in (initial=false) so it does
|
|
37
|
+
// not animate on mount / page load — only actual tab changes animate.
|
|
36
38
|
const animationProps = shouldDisableAnimations ? {} : {
|
|
37
|
-
initial: "initial",
|
|
39
|
+
initial: hasSwitched ? "initial" : false,
|
|
38
40
|
animate: "animate",
|
|
39
41
|
exit: "exit",
|
|
40
42
|
variants: contentVariants,
|
|
@@ -16,7 +16,7 @@ const tagTheme = {
|
|
|
16
16
|
},
|
|
17
17
|
disabledStyle: "opacity-50 cursor-not-allowed",
|
|
18
18
|
removeButtonStyle: "inline-flex items-center justify-center rounded-full p-1 " +
|
|
19
|
-
"hover:bg-[var(--color-
|
|
19
|
+
"hover:bg-[var(--color-surface-hover)] " +
|
|
20
20
|
"text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] " +
|
|
21
21
|
"transition-all duration-300 cursor-pointer focus:outline-none " +
|
|
22
22
|
"focus-visible:ring-2 focus-visible:ring-[var(--color-primary)]",
|
|
@@ -96,12 +96,11 @@ export { SidebarContent } from './Sidebar/SidebarContent.js';
|
|
|
96
96
|
export { SidebarFooter } from './Sidebar/SidebarFooter.js';
|
|
97
97
|
export { SidebarNav } from './Sidebar/SidebarNav.js';
|
|
98
98
|
export { SidebarNavGroup } from './Sidebar/SidebarNavGroup.js';
|
|
99
|
-
export { SidebarSubmenu } from './Sidebar/SidebarSubmenu.js';
|
|
100
99
|
export { SidebarToggle } from './Sidebar/SidebarToggle.js';
|
|
101
100
|
export { SidebarMobileTrigger } from './Sidebar/SidebarMobileTrigger.js';
|
|
102
101
|
export { SidebarUserProfile } from './Sidebar/SidebarUserProfile.js';
|
|
103
102
|
export { SidebarLogo } from './Sidebar/SidebarLogo.js';
|
|
104
|
-
export { useSidebarContext } from './Sidebar/SidebarContext.js';
|
|
103
|
+
export { SidebarProvider, useSidebarContext } from './Sidebar/SidebarContext.js';
|
|
105
104
|
export { OfflineIndicator } from './OfflineIndicator/OfflineIndicator.js';
|
|
106
105
|
export { offlineIndicatorTheme } from './OfflineIndicator/OfflineIndicator.theme.js';
|
|
107
106
|
export { ContextMenu } from './ContextMenu/ContextMenu.js';
|
|
@@ -110,6 +109,7 @@ export { Pill } from './Pill/Pill.js';
|
|
|
110
109
|
export { pillTheme } from './Pill/Pill.theme.js';
|
|
111
110
|
export { Icon } from './Icon/Icon.js';
|
|
112
111
|
export { fileTypes } from './Icon/data/fileTypes.js';
|
|
112
|
+
export { ImagePlaceholder } from './ImagePlaceholder/ImagePlaceholder.js';
|
|
113
113
|
export { AnimatePresence } from 'motion/react';
|
|
114
114
|
export { CardBody } from './Card/CardBody.js';
|
|
115
115
|
export { CardFooter } from './Card/CardFooter.js';
|
|
@@ -14,7 +14,7 @@ const GanttTableGroupRow = React__default.memo(({ row, }) => {
|
|
|
14
14
|
e.preventDefault();
|
|
15
15
|
toggleGroup(row.group.id);
|
|
16
16
|
}
|
|
17
|
-
}, role: "button", tabIndex: 0, "aria-expanded": !isCollapsed, "aria-label": `${row.group.title} — ${row.completedCount} of ${row.taskCount} completed`, children: [jsxs("div", { className: "flex items-center gap-2 px-3 flex-1 min-w-0", children: [jsx(motion.div, { variants: chevronVariants, animate: isCollapsed ? "collapsed" : "open", children: jsx(ChevronRightIcon, { className: "size-4 text-[var(--color-text-muted)]" }) }), jsx("div", { className: "size-2.5 rounded-full shrink-0", style: { backgroundColor: row.group.color } }), jsx("span", { className: "font-medium text-[var(--color-text-primary)] truncate", children: row.group.title }), jsxs("span", { className: "text-sm text-[var(--color-text-muted)] shrink-0", children: [row.completedCount, "/", row.taskCount] })] }), onTaskAdd && !readonly && (jsx("button", { className: "p-1 mr-2 rounded hover:bg-[var(--color-
|
|
17
|
+
}, role: "button", tabIndex: 0, "aria-expanded": !isCollapsed, "aria-label": `${row.group.title} — ${row.completedCount} of ${row.taskCount} completed`, children: [jsxs("div", { className: "flex items-center gap-2 px-3 flex-1 min-w-0", children: [jsx(motion.div, { variants: chevronVariants, animate: isCollapsed ? "collapsed" : "open", children: jsx(ChevronRightIcon, { className: "size-4 text-[var(--color-text-muted)]" }) }), jsx("div", { className: "size-2.5 rounded-full shrink-0", style: { backgroundColor: row.group.color } }), jsx("span", { className: "font-medium text-[var(--color-text-primary)] truncate", children: row.group.title }), jsxs("span", { className: "text-sm text-[var(--color-text-muted)] shrink-0", children: [row.completedCount, "/", row.taskCount] })] }), onTaskAdd && !readonly && (jsx("button", { className: "p-1 mr-2 rounded hover:bg-[var(--color-surface-hover)] text-[var(--color-text-muted)] opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity", "aria-label": `Add task to ${row.group.title}`, onClick: (e) => {
|
|
18
18
|
e.stopPropagation();
|
|
19
19
|
onTaskAdd(row.group.id);
|
|
20
20
|
}, children: jsx(PlusIcon, { className: "size-4" }) }))] }));
|
|
@@ -24,7 +24,13 @@ const KPI = React__default.forwardRef(({ value, label, trend, trendLabel, progre
|
|
|
24
24
|
const sign = trend > 0 ? "+" : "";
|
|
25
25
|
return `${sign}${trend}%`;
|
|
26
26
|
}, [trend, trendLabel]);
|
|
27
|
-
const trendDirection = trend === undefined
|
|
27
|
+
const trendDirection = trend === undefined
|
|
28
|
+
? null
|
|
29
|
+
: trend > 0
|
|
30
|
+
? "up"
|
|
31
|
+
: trend < 0
|
|
32
|
+
? "down"
|
|
33
|
+
: "neutral";
|
|
28
34
|
// Calculate filled bar count from progress
|
|
29
35
|
const filledBars = useMemo(() => {
|
|
30
36
|
if (progress === undefined)
|
|
@@ -32,7 +38,10 @@ const KPI = React__default.forwardRef(({ value, label, trend, trendLabel, progre
|
|
|
32
38
|
return Math.round((Math.min(Math.max(progress, 0), 100) / 100) * barCount);
|
|
33
39
|
}, [progress, barCount]);
|
|
34
40
|
const barSize = kpiTheme.barSizes[size];
|
|
35
|
-
return (jsxs("div", { ref: ref, className: cn(theme.baseStyle, className), ...props, children: [jsxs("div", { className: "flex items-start justify-between", children: [jsxs("div", { className: "flex items-baseline gap-2", children: [jsx("span", { className: cn(theme.valueSizes[size]), children: value }), label &&
|
|
41
|
+
return (jsxs("div", { ref: ref, className: cn(theme.baseStyle, className), ...props, children: [jsxs("div", { className: "flex items-start justify-between", children: [jsxs("div", { className: "flex items-baseline gap-2", children: [jsx("span", { className: cn(theme.valueSizes[size]), children: value }), label &&
|
|
42
|
+
(typeof label === "string" ? (jsxs("span", { className: cn(theme.labelStyle, theme.labelSizes[size]), children: [icon && (jsx("span", { className: "inline-flex items-center mr-1", children: icon })), label] })) : (label))] }), formattedTrend &&
|
|
43
|
+
trendDirection &&
|
|
44
|
+
(typeof formattedTrend === "string" ? (jsxs("span", { className: cn("inline-flex items-center gap-0.5 font-medium", theme.trendSizes[size], trendDirection === "up" && theme.trendPositive, trendDirection === "down" && theme.trendNegative, trendDirection === "neutral" && theme.trendNeutral), children: [formattedTrend, trendDirection === "up" && (jsx(ArrowUpRightIcon, { className: "size-4" })), trendDirection === "down" && (jsx(ArrowDownRightIcon, { className: "size-4" }))] })) : (formattedTrend))] }), progress !== undefined && (jsx("div", { className: cn("flex items-end", barSize.gap), role: "img", "aria-label": `Progress: ${progress}%`, children: Array.from({ length: barCount }, (_, i) => {
|
|
36
45
|
const isFilled = i < filledBars;
|
|
37
46
|
const fadeDistance = filledBars - i;
|
|
38
47
|
const fillOpacity = isFilled && fadeDistance <= 3
|
|
@@ -43,7 +52,8 @@ const KPI = React__default.forwardRef(({ value, label, trend, trendLabel, progre
|
|
|
43
52
|
ease: "easeOut",
|
|
44
53
|
delay: i * 0.03,
|
|
45
54
|
} })) }, i));
|
|
46
|
-
}) })), subtitle &&
|
|
55
|
+
}) })), subtitle &&
|
|
56
|
+
(typeof subtitle === "string" ? (jsx("span", { className: cn(theme.subtitleStyle), children: subtitle })) : (subtitle))] }));
|
|
47
57
|
});
|
|
48
58
|
KPI.displayName = "KPI";
|
|
49
59
|
|
|
@@ -7,7 +7,7 @@ const kpiTheme = {
|
|
|
7
7
|
md: "text-2xl font-bold text-[var(--color-text-primary)]",
|
|
8
8
|
lg: "text-3xl font-bold text-[var(--color-text-primary)]",
|
|
9
9
|
},
|
|
10
|
-
labelStyle: "text-[var(--color-text-
|
|
10
|
+
labelStyle: "text-[var(--color-text-primary)] font-medium",
|
|
11
11
|
labelSizes: {
|
|
12
12
|
sm: "text-xs",
|
|
13
13
|
md: "text-sm",
|
|
@@ -25,13 +25,13 @@ const kpiTheme = {
|
|
|
25
25
|
...semanticBgColors,
|
|
26
26
|
neutral: "bg-[var(--color-text-secondary)]",
|
|
27
27
|
},
|
|
28
|
-
barEmptyColor: "bg-[var(--color-
|
|
28
|
+
barEmptyColor: "bg-[var(--color-surface-sunken)]",
|
|
29
29
|
barSizes: {
|
|
30
30
|
sm: { height: "h-4", width: "w-[3px]", gap: "gap-[2px]" },
|
|
31
31
|
md: { height: "h-6", width: "w-[3px]", gap: "gap-[3px]" },
|
|
32
32
|
lg: { height: "h-8", width: "w-1", gap: "gap-1" },
|
|
33
33
|
},
|
|
34
|
-
subtitleStyle: "text-[var(--color-text-
|
|
34
|
+
subtitleStyle: "text-[var(--color-text-secondary)] text-sm",
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export { kpiTheme };
|
|
@@ -60,7 +60,7 @@ const metricTheme = {
|
|
|
60
60
|
contentPadding: "p-4",
|
|
61
61
|
iconContainer: "size-14",
|
|
62
62
|
label: "text-sm",
|
|
63
|
-
value: "text-
|
|
63
|
+
value: "text-2xl",
|
|
64
64
|
subtitle: "text-xs mt-0.5",
|
|
65
65
|
},
|
|
66
66
|
lg: {
|
|
@@ -68,12 +68,12 @@ const metricTheme = {
|
|
|
68
68
|
contentPadding: "p-5",
|
|
69
69
|
iconContainer: "size-16",
|
|
70
70
|
label: "text-base",
|
|
71
|
-
value: "text-
|
|
71
|
+
value: "text-3xl",
|
|
72
72
|
subtitle: "text-sm mt-1",
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
75
|
// Common element styles
|
|
76
|
-
labelStyle: "font-semibold text-[var(--color-text-
|
|
76
|
+
labelStyle: "font-semibold text-[var(--color-text-primary)] mb-1",
|
|
77
77
|
valueStyle: "font-bold text-[var(--color-text-primary)] tracking-tight",
|
|
78
78
|
subtitleStyle: "text-[var(--color-text-secondary)]/80",
|
|
79
79
|
iconContainerStyle: "rounded-full flex items-center justify-center flex-shrink-0 p-3.5",
|
|
@@ -37,8 +37,8 @@ const TableContext = createContext({
|
|
|
37
37
|
* - Theme customization with className overrides
|
|
38
38
|
*/
|
|
39
39
|
function TableComponent(props) {
|
|
40
|
-
var _a;
|
|
41
|
-
const { data, columns, rowKey, variant = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, caption, } = props;
|
|
40
|
+
var _a, _b;
|
|
41
|
+
const { data, columns, rowKey, variant = "default", padding = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, caption, } = props;
|
|
42
42
|
// Column management hook
|
|
43
43
|
const { visibleColumns, columnOrder, visibleColumnsList, handleToggleColumn, handleColumnReorder, handleResetToDefault, } = useTableColumns({
|
|
44
44
|
columns,
|
|
@@ -104,6 +104,7 @@ function TableComponent(props) {
|
|
|
104
104
|
// Get base classes from theme
|
|
105
105
|
const baseClasses = theme.baseStyle || "";
|
|
106
106
|
const variantClasses = ((_a = theme.variants) === null || _a === void 0 ? void 0 : _a[variant]) || "";
|
|
107
|
+
const paddingClasses = ((_b = theme.paddingStyles) === null || _b === void 0 ? void 0 : _b[padding]) || "";
|
|
107
108
|
// Memoize context value to prevent unnecessary re-renders in consumers
|
|
108
109
|
const contextValue = useMemo(() => ({
|
|
109
110
|
theme,
|
|
@@ -148,7 +149,7 @@ function TableComponent(props) {
|
|
|
148
149
|
selectedRows,
|
|
149
150
|
onSelectionChange,
|
|
150
151
|
]);
|
|
151
|
-
return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children: jsxs("table", { className: cn(baseClasses, variantClasses, className), role: "table", "aria-label": isDeclarativeAPI ? "Data table" : `Data table with ${paginatedData.length} rows`, "aria-rowcount": pagination ? pagination.totalItems : data === null || data === void 0 ? void 0 : data.length, "aria-colcount": visibleColumnsList.length, children: [caption && (jsx("caption", { className: "sr-only", children: caption })), isDeclarativeAPI ? (children) : (jsxs(Fragment, { children: [jsx(TableHeader, { columns: visibleColumnsList, sortConfig: sortConfig, onSortChange: onSortChange, filterConfig: filterConfig, onFilterChange: onFilterChange, freezeHeader: freezeHeader, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, expandable: expandable, expandedRowRender: expandedRowRender, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, totalRowCount: paginatedData.length, theme: theme, data: paginatedData, rowKey: rowKey }), jsx(TableBody, { data: paginatedData, columns: visibleColumnsList, rowKey: rowKey, expandedRowRender: expandedRowRender, expandedRows: expandedRows, onExpandedRowsChange: onExpandedRowsChange, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, theme: theme })] }))] }) })] }) }));
|
|
152
|
+
return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children: jsxs("table", { className: cn(baseClasses, variantClasses, paddingClasses, className), role: "table", "aria-label": isDeclarativeAPI ? "Data table" : `Data table with ${paginatedData.length} rows`, "aria-rowcount": pagination ? pagination.totalItems : data === null || data === void 0 ? void 0 : data.length, "aria-colcount": visibleColumnsList.length, children: [caption && (jsx("caption", { className: "sr-only", children: caption })), isDeclarativeAPI ? (children) : (jsxs(Fragment, { children: [jsx(TableHeader, { columns: visibleColumnsList, sortConfig: sortConfig, onSortChange: onSortChange, filterConfig: filterConfig, onFilterChange: onFilterChange, freezeHeader: freezeHeader, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, expandable: expandable, expandedRowRender: expandedRowRender, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, totalRowCount: paginatedData.length, theme: theme, data: paginatedData, rowKey: rowKey }), jsx(TableBody, { data: paginatedData, columns: visibleColumnsList, rowKey: rowKey, expandedRowRender: expandedRowRender, expandedRows: expandedRows, onExpandedRowsChange: onExpandedRowsChange, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, theme: theme })] }))] }) })] }) }));
|
|
152
153
|
}
|
|
153
154
|
TableComponent.displayName = "Table";
|
|
154
155
|
const Table = TableComponent;
|
|
@@ -20,6 +20,16 @@ const tableTheme = {
|
|
|
20
20
|
compact: "border-[var(--color-border)] dark:border-[var(--color-neutral-700)] " +
|
|
21
21
|
"[&_td]:px-3 [&_td]:py-1 [&_th]:px-3 [&_th]:py-1",
|
|
22
22
|
},
|
|
23
|
+
// Edge-padding modes — applied at the table root via descendant selectors so they
|
|
24
|
+
// work for both the data-driven and declarative APIs. `flush` zeroes the
|
|
25
|
+
// first/last cell side padding (overrides the default `first:pl-4 last:pr-4`).
|
|
26
|
+
// Descendant selectors (.table th:first-child) outrank per-cell first:pl-4 by
|
|
27
|
+
// one specificity point so this wins without !important.
|
|
28
|
+
paddingStyles: {
|
|
29
|
+
default: "",
|
|
30
|
+
flush: "[&_th:first-child]:pl-0 [&_th:last-child]:pr-0 " +
|
|
31
|
+
"[&_td:first-child]:pl-0 [&_td:last-child]:pr-0",
|
|
32
|
+
},
|
|
23
33
|
// Header styles
|
|
24
34
|
// Light: primary text, light border | Dark: neutral-200 text, neutral-700 border
|
|
25
35
|
headerStyle: "text-left font-semibold sticky top-0 border-b first:pl-4 last:pr-4 " +
|
|
@@ -32,7 +42,7 @@ const tableTheme = {
|
|
|
32
42
|
// Light: neutral-50 hover | Dark: neutral-800 hover
|
|
33
43
|
rowStyle: "group transition-all duration-300 relative " +
|
|
34
44
|
"hover:bg-[var(--color-neutral-50)] " +
|
|
35
|
-
"dark:hover:bg-[var(--color-
|
|
45
|
+
"dark:hover:bg-[var(--color-surface-hover)]",
|
|
36
46
|
// Selected row styles — background is applied per-cell (in TableCell) so that
|
|
37
47
|
// sticky/frozen columns repaint correctly. The row only carries the left indicator.
|
|
38
48
|
selectedRowStyle: "shadow-[inset_3px_0_0_0_var(--color-primary)]",
|
|
@@ -18,6 +18,13 @@ export interface BulkAction {
|
|
|
18
18
|
}
|
|
19
19
|
/** Table visual variants for different styling approaches */
|
|
20
20
|
export type TableVariant = 'default' | 'striped' | 'bordered' | 'compact';
|
|
21
|
+
/**
|
|
22
|
+
* Edge padding behavior for table cells.
|
|
23
|
+
* - `default`: First/last cells receive extra side padding (matches inner cells).
|
|
24
|
+
* - `flush`: First/last cells have zero side padding so values align flush with the table's container edges.
|
|
25
|
+
* Useful for invoice-style layouts where the table sits inside a padded card.
|
|
26
|
+
*/
|
|
27
|
+
export type TablePadding = 'default' | 'flush';
|
|
21
28
|
/** Text alignment options for table cells and headers */
|
|
22
29
|
export type TableAlignment = 'left' | 'center' | 'right';
|
|
23
30
|
/** Selection control types for row selection */
|
|
@@ -95,6 +102,8 @@ export interface FilterConfig {
|
|
|
95
102
|
export interface TableThemeOverrides {
|
|
96
103
|
baseStyle?: string;
|
|
97
104
|
variants?: Partial<Record<TableVariant, string>>;
|
|
105
|
+
/** Edge-padding mode classes applied at the table root via descendant selectors */
|
|
106
|
+
paddingStyles?: Partial<Record<TablePadding, string>>;
|
|
98
107
|
headerStyle?: string;
|
|
99
108
|
cellStyle?: string;
|
|
100
109
|
rowStyle?: string;
|
|
@@ -148,6 +157,12 @@ export interface TableProps<T = unknown> {
|
|
|
148
157
|
rowKey?: keyof T | ((row: T, index: number) => string | number);
|
|
149
158
|
/** Visual variant for different table styles */
|
|
150
159
|
variant?: TableVariant;
|
|
160
|
+
/**
|
|
161
|
+
* Edge padding behavior. `flush` removes left padding from the first cell and right padding
|
|
162
|
+
* from the last cell so column values align flush with the table's container edges.
|
|
163
|
+
* @default "default"
|
|
164
|
+
*/
|
|
165
|
+
padding?: TablePadding;
|
|
151
166
|
/** Theme overrides for custom styling */
|
|
152
167
|
theme?: TableThemeOverrides;
|
|
153
168
|
/** Additional CSS classes for customization */
|
|
@@ -18,7 +18,7 @@ function TableColumnManagerComponent({ columns, visibleColumns, columnOrder, onT
|
|
|
18
18
|
const displayName = typeof column.header === 'string' ? column.header : column.id;
|
|
19
19
|
return (jsx(Checkbox, { id: `column-checkbox-${column.id}`, name: `column-${column.id}`, value: column.id, checked: isVisible, onChange: () => onToggleColumn(column.id), "aria-label": `Toggle visibility for ${displayName} column`, label: displayName, state: isLocked ? 'disabled' : 'default' }));
|
|
20
20
|
};
|
|
21
|
-
return (jsxs(Popover, { placement: "bottom-end", offset: 8, children: [jsx(Popover.Trigger, { children: jsxs(Button, { color: "neutral", variant: "filled", "aria-label": "Manage column visibility", children: [jsx(Cog6ToothIcon, { className: "size-5", "aria-hidden": "true" }), "Manage Columns"] }) }), jsx(Popover.Body, { className: "p-0", children: jsxs("div", { className: "w-64 p-3 overflow-hidden", role: "dialog", "aria-labelledby": "column-manager-title", "aria-describedby": "column-manager-description", "aria-modal": "true", children: [jsx("div", { id: "column-manager-title", className: "text-sm uppercase font-bold mb-3 text-[var(--color-text-placeholder)]", children: "Manage Columns" }), jsx("div", { id: "column-manager-description", className: "sr-only", children: "Use checkboxes to control which columns are visible in the table. Locked columns cannot be hidden." }), jsx("div", { className: "space-y-0.5", role: "group", "aria-label": "Column visibility options", children: onColumnReorder ? (jsx(Sortable, { onReorder: onColumnReorder, showDragHandle: true, className: "space-y-0.5", children: orderedColumns.map((column) => (jsx(SortableItem, { id: column.id, disabled: column.locked === true, className: `hover:bg-[var(--color-
|
|
21
|
+
return (jsxs(Popover, { placement: "bottom-end", offset: 8, children: [jsx(Popover.Trigger, { children: jsxs(Button, { color: "neutral", variant: "filled", "aria-label": "Manage column visibility", children: [jsx(Cog6ToothIcon, { className: "size-5", "aria-hidden": "true" }), "Manage Columns"] }) }), jsx(Popover.Body, { className: "p-0", children: jsxs("div", { className: "w-64 p-3 overflow-hidden", role: "dialog", "aria-labelledby": "column-manager-title", "aria-describedby": "column-manager-description", "aria-modal": "true", children: [jsx("div", { id: "column-manager-title", className: "text-sm uppercase font-bold mb-3 text-[var(--color-text-placeholder)]", children: "Manage Columns" }), jsx("div", { id: "column-manager-description", className: "sr-only", children: "Use checkboxes to control which columns are visible in the table. Locked columns cannot be hidden." }), jsx("div", { className: "space-y-0.5", role: "group", "aria-label": "Column visibility options", children: onColumnReorder ? (jsx(Sortable, { onReorder: onColumnReorder, showDragHandle: true, className: "space-y-0.5", children: orderedColumns.map((column) => (jsx(SortableItem, { id: column.id, disabled: column.locked === true, className: `hover:bg-[var(--color-surface-hover)] rounded-lg p-1.5 transition-colors ${column.locked ? 'bg-[var(--color-background-secondary)]' : ''}`, children: renderColumnItem(column) }, column.id))) })) : (orderedColumns.map((column) => (jsx("div", { className: `hover:bg-[var(--color-surface-hover)] rounded-lg p-1.5 transition-colors ${column.locked ? 'bg-[var(--color-background-secondary)]' : ''}`, children: renderColumnItem(column) }, column.id)))) }), jsx("div", { className: "mt-3 flex justify-end", children: jsx(Button, { color: "neutral", variant: "filled", className: "w-full", onClick: onResetToDefault, children: "Reset to Default" }) })] }) })] }));
|
|
22
22
|
}
|
|
23
23
|
const TableColumnManager = TableColumnManagerComponent;
|
|
24
24
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TableProps, Column, SortConfig, FilterConfig, FilterOperator, FilterType, FilterLogic, FilterOption, TableThemeOverrides, TableVariant, TableAlignment, SelectionType, TableHeaderProps, TableBodyProps, TableRowProps, TableCellProps, TableHeaderCellProps, BulkAction, ActionItem, ActionVariant, ActionPriority, TableActionsProps, TableActionsMenuProps, TablePaginationProps, TableSelectionHeaderProps, TableColumnManagerProps, TableFilterProps } from './Table.types';
|
|
1
|
+
import { TableProps, Column, SortConfig, FilterConfig, FilterOperator, FilterType, FilterLogic, FilterOption, TableThemeOverrides, TableVariant, TablePadding, TableAlignment, SelectionType, TableHeaderProps, TableBodyProps, TableRowProps, TableCellProps, TableHeaderCellProps, BulkAction, ActionItem, ActionVariant, ActionPriority, TableActionsProps, TableActionsMenuProps, TablePaginationProps, TableSelectionHeaderProps, TableColumnManagerProps, TableFilterProps } from './Table.types';
|
|
2
2
|
import { TableActions } from './TableActions';
|
|
3
3
|
import { TableActionsMenu } from './TableActionsMenu';
|
|
4
4
|
import { TablePagination } from './TablePagination';
|
|
@@ -16,7 +16,7 @@ interface TableCompound {
|
|
|
16
16
|
}
|
|
17
17
|
declare const Table: TableCompound;
|
|
18
18
|
export { Table };
|
|
19
|
-
export type { TableProps, Column, SortConfig, FilterConfig, FilterOperator, FilterType, FilterLogic, FilterOption, TableThemeOverrides, TableVariant, TableAlignment, SelectionType, TableHeaderProps, TableBodyProps, TableRowProps, TableCellProps, TableHeaderCellProps, BulkAction, ActionItem, ActionVariant, ActionPriority, TableActionsProps, TableActionsMenuProps, TablePaginationProps, TableSelectionHeaderProps, TableColumnManagerProps, TableFilterProps, };
|
|
19
|
+
export type { TableProps, Column, SortConfig, FilterConfig, FilterOperator, FilterType, FilterLogic, FilterOption, TableThemeOverrides, TableVariant, TablePadding, TableAlignment, SelectionType, TableHeaderProps, TableBodyProps, TableRowProps, TableCellProps, TableHeaderCellProps, BulkAction, ActionItem, ActionVariant, ActionPriority, TableActionsProps, TableActionsMenuProps, TablePaginationProps, TableSelectionHeaderProps, TableColumnManagerProps, TableFilterProps, };
|
|
20
20
|
export { TableActions, TableActionsMenu, TablePagination, TableFilter };
|
|
21
21
|
export { inferFilterType, getOperatorsForFilterType, getDefaultOperator, isUnaryOperator, isBetweenOperator, filterData, operatorLabels, } from './Table.filterUtils';
|
|
22
22
|
export { useTableColumns, useTableSelection, useTableExpansion, useTablePagination, useTableFilter, } from './hooks';
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useReducedMotion, motion } from 'motion/react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_COLORS = ['#3b82f6', '#8b5cf6', '#ec4899', '#06b6d4'];
|
|
6
|
+
const clamp = (value, min, max) => {
|
|
7
|
+
if (Number.isNaN(value))
|
|
8
|
+
return min;
|
|
9
|
+
return Math.min(Math.max(value, min), max);
|
|
10
|
+
};
|
|
6
11
|
/**
|
|
7
12
|
* Each blob follows a unique organic path using keyframed x/y translations
|
|
8
13
|
* and scale changes. The durations are intentionally varied so the blobs
|
|
@@ -37,16 +42,32 @@ const blobAnimations = [
|
|
|
37
42
|
scale: [1, 0.95, 1.1, 0.9, 1],
|
|
38
43
|
baseDuration: 31,
|
|
39
44
|
},
|
|
45
|
+
{
|
|
46
|
+
// Blob 4 -- slow vertical ribbon sweep
|
|
47
|
+
x: ['0%', '12%', '-18%', '20%', '0%'],
|
|
48
|
+
y: ['0%', '-35%', '-5%', '24%', '0%'],
|
|
49
|
+
scale: [1, 1.25, 0.95, 1.1, 1],
|
|
50
|
+
baseDuration: 29,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
// Blob 5 -- small counter drift to keep dense palettes from syncing
|
|
54
|
+
x: ['0%', '-18%', '10%', '28%', '0%'],
|
|
55
|
+
y: ['0%', '12%', '-22%', '-8%', '0%'],
|
|
56
|
+
scale: [1, 0.88, 1.18, 0.98, 1],
|
|
57
|
+
baseDuration: 34,
|
|
58
|
+
},
|
|
40
59
|
];
|
|
41
60
|
/**
|
|
42
61
|
* Positions each blob at a different quadrant of the container so they
|
|
43
62
|
* begin spread out. Additional blobs beyond 4 cycle through positions.
|
|
44
63
|
*/
|
|
45
64
|
const blobPositions = [
|
|
46
|
-
{ top: '
|
|
47
|
-
{ top: '
|
|
48
|
-
{ bottom: '
|
|
49
|
-
{ bottom: '
|
|
65
|
+
{ top: '8%', left: '12%' },
|
|
66
|
+
{ top: '0%', right: '8%' },
|
|
67
|
+
{ bottom: '8%', left: '18%' },
|
|
68
|
+
{ bottom: '0%', right: '12%' },
|
|
69
|
+
{ top: '34%', left: '36%' },
|
|
70
|
+
{ bottom: '28%', right: '34%' },
|
|
50
71
|
];
|
|
51
72
|
/**
|
|
52
73
|
* Aurora creates an animated multi-color gradient background effect.
|
|
@@ -71,49 +92,67 @@ const blobPositions = [
|
|
|
71
92
|
*/
|
|
72
93
|
const Aurora = ({ colors = DEFAULT_COLORS, blur = 80, speed = 1, opacity = 0.3, size = 1, className, style, ...props }) => {
|
|
73
94
|
const shouldReduceMotion = useReducedMotion();
|
|
95
|
+
const safeColors = colors.length > 0 ? colors : DEFAULT_COLORS;
|
|
96
|
+
const safeBlur = Math.max(0, blur);
|
|
97
|
+
const safeSpeed = Math.max(0.1, speed);
|
|
98
|
+
const safeOpacity = clamp(opacity, 0, 1);
|
|
99
|
+
const safeSize = Math.max(0.1, size);
|
|
74
100
|
// Compute base blob dimension from the size multiplier.
|
|
75
101
|
// Using percentage-based sizing so it scales with the container.
|
|
76
|
-
const blobDimension = `${40 *
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
102
|
+
const blobDimension = `${40 * safeSize}%`;
|
|
103
|
+
const bleed = safeBlur * 1.5;
|
|
104
|
+
const wash = safeColors
|
|
105
|
+
.slice(0, 4)
|
|
106
|
+
.map((color, index) => {
|
|
107
|
+
const stops = [
|
|
108
|
+
'20% 30%',
|
|
109
|
+
'80% 20%',
|
|
110
|
+
'30% 80%',
|
|
111
|
+
'75% 75%',
|
|
112
|
+
];
|
|
113
|
+
return `radial-gradient(circle at ${stops[index]}, ${color}55, transparent 42%)`;
|
|
114
|
+
})
|
|
115
|
+
.join(', ');
|
|
116
|
+
return (jsxs("div", { className: cn('absolute inset-0 overflow-hidden pointer-events-none', className), style: {
|
|
80
117
|
...style,
|
|
81
|
-
}, "aria-hidden": "true", ...props, children:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
width: blobDimension,
|
|
91
|
-
height: blobDimension,
|
|
92
|
-
borderRadius: '50%',
|
|
93
|
-
background: `radial-gradient(circle at center, ${color}, transparent 70%)`,
|
|
94
|
-
mixBlendMode: 'screen',
|
|
95
|
-
} }, index));
|
|
96
|
-
}
|
|
97
|
-
return (jsx(motion.div, { animate: {
|
|
98
|
-
x: anim.x,
|
|
99
|
-
y: anim.y,
|
|
100
|
-
scale: anim.scale,
|
|
101
|
-
}, transition: {
|
|
102
|
-
duration,
|
|
103
|
-
repeat: Infinity,
|
|
104
|
-
repeatType: 'loop',
|
|
105
|
-
ease: 'easeInOut',
|
|
106
|
-
}, style: {
|
|
118
|
+
}, "aria-hidden": "true", ...props, children: [jsx("div", { className: "absolute inset-0", style: {
|
|
119
|
+
background: wash,
|
|
120
|
+
opacity: safeOpacity * 0.65,
|
|
121
|
+
mixBlendMode: 'screen',
|
|
122
|
+
} }), safeColors.map((color, index) => {
|
|
123
|
+
const anim = blobAnimations[index % blobAnimations.length];
|
|
124
|
+
const position = blobPositions[index % blobPositions.length];
|
|
125
|
+
const duration = anim.baseDuration / safeSpeed;
|
|
126
|
+
const blobStyle = {
|
|
107
127
|
position: 'absolute',
|
|
108
128
|
...position,
|
|
109
129
|
width: blobDimension,
|
|
110
130
|
height: blobDimension,
|
|
111
|
-
borderRadius: '
|
|
112
|
-
background: `radial-gradient(circle at center, ${color}, transparent
|
|
131
|
+
borderRadius: '9999px',
|
|
132
|
+
background: `radial-gradient(circle at center, ${color}, transparent 68%)`,
|
|
133
|
+
filter: `blur(${safeBlur}px)`,
|
|
113
134
|
mixBlendMode: 'screen',
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
135
|
+
opacity: safeOpacity,
|
|
136
|
+
margin: -bleed,
|
|
137
|
+
};
|
|
138
|
+
// When reduced motion is preferred, render static blobs with no animation.
|
|
139
|
+
if (shouldReduceMotion) {
|
|
140
|
+
return (jsx("div", { style: blobStyle }, index));
|
|
141
|
+
}
|
|
142
|
+
return (jsx(motion.div, { animate: {
|
|
143
|
+
x: anim.x,
|
|
144
|
+
y: anim.y,
|
|
145
|
+
scale: anim.scale,
|
|
146
|
+
}, transition: {
|
|
147
|
+
duration,
|
|
148
|
+
repeat: Infinity,
|
|
149
|
+
repeatType: 'loop',
|
|
150
|
+
ease: 'easeInOut',
|
|
151
|
+
}, style: {
|
|
152
|
+
...blobStyle,
|
|
153
|
+
willChange: 'transform',
|
|
154
|
+
} }, index));
|
|
155
|
+
})] }));
|
|
117
156
|
};
|
|
118
157
|
|
|
119
158
|
export { Aurora };
|
|
@@ -15,7 +15,7 @@ const sizeMap = {
|
|
|
15
15
|
md: 'w-[320px]',
|
|
16
16
|
lg: 'w-[400px]',
|
|
17
17
|
};
|
|
18
|
-
/** Click reaction states per character */
|
|
18
|
+
/** Click reaction states per abstract character */
|
|
19
19
|
const CLICK_REACTIONS = {
|
|
20
20
|
blob: 'surprised',
|
|
21
21
|
tall: 'love',
|
|
@@ -47,7 +47,7 @@ const InteractiveCharacters = ({ state = 'idle', trackMouse = true, size = 'md',
|
|
|
47
47
|
const smallState = clickedChar === 'small' ? CLICK_REACTIONS.small : state;
|
|
48
48
|
const isTrackingEnabled = trackMouse && state !== 'password' && !shouldReduceMotion;
|
|
49
49
|
const isIdleAnimated = !shouldReduceMotion;
|
|
50
|
-
// Mouse tracking
|
|
50
|
+
// Mouse tracking: each abstract form gets a slightly different response.
|
|
51
51
|
const blobEye = useMouseTracking({
|
|
52
52
|
containerRef: svgRef,
|
|
53
53
|
enabled: isTrackingEnabled,
|
|
@@ -63,7 +63,7 @@ const InteractiveCharacters = ({ state = 'idle', trackMouse = true, size = 'md',
|
|
|
63
63
|
enabled: isTrackingEnabled,
|
|
64
64
|
...springConfigs.small,
|
|
65
65
|
});
|
|
66
|
-
// Idle sway/breathing/head
|
|
66
|
+
// Idle sway/breathing/head movement runs at different rates and phase offsets.
|
|
67
67
|
const blobIdle = useIdleAnimation({
|
|
68
68
|
enabled: isIdleAnimated,
|
|
69
69
|
swayAmount: 1.5,
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useId } from 'react';
|
|
2
3
|
import { useTransform, motion } from 'motion/react';
|
|
3
4
|
import { eyelidVariants, clickTransition, bodyTransition, blobBodyVariants, blobMouthPaths } from '../InteractiveCharacters.animations.js';
|
|
4
5
|
|
|
5
|
-
const LEFT_EYE = { cx: 32, cy: -
|
|
6
|
-
const RIGHT_EYE = { cx:
|
|
6
|
+
const LEFT_EYE = { cx: 32, cy: -58 };
|
|
7
|
+
const RIGHT_EYE = { cx: 62, cy: -58 };
|
|
7
8
|
const CLICK_STATES = new Set(['surprised', 'love', 'excited']);
|
|
8
9
|
const BlobCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
|
|
10
|
+
const id = useId().replace(/:/g, '');
|
|
11
|
+
const leftClipId = `${id}-aura-eye-l`;
|
|
12
|
+
const rightClipId = `${id}-aura-eye-r`;
|
|
13
|
+
const bodyGradientId = `${id}-aura-body`;
|
|
9
14
|
const leftPupilX = useTransform(eyeOffset.x, (v) => LEFT_EYE.cx + v);
|
|
10
15
|
const leftPupilY = useTransform(eyeOffset.y, (v) => LEFT_EYE.cy + v);
|
|
11
16
|
const rightPupilX = useTransform(eyeOffset.x, (v) => RIGHT_EYE.cx + v);
|
|
@@ -24,13 +29,17 @@ const BlobCharacter = ({ state, eyeOffset, idle, blinkAmount, onClick, }) => {
|
|
|
24
29
|
x: idle.sway,
|
|
25
30
|
y: idle.bob,
|
|
26
31
|
scale: idle.breath,
|
|
27
|
-
}, onClick: onClick, children: [jsx("
|
|
32
|
+
}, onClick: onClick, children: [jsx("defs", { children: jsxs("linearGradient", { id: bodyGradientId, x1: "15", y1: "-105", x2: "92", y2: "-5", gradientUnits: "userSpaceOnUse", children: [jsx("stop", { stopColor: "#16B8A6" }), jsx("stop", { offset: "1", stopColor: "#0F766E" })] }) }), jsx(motion.path, { d: "M 19 -99 C 40 -113 78 -101 92 -75 C 108 -45 91 -10 58 -1 C 26 8 -2 -13 -5 -47 C -8 -72 3 -89 19 -99 Z", fill: `url(#${bodyGradientId})`, animate: {
|
|
33
|
+
d: state === 'surprised'
|
|
34
|
+
? 'M 19 -105 C 42 -121 82 -106 97 -78 C 115 -43 94 -6 57 1 C 21 8 -9 -17 -10 -52 C -11 -79 2 -96 19 -105 Z'
|
|
35
|
+
: 'M 19 -99 C 40 -113 78 -101 92 -75 C 108 -45 91 -10 58 -1 C 26 8 -2 -13 -5 -47 C -8 -72 3 -89 19 -99 Z',
|
|
36
|
+
}, transition: transition }), jsx("path", { d: "M 72 -91 C 91 -78 98 -52 88 -30", fill: "none", stroke: "#7DD3C7", strokeWidth: 5, strokeLinecap: "round", opacity: 0.38 }), jsx("ellipse", { cx: 11, cy: -31, rx: 8, ry: 12, fill: "#5EEAD4", opacity: 0.34, transform: "rotate(-24 11 -31)" }), jsxs(motion.g, { style: {
|
|
28
37
|
originX: '50px',
|
|
29
38
|
originY: '-50px',
|
|
30
39
|
rotate: combinedHeadRotate,
|
|
31
40
|
x: combinedHeadX,
|
|
32
41
|
y: combinedHeadY,
|
|
33
|
-
}, children: [jsxs("g", { children: [jsx("clipPath", { id:
|
|
42
|
+
}, children: [jsxs("g", { children: [jsx("clipPath", { id: leftClipId, children: jsx("ellipse", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, rx: 14, ry: 16 }) }), jsx("ellipse", { cx: LEFT_EYE.cx, cy: LEFT_EYE.cy, rx: 12, ry: 14, fill: "#F8FAFC" }), jsx(motion.circle, { cx: leftPupilX, cy: leftPupilY, r: 7, fill: "#122A33", clipPath: `url(#${leftClipId})` }), jsx("circle", { cx: LEFT_EYE.cx + 4, cy: LEFT_EYE.cy - 5.5, r: 3, fill: "white", opacity: 0.95 }), jsx("circle", { cx: LEFT_EYE.cx - 3, cy: LEFT_EYE.cy + 4, r: 1.5, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: LEFT_EYE.cx - 12, y: LEFT_EYE.cy - 14, width: 24, height: 28, fill: "#16B8A6", rx: 3, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsxs("g", { children: [jsx("clipPath", { id: rightClipId, children: jsx("ellipse", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, rx: 14, ry: 16 }) }), jsx("ellipse", { cx: RIGHT_EYE.cx, cy: RIGHT_EYE.cy, rx: 12, ry: 14, fill: "#F8FAFC" }), jsx(motion.circle, { cx: rightPupilX, cy: rightPupilY, r: 7, fill: "#122A33", clipPath: `url(#${rightClipId})` }), jsx("circle", { cx: RIGHT_EYE.cx + 4, cy: RIGHT_EYE.cy - 5.5, r: 3, fill: "white", opacity: 0.95 }), jsx("circle", { cx: RIGHT_EYE.cx - 3, cy: RIGHT_EYE.cy + 4, r: 1.5, fill: "white", opacity: 0.7 }), jsx(motion.rect, { x: RIGHT_EYE.cx - 12, y: RIGHT_EYE.cy - 14, width: 24, height: 28, fill: "#16B8A6", rx: 3, style: { scaleY: eyelidScale, originX: '50%', originY: '0%', transformBox: 'fill-box' } })] }), jsx("ellipse", { cx: 20, cy: -37, rx: 6, ry: 3.5, fill: "#99F6E4", opacity: state === 'surprised' ? 0.72 : 0.45 }), jsx("ellipse", { cx: 76, cy: -36, rx: 6, ry: 3.5, fill: "#99F6E4", opacity: state === 'surprised' ? 0.72 : 0.45 }), jsx(motion.path, { d: blobMouthPaths[state], fill: "none", stroke: "#122A33", strokeWidth: 2.4, strokeLinecap: "round", animate: { d: blobMouthPaths[state] }, transition: transition, transform: "translate(47, -30)" })] })] }));
|
|
34
43
|
};
|
|
35
44
|
|
|
36
45
|
export { BlobCharacter };
|