@flikk/ui 1.0.0-beta.24 → 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.
Files changed (135) hide show
  1. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  2. package/dist/components/ai/PromptInput/PromptInput.js +1 -0
  3. package/dist/components/ai/PromptInput/VoiceRecorder.js +1 -0
  4. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +1 -1
  5. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
  6. package/dist/components/charts/BarChart/BarChart.js +2 -2
  7. package/dist/components/charts/StackedBarChart/StackedBarChart.js +1 -1
  8. package/dist/components/charts/shared/BarRenderer/BarRenderer.js +2 -2
  9. package/dist/components/core/Accordion/Accordion.theme.js +2 -2
  10. package/dist/components/core/Breadcrumbs/Breadcrumbs.js +1 -1
  11. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
  12. package/dist/components/core/Button/Button.js +1 -1
  13. package/dist/components/core/Button/Button.theme.js +4 -4
  14. package/dist/components/core/Calendar/Calendar.js +1 -1
  15. package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +4 -2
  16. package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +2 -2
  17. package/dist/components/core/ContextMenu/ContextMenu.theme.js +1 -1
  18. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  19. package/dist/components/core/Dropdown/Dropdown.theme.js +2 -2
  20. package/dist/components/core/Empty/Empty.js +1 -1
  21. package/dist/components/core/Empty/Empty.theme.js +1 -1
  22. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.d.ts +14 -0
  23. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.js +28 -0
  24. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.d.ts +6 -0
  25. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.js +8 -0
  26. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.types.d.ts +32 -0
  27. package/dist/components/core/ImagePlaceholder/index.d.ts +2 -0
  28. package/dist/components/core/Link/Link.js +1 -1
  29. package/dist/components/core/Message/Message.theme.js +1 -1
  30. package/dist/components/core/Modal/Modal.theme.js +1 -1
  31. package/dist/components/core/NavItem/NavItem.d.ts +1 -1
  32. package/dist/components/core/NavItem/NavItem.js +171 -68
  33. package/dist/components/core/NavItem/NavItem.theme.js +20 -12
  34. package/dist/components/core/NavItem/NavItem.types.d.ts +17 -39
  35. package/dist/components/core/Pagination/Pagination.theme.js +3 -3
  36. package/dist/components/core/Popover/Popover.theme.js +1 -1
  37. package/dist/components/core/Progress/Progress.theme.js +1 -1
  38. package/dist/components/core/ScrollArea/ScrollArea.js +2 -2
  39. package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
  40. package/dist/components/core/Segmented/Segmented.js +8 -6
  41. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  42. package/dist/components/core/Sidebar/Sidebar.d.ts +1 -1
  43. package/dist/components/core/Sidebar/Sidebar.js +42 -13
  44. package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
  45. package/dist/components/core/Sidebar/Sidebar.types.d.ts +33 -4
  46. package/dist/components/core/Sidebar/SidebarContent.js +6 -6
  47. package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
  48. package/dist/components/core/Sidebar/SidebarContext.js +42 -19
  49. package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
  50. package/dist/components/core/Sidebar/SidebarHeader.js +4 -9
  51. package/dist/components/core/Sidebar/SidebarLogo.js +3 -7
  52. package/dist/components/core/Sidebar/SidebarNav.js +4 -3
  53. package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
  54. package/dist/components/core/Sidebar/SidebarToggle.js +6 -1
  55. package/dist/components/core/Sidebar/SidebarUserProfile.js +33 -17
  56. package/dist/components/core/Sidebar/index.d.ts +2 -3
  57. package/dist/components/core/Skeleton/Skeleton.theme.js +1 -1
  58. package/dist/components/core/Spinner/Spinner.theme.js +1 -1
  59. package/dist/components/core/Tabs/Tabs.js +6 -1
  60. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  61. package/dist/components/core/Tabs/TabsBody.js +5 -3
  62. package/dist/components/core/Tag/Tag.theme.js +1 -1
  63. package/dist/components/core/index.d.ts +1 -0
  64. package/dist/components/core/index.js +2 -2
  65. package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
  66. package/dist/components/data-display/KPI/KPI.js +13 -3
  67. package/dist/components/data-display/KPI/KPI.theme.js +3 -3
  68. package/dist/components/data-display/Metric/Metric.theme.js +3 -3
  69. package/dist/components/data-display/Table/Table.js +4 -3
  70. package/dist/components/data-display/Table/Table.theme.js +11 -1
  71. package/dist/components/data-display/Table/Table.types.d.ts +15 -0
  72. package/dist/components/data-display/Table/TableColumnManager.js +1 -1
  73. package/dist/components/data-display/Table/index.d.ts +2 -2
  74. package/dist/components/data-display/Timeline/Timeline.animations.d.ts +0 -7
  75. package/dist/components/data-display/Timeline/Timeline.animations.js +1 -27
  76. package/dist/components/data-display/Timeline/Timeline.js +3 -4
  77. package/dist/components/data-display/Timeline/Timeline.types.d.ts +0 -13
  78. package/dist/components/data-display/Timeline/TimelineContent.js +3 -7
  79. package/dist/components/data-display/Timeline/TimelineItem.js +4 -17
  80. package/dist/components/data-display/Timeline/index.d.ts +2 -2
  81. package/dist/components/effects/Aurora/Aurora.js +79 -40
  82. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +3 -3
  83. package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +13 -4
  84. package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +13 -4
  85. package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +14 -4
  86. package/dist/components/effects/MorphingText/MorphingText.js +40 -7
  87. package/dist/components/effects/StripePattern/StripePattern.d.ts +18 -0
  88. package/dist/components/effects/StripePattern/StripePattern.js +54 -0
  89. package/dist/components/effects/StripePattern/StripePattern.types.d.ts +53 -0
  90. package/dist/components/effects/StripePattern/index.d.ts +2 -0
  91. package/dist/components/effects/index.d.ts +9 -0
  92. package/dist/components/effects/index.js +4 -0
  93. package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
  94. package/dist/components/forms/CronInput/CronInput.theme.js +16 -16
  95. package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
  96. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +1 -1
  97. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
  98. package/dist/components/forms/FileUpload/FileUpload.js +1 -1
  99. package/dist/components/forms/FileUpload/FileUploadProgress.js +27 -20
  100. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
  101. package/dist/components/forms/Input/Input.theme.js +9 -11
  102. package/dist/components/forms/Mention/Mention.theme.js +3 -3
  103. package/dist/components/forms/Radio/Radio.theme.js +1 -1
  104. package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +18 -0
  105. package/dist/components/forms/RichTextEditor/RichTextEditor.js +1231 -236
  106. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +53 -1
  107. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +99 -25
  108. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +42 -3
  109. package/dist/components/forms/RichTextEditor/index.d.ts +2 -2
  110. package/dist/components/forms/Select/Select.js +2 -2
  111. package/dist/components/forms/Slider/Slider.theme.js +1 -1
  112. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  113. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  114. package/dist/components/forms/TimePicker/TimePickerContent.js +1 -0
  115. package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
  116. package/dist/components/forms/forms.theme.js +12 -12
  117. package/dist/components/forms/index.js +1 -0
  118. package/dist/components/layout/FormLayout/FormLayout.js +6 -4
  119. package/dist/components/layout/FormLayout/FormLayout.types.d.ts +3 -3
  120. package/dist/components/layout/FormLayout/FormLayoutSection.js +6 -1
  121. package/dist/components/layout/PageLayout/PageLayout.d.ts +2 -2
  122. package/dist/components/layout/PageLayout/PageLayout.js +23 -5
  123. package/dist/components/layout/PageLayout/PageLayout.theme.d.ts +2 -1
  124. package/dist/components/layout/PageLayout/PageLayout.theme.js +35 -6
  125. package/dist/components/layout/PageLayout/PageLayout.types.d.ts +45 -0
  126. package/dist/components/layout/PageLayout/PageLayoutContent.js +6 -4
  127. package/dist/components/layout/PageLayout/PageLayoutHeader.js +3 -3
  128. package/dist/components/layout/PageLayout/PageLayoutSidebar.js +5 -3
  129. package/dist/components/layout/PageLayout/index.d.ts +1 -1
  130. package/dist/index.js +2 -2
  131. package/dist/styles.css +1 -1
  132. package/package.json +1 -1
  133. package/src/styles/theme.css +127 -92
  134. package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
  135. package/dist/components/core/Sidebar/SidebarSubmenu.js +0 -12
