@rimelight/ui 0.0.34 → 0.0.35

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 (185) hide show
  1. package/README.md +1 -0
  2. package/package.json +19 -24
  3. package/src/components/astro/RLAAccordion.astro +94 -18
  4. package/src/components/astro/RLAAvatar.astro +135 -68
  5. package/src/components/astro/RLABadge.astro +4 -2
  6. package/src/components/astro/RLABanner.astro +138 -23
  7. package/src/components/astro/RLABreadcrumb.astro +13 -0
  8. package/src/components/astro/RLAButton.astro +8 -4
  9. package/src/components/astro/RLACallout.astro +112 -0
  10. package/src/components/astro/RLACard.astro +6 -5
  11. package/src/components/astro/RLACarousel.astro +104 -75
  12. package/src/components/astro/RLAChart.astro +4 -1
  13. package/src/components/astro/RLAChip.astro +11 -5
  14. package/src/components/astro/RLACollapsible.astro +4 -1
  15. package/src/components/astro/RLAColorArea.astro +432 -0
  16. package/src/components/astro/RLAColorField.astro +300 -0
  17. package/src/components/astro/RLAColorSlider.astro +413 -0
  18. package/src/components/astro/RLAConfirm.astro +13 -11
  19. package/src/components/astro/RLACopyMarkdown.astro +78 -0
  20. package/src/components/astro/RLADate.astro +30 -0
  21. package/src/components/astro/RLADrawer.astro +50 -3
  22. package/src/components/astro/RLADropdownMenu.astro +78 -51
  23. package/src/components/astro/RLAFieldGroup.astro +4 -1
  24. package/src/components/astro/RLAFileUpload.astro +114 -100
  25. package/src/components/astro/RLAFooter.astro +2 -1
  26. package/src/components/astro/RLAFormField.astro +10 -6
  27. package/src/components/astro/RLAGrid.astro +31 -39
  28. package/src/components/astro/RLAHead.astro +139 -39
  29. package/src/components/astro/RLAHeader.astro +2 -1
  30. package/src/components/astro/RLAImage.astro +274 -251
  31. package/src/components/astro/RLAInput.astro +14 -9
  32. package/src/components/astro/RLAInputMenu.astro +119 -83
  33. package/src/components/astro/RLAKbd.astro +5 -5
  34. package/src/components/astro/RLALayoutGrid.astro +395 -0
  35. package/src/components/astro/RLALink.astro +23 -1
  36. package/src/components/astro/RLALinkGroup.astro +99 -99
  37. package/src/components/astro/RLALocaleSelector.astro +29 -35
  38. package/src/components/astro/RLALogo.astro +117 -90
  39. package/src/components/astro/RLAModal.astro +48 -18
  40. package/src/components/astro/RLANavigationMenu.astro +381 -326
  41. package/src/components/astro/{RLADocsPackageManagers.astro → RLAPackageManagers.astro} +81 -87
  42. package/src/components/astro/RLAPageSection.astro +4 -2
  43. package/src/components/astro/RLAPopover.astro +4 -3
  44. package/src/components/astro/RLAPost.astro +11 -2
  45. package/src/components/astro/RLAScrollArea.astro +4 -1
  46. package/src/components/astro/RLAScrollToTop.astro +200 -172
  47. package/src/components/astro/RLASearch.astro +282 -0
  48. package/src/components/astro/RLASelect.astro +78 -55
  49. package/src/components/astro/RLASeparator.astro +11 -6
  50. package/src/components/astro/RLAShare.astro +60 -0
  51. package/src/components/astro/RLAShortcuts.astro +429 -0
  52. package/src/components/astro/RLASidebar.astro +158 -212
  53. package/src/components/astro/RLASlideover.astro +47 -17
  54. package/src/components/astro/RLASplitter.astro +194 -141
  55. package/src/components/astro/RLASteps.astro +118 -71
  56. package/src/components/astro/RLASurround.astro +39 -0
  57. package/src/components/astro/RLATable.astro +4 -1
  58. package/src/components/astro/RLATableOfContents.astro +109 -23
  59. package/src/components/astro/RLATabs.astro +147 -133
  60. package/src/components/astro/RLATextarea.astro +4 -1
  61. package/src/components/astro/RLAThemeSelector.astro +105 -76
  62. package/src/components/astro/RLAToast.astro +35 -21
  63. package/src/components/vue/RLVBanner.vue +230 -0
  64. package/src/components/vue/RLVButton.vue +3 -2
  65. package/src/components/vue/RLVCheckbox.vue +4 -2
  66. package/src/components/vue/RLVFooter.vue +3 -2
  67. package/src/components/vue/RLVHeader.vue +3 -2
  68. package/src/components/vue/RLVIcon.vue +4 -2
  69. package/src/components/vue/RLVInput.vue +5 -4
  70. package/src/components/vue/RLVLogo.vue +25 -3
  71. package/src/components/vue/RLVPlaceholder.vue +3 -2
  72. package/src/components/vue/RLVPost.vue +8 -2
  73. package/src/components/vue/RLVProgress.vue +60 -0
  74. package/src/components/vue/RLVScrollToTop.vue +65 -52
  75. package/src/components/vue/RLVSection.vue +14 -13
  76. package/src/components/vue/RLVToast.vue +17 -4
  77. package/src/composables/index.ts +1 -0
  78. package/src/composables/useHeaderStore.ts +1 -0
  79. package/src/composables/useShortcuts.ts +11 -0
  80. package/src/config/security.ts +43 -36
  81. package/src/config/site.config.ts +1 -0
  82. package/src/docs/components/ApiChangelog.astro +147 -0
  83. package/src/{plugins/starlightDocsApi → docs}/components/ApiEmits.astro +4 -4
  84. package/src/{plugins/starlightDocsApi → docs}/components/ApiProps.astro +4 -4
  85. package/src/{plugins/starlightDocsApi → docs}/components/ApiSlots.astro +4 -4
  86. package/src/docs/components/ApiTheme.astro +100 -0
  87. package/src/docs/extractAll.ts +94 -0
  88. package/src/docs/extractors/extractAstro.ts +69 -0
  89. package/src/docs/extractors/extractTheme.ts +56 -0
  90. package/src/{plugins/starlightDocsApi/extract-vue.ts → docs/extractors/extractVue.ts} +164 -15
  91. package/src/{plugins/starlightDocsApi → docs}/utils.ts +1 -2
  92. package/src/env.d.ts +0 -12
  93. package/src/integrations/ui.ts +35 -19
  94. package/src/middleware/security.ts +75 -19
  95. package/src/presets/index.ts +118 -293
  96. package/src/styles/index.css +0 -2
  97. package/src/themes/accordion.theme.ts +29 -3
  98. package/src/themes/aspect-ratio.theme.ts +1 -1
  99. package/src/themes/avatar-group.theme.ts +1 -1
  100. package/src/themes/avatar.theme.ts +2 -2
  101. package/src/themes/badge.theme.ts +2 -3
  102. package/src/themes/banner.theme.ts +40 -4
  103. package/src/themes/breadcrumb.theme.ts +1 -1
  104. package/src/themes/button.theme.ts +21 -22
  105. package/src/themes/{alert.theme.ts → callout.theme.ts} +12 -39
  106. package/src/themes/card.theme.ts +17 -9
  107. package/src/themes/carousel.theme.ts +1 -1
  108. package/src/themes/checkbox.theme.ts +1 -2
  109. package/src/themes/chip.theme.ts +2 -3
  110. package/src/themes/color-area.theme.ts +33 -0
  111. package/src/themes/color-field.theme.ts +32 -0
  112. package/src/themes/color-slider.theme.ts +34 -0
  113. package/src/themes/confirm.theme.ts +1 -1
  114. package/src/themes/container.theme.ts +1 -1
  115. package/src/themes/drawer.theme.ts +1 -1
  116. package/src/themes/footer.theme.ts +2 -2
  117. package/src/themes/grid.theme.ts +38 -0
  118. package/src/themes/header.theme.ts +4 -3
  119. package/src/themes/hover-card.theme.ts +1 -1
  120. package/src/themes/image.theme.ts +11 -1
  121. package/src/themes/input.theme.ts +7 -1
  122. package/src/themes/kbd.theme.ts +2 -3
  123. package/src/themes/label.theme.ts +1 -1
  124. package/src/themes/layout-grid.theme.ts +16 -0
  125. package/src/themes/link-group.theme.ts +0 -4
  126. package/src/themes/marquee.theme.ts +1 -1
  127. package/src/themes/modal.theme.ts +1 -1
  128. package/src/themes/navigation-menu.theme.ts +2 -2
  129. package/src/themes/page-section.theme.ts +4 -2
  130. package/src/themes/pagination.theme.ts +1 -1
  131. package/src/themes/pin-input.theme.ts +1 -1
  132. package/src/themes/popover.theme.ts +1 -1
  133. package/src/themes/post.theme.ts +23 -3
  134. package/src/themes/progress.theme.ts +1 -2
  135. package/src/themes/scroll-to-top.theme.ts +2 -3
  136. package/src/themes/search.theme.ts +64 -0
  137. package/src/themes/separator.theme.ts +1 -1
  138. package/src/themes/slideover.theme.ts +1 -1
  139. package/src/themes/slider.theme.ts +1 -2
  140. package/src/themes/spinner.theme.ts +1 -2
  141. package/src/themes/{megamenu.theme.ts → steps.theme.ts} +3 -3
  142. package/src/themes/sticky-surface.theme.ts +1 -1
  143. package/src/themes/switch.theme.ts +1 -2
  144. package/src/themes/table-of-contents.theme.ts +21 -2
  145. package/src/themes/timeline.theme.ts +7 -2
  146. package/src/themes/toast.theme.ts +1 -1
  147. package/src/types/components/accordion.ts +35 -2
  148. package/src/types/components/avatar.ts +16 -0
  149. package/src/types/components/banner.ts +42 -0
  150. package/src/types/components/button.ts +3 -4
  151. package/src/types/components/{alert.ts → callout.ts} +6 -6
  152. package/src/types/components/color-area.ts +19 -0
  153. package/src/types/components/color-field.ts +21 -0
  154. package/src/types/components/color-slider.ts +20 -0
  155. package/src/types/components/grid.ts +23 -0
  156. package/src/types/components/input.ts +4 -0
  157. package/src/types/components/layout-grid.ts +145 -0
  158. package/src/types/components/link-group.ts +4 -2
  159. package/src/types/components/link.ts +15 -0
  160. package/src/types/components/logo.ts +3 -3
  161. package/src/types/components/post.ts +12 -0
  162. package/src/types/components/search.ts +29 -0
  163. package/src/types/components/sidebar.ts +2 -6
  164. package/src/types/components/{megamenu.ts → steps.ts} +7 -1
  165. package/src/types/components/table-of-contents.ts +22 -0
  166. package/src/types/components/toast.ts +4 -0
  167. package/src/types/docs.ts +40 -0
  168. package/src/types/index.ts +151 -1
  169. package/src/types/theme.ts +6 -6
  170. package/src/utils/color.ts +165 -0
  171. package/src/utils/docs.ts +260 -110
  172. package/src/utils/headerStack.ts +24 -4
  173. package/src/utils/index.ts +2 -0
  174. package/src/utils/shortcuts.ts +322 -0
  175. package/src/components/TableOfContents.astro +0 -107
  176. package/src/components/astro/RLAAlert.astro +0 -93
  177. package/src/components/astro/RLAChangelog.astro +0 -23
  178. package/src/components/astro/RLAMegamenu.astro +0 -23
  179. package/src/plugins/index.ts +0 -2
  180. package/src/plugins/starlightDocsApi/components/ApiTheme.astro +0 -27
  181. package/src/plugins/starlightDocsApi/index.ts +0 -272
  182. package/src/styles/prism.css +0 -42
  183. package/src/themes/changelog.theme.ts +0 -13
  184. package/src/types/components/index.ts +0 -134
  185. /package/src/{plugins/starlightDocsApi → docs}/types.ts +0 -0
