@autumnsgrove/groveengine 0.6.5 → 0.7.0
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/LICENSE +378 -0
- package/dist/auth/index.d.ts +1 -2
- package/dist/auth/index.js +8 -4
- package/dist/auth/session.d.ts +12 -31
- package/dist/auth/session.js +5 -103
- package/dist/components/custom/ContentWithGutter.svelte +22 -25
- package/dist/ui/components/ui/Glass.svelte +158 -0
- package/dist/ui/components/ui/Glass.svelte.d.ts +52 -0
- package/dist/ui/components/ui/GlassButton.svelte +157 -0
- package/dist/ui/components/ui/GlassButton.svelte.d.ts +39 -0
- package/dist/ui/components/ui/GlassCard.svelte +160 -0
- package/dist/ui/components/ui/GlassCard.svelte.d.ts +39 -0
- package/dist/ui/components/ui/GlassConfirmDialog.svelte +208 -0
- package/dist/ui/components/ui/GlassConfirmDialog.svelte.d.ts +52 -0
- package/dist/ui/components/ui/GlassOverlay.svelte +93 -0
- package/dist/ui/components/ui/GlassOverlay.svelte.d.ts +33 -0
- package/dist/ui/components/ui/index.d.ts +5 -0
- package/dist/ui/components/ui/index.js +6 -0
- package/dist/ui/styles/grove.css +136 -0
- package/dist/utils/gutter.d.ts +2 -8
- package/dist/utils/markdown.d.ts +1 -0
- package/dist/utils/markdown.js +32 -11
- package/package.json +21 -22
- package/static/fonts/alagard.ttf +0 -0
- package/static/robots.txt +34 -1
- package/dist/auth/jwt.d.ts +0 -20
- package/dist/auth/jwt.js +0 -123
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
* Helper to get anchor key with headers context
|
|
65
65
|
* @param {string} anchor
|
|
66
66
|
*/
|
|
67
|
-
function getKey(anchor) {
|
|
67
|
+
function getKey(anchor: string) {
|
|
68
68
|
return getAnchorKey(anchor, headers);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -72,16 +72,16 @@
|
|
|
72
72
|
* Get items for a specific anchor
|
|
73
73
|
* @param {string} anchor
|
|
74
74
|
*/
|
|
75
|
-
function getItems(anchor) {
|
|
75
|
+
function getItems(anchor: string) {
|
|
76
76
|
return getItemsForAnchor(gutterContent, anchor);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Generate unique key for a gutter item
|
|
81
|
-
* @param {
|
|
81
|
+
* @param {GutterItemType} item
|
|
82
82
|
* @param {number} index
|
|
83
83
|
*/
|
|
84
|
-
function getItemKey(item, index) {
|
|
84
|
+
function getItemKey(item: GutterItemType, index: number): string {
|
|
85
85
|
// Combine item properties to create a unique identifier
|
|
86
86
|
const parts = [
|
|
87
87
|
item.type || 'unknown',
|
|
@@ -103,16 +103,15 @@
|
|
|
103
103
|
|
|
104
104
|
// Use getBoundingClientRect for accurate relative positioning
|
|
105
105
|
// This works regardless of offset parent chains and CSS transforms
|
|
106
|
-
const gutterRect = gutterElement.getBoundingClientRect();
|
|
106
|
+
const gutterRect = (gutterElement as HTMLElement).getBoundingClientRect();
|
|
107
107
|
|
|
108
108
|
let lastBottom = 0; // Track the bottom edge of the last positioned item
|
|
109
|
-
|
|
110
|
-
const newOverflowingAnchors = [];
|
|
109
|
+
const newOverflowingAnchors: string[] = [];
|
|
111
110
|
const newPositions = { ...itemPositions };
|
|
112
111
|
|
|
113
112
|
// Sort anchors by their position in the document
|
|
114
113
|
const anchorPositions = uniqueAnchors.map(anchor => {
|
|
115
|
-
const el = findAnchorElement(anchor, contentBodyElement ?? null, headers);
|
|
114
|
+
const el = findAnchorElement(anchor, (contentBodyElement ?? null) as HTMLElement | null, headers);
|
|
116
115
|
if (!el && import.meta.env.DEV) {
|
|
117
116
|
console.warn(`Anchor element not found for: ${anchor}`);
|
|
118
117
|
}
|
|
@@ -168,8 +167,7 @@
|
|
|
168
167
|
|
|
169
168
|
// Setup resize listener on mount with proper cleanup
|
|
170
169
|
onMount(() => {
|
|
171
|
-
|
|
172
|
-
let resizeTimeoutId;
|
|
170
|
+
let resizeTimeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
173
171
|
const handleResize = () => {
|
|
174
172
|
clearTimeout(resizeTimeoutId);
|
|
175
173
|
resizeTimeoutId = setTimeout(() => {
|
|
@@ -186,9 +184,8 @@
|
|
|
186
184
|
|
|
187
185
|
// Setup copy button functionality for code blocks
|
|
188
186
|
onMount(() => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const button = /** @type {HTMLElement} */ (event.currentTarget);
|
|
187
|
+
const handleCopyClick = async (event: Event) => {
|
|
188
|
+
const button = event.currentTarget as HTMLElement;
|
|
192
189
|
const codeText = button.getAttribute('data-code');
|
|
193
190
|
|
|
194
191
|
if (!codeText) return;
|
|
@@ -251,20 +248,19 @@
|
|
|
251
248
|
// Add IDs to headers and position mobile gutter items
|
|
252
249
|
$effect(() => {
|
|
253
250
|
// Track moved elements for cleanup
|
|
254
|
-
|
|
255
|
-
const movedElements = [];
|
|
251
|
+
const movedElements: Array<{ element: HTMLElement; originalParent: HTMLElement | null; originalNextSibling: Node | null }> = [];
|
|
256
252
|
|
|
257
253
|
untrack(() => {
|
|
258
254
|
if (!contentBodyElement) return;
|
|
259
255
|
|
|
260
256
|
// First, add IDs to headers
|
|
261
257
|
if (headers && headers.length > 0) {
|
|
262
|
-
const headerElements = contentBodyElement.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
263
|
-
headerElements.forEach((el) => {
|
|
264
|
-
const text = el.textContent?.trim() || '';
|
|
265
|
-
const matchingHeader = headers.find(
|
|
258
|
+
const headerElements = (contentBodyElement as HTMLElement).querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
259
|
+
headerElements.forEach((el: Element) => {
|
|
260
|
+
const text = (el as HTMLElement).textContent?.trim() || '';
|
|
261
|
+
const matchingHeader = headers.find((h: Header) => h.text === text);
|
|
266
262
|
if (matchingHeader) {
|
|
267
|
-
el.id = matchingHeader.id;
|
|
263
|
+
(el as HTMLElement).id = matchingHeader.id;
|
|
268
264
|
}
|
|
269
265
|
});
|
|
270
266
|
}
|
|
@@ -279,7 +275,7 @@
|
|
|
279
275
|
const originalParent = mobileGutterEl.parentElement;
|
|
280
276
|
const originalNextSibling = mobileGutterEl.nextSibling;
|
|
281
277
|
|
|
282
|
-
const targetEl = findAnchorElement(anchor, contentBodyElement, headers);
|
|
278
|
+
const targetEl = findAnchorElement(anchor, contentBodyElement as HTMLElement, headers);
|
|
283
279
|
|
|
284
280
|
if (targetEl) {
|
|
285
281
|
targetEl.insertAdjacentElement('afterend', mobileGutterEl);
|
|
@@ -308,12 +304,13 @@
|
|
|
308
304
|
const updateHeight = () => {
|
|
309
305
|
if (!contentBodyElement) return;
|
|
310
306
|
// Get the bottom of content-body relative to the article
|
|
311
|
-
const rect = contentBodyElement.getBoundingClientRect();
|
|
312
|
-
const
|
|
307
|
+
const rect = (contentBodyElement as HTMLElement).getBoundingClientRect();
|
|
308
|
+
const articleEl = (contentBodyElement as HTMLElement).closest('.content-article');
|
|
309
|
+
const articleRect = articleEl?.getBoundingClientRect();
|
|
313
310
|
if (articleRect) {
|
|
314
311
|
contentHeight = rect.bottom - articleRect.top;
|
|
315
312
|
} else {
|
|
316
|
-
contentHeight = contentBodyElement.offsetTop + contentBodyElement.offsetHeight;
|
|
313
|
+
contentHeight = (contentBodyElement as HTMLElement).offsetTop + (contentBodyElement as HTMLElement).offsetHeight;
|
|
317
314
|
}
|
|
318
315
|
};
|
|
319
316
|
updateHeight();
|
|
@@ -348,7 +345,7 @@
|
|
|
348
345
|
* @param {string} html
|
|
349
346
|
* @param {string[]} overflowKeys
|
|
350
347
|
*/
|
|
351
|
-
function injectReferenceMarkers(html, overflowKeys) {
|
|
348
|
+
function injectReferenceMarkers(html: string, overflowKeys: string[]): string {
|
|
352
349
|
if (!overflowKeys || overflowKeys.length === 0 || typeof window === 'undefined') {
|
|
353
350
|
return html;
|
|
354
351
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Glass component for creating glassmorphism effects
|
|
8
|
+
*
|
|
9
|
+
* A reusable component that provides translucent, frosted-glass effects
|
|
10
|
+
* perfect for overlays, cards, navbars, and text containers while
|
|
11
|
+
* maintaining visibility of background elements.
|
|
12
|
+
*
|
|
13
|
+
* @example Basic usage
|
|
14
|
+
* ```svelte
|
|
15
|
+
* <Glass variant="card">
|
|
16
|
+
* <p>Content with glass background</p>
|
|
17
|
+
* </Glass>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example As a navbar
|
|
21
|
+
* ```svelte
|
|
22
|
+
* <Glass variant="surface" as="header" class="sticky top-0">
|
|
23
|
+
* <nav>...</nav>
|
|
24
|
+
* </Glass>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example Accent tint for callouts
|
|
28
|
+
* ```svelte
|
|
29
|
+
* <Glass variant="accent" intensity="light" class="p-6 rounded-xl">
|
|
30
|
+
* <p>Important message here</p>
|
|
31
|
+
* </Glass>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
type Variant =
|
|
36
|
+
| "surface" // Headers, navbars - high opacity, subtle blur
|
|
37
|
+
| "overlay" // Modal backdrops - dark, medium blur
|
|
38
|
+
| "card" // Content cards - medium opacity, clean look
|
|
39
|
+
| "tint" // Text containers - light background for readability
|
|
40
|
+
| "accent" // Accent-colored glass for callouts/highlights
|
|
41
|
+
| "muted"; // Subtle background, barely visible
|
|
42
|
+
|
|
43
|
+
type Intensity =
|
|
44
|
+
| "none" // No blur (just transparency)
|
|
45
|
+
| "light" // backdrop-blur-sm (4px)
|
|
46
|
+
| "medium" // backdrop-blur (8px)
|
|
47
|
+
| "strong"; // backdrop-blur-md (12px)
|
|
48
|
+
|
|
49
|
+
type Element = "div" | "section" | "article" | "aside" | "header" | "footer" | "nav" | "main";
|
|
50
|
+
|
|
51
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
52
|
+
/** Visual style variant */
|
|
53
|
+
variant?: Variant;
|
|
54
|
+
/** Blur intensity */
|
|
55
|
+
intensity?: Intensity;
|
|
56
|
+
/** HTML element to render */
|
|
57
|
+
as?: Element;
|
|
58
|
+
/** Include subtle border */
|
|
59
|
+
border?: boolean;
|
|
60
|
+
/** Include shadow */
|
|
61
|
+
shadow?: boolean;
|
|
62
|
+
/** Additional CSS classes */
|
|
63
|
+
class?: string;
|
|
64
|
+
/** Content */
|
|
65
|
+
children?: Snippet;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let {
|
|
69
|
+
variant = "card",
|
|
70
|
+
intensity = "light",
|
|
71
|
+
as: element = "div",
|
|
72
|
+
border = true,
|
|
73
|
+
shadow = false,
|
|
74
|
+
class: className,
|
|
75
|
+
children,
|
|
76
|
+
...restProps
|
|
77
|
+
}: Props = $props();
|
|
78
|
+
|
|
79
|
+
// Background colors per variant - warm grove tones, translucent for glass effect
|
|
80
|
+
const variantClasses: Record<Variant, string> = {
|
|
81
|
+
// High opacity for sticky headers/navbars (uses background color)
|
|
82
|
+
surface: "bg-background/90 dark:bg-background/90",
|
|
83
|
+
|
|
84
|
+
// Dark overlay for modals/sheets
|
|
85
|
+
overlay: "bg-black/50 dark:bg-black/60",
|
|
86
|
+
|
|
87
|
+
// Medium opacity for content cards - translucent with grove warmth
|
|
88
|
+
card: "bg-white/60 dark:bg-emerald-950/25",
|
|
89
|
+
|
|
90
|
+
// Light tint for text readability
|
|
91
|
+
tint: "bg-white/50 dark:bg-emerald-950/20",
|
|
92
|
+
|
|
93
|
+
// Accent-colored glass for highlights/callouts
|
|
94
|
+
accent: "bg-accent/25 dark:bg-accent/15",
|
|
95
|
+
|
|
96
|
+
// Barely visible, very subtle
|
|
97
|
+
muted: "bg-white/30 dark:bg-emerald-950/15"
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Blur intensity classes - default to medium blur for true glass effect
|
|
101
|
+
const intensityClasses: Record<Intensity, string> = {
|
|
102
|
+
none: "",
|
|
103
|
+
light: "backdrop-blur", // 8px
|
|
104
|
+
medium: "backdrop-blur-md", // 12px
|
|
105
|
+
strong: "backdrop-blur-lg" // 16px
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Border classes per variant - subtle borders that complement the glass
|
|
109
|
+
const borderClasses: Record<Variant, string> = {
|
|
110
|
+
surface: "border-border",
|
|
111
|
+
overlay: "border-white/10",
|
|
112
|
+
card: "border-white/40 dark:border-emerald-800/25",
|
|
113
|
+
tint: "border-white/30 dark:border-emerald-800/20",
|
|
114
|
+
accent: "border-accent/30 dark:border-accent/20",
|
|
115
|
+
muted: "border-white/20 dark:border-emerald-800/15"
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Shadow classes
|
|
119
|
+
const shadowClasses: Record<Variant, string> = {
|
|
120
|
+
surface: "shadow-sm",
|
|
121
|
+
overlay: "shadow-2xl",
|
|
122
|
+
card: "shadow-sm",
|
|
123
|
+
tint: "shadow-sm",
|
|
124
|
+
accent: "shadow-sm",
|
|
125
|
+
muted: ""
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const computedClass = $derived(
|
|
129
|
+
cn(
|
|
130
|
+
variantClasses[variant],
|
|
131
|
+
intensityClasses[intensity],
|
|
132
|
+
border && `border ${borderClasses[variant]}`,
|
|
133
|
+
shadow && shadowClasses[variant],
|
|
134
|
+
className
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<!--
|
|
140
|
+
Glassmorphism component for Grove
|
|
141
|
+
|
|
142
|
+
CSS Properties used:
|
|
143
|
+
- backdrop-filter: blur() - Creates the frosted glass effect
|
|
144
|
+
- Background with alpha - Semi-transparent backgrounds (e.g., bg-white/80)
|
|
145
|
+
- Border with alpha - Subtle borders that complement the glass effect
|
|
146
|
+
|
|
147
|
+
Browser Support:
|
|
148
|
+
- backdrop-filter is supported in all modern browsers
|
|
149
|
+
- Falls back gracefully to solid backgrounds in older browsers
|
|
150
|
+
-->
|
|
151
|
+
|
|
152
|
+
<svelte:element
|
|
153
|
+
this={element}
|
|
154
|
+
class={computedClass}
|
|
155
|
+
{...restProps}
|
|
156
|
+
>
|
|
157
|
+
{#if children}{@render children()}{/if}
|
|
158
|
+
</svelte:element>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* Glass component for creating glassmorphism effects
|
|
5
|
+
*
|
|
6
|
+
* A reusable component that provides translucent, frosted-glass effects
|
|
7
|
+
* perfect for overlays, cards, navbars, and text containers while
|
|
8
|
+
* maintaining visibility of background elements.
|
|
9
|
+
*
|
|
10
|
+
* @example Basic usage
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <Glass variant="card">
|
|
13
|
+
* <p>Content with glass background</p>
|
|
14
|
+
* </Glass>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example As a navbar
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <Glass variant="surface" as="header" class="sticky top-0">
|
|
20
|
+
* <nav>...</nav>
|
|
21
|
+
* </Glass>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Accent tint for callouts
|
|
25
|
+
* ```svelte
|
|
26
|
+
* <Glass variant="accent" intensity="light" class="p-6 rounded-xl">
|
|
27
|
+
* <p>Important message here</p>
|
|
28
|
+
* </Glass>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
type Variant = "surface" | "overlay" | "card" | "tint" | "accent" | "muted";
|
|
32
|
+
type Intensity = "none" | "light" | "medium" | "strong";
|
|
33
|
+
type Element = "div" | "section" | "article" | "aside" | "header" | "footer" | "nav" | "main";
|
|
34
|
+
interface Props extends HTMLAttributes<HTMLElement> {
|
|
35
|
+
/** Visual style variant */
|
|
36
|
+
variant?: Variant;
|
|
37
|
+
/** Blur intensity */
|
|
38
|
+
intensity?: Intensity;
|
|
39
|
+
/** HTML element to render */
|
|
40
|
+
as?: Element;
|
|
41
|
+
/** Include subtle border */
|
|
42
|
+
border?: boolean;
|
|
43
|
+
/** Include shadow */
|
|
44
|
+
shadow?: boolean;
|
|
45
|
+
/** Additional CSS classes */
|
|
46
|
+
class?: string;
|
|
47
|
+
/** Content */
|
|
48
|
+
children?: Snippet;
|
|
49
|
+
}
|
|
50
|
+
declare const Glass: import("svelte").Component<Props, {}, "">;
|
|
51
|
+
type Glass = ReturnType<typeof Glass>;
|
|
52
|
+
export default Glass;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from "svelte/elements";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GlassButton - A button with glassmorphism styling
|
|
8
|
+
*
|
|
9
|
+
* Beautiful translucent buttons with backdrop blur effects.
|
|
10
|
+
* Perfect for floating actions, overlays, and modern UI designs.
|
|
11
|
+
*
|
|
12
|
+
* @example Basic glass button
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <GlassButton>Click me</GlassButton>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @example Accent glass button
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <GlassButton variant="accent">Subscribe</GlassButton>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example Ghost glass with icon
|
|
23
|
+
* ```svelte
|
|
24
|
+
* <GlassButton variant="ghost" size="icon">
|
|
25
|
+
* <X class="w-4 h-4" />
|
|
26
|
+
* </GlassButton>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
type GlassVariant =
|
|
31
|
+
| "default" // Light translucent background
|
|
32
|
+
| "accent" // Accent-colored glass
|
|
33
|
+
| "dark" // Dark translucent background
|
|
34
|
+
| "ghost" // No background until hover
|
|
35
|
+
| "outline"; // Transparent with glass border
|
|
36
|
+
|
|
37
|
+
type ButtonSize = "sm" | "md" | "lg" | "icon";
|
|
38
|
+
|
|
39
|
+
interface Props extends Omit<HTMLButtonAttributes, "class"> {
|
|
40
|
+
variant?: GlassVariant;
|
|
41
|
+
size?: ButtonSize;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
href?: string;
|
|
44
|
+
class?: string;
|
|
45
|
+
children?: Snippet;
|
|
46
|
+
ref?: HTMLButtonElement | HTMLAnchorElement | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let {
|
|
50
|
+
variant = "default",
|
|
51
|
+
size = "md",
|
|
52
|
+
disabled = false,
|
|
53
|
+
href,
|
|
54
|
+
class: className,
|
|
55
|
+
children,
|
|
56
|
+
ref = $bindable(null),
|
|
57
|
+
type = "button",
|
|
58
|
+
...restProps
|
|
59
|
+
}: Props = $props();
|
|
60
|
+
|
|
61
|
+
// Base styles shared by all variants - medium blur for glass effect
|
|
62
|
+
const baseClasses = `
|
|
63
|
+
inline-flex items-center justify-center gap-2
|
|
64
|
+
font-medium rounded-lg
|
|
65
|
+
transition-all duration-200
|
|
66
|
+
backdrop-blur-md
|
|
67
|
+
outline-none
|
|
68
|
+
focus-visible:ring-2 focus-visible:ring-accent/50 focus-visible:ring-offset-2
|
|
69
|
+
disabled:opacity-50 disabled:pointer-events-none
|
|
70
|
+
[&_svg]:w-4 [&_svg]:h-4 [&_svg]:shrink-0
|
|
71
|
+
`.trim().replace(/\s+/g, ' ');
|
|
72
|
+
|
|
73
|
+
// Variant-specific styles - warm grove tones with true glass transparency
|
|
74
|
+
const variantClasses: Record<GlassVariant, string> = {
|
|
75
|
+
default: `
|
|
76
|
+
bg-white/60 dark:bg-emerald-950/25
|
|
77
|
+
border border-white/40 dark:border-emerald-800/25
|
|
78
|
+
text-foreground
|
|
79
|
+
hover:bg-white/75 dark:hover:bg-emerald-950/35
|
|
80
|
+
hover:border-white/50 dark:hover:border-emerald-700/30
|
|
81
|
+
shadow-sm hover:shadow-md
|
|
82
|
+
`.trim().replace(/\s+/g, ' '),
|
|
83
|
+
|
|
84
|
+
accent: `
|
|
85
|
+
bg-accent/70 dark:bg-accent/60
|
|
86
|
+
border border-accent/40 dark:border-accent/30
|
|
87
|
+
text-white
|
|
88
|
+
hover:bg-accent/85 dark:hover:bg-accent/75
|
|
89
|
+
hover:border-accent/60 dark:hover:border-accent/50
|
|
90
|
+
shadow-sm hover:shadow-md shadow-accent/20
|
|
91
|
+
`.trim().replace(/\s+/g, ' '),
|
|
92
|
+
|
|
93
|
+
dark: `
|
|
94
|
+
bg-slate-900/50 dark:bg-slate-950/50
|
|
95
|
+
border border-slate-700/30 dark:border-slate-600/30
|
|
96
|
+
text-white
|
|
97
|
+
hover:bg-slate-900/65 dark:hover:bg-slate-950/65
|
|
98
|
+
hover:border-slate-600/40 dark:hover:border-slate-500/35
|
|
99
|
+
shadow-md hover:shadow-lg
|
|
100
|
+
`.trim().replace(/\s+/g, ' '),
|
|
101
|
+
|
|
102
|
+
ghost: `
|
|
103
|
+
bg-transparent
|
|
104
|
+
border border-transparent
|
|
105
|
+
text-foreground
|
|
106
|
+
hover:bg-white/40 dark:hover:bg-emerald-950/25
|
|
107
|
+
hover:border-white/25 dark:hover:border-emerald-800/20
|
|
108
|
+
`.trim().replace(/\s+/g, ' '),
|
|
109
|
+
|
|
110
|
+
outline: `
|
|
111
|
+
bg-transparent
|
|
112
|
+
border border-white/40 dark:border-emerald-800/30
|
|
113
|
+
text-foreground
|
|
114
|
+
hover:bg-white/25 dark:hover:bg-emerald-950/20
|
|
115
|
+
hover:border-white/55 dark:hover:border-emerald-700/40
|
|
116
|
+
`.trim().replace(/\s+/g, ' ')
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Size-specific styles
|
|
120
|
+
const sizeClasses: Record<ButtonSize, string> = {
|
|
121
|
+
sm: "h-8 px-3 text-sm",
|
|
122
|
+
md: "h-10 px-4 text-sm",
|
|
123
|
+
lg: "h-12 px-6 text-base",
|
|
124
|
+
icon: "h-10 w-10 p-0"
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const computedClass = $derived(
|
|
128
|
+
cn(
|
|
129
|
+
baseClasses,
|
|
130
|
+
variantClasses[variant],
|
|
131
|
+
sizeClasses[size],
|
|
132
|
+
className
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
{#if href && !disabled}
|
|
138
|
+
<a
|
|
139
|
+
bind:this={ref}
|
|
140
|
+
{href}
|
|
141
|
+
class={computedClass}
|
|
142
|
+
aria-disabled={disabled}
|
|
143
|
+
{...restProps}
|
|
144
|
+
>
|
|
145
|
+
{#if children}{@render children()}{/if}
|
|
146
|
+
</a>
|
|
147
|
+
{:else}
|
|
148
|
+
<button
|
|
149
|
+
bind:this={ref}
|
|
150
|
+
{type}
|
|
151
|
+
{disabled}
|
|
152
|
+
class={computedClass}
|
|
153
|
+
{...restProps}
|
|
154
|
+
>
|
|
155
|
+
{#if children}{@render children()}{/if}
|
|
156
|
+
</button>
|
|
157
|
+
{/if}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
3
|
+
/**
|
|
4
|
+
* GlassButton - A button with glassmorphism styling
|
|
5
|
+
*
|
|
6
|
+
* Beautiful translucent buttons with backdrop blur effects.
|
|
7
|
+
* Perfect for floating actions, overlays, and modern UI designs.
|
|
8
|
+
*
|
|
9
|
+
* @example Basic glass button
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <GlassButton>Click me</GlassButton>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @example Accent glass button
|
|
15
|
+
* ```svelte
|
|
16
|
+
* <GlassButton variant="accent">Subscribe</GlassButton>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example Ghost glass with icon
|
|
20
|
+
* ```svelte
|
|
21
|
+
* <GlassButton variant="ghost" size="icon">
|
|
22
|
+
* <X class="w-4 h-4" />
|
|
23
|
+
* </GlassButton>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
type GlassVariant = "default" | "accent" | "dark" | "ghost" | "outline";
|
|
27
|
+
type ButtonSize = "sm" | "md" | "lg" | "icon";
|
|
28
|
+
interface Props extends Omit<HTMLButtonAttributes, "class"> {
|
|
29
|
+
variant?: GlassVariant;
|
|
30
|
+
size?: ButtonSize;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
href?: string;
|
|
33
|
+
class?: string;
|
|
34
|
+
children?: Snippet;
|
|
35
|
+
ref?: HTMLButtonElement | HTMLAnchorElement | null;
|
|
36
|
+
}
|
|
37
|
+
declare const GlassButton: import("svelte").Component<Props, {}, "ref">;
|
|
38
|
+
type GlassButton = ReturnType<typeof GlassButton>;
|
|
39
|
+
export default GlassButton;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import { cn } from "../../utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GlassCard - A card component with glassmorphism styling
|
|
8
|
+
*
|
|
9
|
+
* Beautiful translucent cards with backdrop blur effects.
|
|
10
|
+
* Includes optional header, footer, and hoverable state.
|
|
11
|
+
*
|
|
12
|
+
* @example Basic glass card
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <GlassCard title="Settings" description="Manage your preferences">
|
|
15
|
+
* <p>Card content here</p>
|
|
16
|
+
* </GlassCard>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example Accent card with footer
|
|
20
|
+
* ```svelte
|
|
21
|
+
* <GlassCard variant="accent" hoverable>
|
|
22
|
+
* {#snippet header()}<CustomHeader />{/snippet}
|
|
23
|
+
* Content here
|
|
24
|
+
* {#snippet footer()}<Button>Save</Button>{/snippet}
|
|
25
|
+
* </GlassCard>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
type GlassVariant =
|
|
30
|
+
| "default" // Light translucent background
|
|
31
|
+
| "accent" // Accent-colored glass
|
|
32
|
+
| "dark" // Dark translucent background
|
|
33
|
+
| "muted" // Very subtle, barely visible
|
|
34
|
+
| "frosted"; // Stronger blur effect, more opaque
|
|
35
|
+
|
|
36
|
+
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "class"> {
|
|
37
|
+
variant?: GlassVariant;
|
|
38
|
+
title?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
hoverable?: boolean;
|
|
41
|
+
border?: boolean;
|
|
42
|
+
class?: string;
|
|
43
|
+
header?: Snippet;
|
|
44
|
+
footer?: Snippet;
|
|
45
|
+
children?: Snippet;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
variant = "default",
|
|
50
|
+
title,
|
|
51
|
+
description,
|
|
52
|
+
hoverable = false,
|
|
53
|
+
border = true,
|
|
54
|
+
class: className,
|
|
55
|
+
header,
|
|
56
|
+
footer,
|
|
57
|
+
children,
|
|
58
|
+
...restProps
|
|
59
|
+
}: Props = $props();
|
|
60
|
+
|
|
61
|
+
// Variant-specific styles - warm grove tones with true glass transparency
|
|
62
|
+
const variantClasses: Record<GlassVariant, string> = {
|
|
63
|
+
default: `
|
|
64
|
+
bg-white/60 dark:bg-emerald-950/25
|
|
65
|
+
backdrop-blur-md
|
|
66
|
+
`.trim().replace(/\s+/g, ' '),
|
|
67
|
+
|
|
68
|
+
accent: `
|
|
69
|
+
bg-accent/20 dark:bg-accent/15
|
|
70
|
+
backdrop-blur-md
|
|
71
|
+
`.trim().replace(/\s+/g, ' '),
|
|
72
|
+
|
|
73
|
+
dark: `
|
|
74
|
+
bg-slate-900/40 dark:bg-slate-950/40
|
|
75
|
+
backdrop-blur-md
|
|
76
|
+
text-white
|
|
77
|
+
`.trim().replace(/\s+/g, ' '),
|
|
78
|
+
|
|
79
|
+
muted: `
|
|
80
|
+
bg-white/30 dark:bg-emerald-950/15
|
|
81
|
+
backdrop-blur
|
|
82
|
+
`.trim().replace(/\s+/g, ' '),
|
|
83
|
+
|
|
84
|
+
frosted: `
|
|
85
|
+
bg-white/70 dark:bg-emerald-950/35
|
|
86
|
+
backdrop-blur-lg
|
|
87
|
+
`.trim().replace(/\s+/g, ' ')
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Border colors per variant - subtle, warm borders
|
|
91
|
+
const borderClasses: Record<GlassVariant, string> = {
|
|
92
|
+
default: "border-white/40 dark:border-emerald-800/25",
|
|
93
|
+
accent: "border-accent/30 dark:border-accent/20",
|
|
94
|
+
dark: "border-slate-700/30 dark:border-slate-600/30",
|
|
95
|
+
muted: "border-white/20 dark:border-emerald-800/15",
|
|
96
|
+
frosted: "border-white/50 dark:border-emerald-800/30"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Hover styles - slightly more visible on hover
|
|
100
|
+
const hoverClasses: Record<GlassVariant, string> = {
|
|
101
|
+
default: "hover:bg-white/70 dark:hover:bg-emerald-950/35 hover:shadow-lg hover:border-white/50 dark:hover:border-emerald-700/30",
|
|
102
|
+
accent: "hover:bg-accent/30 dark:hover:bg-accent/25 hover:shadow-lg hover:shadow-accent/10 hover:border-accent/40",
|
|
103
|
+
dark: "hover:bg-slate-900/50 dark:hover:bg-slate-950/50 hover:shadow-xl hover:border-slate-600/40",
|
|
104
|
+
muted: "hover:bg-white/40 dark:hover:bg-emerald-950/25 hover:shadow-md hover:border-white/30",
|
|
105
|
+
frosted: "hover:bg-white/80 dark:hover:bg-emerald-950/45 hover:shadow-lg hover:border-white/60"
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const computedClass = $derived(
|
|
109
|
+
cn(
|
|
110
|
+
"rounded-xl transition-all duration-200",
|
|
111
|
+
variantClasses[variant],
|
|
112
|
+
border && `border ${borderClasses[variant]}`,
|
|
113
|
+
hoverable && `cursor-pointer ${hoverClasses[variant]}`,
|
|
114
|
+
"shadow-sm",
|
|
115
|
+
className
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Text color adjustments for dark variant
|
|
120
|
+
const titleClass = $derived(
|
|
121
|
+
variant === "dark"
|
|
122
|
+
? "text-white"
|
|
123
|
+
: "text-foreground"
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const descriptionClass = $derived(
|
|
127
|
+
variant === "dark"
|
|
128
|
+
? "text-slate-300"
|
|
129
|
+
: "text-muted-foreground"
|
|
130
|
+
);
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<div class={computedClass} {...restProps}>
|
|
134
|
+
{#if header || title || description}
|
|
135
|
+
<div class="px-6 py-4 {(children || footer) ? 'border-b border-inherit' : ''}">
|
|
136
|
+
{#if header}
|
|
137
|
+
{@render header()}
|
|
138
|
+
{:else}
|
|
139
|
+
{#if title}
|
|
140
|
+
<h3 class="text-lg font-semibold {titleClass}">{title}</h3>
|
|
141
|
+
{/if}
|
|
142
|
+
{#if description}
|
|
143
|
+
<p class="text-sm {descriptionClass} mt-1">{description}</p>
|
|
144
|
+
{/if}
|
|
145
|
+
{/if}
|
|
146
|
+
</div>
|
|
147
|
+
{/if}
|
|
148
|
+
|
|
149
|
+
{#if children}
|
|
150
|
+
<div class="px-6 py-4">
|
|
151
|
+
{@render children()}
|
|
152
|
+
</div>
|
|
153
|
+
{/if}
|
|
154
|
+
|
|
155
|
+
{#if footer}
|
|
156
|
+
<div class="px-6 py-4 border-t border-inherit">
|
|
157
|
+
{@render footer()}
|
|
158
|
+
</div>
|
|
159
|
+
{/if}
|
|
160
|
+
</div>
|