@@ -1,8 +1,60 @@
1
1
  import { RichTextEditorTheme, ColorOption } from "./RichTextEditor.types";
2
2
  /**
3
- * Color options for text color picker
3
+ * Legacy 10-entry color list — kept for backwards compat with consumers that
4
+ * import `colorOptions` directly. The RichTextEditor's color picker now uses
5
+ * the full Tailwind spectrum below.
4
6
  */
5
7
  export declare const colorOptions: ColorOption[];
8
+ /**
9
+ * Tailwind shade scale (same for every family). Used by both the picker
10
+ * UI and the `getTailwindShadeTokens` utility below.
11
+ */
12
+ export declare const TAILWIND_SHADE_LABELS: readonly ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
13
+ /**
14
+ * The 22 Tailwind color families, in spectrum order.
15
+ * Tailwind v4 exposes each shade as `--color-{family}-{shade}` automatically,
16
+ * so we render swatches via `var(--color-...)` instead of hardcoding hex.
17
+ */
18
+ export declare const TAILWIND_COLOR_FAMILIES: readonly ["slate", "gray", "zinc", "neutral", "stone", "red", "orange", "amber", "yellow", "lime", "green", "emerald", "teal", "cyan", "sky", "blue", "indigo", "violet", "purple", "fuchsia", "pink", "rose"];
19
+ /**
20
+ * Returns shade tokens for a given Tailwind family.
21
+ *
22
+ * @example
23
+ * getTailwindShadeTokens("slate")
24
+ * // → ["slate-50", "slate-100", "slate-200", ..., "slate-950"]
25
+ */
26
+ export declare const getTailwindShadeTokens: (family: string) => string[];
27
+ /**
28
+ * Resolves a Tailwind shade token to a CSS color value usable in inline
29
+ * styles for swatch backgrounds. Reads `--color-{token}` directly; modern
30
+ * browsers accept the resulting `oklch(...)` (or fallback `rgb()`) in
31
+ * `background-color` without normalization.
32
+ *
33
+ * SSR-safe: returns `var(--color-{token})` literal when document is absent.
34
+ */
35
+ export declare const resolveTailwindColor: (token: string) => string;
36
+ /**
37
+ * Tailwind v4 only emits `--color-{family}-{shade}` CSS vars for shades that
38
+ * appear as utility classes in source. Since the picker resolves them at
39
+ * runtime via `var(...)`, we need every shade present. This sentinel string
40
+ * lists `bg-{family}-{shade}` for the full 22×11 palette so Tailwind's
41
+ * content scanner emits all CSS variables. Not used at runtime.
42
+ */
43
+ export declare const _TAILWIND_PALETTE_KEEPALIVE: string;
44
+ /**
45
+ * The two non-family base colors users always reach for, shown in their own row
46
+ * above the spectrum.
47
+ *
48
+ * `value` is the resolved background color (used to paint the swatch);
49
+ * `token` is the Tailwind shade fragment used to build the `text-{token}`
50
+ * class applied to the editor content.
51
+ */
52
+ export declare const MONO_COLORS: {
53
+ name: string;
54
+ value: string;
55
+ token: string;
56
+ }[];
57
+ export declare const _RTE_CLASSES_KEEPALIVE: string;
6
58
  /**
7
59
  * Rich Text Editor theme - extends common forms base theme for consistency
8
60
  */
@@ -1,19 +1,87 @@
1
1
  import { formsBaseTheme } from '../forms.theme.js';
2
2
 
3
3
  /**
4
- * Color options for text color picker
4
+ * Tailwind shade scale (same for every family). Used by both the picker
5
+ * UI and the `getTailwindShadeTokens` utility below.
5
6
  */