@@ -4,7 +4,10 @@ import { useUi, resolveClasses } from "../../utils"
4
4
  import type { InputMenuProps } from "../../types"
5
5
  import inputMenuThemeDefault, { createInputMenuTheme } from "../../themes/input-menu.theme"
6
6
 
7
- const inputMenuTheme = inputMenuThemeDefault
7
+ import { getUIConfig } from "virtual:rimelight-ui"
8
+
9
+ const uiConfig = getUIConfig()
10
+ const inputMenuTheme = uiConfig.colors ? createInputMenuTheme() : inputMenuThemeDefault
8
11
  const inputMenu = scv(inputMenuTheme)
9
12
 
10
13
  const {
@@ -35,97 +38,130 @@ const normalizedItems = items.map(item => {
35
38
  const selectedItem = normalizedItems.find(item => item.value === value)
36
39
  const initialLabel = selectedItem ? selectedItem.label : ""
37
40
  ---
38
- <div class={classes.root} data-slot="root" data-disabled={disabled ? "" : undefined}>
39
- {label && <label class={classes.label} data-slot="label">{label}</label>}
40
-
41
- <div class="relative w-full" data-inputMenu-wrapper>
42
- <button
43
- type="button"
44
- class={classes.trigger}
45
- data-slot="trigger"
46
- disabled={disabled}
47
- data-inputMenu-trigger
48
- {...rest}
49
- >
50
- <span data-inputMenu-value>{initialLabel || placeholder}</span>
51
- <span class="i-lucide-chevrons-up-down opacity-50" />
52
- </button>
53
-
54
- <div class={classes.content + " hidden"} data-inputMenu-dropdown data-slot="content">
55
- <input
56
- type="text"
57
- class={classes.input}
58
- placeholder="Search..."
59
- data-inputMenu-search
60
- />
61
- <div class="py-1" data-inputMenu-items>
62
- {normalizedItems.map(item => (
63
- <div
64
- class={classes.item}
65
- data-slot="item"
66
- data-value={item.value}
67
- data-label={item.label}
68
- data-inputMenu-item
69
- >
70
- <span class={classes.itemText}>{item.label}</span>
71
- <span class={classes.itemIcon + " hidden i-lucide-check"} data-check-icon />
72
- </div>
73
- ))}
41
+ <rla-input-menu {...rest}>
42
+ <div class={classes.root} data-slot="root" data-disabled={disabled ? "" : undefined}>
43
+ {label && <label class={classes.label} data-slot="label">{label}</label>}
44
+
45
+ <div class="relative w-full" data-inputMenu-wrapper>
46
+ <button
47
+ type="button"
48
+ class={classes.trigger}
49
+ data-slot="trigger"
50
+ disabled={disabled}
51
+ data-inputMenu-trigger
52
+ >
53
+ <span data-inputMenu-value>{initialLabel || placeholder}</span>
54
+ <span class="i-lucide-chevrons-up-down opacity-50" />
55
+ </button>
56
+
57
+ <div class={classes.content + " hidden"} data-inputMenu-dropdown data-slot="content">
58
+ <input
59
+ type="text"
60
+ class={classes.input}
61
+ placeholder="Search..."
62
+ data-inputMenu-search
63
+ />
64
+ <div class="py-1" data-inputMenu-items>
65
+ {normalizedItems.map(item => (
66
+ <div
67
+ class={classes.item}
68
+ data-slot="item"
69
+ data-value={item.value}
70
+ data-label={item.label}
71
+ data-inputMenu-item
72
+ >
73
+ <span class={classes.itemText}>{item.label}</span>
74
+ <span class={classes.itemIcon + " hidden i-lucide-check"} data-check-icon />
75
+ </div>
76
+ ))}
77
+ </div>
74
78
  </div>
75
79
  </div>
80
+ <input type="hidden" name={name} value={value} data-inputMenu-hidden-input />
76
81
  </div>
77
- <input type="hidden" name={name} value={value} data-inputMenu-hidden-input />
78
- </div>
82
+ </rla-input-menu>
79
83
 
80
84
  <script>
81
- document.querySelectorAll("[data-inputMenu-wrapper]").forEach(wrapper => {
82
- const trigger = wrapper.querySelector("[data-inputMenu-trigger]") as HTMLButtonElement;
83
- const dropdown = wrapper.querySelector("[data-inputMenu-dropdown]") as HTMLDivElement;
84
- const search = wrapper.querySelector("[data-inputMenu-search]") as HTMLInputElement;
85
- const items = wrapper.querySelectorAll("[data-inputMenu-item]");
86
- const valueSpan = wrapper.querySelector("[data-inputMenu-value]");
87
- const hiddenInput = wrapper.closest("[data-slot=root]")?.querySelector("[data-inputMenu-hidden-input]") as HTMLInputElement;
88
-
89
- if (!trigger || !dropdown) return;
90
-
91
- trigger.addEventListener("click", (e) => {
92
- e.stopPropagation();
93
- dropdown.classList.toggle("hidden");
94
- if (!dropdown.classList.contains("hidden")) {
95
- search?.focus();
96
- }
97
- });
85
+ class RLAInputMenu extends HTMLElement {
86
+ private inputMenuCleanup: (() => void) | null = null;
98
87
 
99
- search?.addEventListener("input", (e) => {
100
- const q = (e.target as HTMLInputElement).value.toLowerCase();
101
- items.forEach(item => {
102
- const label = (item as HTMLElement).dataset.label?.toLowerCase() || "";
103
- if (label.includes(q)) {
104
- (item as HTMLElement).classList.remove("hidden");
105
- } else {
106
- (item as HTMLElement).classList.add("hidden");
88
+ connectedCallback() {
89
+ this.style.display = 'contents';
90
+ const wrapper = this.querySelector("[data-inputMenu-wrapper]") as HTMLElement | null;
91
+ if (!wrapper) return;
92
+
93
+ const trigger = wrapper.querySelector("[data-inputMenu-trigger]") as HTMLButtonElement;
94
+ const dropdown = wrapper.querySelector("[data-inputMenu-dropdown]") as HTMLDivElement;
95
+ const search = wrapper.querySelector("[data-inputMenu-search]") as HTMLInputElement;
96
+ const items = wrapper.querySelectorAll<HTMLElement>("[data-inputMenu-item]");
97
+ const valueSpan = wrapper.querySelector("[data-inputMenu-value]");
98
+ const hiddenInput = this.querySelector("[data-inputMenu-hidden-input]") as HTMLInputElement;
99
+
100
+ if (!trigger || !dropdown) return;
101
+
102
+ const onTriggerClick = (e: MouseEvent) => {
103
+ e.stopPropagation();
104
+ dropdown.classList.toggle("hidden");
105
+ if (!dropdown.classList.contains("hidden")) {
106
+ search?.focus();
107
107
  }
108
+ };
109
+ trigger.addEventListener("click", onTriggerClick);
110
+
111
+ const onSearchInput = (e: Event) => {
112
+ const q = (e.target as HTMLInputElement).value.toLowerCase();
113
+ items.forEach(item => {
114
+ const label = (item as HTMLElement).dataset.label?.toLowerCase() || "";
115
+ if (label.includes(q)) {
116
+ (item as HTMLElement).classList.remove("hidden");
117
+ } else {
118
+ (item as HTMLElement).classList.add("hidden");
119
+ }
120
+ });
121
+ };
122
+ search?.addEventListener("input", onSearchInput);
123
+
124
+ const itemCleanups: (() => void)[] = [];
125
+ items.forEach(item => {
126
+ const onItemClick = (e: MouseEvent) => {
127
+ e.stopPropagation();
128
+ const val = (item as HTMLElement).dataset.value || "";
129
+ const label = (item as HTMLElement).dataset.label || "";
130
+
131
+ if (hiddenInput) hiddenInput.value = val;
132
+ if (valueSpan) valueSpan.textContent = label;
133
+
134
+ items.forEach(i => i.querySelector("[data-check-icon]")?.classList.add("hidden"));
135
+ item.querySelector("[data-check-icon]")?.classList.remove("hidden");
136
+
137
+ dropdown.classList.add("hidden");
138
+ };
139
+ item.addEventListener("click", onItemClick);
140
+ itemCleanups.push(() => item.removeEventListener("click", onItemClick));
108
141
  });
109
- });
110
142
 
111
- items.forEach(item => {
112
- item.addEventListener("click", (e) => {
113
- e.stopPropagation();
114
- const val = (item as HTMLElement).dataset.value || "";
115
- const label = (item as HTMLElement).dataset.label || "";
116
-
117
- if (hiddenInput) hiddenInput.value = val;
118
- if (valueSpan) valueSpan.textContent = label;
119
-
120
- items.forEach(i => i.querySelector("[data-check-icon]")?.classList.add("hidden"));
121
- item.querySelector("[data-check-icon]")?.classList.remove("hidden");
122
-
143
+ const onDocumentClick = () => {
123
144
  dropdown.classList.add("hidden");
124
- });
125
- });
145
+ };
146
+ document.addEventListener("click", onDocumentClick);
147
+
148
+ this.inputMenuCleanup = () => {
149
+ trigger.removeEventListener("click", onTriggerClick);
150
+ search?.removeEventListener("input", onSearchInput);
151
+ itemCleanups.forEach(fn => fn());
152
+ document.removeEventListener("click", onDocumentClick);
153
+ };
154
+ }
126
155
 
127
- document.addEventListener("click", () => {
128
- dropdown.classList.add("hidden");
129
- });
130
- });
156
+ disconnectedCallback() {
157
+ if (this.inputMenuCleanup) {
158
+ this.inputMenuCleanup();
159
+ this.inputMenuCleanup = null;
160
+ }
161
+ }
162
+ }
163
+
164
+ if (!customElements.get('rla-input-menu')) {
165
+ customElements.define('rla-input-menu', RLAInputMenu);
166
+ }
131
167
  </script>
@@ -6,7 +6,7 @@ import kbdThemeDefault, { createKbdTheme } from "../../themes/kbd.theme"
6
6
  import { getUIConfig } from "virtual:rimelight-ui"
7
7
 
8
8
  const uiConfig = getUIConfig()
9
- const kbdTheme = uiConfig.colors ? createKbdTheme() : kbdThemeDefault
9
+ const kbdTheme = uiConfig.colors ? createKbdTheme(uiConfig.colors) : kbdThemeDefault
10
10
 
11
11
  const kbd = scv(kbdTheme)
12
12
 
@@ -18,13 +18,13 @@ const {
18
18
  ui: uiProp,
19
19
  class: className,
20
20
  ...rest
21
- } = Astro.props as KbdProps
21
+ } = Astro.props as KbdProps & { color?: string; variant?: string; theme?: string }
22
22
 
23
23
  const classes = resolveClasses(kbd, {
24
24
  size,
25
- color,
26
- variant,
27
- theme
25
+ color: color as "neutral",
26
+ variant: variant as "solid",
27
+ theme: theme as "dark"
28
28
  }, useUi("kbd", uiProp), className)
29
29
  ---
30
30
  <kbd class={classes.root} data-slot="root" {...rest}>
@@ -0,0 +1,395 @@
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { LayoutGridProps } from "../../types"
5
+ import layoutGridThemeDefault, { createLayoutGridTheme } from "../../themes/layout-grid.theme"
6
+ import { getUIConfig } from "virtual:rimelight-ui"
7
+
8
+ const uiConfig = getUIConfig()
9
+ const layoutGridTheme = uiConfig.colors ? createLayoutGridTheme() : layoutGridThemeDefault
10
+ const layoutGrid = scv(layoutGridTheme)
11
+
12
+ const {
13
+ cols = 4,
14
+ smCols = 4,
15
+ mdCols = 8,
16
+ lgCols = 12,
17
+ xlCols = 12,
18
+ smBreakpoint = 640,
19
+ mdBreakpoint = 768,
20
+ lgBreakpoint = 1024,
21
+ xlBreakpoint = 1280,
22
+ gutter = [0.75, 1, 1.5, 1.5, 1.5],
23
+ margin = [0.75, 1, 1.5, 1.5, 1.5],
24
+ gridColor = "#ff0000",
25
+ gridOpacity = 0.1,
26
+ maxWidth = "1440px",
27
+ innerMaxWidth = "768px",
28
+ innerGridColor = "#10b981",
29
+ innerGridOpacity = 0.1,
30
+ showInnerBackground = true,
31
+ showBackground = true,
32
+ showFullWidth = true,
33
+ fullWidthGridColor = "#3b82f6",
34
+ fullWidthGridOpacity = 0.05,
35
+ showFullWidthBackground = true,
36
+ zIndex = 1000,
37
+ shortcut = "meta_shift_g",
38
+ devOnly = true,
39
+ ui: uiProp,
40
+ class: className,
41
+ ...rest
42
+ } = Astro.props as LayoutGridProps
43
+
44
+ const isDev = import.meta.env.DEV
45
+ const shouldRender = !devOnly || isDev
46
+
47
+ const normalizeArray = (val: number | number[] | undefined, defaultVal: number): number[] => {
48
+ if (val === undefined) return [defaultVal, defaultVal, defaultVal, defaultVal, defaultVal]
49
+ if (!Array.isArray(val)) return [val, val, val, val, val]
50
+ const arr = [...val]
51
+ while (arr.length < 5) {
52
+ const lastVal = arr[arr.length - 1]
53
+ arr.push(lastVal !== undefined ? lastVal : defaultVal)
54
+ }
55
+ return arr
56
+ }
57
+
58
+ const gutterArray = normalizeArray(gutter, 1)
59
+ const marginArray = normalizeArray(margin, 1)
60
+
61
+ const classes = resolveClasses(layoutGrid, {}, useUi("layoutGrid", uiProp), className)
62
+ ---
63
+
64
+ {shouldRender && (
65
+ <rla-layout-grid
66
+ class={classes.root}
67
+ data-cols={cols}
68
+ data-sm-cols={smCols}
69
+ data-md-cols={mdCols}
70
+ data-lg-cols={lgCols}
71
+ data-xl-cols={xlCols}
72
+ data-sm-breakpoint={smBreakpoint}
73
+ data-md-breakpoint={mdBreakpoint}
74
+ data-lg-breakpoint={lgBreakpoint}
75
+ data-xl-breakpoint={xlBreakpoint}
76
+ data-gutter={JSON.stringify(gutterArray)}
77
+ data-margin={JSON.stringify(marginArray)}
78
+ data-grid-color={gridColor}
79
+ data-grid-opacity={gridOpacity}
80
+ data-show-background={showBackground ? "true" : "false"}
81
+ data-max-width={maxWidth}
82
+ data-inner-max-width={innerMaxWidth}
83
+ data-inner-color={innerGridColor}
84
+ data-inner-opacity={innerGridOpacity}
85
+ data-show-inner-background={showInnerBackground ? "true" : "false"}
86
+ data-show-full-width={showFullWidth ? "true" : "false"}
87
+ data-full-width-color={fullWidthGridColor}
88
+ data-full-width-opacity={fullWidthGridOpacity}
89
+ data-show-full-width-background={showFullWidthBackground ? "true" : "false"}
90
+ data-shortcut={shortcut}
91
+ {...rest}
92
+ >
93
+ <aside class={classes.overlay} data-slot="overlay" style={`z-index: ${zIndex}; display: none;`} id="rla-layout-grid-overlay">
94
+ <!-- Container holding absolutely-positioned viewport columns -->
95
+ <div style="position: relative; width: 100%; height: 100%;" data-slot="columns-container"></div>
96
+ <div data-slot="indicator" style="position: fixed; bottom: 20px; right: 20px; background: rgba(0,0,0,0.85); color: #fff; padding: 6px 12px; border-radius: 6px; font-family: var(--font-mono, monospace); font-size: 11px; font-weight: bold; pointer-events: none; border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 4px 12px rgba(0,0,0,0.5); letter-spacing: 0.05em; display: flex; align-items: center; gap: 8px;"></div>
97
+ </aside>
98
+ </rla-layout-grid>
99
+ )}
100
+
101
+ <script>
102
+ import { defineShortcuts } from "../../utils/shortcuts"
103
+
104
+ function hexToRgba(hex: string, alpha: number): string {
105
+ const cleanHex = hex.replace('#', '')
106
+ const num = parseInt(cleanHex, 16)
107
+ const r = (num >> 16) & 255
108
+ const g = (num >> 8) & 255
109
+ const b = num & 255
110
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`
111
+ }
112
+
113
+ class RLALayoutGrid extends HTMLElement {
114
+ private isVisible = false
115
+ private gridElement: HTMLElement | null = null
116
+ private columnsContainer: HTMLElement | null = null
117
+ private indicatorElement: HTMLElement | null = null
118
+ private resizeObserver: ResizeObserver | null = null
119
+ private shortcutsCleanup: (() => void) | null = null
120
+ private currentBreakpoint: 'base' | 'sm' | 'md' | 'lg' | 'xl' = 'base'
121
+
122
+ constructor() {
123
+ super()
124
+ this.handleResize = this.handleResize.bind(this)
125
+ }
126
+
127
+ connectedCallback() {
128
+ this.style.display = 'contents'
129
+ this.gridElement = this.querySelector('[data-slot="overlay"], #rla-layout-grid-overlay') as HTMLElement
130
+ this.columnsContainer = this.querySelector('[data-slot="columns-container"]') as HTMLElement
131
+ this.indicatorElement = this.querySelector('[data-slot="indicator"]') as HTMLElement
132
+
133
+ // Restore visibility state from localStorage
134
+ this.isVisible = localStorage.getItem('rla-grid-visible') === 'true'
135
+ if (this.gridElement) {
136
+ this.gridElement.style.display = this.isVisible ? 'block' : 'none'
137
+ }
138
+
139
+ // Initialize ResizeObserver to monitor viewport changes
140
+ this.resizeObserver = new ResizeObserver(this.handleResize)
141
+ this.resizeObserver.observe(document.documentElement)
142
+
143
+ // Initialize grid
144
+ this.updateGrid()
145
+
146
+ // Set up shortcuts using the library system
147
+ const shortcutKey = this.dataset.shortcut || 'meta_shift_g'
148
+ this.shortcutsCleanup = defineShortcuts({
149
+ [shortcutKey]: {
150
+ handler: (e) => {
151
+ e.preventDefault()
152
+ this.toggleGrid()
153
+ },
154
+ label: "Toggle Layout Grid",
155
+ description: "Show or hide the development layout grid guide overlay",
156
+ category: "development"
157
+ }
158
+ })
159
+ }
160
+
161
+ disconnectedCallback() {
162
+ this.resizeObserver?.disconnect()
163
+ if (this.shortcutsCleanup) {
164
+ this.shortcutsCleanup()
165
+ this.shortcutsCleanup = null
166
+ }
167
+ }
168
+
169
+ private handleResize() {
170
+ const width = document.documentElement.clientWidth
171
+ const smBreakpoint = parseInt(this.dataset.smBreakpoint || '640')
172
+ const mdBreakpoint = parseInt(this.dataset.mdBreakpoint || '768')
173
+ const lgBreakpoint = parseInt(this.dataset.lgBreakpoint || '1024')
174
+ const xlBreakpoint = parseInt(this.dataset.xlBreakpoint || '1280')
175
+
176
+ let newBreakpoint: 'base' | 'sm' | 'md' | 'lg' | 'xl' = 'base'
177
+
178
+ if (width >= xlBreakpoint) {
179
+ newBreakpoint = 'xl'
180
+ } else if (width >= lgBreakpoint) {
181
+ newBreakpoint = 'lg'
182
+ } else if (width >= mdBreakpoint) {
183
+ newBreakpoint = 'md'
184
+ } else if (width >= smBreakpoint) {
185
+ newBreakpoint = 'sm'
186
+ }
187
+
188
+ this.currentBreakpoint = newBreakpoint
189
+ this.updateGrid()
190
+ }
191
+
192
+ private updateGrid() {
193
+ if (!this.columnsContainer) return
194
+
195
+ const width = document.documentElement.clientWidth
196
+ const smBreakpoint = parseInt(this.dataset.smBreakpoint || '640')
197
+ const mdBreakpoint = parseInt(this.dataset.mdBreakpoint || '768')
198
+ const lgBreakpoint = parseInt(this.dataset.lgBreakpoint || '1024')
199
+ const xlBreakpoint = parseInt(this.dataset.xlBreakpoint || '1280')
200
+
201
+ let breakpointIndex = 0
202
+ let colCount = parseInt(this.dataset.cols || '4')
203
+ let breakpointThreshold = 0
204
+
205
+ switch (this.currentBreakpoint) {
206
+ case 'xl':
207
+ breakpointIndex = 4
208
+ colCount = parseInt(this.dataset.xlCols || '12')
209
+ breakpointThreshold = xlBreakpoint
210
+ break
211
+ case 'lg':
212
+ breakpointIndex = 3
213
+ colCount = parseInt(this.dataset.lgCols || '12')
214
+ breakpointThreshold = lgBreakpoint
215
+ break
216
+ case 'md':
217
+ breakpointIndex = 2
218
+ colCount = parseInt(this.dataset.mdCols || '8')
219
+ breakpointThreshold = mdBreakpoint
220
+ break
221
+ case 'sm':
222
+ breakpointIndex = 1
223
+ colCount = parseInt(this.dataset.smCols || '4')
224
+ breakpointThreshold = smBreakpoint
225
+ break
226
+ default:
227
+ breakpointIndex = 0
228
+ colCount = parseInt(this.dataset.cols || '4')
229
+ breakpointThreshold = 0
230
+ break
231
+ }
232
+
233
+ const gutterArray = JSON.parse(this.dataset.gutter || '[1,1,1,1,1]')
234
+ const marginArray = JSON.parse(this.dataset.margin || '[1,1,1,1,1]')
235
+
236
+ const gutterValue = gutterArray[breakpointIndex] ?? gutterArray[gutterArray.length - 1]
237
+ const marginValue = marginArray[breakpointIndex] ?? marginArray[marginArray.length - 1]
238
+
239
+ const gridColor = this.dataset.gridColor || '#ff0000'
240
+ const gridOpacity = parseFloat(this.dataset.gridOpacity || '0.1')
241
+ const showBackground = this.dataset.showBackground === 'true'
242
+
243
+ const showFullWidth = this.dataset.showFullWidth !== 'false'
244
+ const fullWidthColor = this.dataset.fullWidthColor || '#3b82f6'
245
+ const fullWidthOpacity = parseFloat(this.dataset.fullWidthOpacity || '0.05')
246
+ const showFullWidthBackground = this.dataset.showFullWidthBackground === 'true'
247
+
248
+ // Convert rem spacings to pixels dynamically based on root font size
249
+ const rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16
250
+ const marginPx = marginValue * rootFontSize
251
+ const gutterPx = gutterValue * rootFontSize
252
+
253
+ // Calculate max width boundaries
254
+ const maxWidthVal = this.dataset.maxWidth || '1440px'
255
+ let maxWidthPx = width
256
+ if (!maxWidthVal.includes('vw')) {
257
+ maxWidthPx = parseInt(maxWidthVal)
258
+ }
259
+
260
+ const innerMaxWidthVal = this.dataset.innerMaxWidth || '768px'
261
+ let innerMaxWidthPx = width
262
+ if (!innerMaxWidthVal.includes('vw')) {
263
+ innerMaxWidthPx = parseInt(innerMaxWidthVal)
264
+ }
265
+ const innerGridColor = this.dataset.innerColor || '#10b981'
266
+ const innerGridOpacity = parseFloat(this.dataset.innerOpacity || '0.1')
267
+ const showInnerBackground = this.dataset.showInnerBackground === 'true'
268
+
269
+ // Calculate grid properties based on container width or screen width
270
+ const activeWidth = width > maxWidthPx ? maxWidthPx : width
271
+ const innerWidth = activeWidth - 2 * marginPx
272
+ const colWidth = (innerWidth - (colCount - 1) * gutterPx) / colCount
273
+
274
+ // Center container boundaries (where we switch colors)
275
+ const containerLeft = (width - maxWidthPx) / 2
276
+ const containerRight = (width + maxWidthPx) / 2
277
+
278
+ // Clear container
279
+ this.columnsContainer.innerHTML = ''
280
+
281
+ // Set CSS custom properties on the host element and document root
282
+ this.style.setProperty('--rla-grid-cols', colCount.toString())
283
+ this.style.setProperty('--rla-grid-gap', `${gutterPx}px`)
284
+ this.style.setProperty('--rla-grid-margin', `${marginPx}px`)
285
+ this.style.setProperty('--rla-grid-max-width', maxWidthVal)
286
+
287
+ document.documentElement.style.setProperty('--rla-grid-cols', colCount.toString())
288
+ document.documentElement.style.setProperty('--rla-grid-gap', `${gutterPx}px`)
289
+ document.documentElement.style.setProperty('--rla-grid-gutter', `${gutterPx}px`)
290
+ document.documentElement.style.setProperty('--rla-grid-col-width', `${colWidth}px`)
291
+ document.documentElement.style.setProperty('--rla-grid-margin', `${marginPx}px`)
292
+ document.documentElement.style.setProperty('--rla-grid-max-width', maxWidthVal)
293
+
294
+ const containerStartX = width > maxWidthPx ? containerLeft + marginPx : marginPx
295
+ const step = colWidth + gutterPx
296
+
297
+ // Calculate total columns including outer ones
298
+ let totalCols = colCount
299
+ if (width > maxWidthPx && showFullWidth) {
300
+ let leftX = marginPx
301
+ while (leftX + colWidth < containerLeft) {
302
+ totalCols++
303
+ leftX += step
304
+ }
305
+ let rightX = width - marginPx - colWidth
306
+ while (rightX > containerRight) {
307
+ totalCols++
308
+ rightX -= step
309
+ }
310
+ }
311
+
312
+ if (this.indicatorElement) {
313
+ this.indicatorElement.innerHTML = `<span style="color: #3b82f6;">${this.currentBreakpoint.toUpperCase()} (${breakpointThreshold}px)</span> <span style="opacity: 0.35;">|</span> <span>${width}px</span> <span style="opacity: 0.35;">|</span> <span style="color: #ef4444;">${totalCols} Cols</span>`
314
+ }
315
+
316
+
317
+
318
+ // Create centered CSS Grid container using the exact same classes as the page container
319
+ const gridWrapper = document.createElement('div')
320
+ gridWrapper.className = "mx-auto w-full max-w-[1440px] px-xs sm:px-md md:px-xl lg:px-xl gap-xs sm:gap-md md:gap-xl lg:gap-xl h-full box-border grid"
321
+ gridWrapper.style.gridTemplateColumns = 'repeat(var(--rla-grid-cols), 1fr)'
322
+
323
+ const innerLeft = (width - innerMaxWidthPx) / 2
324
+ const innerRight = (width + innerMaxWidthPx) / 2
325
+
326
+ // Add container columns
327
+ for (let i = 0; i < colCount; i++) {
328
+ const colX = containerStartX + i * step
329
+ const colRight = colX + colWidth
330
+ const isInner = (colX >= innerLeft - 1) && (colRight <= innerRight + 1)
331
+
332
+ const col = document.createElement('div')
333
+ col.className = 'grid-column'
334
+ const color = isInner ? innerGridColor : gridColor
335
+ const opacity = isInner ? innerGridOpacity : gridOpacity
336
+ const bg = isInner ? showInnerBackground : showBackground
337
+
338
+ Object.assign(col.style, {
339
+ height: '100%',
340
+ borderInline: `1px solid ${color}`,
341
+ boxSizing: 'border-box',
342
+ backgroundColor: bg ? hexToRgba(color, opacity) : 'transparent'
343
+ })
344
+ gridWrapper.appendChild(col)
345
+ }
346
+
347
+ this.columnsContainer.appendChild(gridWrapper)
348
+
349
+ // If screen is wider than container, draw outer columns using absolute positioning
350
+ if (width > maxWidthPx && showFullWidth) {
351
+ const drawOuterColumn = (x: number) => {
352
+ const col = document.createElement('div')
353
+ col.className = 'rla-outer-column'
354
+ Object.assign(col.style, {
355
+ position: 'absolute',
356
+ top: '0',
357
+ left: `${x}px`,
358
+ width: `${colWidth}px`,
359
+ height: '100%',
360
+ borderInline: `1px solid ${fullWidthColor}`,
361
+ boxSizing: 'border-box',
362
+ backgroundColor: showFullWidthBackground ? hexToRgba(fullWidthColor, fullWidthOpacity) : 'transparent'
363
+ })
364
+ this.columnsContainer!.appendChild(col)
365
+ }
366
+
367
+ // Draw left columns starting from the viewport edge and going right
368
+ let leftX = marginPx
369
+ while (leftX + colWidth < containerLeft) {
370
+ drawOuterColumn(leftX)
371
+ leftX += step
372
+ }
373
+
374
+ // Draw right columns starting from the viewport edge and going left
375
+ let rightX = width - marginPx - colWidth
376
+ while (rightX > containerRight) {
377
+ drawOuterColumn(rightX)
378
+ rightX -= step
379
+ }
380
+ }
381
+ }
382
+
383
+ toggleGrid() {
384
+ this.isVisible = !this.isVisible
385
+ localStorage.setItem('rla-grid-visible', this.isVisible ? 'true' : 'false')
386
+ if (this.gridElement) {
387
+ this.gridElement.style.display = this.isVisible ? 'block' : 'none'
388
+ }
389
+ }
390
+ }
391
+
392
+ if (!customElements.get('rla-layout-grid')) {
393
+ customElements.define('rla-layout-grid', RLALayoutGrid)
394
+ }
395
+ </script>