@olwiba/ui 0.2.25 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/docs/app/app-shell.mdx +71 -0
- package/content/docs/app/auth-section.mdx +73 -0
- package/content/docs/app/billing-panel.mdx +38 -0
- package/content/docs/app/empty-state.mdx +47 -0
- package/content/docs/app/error-page.mdx +31 -0
- package/content/docs/app/index.mdx +13 -0
- package/content/docs/app/meta.json +4 -0
- package/content/docs/app/onboarding-wizard.mdx +41 -0
- package/content/docs/app/settings-section.mdx +37 -0
- package/content/docs/app/team-members-panel.mdx +35 -0
- package/content/docs/app/update-banner.mdx +43 -0
- package/content/docs/components/activity-feed.mdx +42 -0
- package/content/docs/components/chart.mdx +47 -0
- package/content/docs/components/command-menu.mdx +41 -0
- package/content/docs/components/confirm-dialog.mdx +26 -0
- package/content/docs/components/context-menu.mdx +26 -0
- package/content/docs/components/data-table.mdx +40 -0
- package/content/docs/components/dock.mdx +26 -0
- package/content/docs/components/feature-card.mdx +26 -0
- package/content/docs/components/file-upload.mdx +35 -0
- package/content/docs/components/full-page-spinner.mdx +44 -0
- package/content/docs/components/glass-card.mdx +26 -0
- package/content/docs/components/image-card.mdx +26 -0
- package/content/docs/components/index.mdx +49 -0
- package/content/docs/components/load-more.mdx +34 -0
- package/content/docs/components/meta.json +35 -0
- package/content/docs/components/notifications-popover.mdx +34 -0
- package/content/docs/components/notify.mdx +40 -0
- package/content/docs/components/olwiba-ui-context.mdx +69 -0
- package/content/docs/components/page-header.mdx +42 -0
- package/content/docs/components/pricing-card.mdx +26 -0
- package/content/docs/components/register-hotkeys.mdx +50 -0
- package/content/docs/components/root-error-fallback.mdx +39 -0
- package/content/docs/components/spotlight.mdx +26 -0
- package/content/docs/components/stat-card.mdx +26 -0
- package/content/docs/components/suspensed.mdx +42 -0
- package/content/docs/components/testimonial-card.mdx +26 -0
- package/content/docs/components/theme-color-updater.mdx +40 -0
- package/content/docs/components/theme-switch-minimal.mdx +32 -0
- package/content/docs/components/version-banner.mdx +44 -0
- package/content/docs/email/action-email.mdx +62 -0
- package/content/docs/email/email-layout.mdx +58 -0
- package/content/docs/email/email-link-fallback.mdx +46 -0
- package/content/docs/email/index.mdx +32 -0
- package/content/docs/email/meta.json +10 -0
- package/content/docs/email/notice-email.mdx +51 -0
- package/content/docs/hooks/index.mdx +21 -0
- package/content/docs/hooks/meta.json +16 -0
- package/content/docs/hooks/use-confirm.mdx +81 -0
- package/content/docs/hooks/use-controlled-open.mdx +53 -0
- package/content/docs/hooks/use-copy-to-clipboard.mdx +26 -0
- package/content/docs/hooks/use-debounce.mdx +26 -0
- package/content/docs/hooks/use-intersection-observer.mdx +26 -0
- package/content/docs/hooks/use-local-storage.mdx +26 -0
- package/content/docs/hooks/use-media-query.mdx +26 -0
- package/content/docs/hooks/use-mounted.mdx +39 -0
- package/content/docs/hooks/use-pagination.mdx +26 -0
- package/content/docs/hooks/use-scrolled-past.mdx +44 -0
- package/content/docs/index.mdx +68 -0
- package/content/docs/marketing/contact-section.mdx +16 -0
- package/content/docs/marketing/cta-section.mdx +31 -0
- package/content/docs/marketing/faq-section.mdx +16 -0
- package/content/docs/marketing/features-section.mdx +23 -0
- package/content/docs/marketing/footer.mdx +16 -0
- package/content/docs/marketing/hero-section.mdx +16 -0
- package/content/docs/marketing/index.mdx +22 -0
- package/content/docs/marketing/meta.json +20 -0
- package/content/docs/marketing/navbar.mdx +16 -0
- package/content/docs/marketing/newsletter-section.mdx +16 -0
- package/content/docs/marketing/post-list.mdx +32 -0
- package/content/docs/marketing/pricing-section.mdx +16 -0
- package/content/docs/marketing/stats-section.mdx +16 -0
- package/content/docs/marketing/steps-section.mdx +60 -0
- package/content/docs/marketing/team-section.mdx +16 -0
- package/content/docs/marketing/testimonials-section.mdx +16 -0
- package/content/docs/mechanics/carousel.mdx +42 -0
- package/content/docs/mechanics/index.mdx +11 -0
- package/content/docs/mechanics/meta.json +4 -0
- package/content/docs/mechanics/sortable.mdx +44 -0
- package/content/docs/meta.json +4 -0
- package/content/docs/motion/animated-swap.mdx +75 -0
- package/content/docs/motion/count-up.mdx +31 -0
- package/content/docs/motion/fade-in.mdx +34 -0
- package/content/docs/motion/index.mdx +12 -0
- package/content/docs/motion/meta.json +4 -0
- package/content/docs/motion/page-transition.mdx +29 -0
- package/content/docs/motion/stagger-children.mdx +31 -0
- package/content/docs/overlays/index.mdx +9 -0
- package/content/docs/overlays/meta.json +4 -0
- package/content/docs/overlays/overlay.mdx +60 -0
- package/content/docs/overlays/underlay.mdx +56 -0
- package/dist/email/index.d.ts +8 -1
- package/dist/email/index.js +3 -3
- package/dist/email/index.js.map +1 -1
- package/docs-manifest.json +253 -0
- package/package.json +16 -8
- package/site/demos/action-email.tsx +6 -0
- package/site/demos/activity-feed.tsx +42 -0
- package/site/demos/animated-swap.tsx +42 -0
- package/site/demos/app-shell.tsx +47 -0
- package/site/demos/app-ui-page.tsx +9 -0
- package/site/demos/application-sidebar-block.tsx +9 -0
- package/site/demos/auth-section.tsx +15 -0
- package/site/demos/auth-split-block.tsx +9 -0
- package/site/demos/billing-page.tsx +157 -0
- package/site/demos/billing-panel.tsx +26 -0
- package/site/demos/carousel.tsx +34 -0
- package/site/demos/chart.tsx +55 -0
- package/site/demos/command-menu.tsx +39 -0
- package/site/demos/confirm-dialog.tsx +27 -0
- package/site/demos/contact-form-block.tsx +3 -0
- package/site/demos/contact-section.tsx +2 -0
- package/site/demos/context-menu.tsx +30 -0
- package/site/demos/count-up.tsx +23 -0
- package/site/demos/cta-section.tsx +6 -0
- package/site/demos/dashboard-overview-block.tsx +9 -0
- package/site/demos/dashboard-shell-block.tsx +10 -0
- package/site/demos/data-table.tsx +47 -0
- package/site/demos/demo-brand.tsx +23 -0
- package/site/demos/dock.tsx +20 -0
- package/site/demos/document-sidebar-block.tsx +9 -0
- package/site/demos/email-examples.tsx +58 -0
- package/site/demos/email-frame.tsx +50 -0
- package/site/demos/email-layout.tsx +6 -0
- package/site/demos/email-link-fallback.tsx +6 -0
- package/site/demos/email-previews.generated.ts +11 -0
- package/site/demos/empty-state-block.tsx +3 -0
- package/site/demos/empty-state.tsx +23 -0
- package/site/demos/error-page.tsx +5 -0
- package/site/demos/fade-in.tsx +18 -0
- package/site/demos/faq-section.tsx +6 -0
- package/site/demos/feature-card.tsx +13 -0
- package/site/demos/features-section.tsx +6 -0
- package/site/demos/file-upload.tsx +16 -0
- package/site/demos/footer.tsx +42 -0
- package/site/demos/full-page-spinner.tsx +9 -0
- package/site/demos/glass-card.tsx +14 -0
- package/site/demos/glow-effect.tsx +3 -0
- package/site/demos/gradient-background.tsx +3 -0
- package/site/demos/grid-pattern.tsx +3 -0
- package/site/demos/hero-section.tsx +6 -0
- package/site/demos/image-card.tsx +17 -0
- package/site/demos/login-block.tsx +9 -0
- package/site/demos/marketing-cta-block.tsx +3 -0
- package/site/demos/marketing-demo-data.tsx +114 -0
- package/site/demos/marketing-faq-block.tsx +3 -0
- package/site/demos/marketing-features-block.tsx +3 -0
- package/site/demos/marketing-footer-block.tsx +3 -0
- package/site/demos/marketing-hero-block.tsx +10 -0
- package/site/demos/marketing-nav-block.tsx +3 -0
- package/site/demos/marketing-newsletter-block.tsx +3 -0
- package/site/demos/marketing-page.tsx +52 -0
- package/site/demos/marketing-pricing-block.tsx +3 -0
- package/site/demos/marketing-stats-block.tsx +3 -0
- package/site/demos/marketing-team-block.tsx +3 -0
- package/site/demos/marketing-testimonials-block.tsx +3 -0
- package/site/demos/navbar.tsx +32 -0
- package/site/demos/newsletter-section.tsx +2 -0
- package/site/demos/noise-overlay.tsx +3 -0
- package/site/demos/not-found-block.tsx +3 -0
- package/site/demos/notice-email.tsx +6 -0
- package/site/demos/notifications-popover.tsx +27 -0
- package/site/demos/notify.tsx +44 -0
- package/site/demos/olwiba-ui-context.tsx +51 -0
- package/site/demos/onboarding-block.tsx +3 -0
- package/site/demos/onboarding-wizard.tsx +43 -0
- package/site/demos/overlay.tsx +72 -0
- package/site/demos/page-header.tsx +15 -0
- package/site/demos/page-transition.tsx +34 -0
- package/site/demos/post-list.tsx +24 -0
- package/site/demos/pricing-card.tsx +27 -0
- package/site/demos/pricing-section.tsx +6 -0
- package/site/demos/register-hotkeys.tsx +39 -0
- package/site/demos/root-error-fallback.tsx +28 -0
- package/site/demos/settings-layout-block.tsx +3 -0
- package/site/demos/settings-section.tsx +22 -0
- package/site/demos/sign-in-page.tsx +10 -0
- package/site/demos/sortable.tsx +30 -0
- package/site/demos/spotlight.tsx +29 -0
- package/site/demos/stagger-children.tsx +16 -0
- package/site/demos/stat-card.tsx +12 -0
- package/site/demos/stats-section.tsx +6 -0
- package/site/demos/steps-section.tsx +66 -0
- package/site/demos/suspensed.tsx +31 -0
- package/site/demos/team-members-panel.tsx +26 -0
- package/site/demos/team-section.tsx +2 -0
- package/site/demos/testimonial-card.tsx +17 -0
- package/site/demos/testimonials-section.tsx +6 -0
- package/site/demos/theme-color-updater.tsx +32 -0
- package/site/demos/theme-switch-minimal.tsx +10 -0
- package/site/demos/underlay.tsx +74 -0
- package/site/demos/update-banner.tsx +31 -0
- package/site/demos/use-copy-to-clipboard.tsx +21 -0
- package/site/demos/use-debounce.tsx +25 -0
- package/site/demos/use-intersection-observer.tsx +32 -0
- package/site/demos/use-local-storage.tsx +22 -0
- package/site/demos/use-media-query.tsx +31 -0
- package/site/demos/use-pagination.tsx +31 -0
- package/site/demos/user-profile-block.tsx +3 -0
- package/site/demos/version-banner.tsx +27 -0
- package/src/email/EmailLinkFallback.tsx +7 -1
- package/src/email/index.ts +3 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { BillingPanel } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
export default function Demo() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="mx-auto max-w-2xl p-8">
|
|
7
|
+
<BillingPanel
|
|
8
|
+
planName="Nexus Pro"
|
|
9
|
+
planPrice="$29/mo"
|
|
10
|
+
renewalDate="March 12, 2026"
|
|
11
|
+
usage={[
|
|
12
|
+
{ label: 'Seats', used: 6, limit: 10 },
|
|
13
|
+
{ label: 'Storage', used: 42, limit: 100, unit: 'GB' },
|
|
14
|
+
]}
|
|
15
|
+
paymentMethod={{ brand: 'Visa', last4: '4242', expiry: '08/27' }}
|
|
16
|
+
invoices={[
|
|
17
|
+
{ id: 'inv_1', date: 'Feb 12, 2026', amount: '$29.00', status: 'paid', downloadUrl: '#' },
|
|
18
|
+
{ id: 'inv_2', date: 'Jan 12, 2026', amount: '$29.00', status: 'paid', downloadUrl: '#' },
|
|
19
|
+
{ id: 'inv_3', date: 'Dec 12, 2025', amount: '$29.00', status: 'void' },
|
|
20
|
+
]}
|
|
21
|
+
onManagePlan={() => {}}
|
|
22
|
+
onUpdatePaymentMethod={() => {}}
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Carousel, FeatureCard, ImageCard } from '@olwiba/ui';
|
|
3
|
+
import { Blocks, Layers, Palette, Rocket, ShieldCheck, Zap } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
const features = [
|
|
6
|
+
{ icon: Blocks, title: 'Composable blocks', description: 'App-level sections that snap together into full pages.' },
|
|
7
|
+
{ icon: Palette, title: 'Mode-aware', description: 'Default, playful, and smooth looks from one prop.' },
|
|
8
|
+
{ icon: Zap, title: 'Fast by default', description: 'Scroll-snap and CSS transitions — no heavy dependencies.' },
|
|
9
|
+
{ icon: ShieldCheck, title: 'Type-safe', description: 'Every block ships complete TypeScript types.' },
|
|
10
|
+
{ icon: Layers, title: 'Layered system', description: 'Primitives, blocks, and pages that build on each other.' },
|
|
11
|
+
{ icon: Rocket, title: 'Ship faster', description: 'Assemble a SaaS app from ready-made pieces.' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export default function Demo() {
|
|
15
|
+
return (
|
|
16
|
+
<div className="mx-auto max-w-4xl space-y-10 p-8">
|
|
17
|
+
<Carousel ariaLabel="Platform features">
|
|
18
|
+
{features.map((f) => (
|
|
19
|
+
<FeatureCard key={f.title} icon={f.icon} title={f.title} description={f.description} />
|
|
20
|
+
))}
|
|
21
|
+
</Carousel>
|
|
22
|
+
|
|
23
|
+
<Carousel ariaLabel="Screenshots" itemClassName="w-[220px]">
|
|
24
|
+
{[1, 2, 3, 4, 5].map((n) => (
|
|
25
|
+
<ImageCard
|
|
26
|
+
key={n}
|
|
27
|
+
src={`https://picsum.photos/seed/carousel-${n}/440/280`}
|
|
28
|
+
alt={`Screenshot ${n}`}
|
|
29
|
+
/>
|
|
30
|
+
))}
|
|
31
|
+
</Carousel>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Chart } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
const revenue = [
|
|
5
|
+
{ month: 'Jan', mrr: 4200, costs: 2800 },
|
|
6
|
+
{ month: 'Feb', mrr: 4900, costs: 2900 },
|
|
7
|
+
{ month: 'Mar', mrr: 5600, costs: 3100 },
|
|
8
|
+
{ month: 'Apr', mrr: 5400, costs: 3300 },
|
|
9
|
+
{ month: 'May', mrr: 6800, costs: 3400 },
|
|
10
|
+
{ month: 'Jun', mrr: 7900, costs: 3600 },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const signups = [
|
|
14
|
+
{ week: 'W1', count: 132 },
|
|
15
|
+
{ week: 'W2', count: 187 },
|
|
16
|
+
{ week: 'W3', count: 156 },
|
|
17
|
+
{ week: 'W4', count: 231 },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const plans = [
|
|
21
|
+
{ plan: 'Free', users: 1240 },
|
|
22
|
+
{ plan: 'Pro', users: 480 },
|
|
23
|
+
{ plan: 'Team', users: 190 },
|
|
24
|
+
{ plan: 'Enterprise', users: 36 },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export default function Demo() {
|
|
28
|
+
return (
|
|
29
|
+
<div className="mx-auto grid max-w-4xl gap-10 p-8">
|
|
30
|
+
<div>
|
|
31
|
+
<p className="mb-3 text-sm font-medium">Revenue vs costs</p>
|
|
32
|
+
<Chart
|
|
33
|
+
type="line"
|
|
34
|
+
data={revenue}
|
|
35
|
+
xKey="month"
|
|
36
|
+
series={[
|
|
37
|
+
{ key: 'mrr', label: 'MRR' },
|
|
38
|
+
{ key: 'costs', label: 'Costs' },
|
|
39
|
+
]}
|
|
40
|
+
valueFormatter={(v) => `$${v.toLocaleString()}`}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
<div className="grid gap-10 sm:grid-cols-2">
|
|
44
|
+
<div>
|
|
45
|
+
<p className="mb-3 text-sm font-medium">Weekly signups</p>
|
|
46
|
+
<Chart type="bar" data={signups} xKey="week" series={[{ key: 'count', label: 'Signups' }]} height={220} />
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
<p className="mb-3 text-sm font-medium">Users by plan</p>
|
|
50
|
+
<Chart type="donut" data={plans} xKey="plan" series={[{ key: 'users' }]} height={220} />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { CommandMenu } from '@olwiba/ui';
|
|
4
|
+
import { Button } from '@olwiba/cn';
|
|
5
|
+
import { CreditCard, LayoutDashboard, Settings, UserPlus } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
export default function Demo() {
|
|
8
|
+
const [open, setOpen] = React.useState(false);
|
|
9
|
+
const [selected, setSelected] = React.useState<string | null>(null);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex flex-col items-center gap-4 p-8">
|
|
13
|
+
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
14
|
+
Search… <span className="ml-2 text-xs text-muted-foreground">⌘K</span>
|
|
15
|
+
</Button>
|
|
16
|
+
{selected && <p className="text-sm text-muted-foreground">Selected: <strong>{selected}</strong></p>}
|
|
17
|
+
<CommandMenu
|
|
18
|
+
open={open}
|
|
19
|
+
onOpenChange={setOpen}
|
|
20
|
+
groups={[
|
|
21
|
+
{
|
|
22
|
+
heading: 'Navigate',
|
|
23
|
+
items: [
|
|
24
|
+
{ id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard, onSelect: () => setSelected('Dashboard') },
|
|
25
|
+
{ id: 'billing', label: 'Billing', icon: CreditCard, onSelect: () => setSelected('Billing') },
|
|
26
|
+
{ id: 'settings', label: 'Settings', icon: Settings, onSelect: () => setSelected('Settings') },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
heading: 'Actions',
|
|
31
|
+
items: [
|
|
32
|
+
{ id: 'invite', label: 'Invite teammate', icon: UserPlus, shortcut: '⌘I', onSelect: () => setSelected('Invite teammate') },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
]}
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ConfirmDialog, useConfirm } from '@olwiba/ui';
|
|
4
|
+
import { Button } from '@olwiba/cn';
|
|
5
|
+
|
|
6
|
+
export default function Demo() {
|
|
7
|
+
const { confirm, isOpen, options, handleConfirm, handleCancel } = useConfirm();
|
|
8
|
+
const [result, setResult] = React.useState<string | null>(null);
|
|
9
|
+
|
|
10
|
+
async function handleDelete() {
|
|
11
|
+
const ok = await confirm({
|
|
12
|
+
title: 'Delete project?',
|
|
13
|
+
description: 'This action cannot be undone. All data will be permanently removed.',
|
|
14
|
+
confirmLabel: 'Delete',
|
|
15
|
+
cancelLabel: 'Keep it',
|
|
16
|
+
});
|
|
17
|
+
setResult(ok ? 'Deleted' : 'Cancelled');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex flex-col items-center gap-4 p-8">
|
|
22
|
+
<Button variant="destructive" onClick={handleDelete}>Delete project</Button>
|
|
23
|
+
{result && <p className="text-sm text-muted-foreground">Result: <strong>{result}</strong></p>}
|
|
24
|
+
<ConfirmDialog isOpen={isOpen} options={options} handleConfirm={handleConfirm} handleCancel={handleCancel} destructive />
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { ContextMenu } from '@olwiba/ui';
|
|
3
|
+
import type { ContextMenuDef } from '@olwiba/ui';
|
|
4
|
+
|
|
5
|
+
const items: ContextMenuDef[] = [
|
|
6
|
+
{ type: 'item', label: 'Open', shortcut: '↵' },
|
|
7
|
+
{ type: 'item', label: 'Open in new tab', shortcut: '⌘↵' },
|
|
8
|
+
{ type: 'separator' },
|
|
9
|
+
{ type: 'item', label: 'Rename', shortcut: 'F2' },
|
|
10
|
+
{ type: 'item', label: 'Duplicate', shortcut: '⌘D' },
|
|
11
|
+
{ type: 'separator' },
|
|
12
|
+
{ type: 'sub', label: 'Share', items: [
|
|
13
|
+
{ type: 'item', label: 'Copy link' },
|
|
14
|
+
{ type: 'item', label: 'Email...' },
|
|
15
|
+
]},
|
|
16
|
+
{ type: 'separator' },
|
|
17
|
+
{ type: 'item', label: 'Delete', shortcut: '⌫' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export default function Demo() {
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex items-center justify-center p-8">
|
|
23
|
+
<ContextMenu items={items}>
|
|
24
|
+
<div className="flex h-32 w-64 cursor-context-menu select-none items-center justify-center rounded-xl border border-dashed text-sm text-muted-foreground">
|
|
25
|
+
Right-click here
|
|
26
|
+
</div>
|
|
27
|
+
</ContextMenu>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { CountUp } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
const stats = [
|
|
5
|
+
{ to: 12400, prefix: '£', suffix: '', decimals: 0, label: 'Revenue' },
|
|
6
|
+
{ to: 98.6, prefix: '', suffix: '%', decimals: 1, label: 'Uptime' },
|
|
7
|
+
{ to: 3200, prefix: '', suffix: '+', decimals: 0, label: 'Users' },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export default function Demo() {
|
|
11
|
+
return (
|
|
12
|
+
<div className="grid grid-cols-3 gap-4 p-8 text-center">
|
|
13
|
+
{stats.map(({ to, prefix, suffix, decimals, label }) => (
|
|
14
|
+
<div key={label} className="rounded-2xl border bg-card p-6">
|
|
15
|
+
<div className="text-3xl font-bold tabular-nums">
|
|
16
|
+
<CountUp to={to} prefix={prefix} suffix={suffix} decimals={decimals} duration={1800} />
|
|
17
|
+
</div>
|
|
18
|
+
<div className="mt-1 text-sm text-muted-foreground">{label}</div>
|
|
19
|
+
</div>
|
|
20
|
+
))}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AppShell } from '@olwiba/ui';
|
|
2
|
+
|
|
3
|
+
export default function DashboardShellBlockDemo() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="h-full min-h-0 w-full max-w-[1400px]">
|
|
6
|
+
{/* contained: rail is absolute within this shell; provider uses layout=embedded + sandbox height */}
|
|
7
|
+
<AppShell sidebarPosition="contained" />
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import type { ColumnDef } from '@tanstack/react-table';
|
|
4
|
+
import { DataTable } from '@olwiba/ui';
|
|
5
|
+
import { Badge, Button } from '@olwiba/cn';
|
|
6
|
+
|
|
7
|
+
interface Project {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
status: 'live' | 'building' | 'paused';
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projects: Project[] = [
|
|
15
|
+
{ id: '1', name: 'nexus-genesis', status: 'live', updatedAt: '2h ago' },
|
|
16
|
+
{ id: '2', name: 'kiwiana-storefront', status: 'building', updatedAt: '5h ago' },
|
|
17
|
+
{ id: '3', name: 'olwiba-docs', status: 'live', updatedAt: '1d ago' },
|
|
18
|
+
{ id: '4', name: 'genesis-render', status: 'paused', updatedAt: '3d ago' },
|
|
19
|
+
{ id: '5', name: 'nexus-sync', status: 'live', updatedAt: '4d ago' },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const statusVariant = { live: 'secondary', building: 'outline', paused: 'destructive' } as const;
|
|
23
|
+
|
|
24
|
+
const columns: ColumnDef<Project>[] = [
|
|
25
|
+
{ accessorKey: 'name', header: 'Project' },
|
|
26
|
+
{
|
|
27
|
+
accessorKey: 'status',
|
|
28
|
+
header: 'Status',
|
|
29
|
+
cell: ({ row }) => <Badge variant={statusVariant[row.original.status]} className="capitalize">{row.original.status}</Badge>,
|
|
30
|
+
},
|
|
31
|
+
{ accessorKey: 'updatedAt', header: 'Updated' },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export default function Demo() {
|
|
35
|
+
return (
|
|
36
|
+
<div className="p-8">
|
|
37
|
+
<DataTable
|
|
38
|
+
columns={columns}
|
|
39
|
+
data={projects}
|
|
40
|
+
searchKey="name"
|
|
41
|
+
searchPlaceholder="Search projects…"
|
|
42
|
+
toolbar={<Button size="sm">New project</Button>}
|
|
43
|
+
pageSize={3}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Orbit } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The brand lockup shared by the Navbar and AppShell demos.
|
|
5
|
+
*
|
|
6
|
+
* One definition rather than two copies, because the whole point of showing it
|
|
7
|
+
* in both places is that they agree — a badge whose radius or size drifted
|
|
8
|
+
* between the two previews would be demonstrating the opposite.
|
|
9
|
+
*
|
|
10
|
+
* `rounded-lg` matches AppShell's own fallback badge, so a product that passes
|
|
11
|
+
* no logo lands on the same silhouette as one that does. Orbit is radially
|
|
12
|
+
* balanced, so it has no optical top or bottom to fight the badge's centring.
|
|
13
|
+
*/
|
|
14
|
+
export const demoBrandLogo = (
|
|
15
|
+
<span
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground"
|
|
18
|
+
>
|
|
19
|
+
<Orbit className="size-5" />
|
|
20
|
+
</span>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export const demoBrandName = 'nexus';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Dock } from '@olwiba/ui';
|
|
3
|
+
import { Bell, Home, Layers, Search, Settings, User } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
const items = [
|
|
6
|
+
{ icon: Home, label: 'Home', onClick: () => {} },
|
|
7
|
+
{ icon: Search, label: 'Search', onClick: () => {} },
|
|
8
|
+
{ icon: Layers, label: 'Projects', onClick: () => {} },
|
|
9
|
+
{ icon: Bell, label: 'Notifications', onClick: () => {} },
|
|
10
|
+
{ icon: User, label: 'Profile', onClick: () => {} },
|
|
11
|
+
{ icon: Settings, label: 'Settings', onClick: () => {} },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export default function Demo() {
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex min-h-64 items-end justify-center rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/10 pb-8">
|
|
17
|
+
<Dock items={items} />
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActionEmail,
|
|
4
|
+
EmailLayout,
|
|
5
|
+
EmailLinkFallback,
|
|
6
|
+
NoticeEmail,
|
|
7
|
+
} from '@olwiba/ui/email';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The examples shown on the email documentation pages.
|
|
11
|
+
*
|
|
12
|
+
* Kept in one module because they are rendered twice from different runtimes:
|
|
13
|
+
* `scripts/generate-email-previews.ts` renders them to HTML in Node, and the
|
|
14
|
+
* pages display that HTML. Defining them beside the demo that shows them would
|
|
15
|
+
* mean the generator importing a component that renders an iframe of itself.
|
|
16
|
+
*
|
|
17
|
+
* Emails cannot be previewed the way other blocks are. `EmailLayout` renders
|
|
18
|
+
* `<html>`, `<head>` and `<body>`, so mounting one inside the sandbox's iframe
|
|
19
|
+
* root nests a document inside a `div` — React tolerates it on the server and
|
|
20
|
+
* hydration dies on the client. Rendering to HTML sidesteps that and is more
|
|
21
|
+
* honest anyway: the page shows the markup that actually gets sent.
|
|
22
|
+
*/
|
|
23
|
+
export const emailExamples: Record<string, ReactElement> = {
|
|
24
|
+
'action-email': (
|
|
25
|
+
<ActionEmail
|
|
26
|
+
appName="Nexus Inc"
|
|
27
|
+
preview="Sign in to Nexus Inc"
|
|
28
|
+
heading="Sign in with one click"
|
|
29
|
+
description="You asked to sign in to Nexus Inc. This link expires in 15 minutes and can only be used once."
|
|
30
|
+
actionLabel="Sign in"
|
|
31
|
+
actionUrl="https://nexus.example/auth/magic?token=kowhai"
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
|
|
35
|
+
'notice-email': (
|
|
36
|
+
<NoticeEmail
|
|
37
|
+
appName="Nexus Inc"
|
|
38
|
+
preview="Your export is ready"
|
|
39
|
+
heading="Your export is ready"
|
|
40
|
+
body="The Pōhutukawa workspace export you requested has finished. It stays available for seven days, then it is deleted."
|
|
41
|
+
/>
|
|
42
|
+
),
|
|
43
|
+
|
|
44
|
+
'email-layout': (
|
|
45
|
+
<EmailLayout appName="Nexus Inc" preview="A message from Nexus Inc">
|
|
46
|
+
<EmailLinkFallback
|
|
47
|
+
actionUrl="https://nexus.example/workspaces/paua"
|
|
48
|
+
label="Everything inside the shell is yours. This is one block placed in it:"
|
|
49
|
+
/>
|
|
50
|
+
</EmailLayout>
|
|
51
|
+
),
|
|
52
|
+
|
|
53
|
+
'email-link-fallback': (
|
|
54
|
+
<EmailLayout appName="Nexus Inc" preview="Sign in to Nexus Inc">
|
|
55
|
+
<EmailLinkFallback actionUrl="https://nexus.example/auth/magic?token=kowhai" />
|
|
56
|
+
</EmailLayout>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { emailPreviews } from './email-previews.generated';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shows a rendered email the way a mail client would.
|
|
8
|
+
*
|
|
9
|
+
* The iframe is not decoration. Email HTML carries its own `<html>`, `<body>`
|
|
10
|
+
* and inline styles, so dropping it into the page would both produce invalid
|
|
11
|
+
* markup and let the documentation site's stylesheet reach into a message that
|
|
12
|
+
* will never see it. A separate document is the only honest frame.
|
|
13
|
+
*
|
|
14
|
+
* `sandbox` is set without `allow-scripts`: this is untrusted-shaped content
|
|
15
|
+
* being displayed, and nothing in an email should be executing anyway.
|
|
16
|
+
*/
|
|
17
|
+
export function EmailFrame({ id }: { id: string }) {
|
|
18
|
+
const html = emailPreviews[id];
|
|
19
|
+
const ref = React.useRef<HTMLIFrameElement | null>(null);
|
|
20
|
+
const [height, setHeight] = React.useState(420);
|
|
21
|
+
|
|
22
|
+
// Emails size themselves, so the frame follows the content rather than
|
|
23
|
+
// imposing a height that would either clip the message or pad it.
|
|
24
|
+
const measure = React.useCallback(() => {
|
|
25
|
+
const doc = ref.current?.contentDocument;
|
|
26
|
+
if (!doc?.body) return;
|
|
27
|
+
const next = Math.ceil(doc.documentElement.scrollHeight || doc.body.scrollHeight);
|
|
28
|
+
if (next > 0) setHeight(next);
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
if (!html) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="text-muted-foreground p-6 text-sm">
|
|
34
|
+
No rendered preview for <code>{id}</code>. Run <code>bun run email:generate</code>.
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<iframe
|
|
41
|
+
ref={ref}
|
|
42
|
+
title={`${id} email preview`}
|
|
43
|
+
srcDoc={html}
|
|
44
|
+
onLoad={measure}
|
|
45
|
+
sandbox="allow-same-origin"
|
|
46
|
+
className="w-full border-0 bg-white"
|
|
47
|
+
style={{ height }}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @generated by scripts/generate-email-previews.ts. DO NOT EDIT.
|
|
2
|
+
//
|
|
3
|
+
// Email examples rendered to the HTML a mail client receives. Regenerated by
|
|
4
|
+
// `bun run email:generate`; edit site/demos/email-examples.tsx instead.
|
|
5
|
+
|
|
6
|
+
export const emailPreviews: Record<string, string> = {
|
|
7
|
+
"action-email": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html dir=\"ltr\" lang=\"en\">\n <head>\n <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n <meta name=\"x-apple-disable-message-reformatting\" />\n </head>\n <body\n style='background-color:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;margin:0;padding:24px 0'>\n <!--$--><!--html--><!--head-->\n <div\n style=\"display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0\">\n Sign in to Nexus Inc\n <div>\n \n </div>\n </div>\n <!--body-->\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"max-width:560px;margin:0 auto;background-color:#ffffff;border-radius:16px;overflow:hidden;border:1px solid #e4e4e7\">\n <tbody>\n <tr style=\"width:100%\">\n <td>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:24px 32px;border-bottom:1px solid #e4e4e7\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:18px;line-height:24px;margin:0;color:#10b981;font-weight:600\">\n Nexus Inc\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:32px\">\n <tbody>\n <tr>\n <td>\n <h1\n style=\"margin:0 0 12px;font-size:24px;font-weight:600;line-height:32px;color:#18181b\">\n Sign in with one click\n </h1>\n <p\n style=\"font-size:15px;line-height:24px;margin:0 0 24px;color:#71717a\">\n You asked to sign in to Nexus Inc. This link expires in 15\n minutes and can only be used once.\n </p>\n <a\n href=\"https://nexus.example/auth/magic?token=kowhai\"\n style=\"line-height:1;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;background-color:#10b981;border-radius:8px;color:#ffffff;font-size:14px;font-weight:600;padding:12px 20px 12px 20px;text-align:center\"\n target=\"_blank\"\n ><span\n ><!--[if mso]><i style=\"mso-font-width:500%;mso-text-raise:18\" hidden>  </i><![endif]--></span\n ><span\n style=\"max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px\"\n >Sign in</span\n ><span\n ><!--[if mso]><i style=\"mso-font-width:500%\" hidden>  ​</i><![endif]--></span\n ></a\n >\n <p\n style=\"font-size:13px;line-height:20px;margin:24px 0 8px;color:#71717a\">\n Or copy and paste this link into your browser:\n </p>\n <p\n style=\"font-size:15px;line-height:24px;margin:0;color:#18181b\">\n <a\n href=\"https://nexus.example/auth/magic?token=kowhai\"\n style=\"color:#10b981;text-decoration-line:none;word-break:break-all\"\n target=\"_blank\"\n >https://nexus.example/auth/magic?token=kowhai</a\n >\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:20px 32px;border-top:1px solid #e4e4e7;background-color:#fafafa\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:13px;line-height:20px;margin:0;color:#71717a;text-align:center\">\n ©\n <!-- -->2026<!-- -->\n <!-- -->Nexus Inc<!-- -->. All rights reserved.\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n </td>\n </tr>\n </tbody>\n </table>\n <!--/$-->\n </body>\n</html>\n",
|
|
8
|
+
"notice-email": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html dir=\"ltr\" lang=\"en\">\n <head>\n <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n <meta name=\"x-apple-disable-message-reformatting\" />\n </head>\n <body\n style='background-color:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;margin:0;padding:24px 0'>\n <!--$--><!--html--><!--head-->\n <div\n style=\"display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0\">\n Your export is ready\n <div>\n \n </div>\n </div>\n <!--body-->\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"max-width:560px;margin:0 auto;background-color:#ffffff;border-radius:16px;overflow:hidden;border:1px solid #e4e4e7\">\n <tbody>\n <tr style=\"width:100%\">\n <td>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:24px 32px;border-bottom:1px solid #e4e4e7\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:18px;line-height:24px;margin:0;color:#10b981;font-weight:600\">\n Nexus Inc\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:32px\">\n <tbody>\n <tr>\n <td>\n <h1\n style=\"margin:0 0 12px;font-size:24px;font-weight:600;line-height:32px;color:#18181b\">\n Your export is ready\n </h1>\n <p\n style=\"font-size:15px;line-height:24px;margin:0;color:#71717a;white-space:pre-wrap\">\n The Pōhutukawa workspace export you requested has\n finished. It stays available for seven days, then it is\n deleted.\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:20px 32px;border-top:1px solid #e4e4e7;background-color:#fafafa\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:13px;line-height:20px;margin:0;color:#71717a;text-align:center\">\n ©\n <!-- -->2026<!-- -->\n <!-- -->Nexus Inc<!-- -->. All rights reserved.\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n </td>\n </tr>\n </tbody>\n </table>\n <!--/$-->\n </body>\n</html>\n",
|
|
9
|
+
"email-layout": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html dir=\"ltr\" lang=\"en\">\n <head>\n <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n <meta name=\"x-apple-disable-message-reformatting\" />\n </head>\n <body\n style='background-color:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;margin:0;padding:24px 0'>\n <!--$--><!--html--><!--head-->\n <div\n style=\"display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0\">\n A message from Nexus Inc\n <div>\n \n </div>\n </div>\n <!--body-->\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"max-width:560px;margin:0 auto;background-color:#ffffff;border-radius:16px;overflow:hidden;border:1px solid #e4e4e7\">\n <tbody>\n <tr style=\"width:100%\">\n <td>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:24px 32px;border-bottom:1px solid #e4e4e7\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:18px;line-height:24px;margin:0;color:#10b981;font-weight:600\">\n Nexus Inc\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:32px\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:13px;line-height:20px;margin:24px 0 8px;color:#71717a\">\n Everything inside the shell is yours. This is one block\n placed in it:\n </p>\n <p\n style=\"font-size:15px;line-height:24px;margin:0;color:#18181b\">\n <a\n href=\"https://nexus.example/workspaces/paua\"\n style=\"color:#10b981;text-decoration-line:none;word-break:break-all\"\n target=\"_blank\"\n >https://nexus.example/workspaces/paua</a\n >\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:20px 32px;border-top:1px solid #e4e4e7;background-color:#fafafa\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:13px;line-height:20px;margin:0;color:#71717a;text-align:center\">\n ©\n <!-- -->2026<!-- -->\n <!-- -->Nexus Inc<!-- -->. All rights reserved.\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n </td>\n </tr>\n </tbody>\n </table>\n <!--/$-->\n </body>\n</html>\n",
|
|
10
|
+
"email-link-fallback": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html dir=\"ltr\" lang=\"en\">\n <head>\n <meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" />\n <meta name=\"x-apple-disable-message-reformatting\" />\n </head>\n <body\n style='background-color:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;margin:0;padding:24px 0'>\n <!--$--><!--html--><!--head-->\n <div\n style=\"display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0\">\n Sign in to Nexus Inc\n <div>\n \n </div>\n </div>\n <!--body-->\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"max-width:560px;margin:0 auto;background-color:#ffffff;border-radius:16px;overflow:hidden;border:1px solid #e4e4e7\">\n <tbody>\n <tr style=\"width:100%\">\n <td>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:24px 32px;border-bottom:1px solid #e4e4e7\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:18px;line-height:24px;margin:0;color:#10b981;font-weight:600\">\n Nexus Inc\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:32px\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:13px;line-height:20px;margin:24px 0 8px;color:#71717a\">\n Or copy and paste this link into your browser:\n </p>\n <p\n style=\"font-size:15px;line-height:24px;margin:0;color:#18181b\">\n <a\n href=\"https://nexus.example/auth/magic?token=kowhai\"\n style=\"color:#10b981;text-decoration-line:none;word-break:break-all\"\n target=\"_blank\"\n >https://nexus.example/auth/magic?token=kowhai</a\n >\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n <table\n align=\"center\"\n width=\"100%\"\n border=\"0\"\n cellpadding=\"0\"\n cellspacing=\"0\"\n role=\"presentation\"\n style=\"padding:20px 32px;border-top:1px solid #e4e4e7;background-color:#fafafa\">\n <tbody>\n <tr>\n <td>\n <p\n style=\"font-size:13px;line-height:20px;margin:0;color:#71717a;text-align:center\">\n ©\n <!-- -->2026<!-- -->\n <!-- -->Nexus Inc<!-- -->. All rights reserved.\n </p>\n </td>\n </tr>\n </tbody>\n </table>\n </td>\n </tr>\n </tbody>\n </table>\n <!--/$-->\n </body>\n</html>\n"
|
|
11
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { EmptyState, Button } from '@olwiba/ui';
|
|
2
|
+
import { FolderOpen } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
export default function EmptyStateDemo() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="w-full max-w-lg">
|
|
7
|
+
<EmptyState
|
|
8
|
+
icon={FolderOpen}
|
|
9
|
+
title="No projects yet"
|
|
10
|
+
description="Create your first project to get started."
|
|
11
|
+
eyebrow="Your workspace"
|
|
12
|
+
variant="card"
|
|
13
|
+
compact
|
|
14
|
+
action={<Button size="sm">New project</Button>}
|
|
15
|
+
secondaryAction={
|
|
16
|
+
<Button size="sm" variant="ghost">
|
|
17
|
+
Learn more
|
|
18
|
+
</Button>
|
|
19
|
+
}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { FadeIn } from '@olwiba/ui';
|
|
3
|
+
|
|
4
|
+
const directions = ['up', 'down', 'left', 'right'] as const;
|
|
5
|
+
|
|
6
|
+
export default function Demo() {
|
|
7
|
+
return (
|
|
8
|
+
<div className="space-y-3 p-6">
|
|
9
|
+
{directions.map((dir, i) => (
|
|
10
|
+
<FadeIn key={dir} direction={dir} delay={i * 120}>
|
|
11
|
+
<div className="rounded-xl border bg-card p-4 text-sm">
|
|
12
|
+
Fade in from <strong>{dir}</strong>
|
|
13
|
+
</div>
|
|
14
|
+
</FadeIn>
|
|
15
|
+
))}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|