6
- const colorOptions = [
7
- { label: "Black", value: "#000000", className: "text-black" },
8
- { label: "Gray", value: "#6B7280", className: "text-gray-500" },
9
- { label: "Red", value: "#EF4444", className: "text-red-500" },
10
- { label: "Orange", value: "#F97316", className: "text-orange-500" },
11
- { label: "Yellow", value: "#EAB308", className: "text-yellow-500" },
12
- { label: "Green", value: "#22C55E", className: "text-green-500" },
13
- { label: "Blue", value: "#3B82F6", className: "text-blue-500" },
14
- { label: "Indigo", value: "#6366F1", className: "text-indigo-500" },
15
- { label: "Purple", value: "#A855F7", className: "text-purple-500" },
16
- { label: "Pink", value: "#EC4899", className: "text-pink-500" },
7
+ const TAILWIND_SHADE_LABELS = [
8
+ "50",
9
+ "100",
10
+ "200",
11
+ "300",
12
+ "400",
13
+ "500",
14
+ "600",
15
+ "700",
16
+ "800",
17
+ "900",
18
+ "950",
19
+ ];
20
+ /**
21
+ * The 22 Tailwind color families, in spectrum order.
22
+ * Tailwind v4 exposes each shade as `--color-{family}-{shade}` automatically,
23
+ * so we render swatches via `var(--color-...)` instead of hardcoding hex.
24
+ */
25
+ const TAILWIND_COLOR_FAMILIES = [
26
+ "slate",
27
+ "gray",
28
+ "zinc",
29
+ "neutral",
30
+ "stone",
31
+ "red",
32
+ "orange",
33
+ "amber",
34
+ "yellow",
35
+ "lime",
36
+ "green",
37
+ "emerald",
38
+ "teal",
39
+ "cyan",
40
+ "sky",
41
+ "blue",
42
+ "indigo",
43
+ "violet",
44
+ "purple",
45
+ "fuchsia",
46
+ "pink",
47
+ "rose",
48
+ ];
49
+ /**
50
+ * Returns shade tokens for a given Tailwind family.
51
+ *
52
+ * @example
53
+ * getTailwindShadeTokens("slate")
54
+ * // → ["slate-50", "slate-100", "slate-200", ..., "slate-950"]
55
+ */
56
+ const getTailwindShadeTokens = (family) => TAILWIND_SHADE_LABELS.map((shade) => `${family}-${shade}`);
57
+ /**
58
+ * Resolves a Tailwind shade token to a CSS color value usable in inline
59
+ * styles for swatch backgrounds. Reads `--color-{token}` directly; modern
60
+ * browsers accept the resulting `oklch(...)` (or fallback `rgb()`) in
61
+ * `background-color` without normalization.
62
+ *
63
+ * SSR-safe: returns `var(--color-{token})` literal when document is absent.
64
+ */
65
+ const resolveTailwindColor = (token) => {
66
+ const cssVar = `--color-${token}`;
67
+ if (typeof window === "undefined")
68
+ return `var(${cssVar})`;
69
+ const resolved = getComputedStyle(document.documentElement)
70
+ .getPropertyValue(cssVar)
71
+ .trim();
72
+ return resolved || `var(${cssVar})`;
73
+ };
74
+ /**
75
+ * The two non-family base colors users always reach for, shown in their own row
76
+ * above the spectrum.
77
+ *
78
+ * `value` is the resolved background color (used to paint the swatch);
79
+ * `token` is the Tailwind shade fragment used to build the `text-{token}`
80
+ * class applied to the editor content.
81
+ */
82
+ const MONO_COLORS = [
83
+ { name: "white", value: "#ffffff", token: "white" },
84
+ { name: "black", value: "#000000", token: "black" },
17
85
  ];
18
86
  /**
19
87
  * Rich Text Editor theme - extends common forms base theme for consistency
@@ -25,20 +93,26 @@ const richTextEditorTheme = {
25
93
  containerStyle: `${formsBaseTheme.inputGroupBaseStyle} flex-col min-h-[120px] overflow-hidden`,
26
94
  // Editor content area - extends common control base style
27
95
  editorStyle: `${formsBaseTheme.controlBaseStyle} min-h-[100px] px-3 py-2 resize-none overflow-y-auto [&_p]:my-1 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0 [&_strong]:font-semibold [&_em]:italic [&_u]:underline data-[state=disabled]:cursor-not-allowed empty:before:content-[attr(data-placeholder)] empty:before:text-[var(--color-text-placeholder)] empty:before:pointer-events-none`,
28
- // Toolbar styling (RTE-specific)
29
- toolbarStyle: "flex items-center gap-1 p-2 border-b border-[var(--color-border)] bg-white dark:bg-[var(--color-background-secondary)]",
30
- // Toolbar button styling
31
- toolbarButtonStyle: "inline-flex items-center justify-center size-8 rounded-[calc(var(--radius-base)*0.75)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:bg-white active:bg-[var(--color-background-tertiary)] transition-colors duration-300 disabled:opacity-50 disabled:cursor-not-allowed data-[state=active]:bg-[var(--color-background-tertiary)] data-[state=active]:text-[var(--color-text-primary)] data-[state=active]:hover:bg-[var(--color-primary-50)] border border-transparent data-[pressed=true]:bg-[var(--color-primary-50)] data-[pressed=true]:text-[var(--color-primary-900)] data-[pressed=true]:shadow-inner data-[pressed=true]:border-[var(--color-primary-200)]",
32
- // Dropdown styling (font size, color picker)
33
- dropdownStyle: "fixed z-[1000] min-w-[120px] py-1 bg-white/90 backdrop-blur-md rounded-[var(--form-radius)] shadow-xl ring-1 ring-[var(--color-border)] focus:outline-none",
34
- // Dropdown item styling
35
- dropdownItemStyle: "block w-full px-3 py-2 text-left text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-background-tertiary)] transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed",
96
+ // Toolbar styling (docked variant)
97
+ // `rounded-t-[var(--form-radius)]` matches the container's radius so the
98
+ // toolbar's own bg/backdrop-blur respects the top corners — prevents the
99
+ // 1px bleed past the parent's `overflow-hidden` clip in Safari/Chromium.
100
+ toolbarStyle: "flex items-center gap-1 p-1 rounded-t-[var(--form-radius)] border-b border-[var(--color-border)] dark:bg-[var(--color-background-secondary)]",
101
+ // Toolbar styling (floating variant selection-anchored)
102
+ floatingToolbarStyle: "flex items-center gap-1 p-1 rounded-xl border border-[var(--color-border)] bg-white/90 backdrop-blur-sm shadow-real-xl overflow-hidden dark:bg-[var(--color-background-secondary)]/80",
103
+ // Toolbar button single source of truth via data-[state=active]
104
+ // (aria-pressed remains as proper ARIA on toggle buttons, but styling is decoupled)
105
+ toolbarButtonStyle: "inline-flex items-center justify-center size-8 rounded-[calc(var(--radius-base)*0.85)] border border-transparent bg-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-background-secondary)] transition-colors duration-300 disabled:opacity-50 disabled:cursor-not-allowed data-[state=active]:bg-[var(--color-background-secondary)] data-[state=active]:text-[var(--color-text-primary)] data-[state=active]:border-[var(--color-border)] data-[state=active]:shadow-inner",
106
+ // Dropdown surface (visual only — positioning handled at use site via portal)
107
+ dropdownStyle: "min-w-[120px] max-h-[18rem] overflow-y-auto py-1 bg-white/90 backdrop-blur-md rounded-[var(--form-radius)] shadow-xl ring-1 ring-[var(--color-border)] focus:outline-none",
108
+ // Dropdown item — uses data-[active=true] for selected state
109
+ dropdownItemStyle: "block w-full px-3 py-2 text-left text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed data-[active=true]:bg-[var(--color-primary-50)] data-[active=true]:text-[var(--color-primary-900)]",
36
110
  // Separator between toolbar sections
37
111
  separatorStyle: "w-px h-6 bg-[var(--color-border)] mx-1",
38
- // Color picker grid layout
39
- colorPickerStyle: "flex flex-wrap gap-1 w-[152px]",
40
- // Color swatch button
41
- colorSwatchStyle: "size-6 rounded-[calc(var(--radius-base)*0.75)] cursor-pointer hover:scale-105 transition-transform focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-offset-1",
112
+ // Color picker layout — column of family rows, plus optional Recent + Mono rows
113
+ colorPickerStyle: "flex flex-col gap-1 p-2 w-fit",
114
+ // Color swatch button (original size + rounding)
115
+ colorSwatchStyle: "size-6 rounded-[calc(var(--radius-base)*0.75)] cursor-pointer hover:scale-105 transition-transform focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-offset-1 data-[active=true]:ring-2 data-[active=true]:ring-[var(--color-primary)] data-[active=true]:ring-offset-1 border border-[var(--color-border)]",
42
116
  // Size variants - use common form sizes for consistency
43
117
  sizes: {
44
118
  sm: `${formsBaseTheme.sizes.sm.text} min-h-[80px]`,
@@ -51,4 +125,4 @@ const richTextEditorTheme = {
51
125
  helperText: formsBaseTheme.helperText,
52
126
  };
53
127
 
54
- export { colorOptions, richTextEditorTheme };
128
+ export { MONO_COLORS, TAILWIND_COLOR_FAMILIES, TAILWIND_SHADE_LABELS, getTailwindShadeTokens, resolveTailwindColor, richTextEditorTheme };
@@ -48,6 +48,19 @@ export interface RichTextEditorProps extends Omit<React.HTMLAttributes<HTMLDivEl
48
48
  toolbar?: ToolbarConfig;
49
49
  /** Component-level theme overrides */
50
50
  theme?: RichTextEditorThemeOverrides;
51
+ /**
52
+ * Custom list of font families shown by the `fontFamily` toolbar tool.
53
+ * If omitted, ships with a curated default set (system Default + 8 Google Fonts).
54
+ * Provide your own list when you self-host fonts or want a custom curation.
55
+ */
56
+ fontFamilies?: FontFamilyOption[];
57
+ /**
58
+ * Whether the editor should lazy-load Google Fonts when picked.
59
+ * Defaults to `true`. Set to `false` if you load fonts yourself
60
+ * (e.g. via `next/font` or a `<link>` in your app head) — the picker still
61
+ * applies the font, but no network request is fired.
62
+ */
63
+ loadFontsOnDemand?: boolean;
51
64
  /**
52
65
  * Optional HTML sanitizer function applied before setting innerHTML.
53
66
  * Receives raw HTML string, must return sanitized HTML string.
@@ -78,8 +91,27 @@ export interface ToolbarConfig {
78
91
  /**
79
92
  * Available toolbar tools
80
93
  */
81
- export type ToolbarTool = 'bold' | 'italic' | 'underline' | 'fontSize' | 'textColor' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'undo' | 'redo' | 'separator';
82
- export type RichTextFontSizeKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
94
+ export type ToolbarTool = 'bold' | 'italic' | 'underline' | 'fontWeight' | 'fontFamily' | 'fontSize' | 'lineHeight' | 'letterSpacing' | 'textColor' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'undo' | 'redo' | 'separator';
95
+ /**
96
+ * Font family option used by the RichTextEditor font picker.
97
+ * - `key` is the stable identifier matched against the current selection.
98
+ * - `family` is the canonical Google Fonts family name (used to build the load URL).
99
+ * Omit for system fonts (e.g. "Default" or "Sans") — they will not be lazy-loaded.
100
+ * - `stack` is the CSS font-family value applied to the selection. Always include
101
+ * safe fallbacks so text remains readable while the font is loading.
102
+ * - `weights` is the optional list of weights to request (defaults to all 9).
103
+ */
104
+ export interface FontFamilyOption {
105
+ key: string;
106
+ label: string;
107
+ stack: string;
108
+ family?: string;
109
+ weights?: number[];
110
+ }
111
+ export type RichTextFontSizeKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | '8xl' | '9xl';
112
+ export type RichTextFontWeightKey = 'thin' | 'extralight' | 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | 'extrabold' | 'black';
113
+ export type RichTextLineHeightKey = 'tight' | 'snug' | 'normal' | 'relaxed' | 'loose';
114
+ export type RichTextLetterSpacingKey = 'tighter' | 'tight' | 'normal' | 'wide' | 'wider' | 'widest';
83
115
  /**
84
116
  * Text formatting state
85
117
  */
@@ -89,6 +121,10 @@ export interface FormattingState {
89
121
  underline: boolean;
90
122
  textAlign: 'left' | 'center' | 'right' | 'justify';
91
123
  fontSize: RichTextFontSizeKey;
124
+ fontWeight: RichTextFontWeightKey;
125
+ fontFamily: string;
126
+ lineHeight: RichTextLineHeightKey;
127
+ letterSpacing: RichTextLetterSpacingKey;
92
128
  textColor: string;
93
129
  }
94
130
  /**
@@ -109,8 +145,10 @@ export interface RichTextEditorThemeOverrides {
109
145
  containerStyle?: string;
110
146
  /** ContentEditable element style */
111
147
  editorStyle?: string;
112
- /** Toolbar container style */
148
+ /** Toolbar container style (docked variant) */
113
149
  toolbarStyle?: string;
150
+ /** Floating toolbar container style (selection-anchored variant) */
151
+ floatingToolbarStyle?: string;
114
152
  /** Toolbar button style */
115
153
  toolbarButtonStyle?: string;
116
154
  /** Size variants */
@@ -132,6 +170,7 @@ export interface RichTextEditorTheme {
132
170
  containerStyle: string;
133
171
  editorStyle: string;
134
172
  toolbarStyle: string;
173
+ floatingToolbarStyle: string;
135
174
  toolbarButtonStyle: string;
136
175
  dropdownStyle: string;
137
176
  dropdownItemStyle: string;
@@ -1,3 +1,3 @@
1
- export { RichTextEditor } from './RichTextEditor';
2
- export type { RichTextEditorProps, RichTextEditorSize, RichTextEditorState, ToolbarConfig, ToolbarTool, FormattingState, ColorOption, RichTextEditorTheme, RichTextEditorThemeOverrides, } from './RichTextEditor.types';
1
+ export { RichTextEditor, DEFAULT_FONT_FAMILY_OPTIONS, loadGoogleFont, } from './RichTextEditor';
2
+ export type { RichTextEditorProps, RichTextEditorSize, RichTextEditorState, ToolbarConfig, ToolbarTool, FormattingState, FontFamilyOption, ColorOption, RichTextEditorTheme, RichTextEditorThemeOverrides, } from './RichTextEditor.types';
3
3
  export { richTextEditorTheme, colorOptions } from './RichTextEditor.theme';
@@ -96,7 +96,7 @@ const SelectButton = ({ id, disabled, isOpen, state = "default", size = "md", ic
96
96
  // Use either the themed focus state or directly apply the focus classes if the theme is not working
97
97
  const activeFocusState = isOpen
98
98
  ? focusStateStyle ||
99
- "outline-2 outline-[var(--color-primary)] -outline-offset-1 ring-4 ring-[var(--color-primary)]/10"
99
+ "shadow-[inset_0_0_0_2px_var(--color-primary)] ring-4 ring-[var(--color-primary)]/10"
100
100
  : "";
101
101
  return (jsxs("div", { className: cn(buttonWrapperStyle, "flex items-stretch"), children: [contentStart && (jsx("div", { "data-slot": "content-start", className: cn(contentSlotStyle, "pr-0 border-r border-[var(--color-border)]"), children: contentStart })), jsxs("button", { ref: buttonRef, id: id, type: "button", className: cn(baseStyle, sizeStyle, stateStyle, hoverStateStyle, activeFocusState, iconStartPadding, iconEndPadding, buttonStyle, "flex-1 min-w-0", className), onClick: onClick, onFocus: onFocus, onBlur: onBlur, disabled: disabled, "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": isOpen && id ? `${id}-listbox` : undefined, "aria-labelledby": ariaLabelledby, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, children: [iconStart && jsx("span", { className: iconStartStyle, children: iconStart }), children, iconEnd && jsx("span", { className: iconEndStyle, children: iconEnd })] }), contentEnd && (jsx("div", { "data-slot": "content-end", className: cn(contentSlotStyle, "pl-0 border-l border-[var(--color-border)]"), children: contentEnd }))] }));
102
102
  };
@@ -379,7 +379,7 @@ const SelectInner = ({ id, name, size = "md", state = "default", value, displayV
379
379
  .filter(Boolean);
380
380
  const displayed = selectedOpts.slice(0, maxDisplayedChips);
381
381
  const overflow = selectedOpts.length - maxDisplayedChips;
382
- return (jsxs("div", { className: theme.chipContainerStyle, children: [jsx(AnimatePresence, { mode: "popLayout", children: displayed.map((opt) => (jsx(Tag, { size: "sm", onRemove: () => removeValue(opt.value), className: "shrink-0 max-w-[120px] truncate rounded-md border-none", children: opt.label }, String(opt.id)))) }), overflow > 0 && (jsxs("span", { className: theme.chipOverflowStyle, children: ["+", overflow, " more"] }))] }));
382
+ return (jsxs("div", { className: theme.chipContainerStyle, children: [jsx(AnimatePresence, { mode: "popLayout", children: displayed.map((opt) => (jsx(Tag, { size: "sm", onRemove: () => removeValue(opt.value), className: "shrink-0 max-w-[120px] truncate", children: opt.label }, String(opt.id)))) }), overflow > 0 && (jsxs("span", { className: theme.chipOverflowStyle, children: ["+", overflow, " more"] }))] }));
383
383
  }, [multiple, selectedValues, options, maxDisplayedChips, theme.chipContainerStyle, theme.chipOverflowStyle, removeValue]);
384
384
  const computedDisplayValue = displayValue ||
385
385
  (multiple
@@ -10,7 +10,7 @@ const sliderTheme = {
10
10
  // Root container with layout
11
11
  rootStyle: "relative flex items-center w-full touch-none select-none data-[orientation=vertical]:flex-col data-[orientation=vertical]:h-full",
12
12
  // Track (background)
13
- trackStyle: "relative w-full h-2 grow rounded-full bg-[var(--color-background-tertiary)] overflow-hidden data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[orientation=vertical]:w-2 data-[orientation=vertical]:h-full shadow-inner",
13
+ trackStyle: "relative w-full h-2 grow rounded-full bg-[var(--color-surface-sunken)] overflow-hidden data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[orientation=vertical]:w-2 data-[orientation=vertical]:h-full shadow-inner",
14
14
  // Range (filled portion) - rounded to match track clipping
15
15
  rangeStyle: "absolute h-full bg-gradient-to-r from-[var(--color-primary-500)] to-[var(--color-primary-700)] transition-all rounded-full data-[disabled]:bg-[var(--color-background-disabled)] data-[invalid]:bg-[var(--color-danger)]",
16
16
  // Thumb (draggable handle)
@@ -3,7 +3,7 @@
3
3
  */
4
4
  const switchTheme = {
5
5
  container: "flex w-full justify-between items-center gap-4",
6
- track: "rounded-full cursor-pointer shadow-inner bg-[var(--color-background-tertiary)] data-[state=checked]:bg-[var(--color-primary)] data-[state=disabled]:bg-[var(--color-background-disabled)] data-[state=disabled]:cursor-not-allowed data-[state=invalid]:border-2 data-[state=invalid]:border-[var(--color-danger)] outline outline-[var(--color-border)] transition-colors duration-300",
6
+ track: "rounded-full cursor-pointer shadow-inner bg-[var(--color-surface-sunken)] data-[state=checked]:bg-[var(--color-primary)] data-[state=disabled]:bg-[var(--color-background-disabled)] data-[state=disabled]:cursor-not-allowed data-[state=invalid]:border-2 data-[state=invalid]:border-[var(--color-danger)] outline outline-[var(--color-border)] transition-colors duration-300",
7
7
  thumb: "rounded-full bg-transparent overflow-hidden border border-[var(--color-border)]/40 shadow-lg",
8
8
  description: "text-sm text-[var(--color-text-muted)] -mt-1.5",
9
9
  trackSizes: {
@@ -78,8 +78,8 @@ const timePickerTheme = {
78
78
  "dark:text-[var(--color-neutral-200)] dark:placeholder:text-[var(--color-neutral-500)]",
79
79
  // AM/PM period toggle button
80
80
  periodToggleStyle: "px-1.5 py-0.5 text-xs font-semibold rounded " +
81
- "bg-[var(--color-background-tertiary)] text-[var(--color-text-primary)] " +
82
- "hover:bg-[var(--color-background-quaternary)] transition-colors cursor-pointer select-none " +
81
+ "bg-[var(--color-surface-sunken)] text-[var(--color-text-primary)] " +
82
+ "hover:bg-[var(--color-surface-hover)] transition-colors cursor-pointer select-none " +
83
83
  "dark:bg-[var(--color-neutral-700)] dark:text-[var(--color-neutral-200)] " +
84
84
  "dark:hover:bg-[var(--color-neutral-600)]",
85
85
  };
@@ -92,6 +92,7 @@ import '../../core/ContextMenu/ContextMenu.js';
92
92
  import '../../core/ContextMenu/ContextMenu.theme.js';
93
93
  import '../../core/Pill/Pill.js';
94
94
  import '../../core/Icon/Icon.js';
95
+ import '../../core/ImagePlaceholder/ImagePlaceholder.js';
95
96
  import { useIsClient } from '../../../hooks/useIsClient.js';
96
97
  import { useDarkMode } from '../../../hooks/useDarkMode.js';
97
98
 
@@ -25,7 +25,7 @@ const TimePickerTrigger = ({ id, disabled, isOpen, state = 'default', size = 'md
25
25
  const periodToggleStyle = theme.periodToggleStyle || "";
26
26
  // Apply focused styles when open — same pattern as SelectButton and DatePickerTrigger
27
27
  const activeFocusState = isOpen
28
- ? ((_d = theme.triggerFocusStates) === null || _d === void 0 ? void 0 : _d[state]) || "outline-2 outline-[var(--color-primary-600)] -outline-offset-1 ring-4 ring-[var(--color-primary)]/10 dark:outline-[var(--color-primary-500)] dark:ring-[var(--color-primary-500)]/20"
28
+ ? ((_d = theme.triggerFocusStates) === null || _d === void 0 ? void 0 : _d[state]) || "shadow-[inset_0_0_0_2px_var(--color-primary-600)] ring-4 ring-[var(--color-primary)]/10 dark:shadow-[inset_0_0_0_2px_var(--color-primary-500)] dark:ring-[var(--color-primary-500)]/20"
29
29
  : "";
30
30
  // Determine whether start icon is present (for input padding)
31
31
  const hasStartIcon = iconStart !== undefined ? Boolean(iconStart) : true;
@@ -8,12 +8,12 @@ const formsBaseTheme = {
8
8
  // Common wrapper style
9
9
  wrapperStyle: "relative w-full",
10
10
  // Common input group style with focus, hover, and state variations
11
- inputGroupBaseStyle: "relative flex rounded-[var(--form-radius)] outline outline-1 outline-[var(--color-border)] " +
12
- "focus-within:outline-2 focus-within:outline-[var(--color-primary-600)] focus-within:-outline-offset-1 " +
11
+ inputGroupBaseStyle: "relative flex rounded-[var(--form-radius)] outline-none shadow-[inset_0_0_0_1px_var(--color-border)] " +
12
+ "focus-within:shadow-[inset_0_0_0_2px_var(--color-primary-600)] " +
13
13
  "focus-within:ring-4 focus-within:ring-[var(--color-primary)]/10 bg-white transition-all " +
14
14
  // Dark mode
15
- "dark:bg-[var(--color-neutral-900)] dark:outline-[var(--color-neutral-700)] " +
16
- "dark:focus-within:outline-[var(--color-primary-500)] dark:focus-within:ring-[var(--color-primary-500)]/20",
15
+ "dark:bg-[var(--color-neutral-900)] dark:shadow-[inset_0_0_0_1px_var(--color-neutral-700)] " +
16
+ "dark:focus-within:shadow-[inset_0_0_0_2px_var(--color-primary-500)] dark:focus-within:ring-[var(--color-primary-500)]/20",
17
17
  // Common base input/control style
18
18
  controlBaseStyle: "w-full bg-transparent text-sm text-[var(--color-text-primary)] " +
19
19
  "placeholder:text-[var(--color-text-placeholder)] focus:outline-none " +
@@ -44,7 +44,7 @@ const formsBaseTheme = {
44
44
  // Common state variants
45
45
  states: {
46
46
  default: "",
47
- disabled: "cursor-not-allowed bg-[var(--color-background-disabled)] outline-[var(--color-border)] " +
47
+ disabled: "cursor-not-allowed bg-[var(--color-background-disabled)] shadow-[inset_0_0_0_1px_var(--color-border)] " +
48
48
  "[&_input]:text-[var(--color-text-disabled)] [&_textarea]:text-[var(--color-text-disabled)] " +
49
49
  "[&_select]:text-[var(--color-text-disabled)] [&_input]:cursor-not-allowed " +
50
50
  "[&_textarea]:cursor-not-allowed [&_select]:cursor-not-allowed " +
@@ -52,17 +52,17 @@ const formsBaseTheme = {
52
52
  "[&_textarea]:placeholder:text-[var(--color-text-placeholder)] " +
53
53
  "[&_select]:placeholder:text-[var(--color-text-placeholder)] [&_button]:cursor-not-allowed " +
54
54
  // Dark mode
55
- "dark:bg-[var(--color-neutral-800)] dark:outline-[var(--color-neutral-700)]",
56
- invalid: "outline-[var(--color-danger)] bg-[var(--color-danger-50)] focus-within:outline-[var(--color-danger)] " +
55
+ "dark:bg-[var(--color-neutral-800)] dark:shadow-[inset_0_0_0_1px_var(--color-neutral-700)]",
56
+ invalid: "shadow-[inset_0_0_0_1px_var(--color-danger)] bg-[var(--color-danger-50)] focus-within:shadow-[inset_0_0_0_2px_var(--color-danger)] " +
57
57
  // Dark mode
58
- "dark:bg-[var(--color-danger-950)]/30 dark:outline-[var(--color-danger-500)]",
58
+ "dark:bg-[var(--color-danger-950)]/30 dark:shadow-[inset_0_0_0_1px_var(--color-danger-500)]",
59
59
  },
60
60
  // Common focus state variants
61
61
  focusStates: {
62
- default: "outline-2 outline-[var(--color-primary-600)] -outline-offset-1 ring-4 ring-[var(--color-primary)]/10 " +
63
- "dark:outline-[var(--color-primary-500)] dark:ring-[var(--color-primary-500)]/20",
64
- disabled: "outline-none",
65
- invalid: "outline-2 outline-[var(--color-danger-500)] -outline-offset-1 ring-4 ring-[var(--color-danger-500)]/10 " +
62
+ default: "shadow-[inset_0_0_0_2px_var(--color-primary-600)] ring-4 ring-[var(--color-primary)]/10 " +
63
+ "dark:shadow-[inset_0_0_0_2px_var(--color-primary-500)] dark:ring-[var(--color-primary-500)]/20",
64
+ disabled: "shadow-none",
65
+ invalid: "shadow-[inset_0_0_0_2px_var(--color-danger-500)] ring-4 ring-[var(--color-danger-500)]/10 " +
66
66
  "dark:ring-[var(--color-danger-500)]/20",
67
67
  },
68
68
  // Common hover state variants
@@ -114,6 +114,7 @@ import '../core/ContextMenu/ContextMenu.js';
114
114
  import '../core/ContextMenu/ContextMenu.theme.js';
115
115
  import '../core/Pill/Pill.js';
116
116
  import '../core/Icon/Icon.js';
117
+ import '../core/ImagePlaceholder/ImagePlaceholder.js';
117
118
  import './TimePicker/WheelColumn.js';
118
119
  export { timePickerTheme } from './TimePicker/TimePicker.theme.js';
119
120
  export { Slider } from './Slider/Slider.js';
@@ -77,7 +77,6 @@ import '../../core/Sidebar/SidebarContent.js';
77
77
  import '../../core/Sidebar/SidebarFooter.js';
78
78
  import '../../core/Sidebar/SidebarNav.js';
79
79
  import '../../core/Sidebar/SidebarNavGroup.js';
80
- import 'motion/react';
81
80
  import '../../core/Sidebar/SidebarToggle.js';
82
81
  import '../../core/Sidebar/SidebarMobileTrigger.js';
83
82
  import '../../core/Sidebar/SidebarUserProfile.js';
@@ -88,6 +87,7 @@ import '../../core/ContextMenu/ContextMenu.js';
88
87
  import '../../core/ContextMenu/ContextMenu.theme.js';
89
88
  import '../../core/Pill/Pill.js';
90
89
  import '../../core/Icon/Icon.js';
90
+ import '../../core/ImagePlaceholder/ImagePlaceholder.js';
91
91
  import { formLayoutTheme } from './FormLayout.theme.js';
92
92
  import { FormLayoutSection } from './FormLayoutSection.js';
93
93
  import { FormLayoutFooter } from './FormLayoutFooter.js';
@@ -95,13 +95,15 @@ import { FormLayoutFooter } from './FormLayoutFooter.js';
95
95
  const isFooter = (child) => React__default.isValidElement(child) && child.type === FormLayoutFooter;
96
96
  const FormLayoutRoot = React__default.forwardRef(({ className, theme: customTheme, children, ...props }, ref) => {
97
97
  const theme = { ...formLayoutTheme, ...customTheme };
98
- // Inject <Separator /> between any adjacent children, unless one of them is Footer
99
- // (Footer already has border-t, so no separator needed before it)
98
+ // Inject <Separator /> between any adjacent children, unless one of them is Footer.
100
99
  const childArray = React__default.Children.toArray(children).filter(Boolean);
101
100
  const rendered = childArray.map((child, index) => {
102
101
  const prev = childArray[index - 1];
103
102
  const needsSeparator = index > 0 && !isFooter(child) && !isFooter(prev);
104
- return (jsxs(React__default.Fragment, { children: [needsSeparator && jsx(Separator, {}), child] }, index));
103
+ const childKey = React__default.isValidElement(child) && child.key != null
104
+ ? child.key
105
+ : `form-layout-child-${index}`;
106
+ return (jsxs(React__default.Fragment, { children: [needsSeparator && jsx(Separator, {}), child] }, childKey));
105
107
  });
106
108
  return (jsx("div", { ref: ref, className: cn(theme.root, className), ...props, children: rendered }));
107
109
  });
@@ -25,11 +25,11 @@ export interface FormLayoutProps extends React.HTMLAttributes<HTMLDivElement> {
25
25
  /** Theme overrides for the root container. */
26
26
  theme?: FormLayoutThemeOverrides;
27
27
  }
28
- export interface FormLayoutSectionProps extends React.HTMLAttributes<HTMLElement> {
28
+ export interface FormLayoutSectionProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
29
29
  /** Optional section title. If omitted along with subtitle and action, the sidebar column is hidden. */
30
- title?: string;
30
+ title?: React.ReactNode;
31
31
  /** Optional subtitle displayed below the title. */
32
- subtitle?: string;
32
+ subtitle?: React.ReactNode;
33
33
  /** Optional action element rendered below the subtitle. */
34
34
  action?: React.ReactNode;
35
35
  /** Additional className applied to the content area. */
@@ -4,9 +4,14 @@ import { cn } from '../../../utils/cn.js';
4
4
  import { formLayoutTheme } from './FormLayout.theme.js';
5
5
 
6
6
  const FormLayoutSection = React__default.forwardRef(({ title, subtitle, action, className, contentClassName, theme: customTheme, children, ...props }, ref) => {
7
+ var _a;
7
8
  const theme = { ...formLayoutTheme, ...customTheme };
8
9
  const hasSidebar = !!(title || subtitle || action);
9
- return (jsxs("section", { ref: ref, className: cn(theme.section, className), ...props, children: [hasSidebar && (jsxs("div", { className: theme.sectionSidebar, children: [title && jsx("div", { className: theme.sectionTitle, children: title }), subtitle && (jsx("div", { className: theme.sectionSubtitle, children: subtitle })), action && jsx("div", { className: theme.sectionAction, children: action })] })), jsx("div", { className: cn(hasSidebar ? theme.sectionContent : theme.sectionContentFull, contentClassName), children: children })] }));
10
+ const generatedTitleId = React__default.useId();
11
+ const titleId = title
12
+ ? (_a = props["aria-labelledby"]) !== null && _a !== void 0 ? _a : generatedTitleId
13
+ : props["aria-labelledby"];
14
+ return (jsxs("section", { ref: ref, className: cn(theme.section, className), "aria-labelledby": titleId, ...props, children: [hasSidebar && (jsxs("div", { className: theme.sectionSidebar, children: [title && (jsx("h2", { id: titleId, className: theme.sectionTitle, children: title })), subtitle && (jsx("div", { className: theme.sectionSubtitle, children: subtitle })), action && jsx("div", { className: theme.sectionAction, children: action })] })), jsx("div", { className: cn(hasSidebar ? theme.sectionContent : theme.sectionContentFull, contentClassName), children: children })] }));
10
15
  });
11
16
  FormLayoutSection.displayName = "FormLayout.Section";
12
17
 
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
- import type { PageLayoutProps } from "./PageLayout.types";
2
+ import type { PageLayoutContentProps, PageLayoutProps } from "./PageLayout.types";
3
3
  export declare const PageLayout: React.ForwardRefExoticComponent<PageLayoutProps & React.RefAttributes<HTMLDivElement>> & {
4
4
  Sidebar: React.ForwardRefExoticComponent<import("./PageLayout.types").PageLayoutSidebarProps & React.RefAttributes<HTMLElement>>;
5
5
  Header: React.ForwardRefExoticComponent<import("./PageLayout.types").PageLayoutHeaderProps & React.RefAttributes<HTMLElement>>;
6
- Content: React.ForwardRefExoticComponent<import("./PageLayout.types").PageLayoutContentProps & React.RefAttributes<HTMLElement>>;
6
+ Content: React.ForwardRefExoticComponent<PageLayoutContentProps & React.RefAttributes<HTMLElement>>;
7
7
  };
@@ -1,13 +1,13 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import React__default from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
- import { pageLayoutTheme } from './PageLayout.theme.js';
4
+ import { getPageLayoutTheme } from './PageLayout.theme.js';
5
5
  import { PageLayoutSidebar } from './PageLayoutSidebar.js';
6
6
  import { PageLayoutHeader } from './PageLayoutHeader.js';
7
7
  import { PageLayoutContent } from './PageLayoutContent.js';
8
8
 
9
- const PageLayoutRoot = React__default.forwardRef(({ className, theme: customTheme, children, ...props }, ref) => {
10
- const theme = { ...pageLayoutTheme, ...customTheme };
9
+ const PageLayoutRoot = React__default.forwardRef(({ className, theme: customTheme, variant = "auto", contentWidth = "full", contentPadding = "md", children, ...props }, ref) => {
10
+ const theme = getPageLayoutTheme(customTheme);
11
11
  // Separate Sidebar children from the rest (Header, Content)
12
12
  // so we can wrap non-sidebar children in a vertical main column.
13
13
  const childArray = React__default.Children.toArray(children);
@@ -21,7 +21,25 @@ const PageLayoutRoot = React__default.forwardRef(({ className, theme: customThem
21
21
  main.push(child);
22
22
  }
23
23
  });
24
- return (jsxs("div", { ref: ref, className: cn(theme.root, className), ...props, children: [sidebar, jsx("div", { className: cn(theme.main), children: main })] }));
24
+ const resolvedVariant = variant === "auto"
25
+ ? sidebar.length > 0
26
+ ? "sidebar"
27
+ : "topbar"
28
+ : variant;
29
+ const mainChildren = main.map((child) => {
30
+ var _a, _b;
31
+ if (React__default.isValidElement(child) &&
32
+ child.type === PageLayoutContent) {
33
+ return React__default.cloneElement(child, {
34
+ contentWidth: (_a = child.props.contentWidth) !== null && _a !== void 0 ? _a : contentWidth,
35
+ contentPadding: (_b = child.props.contentPadding) !== null && _b !== void 0 ? _b : contentPadding,
36
+ });
37
+ }
38
+ return child;
39
+ });
40
+ return (jsx("div", { ref: ref, className: cn(theme.root, className), ...props, children: jsxs("div", { className: cn(theme.shell, resolvedVariant === "sidebar"
41
+ ? theme.sidebarLayout
42
+ : theme.topbarLayout), children: [resolvedVariant === "sidebar" && sidebar, jsx("div", { className: cn(theme.main), children: mainChildren })] }) }));
25
43
  });
26
44
  PageLayoutRoot.displayName = "PageLayout";
27
45
  const PageLayout = Object.assign(PageLayoutRoot, {
@@ -1,2 +1,3 @@
1
- import type { PageLayoutTheme } from "./PageLayout.types";
1
+ import type { PageLayoutTheme, PageLayoutThemeOverrides } from "./PageLayout.types";
2
2
  export declare const pageLayoutTheme: PageLayoutTheme;
3
+ export declare const getPageLayoutTheme: (customTheme?: PageLayoutThemeOverrides) => PageLayoutTheme;
@@ -1,10 +1,39 @@
1
1
  const pageLayoutTheme = {
2
- root: "flex h-screen overflow-hidden bg-[var(--color-background)]",
3
- sidebar: "flex flex-col shrink-0 border-r border-[var(--color-border)] bg-[var(--color-background-secondary)] overflow-y-auto",
2
+ root: "min-h-screen overflow-hidden bg-white text-[var(--color-text-primary)]",
3
+ shell: "h-full min-h-[inherit] min-w-0",
4
+ sidebarLayout: "flex",
5
+ topbarLayout: "flex flex-col",
6
+ sidebar: "flex flex-col shrink-0 overflow-hidden border-r border-[var(--color-border)] bg-[var(--color-background)]/95 backdrop-blur supports-[backdrop-filter]:bg-[var(--color-background)]/85",
7
+ sidebarScroll: "min-h-0 flex-1",
4
8
  sidebarCollapsed: "hidden lg:flex",
5
- main: "flex flex-col flex-1 min-w-0 overflow-hidden",
6
- header: "shrink-0 border-b border-[var(--color-border)] bg-[var(--color-background)]",
7
- content: "flex-1 overflow-y-auto",
9
+ main: "flex flex-col flex-1 min-h-0 min-w-0 overflow-hidden",
10
+ header: "shrink-0 border-b border-[var(--color-border)] bg-[var(--color-background)]/90 backdrop-blur supports-[backdrop-filter]:bg-[var(--color-background)]/75",
11
+ content: "flex flex-1 min-h-0 min-w-0",
12
+ contentScroll: "min-h-0 flex-1",
13
+ contentInner: "mx-auto w-full",
14
+ contentPadding: {
15
+ none: "p-0",
16
+ sm: "p-3 sm:p-4",
17
+ md: "p-4 sm:p-6 lg:p-8",
18
+ lg: "p-5 sm:p-8 lg:p-10",
19
+ },
20
+ contentWidth: {
21
+ full: "max-w-none",
22
+ wide: "max-w-7xl",
23
+ contained: "max-w-5xl",
24
+ },
8
25
  };
26
+ const getPageLayoutTheme = (customTheme) => ({
27
+ ...pageLayoutTheme,
28
+ ...customTheme,
29
+ contentPadding: {
30
+ ...pageLayoutTheme.contentPadding,
31
+ ...customTheme === null || customTheme === void 0 ? void 0 : customTheme.contentPadding,
32
+ },
33
+ contentWidth: {
34
+ ...pageLayoutTheme.contentWidth,
35
+ ...customTheme === null || customTheme === void 0 ? void 0 : customTheme.contentWidth,
36
+ },
37
+ });
9
38
 
10
- export { pageLayoutTheme };
39
+ export { getPageLayoutTheme, pageLayoutTheme };