yaxa-svelte 1.11.0 → 1.12.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/README.md +13 -12
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/springTilt.d.ts +15 -0
- package/dist/actions/springTilt.js +57 -0
- package/dist/actions/springTilt.test.d.ts +1 -0
- package/dist/actions/springTilt.test.js +28 -0
- package/dist/components/blocks/FAQ.svelte +4 -2
- package/dist/components/blocks/FeatureGrid.svelte +50 -40
- package/dist/components/blocks/FeatureGrid.svelte.d.ts +1 -0
- package/dist/components/blocks/Testimonials.svelte +45 -31
- package/dist/components/blocks/Testimonials.svelte.d.ts +1 -0
- package/dist/components/elements/CodeDiff.svelte +454 -0
- package/dist/components/elements/CodeDiff.svelte.d.ts +38 -0
- package/dist/components/elements/CodeDiff.test.d.ts +1 -0
- package/dist/components/elements/CodeDiff.test.js +52 -0
- package/dist/components/elements/DataTable.dom.test.js +30 -2
- package/dist/components/elements/DataTable.svelte +128 -80
- package/dist/components/elements/DataTable.svelte.d.ts +19 -2
- package/dist/components/forms/Checkbox.svelte +30 -5
- package/dist/components/forms/Checkbox.svelte.d.ts +1 -0
- package/dist/components/forms/Combobox.svelte +31 -5
- package/dist/components/forms/Combobox.svelte.d.ts +6 -0
- package/dist/components/forms/CreditCardInput.svelte +32 -8
- package/dist/components/forms/CreditCardInput.svelte.d.ts +3 -0
- package/dist/components/forms/Form.dom.test.d.ts +1 -0
- package/dist/components/forms/Form.dom.test.js +59 -0
- package/dist/components/forms/Form.svelte +93 -3
- package/dist/components/forms/Form.svelte.d.ts +2 -0
- package/dist/components/forms/FormField.svelte +75 -11
- package/dist/components/forms/FormField.svelte.d.ts +2 -1
- package/dist/components/forms/Input.svelte +18 -1
- package/dist/components/forms/InputOTP.svelte +12 -3
- package/dist/components/forms/MultiSelect.svelte +33 -7
- package/dist/components/forms/MultiSelect.svelte.d.ts +6 -0
- package/dist/components/forms/NumberInput.svelte +23 -5
- package/dist/components/forms/NumberInput.svelte.d.ts +3 -0
- package/dist/components/forms/PhoneInput.svelte +36 -2
- package/dist/components/forms/PhoneInput.svelte.d.ts +5 -0
- package/dist/components/forms/Select.svelte +36 -18
- package/dist/components/forms/Select.svelte.d.ts +3 -0
- package/dist/components/forms/Switch.svelte +22 -3
- package/dist/components/forms/Switch.svelte.d.ts +1 -0
- package/dist/components/forms/Textarea.svelte +23 -2
- package/dist/components/forms/advanced-forms.test.js +21 -0
- package/dist/components/forms/form-context.d.ts +38 -0
- package/dist/components/forms/form-context.js +15 -0
- package/dist/components/layout/Footer.svelte +18 -30
- package/dist/components/saas/AuthCard.svelte +299 -30
- package/dist/components/saas/AuthCard.svelte.d.ts +5 -1
- package/dist/components/saas/PasskeyUI.svelte +235 -0
- package/dist/components/saas/PasskeyUI.svelte.d.ts +13 -0
- package/dist/components/saas/TwoFactorModal.svelte +253 -0
- package/dist/components/saas/TwoFactorModal.svelte.d.ts +9 -0
- package/dist/composables/useAuth.svelte.d.ts +732 -1
- package/dist/composables/useAuth.svelte.js +240 -2
- package/dist/composables/useDataTable.svelte.d.ts +34 -0
- package/dist/composables/useDataTable.svelte.js +130 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +11 -2
- package/dist/motion/transitions.d.ts +22 -0
- package/dist/motion/transitions.js +55 -0
- package/dist/server/auth/index.d.ts +25 -1
- package/dist/server/auth/index.js +10 -3
- package/dist/server/db/pagination.d.ts +36 -0
- package/dist/server/db/pagination.js +40 -0
- package/dist/server/db/pagination.test.d.ts +1 -0
- package/dist/server/db/pagination.test.js +41 -0
- package/dist/server/db/schema-pg.d.ts +269 -0
- package/dist/server/db/schema-pg.js +28 -2
- package/dist/server/db/schema-sqlite.d.ts +291 -0
- package/dist/server/db/schema-sqlite.js +30 -4
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,18 +231,19 @@ export const POST = createPolarWebhookHandler();
|
|
|
231
231
|
|
|
232
232
|
## 🧩 Component Library
|
|
233
233
|
|
|
234
|
-
| Category
|
|
235
|
-
|
|
|
236
|
-
| **Theming & Studio**
|
|
237
|
-
| **AI Primitives**
|
|
238
|
-
| **Charts (Pure SVG)**
|
|
239
|
-
| **SaaS Suite**
|
|
240
|
-
| **Admin Suite**
|
|
241
|
-
| **Elements**
|
|
242
|
-
| **Forms**
|
|
243
|
-
| **Layout & Blocks**
|
|
244
|
-
| **Overlays & Nav**
|
|
245
|
-
| **
|
|
234
|
+
| Category | Components |
|
|
235
|
+
| :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
236
|
+
| **Theming & Studio** | `<ThemeStudio>`, `theme`, `ACCENT_PALETTES`, `NEUTRAL_PALETTES`, `RADIUS_PRESETS`, Harmonic Radius Multiplier Scale, Chart Palette Series Tokens |
|
|
237
|
+
| **AI Primitives** | `<AiChat>`, `<PromptBar>`, `<AiThought>`, `<AiToolCall>` |
|
|
238
|
+
| **Charts (Pure SVG)** | `<LineChart>`, `<BarChart>`, `<DonutChart>` (0 external charting dependencies, auto-responsive, dark/light theme aware) |
|
|
239
|
+
| **SaaS & Auth Suite** | `<PasskeyUI>` (WebAuthn), `<TwoFactorModal>` (TOTP), `<AuthCard>` (Polymorphic), `<Gate>`, `<UserMenu>`, `<OrgSwitcher>`, `<PricingCard>`, `<PricingTable>`, `<SubscriptionCard>`, `useAuth`, `useGate`, `useUpload` |
|
|
240
|
+
| **Admin Suite** | `<AdminDashboard>`, `<RecordDrawer>`, `<DevSandbox>`, `<ImpersonationBanner>`, `createYaxaAdminHook`, `createDrizzleAdmin` |
|
|
241
|
+
| **Elements & Code** | `<CodeDiff>` (Interactive Diff & Changelog), `<CodeBlock>`, `<DataTable>` (Client & Server Modes), `Button`, `ButtonGroup`, `Badge`, `Avatar`, `AvatarGroup`, `Chip`, `Meter`, `MetricCard`, `Kbd`, `Icon`, `Spinner`, `Progress`, `Skeleton`, `Link`, `Logo`, `Tree`, `Timeline`, `Terminal`, `VirtualList`, `Carousel` |
|
|
242
|
+
| **Forms (Auto-Wired)** | `<Form>`, `<FormField>`, `Input`, `InputOTP`, `Textarea`, `Checkbox`, `Switch`, `Select`, `Combobox`, `MultiSelect`, `NumberInput`, `PhoneInput`, `CreditCardInput`, `RadioGroup`, `Slider`, `ColorPicker`, `ToggleGroup`, `DatePicker`, `DateRangePicker`, `Dropzone`, `<RichTextEditor>` |
|
|
243
|
+
| **Layout & Blocks** | `Container`, `Header`, `Footer`, `Section`, `Card`, `Divider`, `YaxaApp`, `DashboardShell` (with independent sidebar tokens), `ResizablePanels`, Page Blocks (`Hero`, `SaaS Metrics`, `FAQ`, `Testimonials`, `FeatureGrid`) |
|
|
244
|
+
| **Overlays & Nav** | `Tabs`, `Breadcrumb`, `Pagination`, `CommandPalette` (`⌘K`), `DropdownMenu`, `ContextMenu`, `Modal`, `Drawer`, `Slideover`, `Popover`, `Tooltip` (with `arrow={false}` & `unstyled`), `Alert`, `Accordion`, `NotificationCenter`, `Stepper` |
|
|
245
|
+
| **Motion & Actions** | `springTilt`, `smoothSlide`, `staggerFly`, `autosize`, `clickOutside`, `portal`, `sortableItem`, `infiniteScrollAction` |
|
|
246
|
+
| **Composables** | `useAuth`, `useGate`, `useUpload`, `useClipboard`, `useShortcuts`, `useColorMode`, `useToast`, `useMediaQuery`, `useDebounce`, `useIdle`, `useInfiniteScroll`, `useSortable`, `useDataTable`, `useLocale` |
|
|
246
247
|
|
|
247
248
|
---
|
|
248
249
|
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -8,3 +8,5 @@ export { infiniteScroll } from './infiniteScroll';
|
|
|
8
8
|
export type { InfiniteScrollActionOptions } from './infiniteScroll';
|
|
9
9
|
export { sortableItem } from './sortable';
|
|
10
10
|
export type { SortableItemOptions } from './sortable';
|
|
11
|
+
export { springTilt } from './springTilt';
|
|
12
|
+
export type { SpringTiltOptions } from './springTilt';
|
package/dist/actions/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SpringTiltOptions {
|
|
2
|
+
max?: number;
|
|
3
|
+
perspective?: number;
|
|
4
|
+
scale?: number;
|
|
5
|
+
stiffness?: number;
|
|
6
|
+
damping?: number;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Svelte Action that applies physics-driven 3D spring tilt on hover.
|
|
11
|
+
*/
|
|
12
|
+
export declare function springTilt(node: HTMLElement, options?: SpringTiltOptions): {
|
|
13
|
+
update(newOptions?: SpringTiltOptions): void;
|
|
14
|
+
destroy(): void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { spring } from 'svelte/motion';
|
|
2
|
+
/**
|
|
3
|
+
* Svelte Action that applies physics-driven 3D spring tilt on hover.
|
|
4
|
+
*/
|
|
5
|
+
export function springTilt(node, options = {}) {
|
|
6
|
+
let max = options.max ?? 10;
|
|
7
|
+
let perspective = options.perspective ?? 1000;
|
|
8
|
+
let scale = options.scale ?? 1.02;
|
|
9
|
+
const stiffness = options.stiffness ?? 0.12;
|
|
10
|
+
const damping = options.damping ?? 0.28;
|
|
11
|
+
let disabled = options.disabled ?? false;
|
|
12
|
+
const coords = spring({ x: 0, y: 0, scale: 1 }, { stiffness, damping });
|
|
13
|
+
const unsub = coords.subscribe((val) => {
|
|
14
|
+
if (disabled) {
|
|
15
|
+
node.style.transform = '';
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
node.style.transform = `perspective(${perspective}px) rotateX(${val.x.toFixed(2)}deg) rotateY(${val.y.toFixed(2)}deg) scale(${val.scale.toFixed(3)})`;
|
|
19
|
+
node.style.transformStyle = 'preserve-3d';
|
|
20
|
+
});
|
|
21
|
+
function onMouseMove(e) {
|
|
22
|
+
if (disabled)
|
|
23
|
+
return;
|
|
24
|
+
const rect = node.getBoundingClientRect();
|
|
25
|
+
const x = e.clientX - rect.left;
|
|
26
|
+
const y = e.clientY - rect.top;
|
|
27
|
+
const centerX = rect.width / 2;
|
|
28
|
+
const centerY = rect.height / 2;
|
|
29
|
+
const rotateX = -((y - centerY) / (centerY || 1)) * max;
|
|
30
|
+
const rotateY = ((x - centerX) / (centerX || 1)) * max;
|
|
31
|
+
coords.set({ x: rotateX, y: rotateY, scale });
|
|
32
|
+
}
|
|
33
|
+
function onMouseLeave() {
|
|
34
|
+
if (disabled)
|
|
35
|
+
return;
|
|
36
|
+
coords.set({ x: 0, y: 0, scale: 1 });
|
|
37
|
+
}
|
|
38
|
+
node.addEventListener('mousemove', onMouseMove);
|
|
39
|
+
node.addEventListener('mouseleave', onMouseLeave);
|
|
40
|
+
return {
|
|
41
|
+
update(newOptions = {}) {
|
|
42
|
+
max = newOptions.max ?? 10;
|
|
43
|
+
perspective = newOptions.perspective ?? 1000;
|
|
44
|
+
scale = newOptions.scale ?? 1.02;
|
|
45
|
+
disabled = Boolean(newOptions.disabled);
|
|
46
|
+
if (disabled) {
|
|
47
|
+
node.style.transform = '';
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
destroy() {
|
|
51
|
+
unsub();
|
|
52
|
+
node.removeEventListener('mousemove', onMouseMove);
|
|
53
|
+
node.removeEventListener('mouseleave', onMouseLeave);
|
|
54
|
+
node.style.transform = '';
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { springTilt } from './springTilt';
|
|
3
|
+
describe('springTilt Action', () => {
|
|
4
|
+
it('attaches event listeners and updates transform on mousemove', () => {
|
|
5
|
+
const div = document.createElement('div');
|
|
6
|
+
Object.defineProperty(div, 'getBoundingClientRect', {
|
|
7
|
+
value: () => ({
|
|
8
|
+
left: 0,
|
|
9
|
+
top: 0,
|
|
10
|
+
width: 200,
|
|
11
|
+
height: 200,
|
|
12
|
+
right: 200,
|
|
13
|
+
bottom: 200
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
const action = springTilt(div, { max: 15, perspective: 800 });
|
|
17
|
+
expect(div.style.transformStyle).toBe('preserve-3d');
|
|
18
|
+
// Dispatch mousemove
|
|
19
|
+
const moveEvent = new MouseEvent('mousemove', {
|
|
20
|
+
clientX: 150,
|
|
21
|
+
clientY: 150
|
|
22
|
+
});
|
|
23
|
+
div.dispatchEvent(moveEvent);
|
|
24
|
+
// Cleanup
|
|
25
|
+
action.destroy();
|
|
26
|
+
expect(div.style.transform).toBe('');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<script lang="ts">
|
|
26
26
|
import Icon from '../elements/Icon.svelte';
|
|
27
27
|
import Badge from '../elements/Badge.svelte';
|
|
28
|
+
import { smoothSlide } from '../../motion/transitions';
|
|
28
29
|
|
|
29
30
|
let {
|
|
30
31
|
title = 'Frequently Asked Questions',
|
|
@@ -120,14 +121,15 @@
|
|
|
120
121
|
<Icon
|
|
121
122
|
name="chevron-down"
|
|
122
123
|
size="sm"
|
|
123
|
-
class="ml-4 shrink-0 text-neutral-400 transition-transform duration-
|
|
124
|
-
? 'rotate-180'
|
|
124
|
+
class="ml-4 shrink-0 text-neutral-400 transition-transform duration-250 ease-out {isOpen
|
|
125
|
+
? 'rotate-180 text-primary-600 dark:text-primary-400'
|
|
125
126
|
: ''}"
|
|
126
127
|
/>
|
|
127
128
|
</button>
|
|
128
129
|
|
|
129
130
|
{#if isOpen}
|
|
130
131
|
<div
|
|
132
|
+
transition:smoothSlide={{ duration: 220 }}
|
|
131
133
|
class="border-t border-neutral-100/60 px-5 pt-3 pb-5 text-xs leading-relaxed text-neutral-600 dark:border-neutral-800/60 dark:text-neutral-400"
|
|
132
134
|
>
|
|
133
135
|
{item.answer}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
badge?: string;
|
|
22
22
|
features?: FeatureItem[];
|
|
23
23
|
columns?: 2 | 3 | 4;
|
|
24
|
+
interactive?: boolean;
|
|
24
25
|
class?: string;
|
|
25
26
|
};
|
|
26
27
|
</script>
|
|
@@ -29,6 +30,8 @@
|
|
|
29
30
|
import Icon from '../elements/Icon.svelte';
|
|
30
31
|
import Badge from '../elements/Badge.svelte';
|
|
31
32
|
import Card from '../layout/Card.svelte';
|
|
33
|
+
import { springTilt } from '../../actions/springTilt';
|
|
34
|
+
import { staggerFly } from '../../motion/transitions';
|
|
32
35
|
|
|
33
36
|
let {
|
|
34
37
|
title = 'Everything you need to ship faster',
|
|
@@ -36,6 +39,7 @@
|
|
|
36
39
|
badge = 'Features',
|
|
37
40
|
features = [],
|
|
38
41
|
columns = 3,
|
|
42
|
+
interactive = true,
|
|
39
43
|
class: className = ''
|
|
40
44
|
}: FeatureGridProps = $props();
|
|
41
45
|
|
|
@@ -76,51 +80,57 @@
|
|
|
76
80
|
{/if}
|
|
77
81
|
|
|
78
82
|
<div class="grid gap-6 {gridCols}">
|
|
79
|
-
{#each features as feature (feature.id)}
|
|
80
|
-
<
|
|
81
|
-
|
|
83
|
+
{#each features as feature, idx (feature.id)}
|
|
84
|
+
<div
|
|
85
|
+
in:staggerFly={{ index: idx, y: 24, duration: 320 }}
|
|
86
|
+
use:springTilt={{ disabled: !interactive, max: 8, scale: 1.015 }}
|
|
87
|
+
class="h-full"
|
|
82
88
|
>
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
<Card
|
|
90
|
+
class="group relative flex h-full flex-col justify-between p-6 transition-all duration-200 hover:shadow-xl dark:hover:border-neutral-700"
|
|
91
|
+
>
|
|
92
|
+
<div>
|
|
93
|
+
<!-- Icon & Tag Row -->
|
|
94
|
+
<div class="mb-4 flex items-center justify-between">
|
|
95
|
+
{#if feature.icon}
|
|
96
|
+
<div
|
|
97
|
+
class="flex h-10 w-10 items-center justify-center rounded-xl bg-primary-50 text-primary-600 transition-colors group-hover:bg-primary-600 group-hover:text-white dark:bg-primary-950/60 dark:text-primary-400"
|
|
98
|
+
>
|
|
99
|
+
<Icon name={feature.icon} size="sm" />
|
|
100
|
+
</div>
|
|
101
|
+
{/if}
|
|
93
102
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
{#if feature.tag}
|
|
104
|
+
<Badge size="xs" variant="subtle" color="primary">
|
|
105
|
+
{feature.tag}
|
|
106
|
+
</Badge>
|
|
107
|
+
{/if}
|
|
108
|
+
</div>
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
<!-- Title -->
|
|
111
|
+
<h3 class="text-base font-bold text-neutral-900 dark:text-white">
|
|
112
|
+
{feature.title}
|
|
113
|
+
</h3>
|
|
105
114
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
</div>
|
|
111
|
-
|
|
112
|
-
{#if feature.href}
|
|
113
|
-
<div class="mt-4 border-t border-neutral-100 pt-3 dark:border-neutral-800/80">
|
|
114
|
-
<a
|
|
115
|
-
href={feature.href}
|
|
116
|
-
class="inline-flex items-center gap-1 text-xs font-semibold text-primary-600 transition-colors hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300"
|
|
117
|
-
>
|
|
118
|
-
Learn more
|
|
119
|
-
<Icon name="arrow-right" size="xs" />
|
|
120
|
-
</a>
|
|
115
|
+
<!-- Description -->
|
|
116
|
+
<p class="mt-2 text-xs leading-relaxed text-neutral-600 dark:text-neutral-400">
|
|
117
|
+
{feature.description}
|
|
118
|
+
</p>
|
|
121
119
|
</div>
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
|
|
121
|
+
{#if feature.href}
|
|
122
|
+
<div class="mt-4 border-t border-neutral-100 pt-3 dark:border-neutral-800/80">
|
|
123
|
+
<a
|
|
124
|
+
href={feature.href}
|
|
125
|
+
class="inline-flex items-center gap-1 text-xs font-semibold text-primary-600 transition-colors hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300"
|
|
126
|
+
>
|
|
127
|
+
Learn more
|
|
128
|
+
<Icon name="arrow-right" size="xs" />
|
|
129
|
+
</a>
|
|
130
|
+
</div>
|
|
131
|
+
{/if}
|
|
132
|
+
</Card>
|
|
133
|
+
</div>
|
|
124
134
|
{/each}
|
|
125
135
|
</div>
|
|
126
136
|
</section>
|
|
@@ -15,6 +15,7 @@ export type FeatureGridProps = VariantProps<typeof featureGridVariants> & {
|
|
|
15
15
|
badge?: string;
|
|
16
16
|
features?: FeatureItem[];
|
|
17
17
|
columns?: 2 | 3 | 4;
|
|
18
|
+
interactive?: boolean;
|
|
18
19
|
class?: string;
|
|
19
20
|
};
|
|
20
21
|
declare const FeatureGrid: import("svelte").Component<FeatureGridProps, {}, "">;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
badge?: string;
|
|
22
22
|
items?: TestimonialItem[];
|
|
23
23
|
columns?: 1 | 2 | 3;
|
|
24
|
+
interactive?: boolean;
|
|
24
25
|
class?: string;
|
|
25
26
|
};
|
|
26
27
|
</script>
|
|
@@ -30,6 +31,8 @@
|
|
|
30
31
|
import Card from '../layout/Card.svelte';
|
|
31
32
|
import Badge from '../elements/Badge.svelte';
|
|
32
33
|
import Avatar from '../elements/Avatar.svelte';
|
|
34
|
+
import { springTilt } from '../../actions/springTilt';
|
|
35
|
+
import { staggerFly } from '../../motion/transitions';
|
|
33
36
|
|
|
34
37
|
let {
|
|
35
38
|
title = 'Loved by solo founders & engineering teams',
|
|
@@ -37,6 +40,7 @@
|
|
|
37
40
|
badge = 'Social Proof',
|
|
38
41
|
items = [],
|
|
39
42
|
columns = 3,
|
|
43
|
+
interactive = true,
|
|
40
44
|
class: className = ''
|
|
41
45
|
}: TestimonialsProps = $props();
|
|
42
46
|
|
|
@@ -77,42 +81,52 @@
|
|
|
77
81
|
{/if}
|
|
78
82
|
|
|
79
83
|
<div class="grid gap-6 {gridCols}">
|
|
80
|
-
{#each items as item (item.id)}
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
{#each items as item, idx (item.id)}
|
|
85
|
+
<div
|
|
86
|
+
in:staggerFly={{ index: idx, y: 20, duration: 300 }}
|
|
87
|
+
use:springTilt={{ disabled: !interactive, max: 6, scale: 1.01 }}
|
|
88
|
+
class="h-full"
|
|
89
|
+
>
|
|
90
|
+
<Card
|
|
91
|
+
class="flex h-full flex-col justify-between p-6 transition-all duration-200 hover:shadow-lg dark:hover:border-neutral-700"
|
|
92
|
+
>
|
|
93
|
+
<div>
|
|
94
|
+
<!-- Star Rating -->
|
|
95
|
+
{#if item.rating}
|
|
96
|
+
<div class="mb-4 flex items-center gap-1 text-amber-400">
|
|
97
|
+
{#each Array.from({ length: item.rating }, (_, i) => i) as starIndex (starIndex)}
|
|
98
|
+
<Icon name="star" size="xs" />
|
|
99
|
+
{/each}
|
|
100
|
+
</div>
|
|
101
|
+
{/if}
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
<!-- Quote Text -->
|
|
104
|
+
<blockquote
|
|
105
|
+
class="text-sm leading-relaxed text-neutral-700 italic dark:text-neutral-300"
|
|
106
|
+
>
|
|
107
|
+
"{item.quote}"
|
|
108
|
+
</blockquote>
|
|
109
|
+
</div>
|
|
97
110
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
<!-- Author Info -->
|
|
112
|
+
<div
|
|
113
|
+
class="mt-6 flex items-center gap-3 border-t border-neutral-100 pt-4 dark:border-neutral-800"
|
|
114
|
+
>
|
|
115
|
+
<Avatar src={item.avatar} alt={item.author} size="sm" />
|
|
103
116
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</div>
|
|
108
|
-
{#if item.role || item.company}
|
|
109
|
-
<div class="truncate text-[11px] text-neutral-500 dark:text-neutral-400">
|
|
110
|
-
{item.role}{item.role && item.company ? ' • ' : ''}{item.company}
|
|
117
|
+
<div class="min-w-0 flex-1">
|
|
118
|
+
<div class="truncate text-xs font-semibold text-neutral-900 dark:text-white">
|
|
119
|
+
{item.author}
|
|
111
120
|
</div>
|
|
112
|
-
|
|
121
|
+
{#if item.role || item.company}
|
|
122
|
+
<div class="truncate text-[11px] text-neutral-500 dark:text-neutral-400">
|
|
123
|
+
{item.role}{item.role && item.company ? ' • ' : ''}{item.company}
|
|
124
|
+
</div>
|
|
125
|
+
{/if}
|
|
126
|
+
</div>
|
|
113
127
|
</div>
|
|
114
|
-
</
|
|
115
|
-
</
|
|
128
|
+
</Card>
|
|
129
|
+
</div>
|
|
116
130
|
{/each}
|
|
117
131
|
</div>
|
|
118
132
|
</section>
|
|
@@ -15,6 +15,7 @@ export type TestimonialsProps = VariantProps<typeof testimonialsVariants> & {
|
|
|
15
15
|
badge?: string;
|
|
16
16
|
items?: TestimonialItem[];
|
|
17
17
|
columns?: 1 | 2 | 3;
|
|
18
|
+
interactive?: boolean;
|
|
18
19
|
class?: string;
|
|
19
20
|
};
|
|
20
21
|
declare const Testimonials: import("svelte").Component<TestimonialsProps, {}, "">;
|