@rimelight/ui 0.0.34 → 0.0.36
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/README.md +1 -0
- package/package.json +19 -24
- package/src/components/astro/RLAAccordion.astro +94 -18
- package/src/components/astro/RLAAvatar.astro +135 -68
- package/src/components/astro/RLABadge.astro +4 -2
- package/src/components/astro/RLABanner.astro +138 -23
- package/src/components/astro/RLABreadcrumb.astro +13 -0
- package/src/components/astro/RLAButton.astro +8 -4
- package/src/components/astro/RLACallout.astro +112 -0
- package/src/components/astro/RLACard.astro +6 -5
- package/src/components/astro/RLACarousel.astro +104 -75
- package/src/components/astro/RLAChart.astro +4 -1
- package/src/components/astro/RLAChip.astro +11 -5
- package/src/components/astro/RLACollapsible.astro +4 -1
- package/src/components/astro/RLAColorArea.astro +432 -0
- package/src/components/astro/RLAColorField.astro +300 -0
- package/src/components/astro/RLAColorSlider.astro +413 -0
- package/src/components/astro/RLAConfirm.astro +13 -11
- package/src/components/astro/RLACopyMarkdown.astro +78 -0
- package/src/components/astro/RLADate.astro +30 -0
- package/src/components/astro/RLADrawer.astro +50 -3
- package/src/components/astro/RLADropdownMenu.astro +78 -51
- package/src/components/astro/RLAFieldGroup.astro +4 -1
- package/src/components/astro/RLAFileUpload.astro +114 -100
- package/src/components/astro/RLAFooter.astro +2 -1
- package/src/components/astro/RLAFormField.astro +10 -6
- package/src/components/astro/RLAGrid.astro +31 -39
- package/src/components/astro/RLAHead.astro +139 -39
- package/src/components/astro/RLAHeader.astro +2 -1
- package/src/components/astro/RLAImage.astro +274 -251
- package/src/components/astro/RLAInput.astro +14 -9
- package/src/components/astro/RLAInputMenu.astro +119 -83
- package/src/components/astro/RLAKbd.astro +5 -5
- package/src/components/astro/RLALayoutGrid.astro +395 -0
- package/src/components/astro/RLALink.astro +23 -1
- package/src/components/astro/RLALinkGroup.astro +99 -99
- package/src/components/astro/RLALocaleSelector.astro +34 -41
- package/src/components/astro/RLALogo.astro +117 -90
- package/src/components/astro/RLAModal.astro +48 -18
- package/src/components/astro/RLANavigationMenu.astro +381 -326
- package/src/components/astro/{RLADocsPackageManagers.astro → RLAPackageManagers.astro} +81 -87
- package/src/components/astro/RLAPageSection.astro +4 -2
- package/src/components/astro/RLAPopover.astro +4 -3
- package/src/components/astro/RLAPost.astro +11 -2
- package/src/components/astro/RLAScrollArea.astro +4 -1
- package/src/components/astro/RLAScrollToTop.astro +200 -172
- package/src/components/astro/RLASearch.astro +282 -0
- package/src/components/astro/RLASelect.astro +78 -55
- package/src/components/astro/RLASeparator.astro +11 -6
- package/src/components/astro/RLAShare.astro +60 -0
- package/src/components/astro/RLAShortcuts.astro +429 -0
- package/src/components/astro/RLASidebar.astro +158 -212
- package/src/components/astro/RLASlideover.astro +47 -17
- package/src/components/astro/RLASplitter.astro +194 -141
- package/src/components/astro/RLASteps.astro +118 -71
- package/src/components/astro/RLASurround.astro +39 -0
- package/src/components/astro/RLATable.astro +4 -1
- package/src/components/astro/RLATableOfContents.astro +109 -23
- package/src/components/astro/RLATabs.astro +147 -133
- package/src/components/astro/RLATextarea.astro +4 -1
- package/src/components/astro/RLAThemeSelector.astro +105 -76
- package/src/components/astro/RLAToast.astro +35 -21
- package/src/components/vue/RLVBanner.vue +230 -0
- package/src/components/vue/RLVButton.vue +3 -2
- package/src/components/vue/RLVCheckbox.vue +4 -2
- package/src/components/vue/RLVFooter.vue +3 -2
- package/src/components/vue/RLVHeader.vue +3 -2
- package/src/components/vue/RLVIcon.vue +4 -2
- package/src/components/vue/RLVInput.vue +5 -4
- package/src/components/vue/RLVLogo.vue +25 -3
- package/src/components/vue/RLVPlaceholder.vue +3 -2
- package/src/components/vue/RLVPost.vue +8 -2
- package/src/components/vue/RLVProgress.vue +60 -0
- package/src/components/vue/RLVScrollToTop.vue +65 -52
- package/src/components/vue/RLVSection.vue +14 -13
- package/src/components/vue/RLVToast.vue +17 -4
- package/src/composables/index.ts +1 -0
- package/src/composables/useHeaderStore.ts +1 -0
- package/src/composables/useShortcuts.ts +11 -0
- package/src/config/security.ts +43 -36
- package/src/config/site.config.ts +1 -0
- package/src/docs/components/ApiChangelog.astro +147 -0
- package/src/{plugins/starlightDocsApi → docs}/components/ApiEmits.astro +4 -4
- package/src/{plugins/starlightDocsApi → docs}/components/ApiProps.astro +4 -4
- package/src/{plugins/starlightDocsApi → docs}/components/ApiSlots.astro +4 -4
- package/src/docs/components/ApiTheme.astro +100 -0
- package/src/docs/extractAll.ts +94 -0
- package/src/docs/extractors/extractAstro.ts +69 -0
- package/src/docs/extractors/extractTheme.ts +56 -0
- package/src/{plugins/starlightDocsApi/extract-vue.ts → docs/extractors/extractVue.ts} +164 -15
- package/src/{plugins/starlightDocsApi → docs}/utils.ts +1 -2
- package/src/env.d.ts +0 -12
- package/src/integrations/ui.ts +35 -19
- package/src/middleware/security.ts +75 -19
- package/src/presets/index.ts +118 -293
- package/src/styles/index.css +0 -2
- package/src/themes/accordion.theme.ts +29 -3
- package/src/themes/aspect-ratio.theme.ts +1 -1
- package/src/themes/avatar-group.theme.ts +1 -1
- package/src/themes/avatar.theme.ts +2 -2
- package/src/themes/badge.theme.ts +2 -3
- package/src/themes/banner.theme.ts +40 -4
- package/src/themes/breadcrumb.theme.ts +1 -1
- package/src/themes/button.theme.ts +21 -22
- package/src/themes/{alert.theme.ts → callout.theme.ts} +12 -39
- package/src/themes/card.theme.ts +17 -9
- package/src/themes/carousel.theme.ts +1 -1
- package/src/themes/checkbox.theme.ts +1 -2
- package/src/themes/chip.theme.ts +2 -3
- package/src/themes/color-area.theme.ts +33 -0
- package/src/themes/color-field.theme.ts +32 -0
- package/src/themes/color-slider.theme.ts +34 -0
- package/src/themes/confirm.theme.ts +1 -1
- package/src/themes/container.theme.ts +1 -1
- package/src/themes/drawer.theme.ts +1 -1
- package/src/themes/footer.theme.ts +2 -2
- package/src/themes/grid.theme.ts +38 -0
- package/src/themes/header.theme.ts +4 -3
- package/src/themes/hover-card.theme.ts +1 -1
- package/src/themes/image.theme.ts +11 -1
- package/src/themes/input.theme.ts +7 -1
- package/src/themes/kbd.theme.ts +2 -3
- package/src/themes/label.theme.ts +1 -1
- package/src/themes/layout-grid.theme.ts +16 -0
- package/src/themes/link-group.theme.ts +0 -4
- package/src/themes/marquee.theme.ts +1 -1
- package/src/themes/modal.theme.ts +1 -1
- package/src/themes/navigation-menu.theme.ts +2 -2
- package/src/themes/page-section.theme.ts +4 -2
- package/src/themes/pagination.theme.ts +1 -1
- package/src/themes/pin-input.theme.ts +1 -1
- package/src/themes/popover.theme.ts +1 -1
- package/src/themes/post.theme.ts +23 -3
- package/src/themes/progress.theme.ts +1 -2
- package/src/themes/scroll-to-top.theme.ts +2 -3
- package/src/themes/search.theme.ts +64 -0
- package/src/themes/separator.theme.ts +1 -1
- package/src/themes/slideover.theme.ts +1 -1
- package/src/themes/slider.theme.ts +1 -2
- package/src/themes/spinner.theme.ts +1 -2
- package/src/themes/{megamenu.theme.ts → steps.theme.ts} +3 -3
- package/src/themes/sticky-surface.theme.ts +1 -1
- package/src/themes/switch.theme.ts +1 -2
- package/src/themes/table-of-contents.theme.ts +21 -2
- package/src/themes/timeline.theme.ts +7 -2
- package/src/themes/toast.theme.ts +1 -1
- package/src/types/components/accordion.ts +35 -2
- package/src/types/components/avatar.ts +16 -0
- package/src/types/components/banner.ts +42 -0
- package/src/types/components/button.ts +3 -4
- package/src/types/components/{alert.ts → callout.ts} +6 -6
- package/src/types/components/color-area.ts +19 -0
- package/src/types/components/color-field.ts +21 -0
- package/src/types/components/color-slider.ts +20 -0
- package/src/types/components/grid.ts +23 -0
- package/src/types/components/input.ts +4 -0
- package/src/types/components/layout-grid.ts +145 -0
- package/src/types/components/link-group.ts +4 -2
- package/src/types/components/link.ts +15 -0
- package/src/types/components/logo.ts +3 -3
- package/src/types/components/post.ts +12 -0
- package/src/types/components/search.ts +29 -0
- package/src/types/components/sidebar.ts +2 -6
- package/src/types/components/{megamenu.ts → steps.ts} +7 -1
- package/src/types/components/table-of-contents.ts +22 -0
- package/src/types/components/toast.ts +4 -0
- package/src/types/docs.ts +40 -0
- package/src/types/index.ts +151 -1
- package/src/types/theme.ts +6 -6
- package/src/utils/color.ts +165 -0
- package/src/utils/docs.ts +260 -110
- package/src/utils/headerStack.ts +24 -4
- package/src/utils/index.ts +2 -0
- package/src/utils/shortcuts.ts +322 -0
- package/src/components/TableOfContents.astro +0 -107
- package/src/components/astro/RLAAlert.astro +0 -93
- package/src/components/astro/RLAChangelog.astro +0 -23
- package/src/components/astro/RLAMegamenu.astro +0 -23
- package/src/plugins/index.ts +0 -2
- package/src/plugins/starlightDocsApi/components/ApiTheme.astro +0 -27
- package/src/plugins/starlightDocsApi/index.ts +0 -272
- package/src/styles/prism.css +0 -42
- package/src/themes/changelog.theme.ts +0 -13
- package/src/types/components/index.ts +0 -134
- /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
|
-
|
|
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
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
78
|
-
</div>
|
|
82
|
+
</rla-input-menu>
|
|
79
83
|
|
|
80
84
|
<script>
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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>
|