feat(monorepo): restructure web components and update configurations
CI / changes (push) Successful in 10s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 27s
CI / web (push) Successful in 38s
CI / go (push) Successful in 2m36s
CI / bird2 (push) Successful in 15s
CI / release (push) Failing after 3m7s

Refactored the project structure to support a monorepo setup, moving the web application to `apps/web/` and updating related configurations. Adjusted pre-commit hooks to use `pnpm` for linting and formatting. Updated CI workflows to reflect the new directory structure and dependencies. Removed legacy files and configurations from the previous `web/` directory, streamlining the project for better maintainability and clarity.
This commit is contained in:
Denozordec
2026-06-30 23:54:28 +07:00
parent db75126bea
commit a37c931ee7
377 changed files with 8537 additions and 1093 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"name": "@evobgp/shared",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./types/*": "./src/types/*",
"./contracts/*": "./src/contracts/*"
},
"dependencies": {
"zod": "^4.4.3"
}
}
+8
View File
@@ -0,0 +1,8 @@
import { z } from 'zod';
export const pageSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
z.object({
items: z.array(itemSchema),
next_cursor: z.string().nullable(),
has_more: z.boolean()
});
+38
View File
@@ -0,0 +1,38 @@
import { z } from 'zod';
export const moduleTypeSchema = z.enum(['AS_PREFIXES', 'CDN_CIDRS', 'DOMAINS', 'IP_RANGES']);
export const dohResolverPolicySchema = z.enum(['primary_only', 'failover', 'union']);
export const moduleRowSchema = z.object({
id: z.string(),
type: moduleTypeSchema,
name: z.string().min(1),
enabled: z.boolean(),
priority: z.number().int(),
refresh_interval_sec: z.number().int().nullable(),
cron_expr: z.string().nullable(),
default_community_id: z.string().nullable(),
doh_profile_id: z.string().nullable(),
doh_profile_ids: z.array(z.string()),
doh_resolver_policy: dohResolverPolicySchema,
last_refreshed_at: z.string().nullable()
});
export const moduleCreateSchema = z.object({
type: moduleTypeSchema,
name: z.string().min(1, 'Укажите название'),
enabled: z.boolean().optional(),
priority: z.number().int().optional(),
doh_profile_id: z.string().nullable().optional(),
doh_profile_ids: z.array(z.string()).optional(),
doh_resolver_policy: dohResolverPolicySchema.optional(),
refresh_interval_sec: z.number().int().nullable().optional(),
cron_expr: z.string().nullable().optional(),
default_community_id: z.string().nullable().optional()
});
export const modulePatchSchema = moduleCreateSchema.omit({ type: true }).partial();
export type ModuleCreateInput = z.infer<typeof moduleCreateSchema>;
export type ModulePatchInput = z.infer<typeof modulePatchSchema>;
+2
View File
@@ -0,0 +1,2 @@
export * from './types/api.js';
export * from './contracts/modules.js';
+340
View File
@@ -0,0 +1,340 @@
// ---- Pagination ----
export type Page<T> = {
items: T[];
next_cursor: string | null;
has_more: boolean;
};
// ---- Modules ----
export type ModuleType = 'AS_PREFIXES' | 'CDN_CIDRS' | 'DOMAINS' | 'IP_RANGES';
export type DohResolverPolicy = 'primary_only' | 'failover' | 'union';
export type ModuleRow = {
id: string;
type: ModuleType;
name: string;
enabled: boolean;
priority: number;
refresh_interval_sec: number | null;
cron_expr: string | null;
default_community_id: string | null;
/** @deprecated use doh_profile_ids */
doh_profile_id: string | null;
doh_profile_ids: string[];
doh_resolver_policy: DohResolverPolicy;
last_refreshed_at: string | null;
};
export type ModulesResponse = Page<ModuleRow>;
export type RouterListsCatalogResponse = {
modules: { items: ModuleRow[] };
domains: { items: { module_id: string; entry: DomainEntry }[] };
asns: { items: { module_id: string; entry: AsEntry }[] };
ip_ranges: { items: { module_id: string; entry: IpRangeEntry }[] };
communities: { items: BgpCommunity[] };
};
export type ModuleCreate = {
type: ModuleType;
name: string;
enabled?: boolean;
priority?: number;
doh_profile_id?: string | null;
doh_profile_ids?: string[];
doh_resolver_policy?: DohResolverPolicy;
refresh_interval_sec?: number | null;
cron_expr?: string | null;
default_community_id?: string | null;
};
export type ModulePatch = Partial<Omit<ModuleCreate, 'type'>>;
// ---- AS Entries ----
export type AsEntry = {
id: string;
asn: number;
community_id: string | null;
/** Имя/держатель AS (RIPEstat), после успешного обновления модуля */
asn_name?: string | null;
/** Число объявленных префиксов на момент последнего резолва */
prefix_count?: number | null;
/** ISO-время последнего успешного резолва ASN */
asn_resolved_at?: string | null;
};
export type AsEntryCreate = {
asn: number;
community_id?: string | null;
};
export type AsEntryPatch = {
asn?: number;
community_id?: string | null;
};
export type AsEntriesResponse = Page<AsEntry>;
// ---- CDN Sources ----
export type CdnSource = {
id: string;
url: string;
source_kind: string;
prefix_path: string;
community_id: string | null;
refresh_interval_sec: number | null;
last_refreshed_at: string | null;
};
export type CdnSourceCreate = {
url: string;
source_kind: string;
prefix_path?: string;
community_id?: string | null;
};
export type CdnSourcePatch = Partial<CdnSourceCreate> & { refresh_interval_sec?: number | null };
export type CdnSourcesResponse = Page<CdnSource>;
export type CdnPreviewResponse = {
items: string[];
total: number;
truncated: boolean;
source_url: string;
};
// ---- Domain Entries ----
export type DomainEntry = {
id: string;
fqdn: string;
community_id: string | null;
};
export type DomainEntryCreate = {
fqdn: string;
community_id?: string | null;
};
export type DomainEntriesResponse = Page<DomainEntry>;
// ---- IP Range Entries ----
export type IpRangeEntry = {
id: string;
prefix: string;
community_id: string;
};
export type IpRangeEntryCreate = {
prefix: string;
community_id: string;
};
export type IpRangeEntriesResponse = Page<IpRangeEntry>;
// ---- DoH Profiles ----
export type DohProfile = {
id: string;
name?: string;
url: string;
timeout_ms: number | null;
vault_secret_ref: string | null;
};
export type DohProfileCreate = {
name?: string;
url: string;
timeout_ms?: number | null;
vault_secret_ref?: string | null;
};
export type DohProfilePatch = Partial<DohProfileCreate>;
export type DohProfilesResponse = Page<DohProfile>;
// ---- Communities ----
export type BgpCommunity = {
id: string;
community: string;
title: string;
};
export type BgpCommunityCreate = {
community: string;
title?: string;
};
export type BgpCommunityPatch = Partial<BgpCommunityCreate>;
export type CommunitiesResponse = Page<BgpCommunity>;
// ---- Peers ----
export type PeerSessionOnSpeaker = {
speaker_id: string;
label: string;
state: string;
poll_error?: string;
};
export type PeerRow = {
id: string;
name?: string;
neighbor: string;
remote_asn?: number;
enabled?: boolean;
session_state: string;
bgp_speaker_id: string | null;
connected_speaker_id?: string | null;
connected_speaker_label?: string;
session_on_speakers?: PeerSessionOnSpeaker[];
established_on_speakers?: PeerSessionOnSpeaker[];
session_mismatch?: boolean;
};
export type LiveSpeakerPoll = {
speaker_id: string;
label: string;
ok: boolean;
session_count: number;
poll_error?: string;
};
export type PeersResponse = Page<PeerRow> & { live_speaker_poll?: LiveSpeakerPoll[] };
export type BgpPeerCreate = {
name?: string;
neighbor: string;
remote_asn: number;
bgp_speaker_id?: string | null;
enabled?: boolean;
};
export type BgpPeerPatch = Partial<BgpPeerCreate>;
// ---- Speakers ----
export type BgpSessionLive = {
name: string;
neighbor?: string;
state: string;
};
export type SpeakerLiveStatus = {
label?: string;
agent_ok?: boolean;
agent_error?: string;
agent_last_sync_at?: string;
agent_last_applied_revision_id?: string;
bgp_poll_ok?: boolean;
bgp_poll_error?: string;
bgp_sessions_total?: number;
bgp_established?: number;
sessions?: BgpSessionLive[];
};
export type SpeakerRow = {
id: string;
role: string;
endpoint: string;
last_applied_revision_id: string | null;
published_revision_id?: string | null;
published_at?: string | null;
agent_domain?: string;
node_ipv4?: string;
bird_bgp_source_ipv4?: string;
dispatch_status?: string;
sync_status?: string;
last_dispatch_at?: string | null;
last_dispatch_error?: string | null;
meta_json?: Record<string, unknown>;
agent_secret?: string;
live?: SpeakerLiveStatus;
};
export type SpeakersResponse = Page<SpeakerRow>;
export type BgpSpeakerCreate = {
endpoint: string;
role?: string;
meta_json?: string;
};
export type BgpSpeakerPatch = Partial<BgpSpeakerCreate>;
export type BundleSigningPublicKey = {
public_key_base64: string;
};
// ---- Revisions ----
export type RevisionRow = {
id: string;
content_hash: string;
created_at: string;
parent_revision_id: string | null;
materialized_prefix_count: number;
module_id: string | null;
};
export type RevisionsResponse = Page<RevisionRow>;
export type RevisionPrefix = {
/** Обычно CIDR; для AS-модуля в снимке ревизии — строка вида `as:<номер_asn>`. */
prefix: string;
/** Источник материализации (например, domain:<fqdn>, as:<asn>, cdn:<source_id>, ip_range). */
source?: string;
community_id?: string | null;
};
export type RevisionPrefixesResponse = Page<RevisionPrefix>;
export type RevisionPreview = {
id: string;
prefixes?: RevisionPrefix[];
[key: string]: unknown;
};
/** Ответ GET /v1/revisions/{a}/diff/{b}: префиксы в `prefixes` (источник правды в бэкенде). */
export type RevisionDiff = {
revision_a?: string;
revision_b?: string;
prefixes?: {
added: string[];
removed: string[];
unchanged_count?: number;
};
/** Устаревший/нормализованный вид — см. нормализацию в UI */
added?: (string | RevisionPrefix)[];
removed?: (string | RevisionPrefix)[];
[key: string]: unknown;
};
// ---- BIRD (локальный birdc на хосте с API, если задан EVOBGP_BIRDC_SOCKET) ----
export type BirdStatus = {
birdc_configured: boolean;
message?: string;
error?: string;
protocols_excerpt?: string;
bgp_sessions_total: number;
bgp_established: number;
healthy: boolean | null;
};
// ---- Jobs ----
export type JobRow = {
job_id: string;
kind: string;
status: string;
idempotency_key?: string | null;
created_at?: string;
started_at?: string | null;
finished_at?: string | null;
error?: string | null;
meta?: Record<string, unknown>;
};
export type JobsResponse = Page<JobRow>;
// ---- Settings ----
export type AppSettings = Record<string, unknown>;
// ---- Auth / API keys ----
export type AuthSession = {
tenant_id: string;
role: 'viewer' | 'editor' | 'operator' | 'node';
};
export type ApiKeyRole = AuthSession['role'];
export type ApiKey = {
id: string;
name: string;
role: ApiKeyRole;
prefix: string;
created_at: string;
updated_at: string;
expires_at: string | null;
revoked_at: string | null;
last_used_at: string | null;
};
export type ApiKeysResponse = Page<ApiKey>;
export type ApiKeyCreate = {
name: string;
role: ApiKeyRole;
expires_at?: string | null;
};
export type ApiKeyCreated = ApiKey & { token: string };
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*"]
}
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://www.shadcn-svelte.com/schema.json",
"tailwind": {
"css": "src/styles/globals.css",
"baseColor": "neutral"
},
"aliases": {
"components": "src/components",
"utils": "src/lib/utils",
"ui": "src/components",
"hooks": "src/hooks",
"lib": "src/lib"
},
"typescript": true,
"iconLibrary": "lucide"
}
+30
View File
@@ -0,0 +1,30 @@
{
"name": "@evobgp/ui",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
"./components/*": "./src/components/*",
"./hooks/*": "./src/hooks/*",
"./lib/utils": "./src/lib/utils.ts",
"./globals.css": "./src/styles/globals.css",
"./styles/globals.css": "./src/styles/globals.css"
},
"dependencies": {
"@lucide/svelte": "^1.16.0",
"@tanstack/table-core": "^8.21.3",
"bits-ui": "^2.17.2",
"clsx": "^2.1.1",
"formsnap": "^2.0.1",
"sveltekit-superforms": "^2.30.1",
"tailwind-merge": "^3.5.0",
"tailwind-variants": "^3.2.2",
"tw-animate-css": "^1.4.0"
},
"peerDependencies": {
"svelte": "^5.54.0"
},
"devDependencies": {
"tailwindcss": "^4.1.18"
}
}
@@ -0,0 +1,27 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import {
buttonVariants,
type ButtonVariant,
type ButtonSize
} from '@evobgp/ui/components/button/index.js';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
variant = 'default',
size = 'default',
...restProps
}: AlertDialogPrimitive.ActionProps & {
variant?: ButtonVariant;
size?: ButtonSize;
} = $props();
</script>
<AlertDialogPrimitive.Action
bind:ref
data-slot="alert-dialog-action"
class={cn(buttonVariants({ variant, size }), 'cn-alert-dialog-action', className)}
{...restProps}
/>
@@ -0,0 +1,27 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import {
buttonVariants,
type ButtonVariant,
type ButtonSize
} from '@evobgp/ui/components/button/index.js';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
variant = 'outline',
size = 'default',
...restProps
}: AlertDialogPrimitive.CancelProps & {
variant?: ButtonVariant;
size?: ButtonSize;
} = $props();
</script>
<AlertDialogPrimitive.Cancel
bind:ref
data-slot="alert-dialog-cancel"
class={cn(buttonVariants({ variant, size }), 'cn-alert-dialog-cancel', className)}
{...restProps}
/>
@@ -0,0 +1,32 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import AlertDialogPortal from './alert-dialog-portal.svelte';
import AlertDialogOverlay from './alert-dialog-overlay.svelte';
import { cn, type WithoutChild, type WithoutChildrenOrChild } from '@evobgp/ui/lib/utils';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
size = 'default',
portalProps,
...restProps
}: WithoutChild<AlertDialogPrimitive.ContentProps> & {
size?: 'default' | 'sm';
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof AlertDialogPortal>>;
} = $props();
</script>
<AlertDialogPortal {...portalProps}>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
bind:ref
data-slot="alert-dialog-content"
data-size={size}
class={cn(
'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm',
className
)}
{...restProps}
/>
</AlertDialogPortal>
@@ -0,0 +1,20 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.DescriptionProps = $props();
</script>
<AlertDialogPrimitive.Description
bind:ref
data-slot="alert-dialog-description"
class={cn(
'text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
className
)}
{...restProps}
/>
@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-dialog-footer"
class={cn(
'-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-dialog-header"
class={cn(
'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-dialog-media"
class={cn(
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,20 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.OverlayProps = $props();
</script>
<AlertDialogPrimitive.Overlay
bind:ref
data-slot="alert-dialog-overlay"
class={cn(
'fixed inset-0 z-50 bg-black/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-open:animate-in data-open:fade-in-0 supports-backdrop-filter:backdrop-blur-xs',
className
)}
{...restProps}
/>
@@ -0,0 +1,7 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
let { ...restProps }: AlertDialogPrimitive.PortalProps = $props();
</script>
<AlertDialogPrimitive.Portal {...restProps} />
@@ -0,0 +1,20 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: AlertDialogPrimitive.TitleProps = $props();
</script>
<AlertDialogPrimitive.Title
bind:ref
data-slot="alert-dialog-title"
class={cn(
'text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',
className
)}
{...restProps}
/>
@@ -0,0 +1,7 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: AlertDialogPrimitive.TriggerProps = $props();
</script>
<AlertDialogPrimitive.Trigger bind:ref data-slot="alert-dialog-trigger" {...restProps} />
@@ -0,0 +1,7 @@
<script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: AlertDialogPrimitive.RootProps = $props();
</script>
<AlertDialogPrimitive.Root bind:open {...restProps} />
@@ -0,0 +1,40 @@
import Root from './alert-dialog.svelte';
import Portal from './alert-dialog-portal.svelte';
import Trigger from './alert-dialog-trigger.svelte';
import Title from './alert-dialog-title.svelte';
import Action from './alert-dialog-action.svelte';
import Cancel from './alert-dialog-cancel.svelte';
import Footer from './alert-dialog-footer.svelte';
import Header from './alert-dialog-header.svelte';
import Overlay from './alert-dialog-overlay.svelte';
import Content from './alert-dialog-content.svelte';
import Description from './alert-dialog-description.svelte';
import Media from './alert-dialog-media.svelte';
export {
Root,
Title,
Action,
Cancel,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Media,
//
Root as AlertDialog,
Title as AlertDialogTitle,
Action as AlertDialogAction,
Cancel as AlertDialogCancel,
Portal as AlertDialogPortal,
Footer as AlertDialogFooter,
Header as AlertDialogHeader,
Trigger as AlertDialogTrigger,
Overlay as AlertDialogOverlay,
Content as AlertDialogContent,
Description as AlertDialogDescription,
Media as AlertDialogMedia
};
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-action"
class={cn('absolute top-2 right-2', className)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-description"
class={cn(
'text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="alert-title"
class={cn(
'font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,44 @@
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const alertVariants = tv({
base: "grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 group/alert relative w-full",
variants: {
variant: {
default: 'bg-card text-card-foreground',
destructive:
'text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current'
}
},
defaultVariants: {
variant: 'default'
}
});
export type AlertVariant = VariantProps<typeof alertVariants>['variant'];
</script>
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
variant = 'default',
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
variant?: AlertVariant;
} = $props();
</script>
<div
bind:this={ref}
data-slot="alert"
role="alert"
class={cn(alertVariants({ variant }), className)}
{...restProps}
>
{@render children?.()}
</div>
+17
View File
@@ -0,0 +1,17 @@
import Root from './alert.svelte';
import Description from './alert-description.svelte';
import Title from './alert-title.svelte';
import Action from './alert-action.svelte';
export { alertVariants, type AlertVariant } from './alert.svelte';
export {
Root,
Description,
Title,
Action,
//
Root as Alert,
Description as AlertDescription,
Title as AlertTitle,
Action as AlertAction
};
@@ -0,0 +1,50 @@
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const badgeVariants = tv({
base: 'h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-[3px] [&>svg]:pointer-events-none',
variants: {
variant: {
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
secondary: 'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',
destructive:
'bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20',
outline: 'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',
ghost: 'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',
link: 'text-primary underline-offset-4 hover:underline'
}
},
defaultVariants: {
variant: 'default'
}
});
export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
</script>
<script lang="ts">
import type { HTMLAnchorAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
href,
class: className,
variant = 'default',
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
variant?: BadgeVariant;
} = $props();
</script>
<svelte:element
this={href ? 'a' : 'span'}
bind:this={ref}
data-slot="badge"
{href}
class={cn(badgeVariants({ variant }), className)}
{...restProps}
>
{@render children?.()}
</svelte:element>
@@ -0,0 +1,2 @@
export { default as Badge } from './badge.svelte';
export { badgeVariants, type BadgeVariant } from './badge.svelte';
@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef, type WithoutChildren } from "@evobgp/ui/lib/utils";
import MoreHorizontalIcon from '@lucide/svelte/icons/more-horizontal';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
</script>
<span
bind:this={ref}
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
class={cn("size-5 [&>svg]:size-4 flex items-center justify-center", className)}
{...restProps}
>
<MoreHorizontalIcon />
<span class="sr-only">More</span>
</span>
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLLiAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "@evobgp/ui/lib/utils";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li
bind:this={ref}
data-slot="breadcrumb-item"
class={cn("gap-1 inline-flex items-center", className)}
{...restProps}
>
{@render children?.()}
</li>
@@ -0,0 +1,31 @@
<script lang="ts">
import type { HTMLAnchorAttributes } from "svelte/elements";
import type { Snippet } from "svelte";
import { cn, type WithElementRef } from "@evobgp/ui/lib/utils";
let {
ref = $bindable(null),
class: className,
href = undefined,
child,
children,
...restProps
}: WithElementRef<HTMLAnchorAttributes> & {
child?: Snippet<[{ props: HTMLAnchorAttributes }]>;
} = $props();
const attrs = $derived({
"data-slot": "breadcrumb-link",
class: cn("hover:text-foreground transition-colors", className),
href,
...restProps,
});
</script>
{#if child}
{@render child({ props: attrs })}
{:else}
<a bind:this={ref} {...attrs}>
{@render children?.()}
</a>
{/if}
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLOlAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "@evobgp/ui/lib/utils";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLOlAttributes> = $props();
</script>
<ol
bind:this={ref}
data-slot="breadcrumb-list"
class={cn("text-muted-foreground gap-1.5 text-sm flex flex-wrap items-center wrap-break-word", className)}
{...restProps}
>
{@render children?.()}
</ol>
@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "@evobgp/ui/lib/utils";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
class={cn("text-foreground font-normal", className)}
{...restProps}
>
{@render children?.()}
</span>
@@ -0,0 +1,27 @@
<script lang="ts">
import { cn, type WithElementRef } from "@evobgp/ui/lib/utils";
import type { HTMLLiAttributes } from "svelte/elements";
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li
bind:this={ref}
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
class={cn("[&>svg]:size-3.5", className)}
{...restProps}
>
{#if children}
{@render children?.()}
{:else}
<ChevronRightIcon />
{/if}
</li>
@@ -0,0 +1,22 @@
<script lang="ts">
import type { WithElementRef } from "@evobgp/ui/lib/utils";
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "@evobgp/ui/lib/utils";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
</script>
<nav
bind:this={ref}
data-slot="breadcrumb"
aria-label="breadcrumb"
class={cn("cn-breadcrumb", className)}
{...restProps}
>
{@render children?.()}
</nav>
@@ -0,0 +1,25 @@
import Root from "./breadcrumb.svelte";
import Ellipsis from "./breadcrumb-ellipsis.svelte";
import Item from "./breadcrumb-item.svelte";
import Separator from "./breadcrumb-separator.svelte";
import Link from "./breadcrumb-link.svelte";
import List from "./breadcrumb-list.svelte";
import Page from "./breadcrumb-page.svelte";
export {
Root,
Ellipsis,
Item,
Separator,
Link,
List,
Page,
//
Root as Breadcrumb,
Ellipsis as BreadcrumbEllipsis,
Item as BreadcrumbItem,
Separator as BreadcrumbSeparator,
Link as BreadcrumbLink,
List as BreadcrumbList,
Page as BreadcrumbPage,
};
@@ -0,0 +1,89 @@
<script lang="ts" module>
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants';
export const buttonVariants = tv({
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:not-aria-[haspopup]:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: {
variant: {
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
outline:
'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
ghost:
'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',
destructive:
'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default:
'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
icon: 'size-8',
'icon-xs':
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
'icon-sm':
'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
'icon-lg': 'size-9'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>
<script lang="ts">
let {
class: className,
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = 'button',
disabled,
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
data-slot="button"
class={cn(buttonVariants({ variant, size }), className)}
{type}
{disabled}
{...restProps}
>
{@render children?.()}
</button>
{/if}
@@ -0,0 +1,17 @@
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants
} from './button.svelte';
export {
Root,
type ButtonProps as Props,
//
Root as Button,
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant
};
@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-action"
class={cn(
'cn-card-action col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-content"
class={cn('px-4 group-data-[size=sm]/card:px-3', className)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
</script>
<p
bind:this={ref}
data-slot="card-description"
class={cn('text-sm text-muted-foreground', className)}
{...restProps}
>
{@render children?.()}
</p>
@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-footer"
class={cn(
'flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-header"
class={cn(
'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-title"
class={cn('text-base leading-snug font-medium group-data-[size=sm]/card:text-sm', className)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,25 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
size = 'default',
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { size?: 'default' | 'sm' } = $props();
</script>
<div
bind:this={ref}
data-slot="card"
data-size={size}
class={cn(
'group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',
className
)}
{...restProps}
>
{@render children?.()}
</div>
+25
View File
@@ -0,0 +1,25 @@
import Root from './card.svelte';
import Content from './card-content.svelte';
import Description from './card-description.svelte';
import Footer from './card-footer.svelte';
import Header from './card-header.svelte';
import Title from './card-title.svelte';
import Action from './card-action.svelte';
export {
Root,
Content,
Description,
Footer,
Header,
Title,
Action,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle,
Action as CardAction
};
@@ -0,0 +1,39 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
import { cn, type WithoutChildrenOrChild } from '@evobgp/ui/lib/utils';
import CheckIcon from '@lucide/svelte/icons/check';
import MinusIcon from '@lucide/svelte/icons/minus';
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script>
<CheckboxPrimitive.Root
bind:ref
data-slot="checkbox"
class={cn(
'peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:data-checked:bg-primary',
className
)}
bind:checked
bind:indeterminate
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<div
data-slot="checkbox-indicator"
class="grid place-content-center text-current transition-none [&>svg]:size-3.5"
>
{#if checked}
<CheckIcon />
{:else if indeterminate}
<MinusIcon />
{/if}
</div>
{/snippet}
</CheckboxPrimitive.Root>
@@ -0,0 +1,6 @@
import Root from './checkbox.svelte';
export {
Root,
//
Root as Checkbox
};
@@ -0,0 +1,142 @@
import {
type RowData,
type TableOptions,
type TableOptionsResolved,
type TableState,
type Updater,
createTable
} from '@tanstack/table-core';
/**
* Creates a reactive TanStack table object for Svelte.
* @param options Table options to create the table with.
* @returns A reactive table object.
* @example
* ```svelte
* <script>
* const table = createSvelteTable({ ... })
* </script>
*
* <table>
* <thead>
* {#each table.getHeaderGroups() as headerGroup}
* <tr>
* {#each headerGroup.headers as header}
* <th colspan={header.colSpan}>
* <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
* </th>
* {/each}
* </tr>
* {/each}
* </thead>
* <!-- ... -->
* </table>
* ```
*/
export function createSvelteTable<TData extends RowData>(options: TableOptions<TData>) {
const resolvedOptions: TableOptionsResolved<TData> = mergeObjects(
{
state: {},
onStateChange() {},
renderFallbackValue: null,
mergeOptions: (
defaultOptions: TableOptions<TData>,
options: Partial<TableOptions<TData>>
) => {
return mergeObjects(defaultOptions, options);
}
},
options
);
const table = createTable(resolvedOptions);
let state = $state<TableState>(table.initialState);
function updateOptions() {
table.setOptions(() => {
return mergeObjects(resolvedOptions, options, {
state: mergeObjects(state, options.state || {}),
onStateChange: (updater: Updater<TableState>) => {
if (updater instanceof Function) state = updater(state);
else state = mergeObjects(state, updater);
options.onStateChange?.(updater);
}
});
});
}
updateOptions();
$effect.pre(() => {
updateOptions();
});
return table;
}
type MaybeThunk<T extends object> = T | (() => T | null | undefined);
type Intersection<T extends readonly unknown[]> = (T extends [infer H, ...infer R]
? H & Intersection<R>
: unknown) & {};
/**
* Lazily merges several objects (or thunks) while preserving
* getter semantics from every source.
*
* Proxy-based to avoid known WebKit recursion issue.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function mergeObjects<Sources extends readonly MaybeThunk<any>[]>(
...sources: Sources
): Intersection<{ [K in keyof Sources]: Sources[K] }> {
const resolve = <T extends object>(src: MaybeThunk<T>): T | undefined =>
typeof src === 'function' ? (src() ?? undefined) : src;
const findSourceWithKey = (key: PropertyKey) => {
for (let i = sources.length - 1; i >= 0; i--) {
const obj = resolve(sources[i]);
if (obj && key in obj) return obj;
}
return undefined;
};
return new Proxy(Object.create(null), {
get(_, key) {
const src = findSourceWithKey(key);
return src?.[key as never];
},
has(_, key) {
return !!findSourceWithKey(key);
},
ownKeys(): (string | symbol)[] {
// eslint-disable-next-line svelte/prefer-svelte-reactivity
const all = new Set<string | symbol>();
for (const s of sources) {
const obj = resolve(s);
if (obj) {
for (const k of Reflect.ownKeys(obj) as (string | symbol)[]) {
all.add(k);
}
}
}
return [...all];
},
getOwnPropertyDescriptor(_, key) {
const src = findSourceWithKey(key);
if (!src) return undefined;
return {
configurable: true,
enumerable: true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: (src as any)[key],
writable: true
};
}
}) as Intersection<{ [K in keyof Sources]: Sources[K] }>;
}
@@ -0,0 +1,40 @@
<script
lang="ts"
generics="TData, TValue, TContext extends HeaderContext<TData, TValue> | CellContext<TData, TValue>"
>
import type { CellContext, ColumnDefTemplate, HeaderContext } from '@tanstack/table-core';
import { RenderComponentConfig, RenderSnippetConfig } from './render-helpers.js';
import type { Attachment } from 'svelte/attachments';
type Props = {
/** The cell or header field of the current cell's column definition. */
content?: TContext extends HeaderContext<TData, TValue>
? ColumnDefTemplate<HeaderContext<TData, TValue>>
: TContext extends CellContext<TData, TValue>
? ColumnDefTemplate<CellContext<TData, TValue>>
: never;
/** The result of the `getContext()` function of the header or cell */
context: TContext;
/** Used to pass attachments that can't be gotten through context */
attach?: Attachment;
};
let { content, context, attach }: Props = $props();
</script>
{#if typeof content === 'string'}
{content}
{:else if content instanceof Function}
<!-- It's unlikely that a CellContext will be passed to a Header -->
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
{@const result = content(context as any)}
{#if result instanceof RenderComponentConfig}
{@const { component: Component, props } = result}
<Component {...props} {attach} />
{:else if result instanceof RenderSnippetConfig}
{@const { snippet, params } = result}
{@render snippet({ ...params, attach })}
{:else}
{result}
{/if}
{/if}
@@ -0,0 +1,3 @@
export { default as FlexRender } from './flex-render.svelte';
export { renderComponent, renderSnippet } from './render-helpers.js';
export { createSvelteTable } from './data-table.svelte.js';
@@ -0,0 +1,111 @@
import type { Component, ComponentProps, Snippet } from 'svelte';
/**
* A helper class to make it easy to identify Svelte components in
* `columnDef.cell` and `columnDef.header` properties.
*
* > NOTE: This class should only be used internally by the adapter. If you're
* reading this and you don't know what this is for, you probably don't need it.
*
* @example
* ```svelte
* {@const result = content(context as any)}
* {#if result instanceof RenderComponentConfig}
* {@const { component: Component, props } = result}
* <Component {...props} />
* {/if}
* ```
*/
export class RenderComponentConfig<TComponent extends Component> {
component: TComponent;
props: ComponentProps<TComponent> | Record<string, never>;
constructor(
component: TComponent,
props: ComponentProps<TComponent> | Record<string, never> = {}
) {
this.component = component;
this.props = props;
}
}
/**
* A helper class to make it easy to identify Svelte Snippets in `columnDef.cell` and `columnDef.header` properties.
*
* > NOTE: This class should only be used internally by the adapter. If you're
* reading this and you don't know what this is for, you probably don't need it.
*
* @example
* ```svelte
* {@const result = content(context as any)}
* {#if result instanceof RenderSnippetConfig}
* {@const { snippet, params } = result}
* {@render snippet(params)}
* {/if}
* ```
*/
export class RenderSnippetConfig<TProps> {
snippet: Snippet<[TProps]>;
params: TProps;
constructor(snippet: Snippet<[TProps]>, params: TProps) {
this.snippet = snippet;
this.params = params;
}
}
/**
* A helper function to help create cells from Svelte components through ColumnDef's `cell` and `header` properties.
*
* This is only to be used with Svelte Components - use `renderSnippet` for Svelte Snippets.
*
* @param component A Svelte component
* @param props The props to pass to `component`
* @returns A `RenderComponentConfig` object that helps svelte-table know how to render the header/cell component.
* @example
* ```ts
* // +page.svelte
* const defaultColumns = [
* columnHelper.accessor('name', {
* header: header => renderComponent(SortHeader, { label: 'Name', header }),
* }),
* columnHelper.accessor('state', {
* header: header => renderComponent(SortHeader, { label: 'State', header }),
* }),
* ]
* ```
* @see {@link https://tanstack.com/table/latest/docs/guide/column-defs}
*/
export function renderComponent<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends Component<any>,
Props extends ComponentProps<T>
>(component: T, props: Props = {} as Props) {
return new RenderComponentConfig(component, props);
}
/**
* A helper function to help create cells from Svelte Snippets through ColumnDef's `cell` and `header` properties.
*
* The snippet must only take one parameter.
*
* This is only to be used with Snippets - use `renderComponent` for Svelte Components.
*
* @param snippet
* @param params
* @returns - A `RenderSnippetConfig` object that helps svelte-table know how to render the header/cell snippet.
* @example
* ```ts
* // +page.svelte
* const defaultColumns = [
* columnHelper.accessor('name', {
* cell: cell => renderSnippet(nameSnippet, { name: cell.row.name }),
* }),
* columnHelper.accessor('state', {
* cell: cell => renderSnippet(stateSnippet, { state: cell.row.state }),
* }),
* ]
* ```
* @see {@link https://tanstack.com/table/latest/docs/guide/column-defs}
*/
export function renderSnippet<TProps>(snippet: Snippet<[TProps]>, params: TProps = {} as TProps) {
return new RenderSnippetConfig(snippet, params);
}
@@ -0,0 +1,11 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let {
ref = $bindable(null),
type = 'button',
...restProps
}: DialogPrimitive.CloseProps = $props();
</script>
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {type} {...restProps} />
@@ -0,0 +1,48 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import DialogPortal from './dialog-portal.svelte';
import type { Snippet } from 'svelte';
import * as Dialog from './index.js';
import { cn, type WithoutChildrenOrChild } from '@evobgp/ui/lib/utils';
import type { ComponentProps } from 'svelte';
import { Button } from '@evobgp/ui/components/button/index.js';
import XIcon from '@lucide/svelte/icons/x';
let {
ref = $bindable(null),
class: className,
portalProps,
children,
showCloseButton = true,
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DialogPortal>>;
children: Snippet;
showCloseButton?: boolean;
} = $props();
</script>
<DialogPortal {...portalProps}>
<Dialog.Overlay />
<DialogPrimitive.Content
bind:ref
data-slot="dialog-content"
class={cn(
'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 sm:max-w-sm',
className
)}
{...restProps}
>
{@render children?.()}
{#if showCloseButton}
<DialogPrimitive.Close data-slot="dialog-close">
{#snippet child({ props })}
<Button variant="ghost" class="absolute top-2 right-2" size="icon-sm" {...props}>
<XIcon />
<span class="sr-only">Close</span>
</Button>
{/snippet}
</DialogPrimitive.Close>
{/if}
</DialogPrimitive.Content>
</DialogPortal>
@@ -0,0 +1,20 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.DescriptionProps = $props();
</script>
<DialogPrimitive.Description
bind:ref
data-slot="dialog-description"
class={cn(
'text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
className
)}
{...restProps}
/>
@@ -0,0 +1,35 @@
<script lang="ts">
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
import { Dialog as DialogPrimitive } from 'bits-ui';
import { Button } from '@evobgp/ui/components/button/index.js';
let {
ref = $bindable(null),
class: className,
children,
showCloseButton = false,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
showCloseButton?: boolean;
} = $props();
</script>
<div
bind:this={ref}
data-slot="dialog-footer"
class={cn(
'-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',
className
)}
{...restProps}
>
{@render children?.()}
{#if showCloseButton}
<DialogPrimitive.Close>
{#snippet child({ props })}
<Button variant="outline" {...props}>Close</Button>
{/snippet}
</DialogPrimitive.Close>
{/if}
</div>
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="dialog-header"
class={cn('flex flex-col gap-2', className)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,20 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
</script>
<DialogPrimitive.Overlay
bind:ref
data-slot="dialog-overlay"
class={cn(
'fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-open:animate-in data-open:fade-in-0 supports-backdrop-filter:backdrop-blur-xs',
className
)}
{...restProps}
/>
@@ -0,0 +1,7 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let { ...restProps }: DialogPrimitive.PortalProps = $props();
</script>
<DialogPrimitive.Portal {...restProps} />
@@ -0,0 +1,17 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.TitleProps = $props();
</script>
<DialogPrimitive.Title
bind:ref
data-slot="dialog-title"
class={cn('text-base leading-none font-medium', className)}
{...restProps}
/>
@@ -0,0 +1,11 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let {
ref = $bindable(null),
type = 'button',
...restProps
}: DialogPrimitive.TriggerProps = $props();
</script>
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {type} {...restProps} />
@@ -0,0 +1,7 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: DialogPrimitive.RootProps = $props();
</script>
<DialogPrimitive.Root bind:open {...restProps} />
@@ -0,0 +1,34 @@
import Root from './dialog.svelte';
import Portal from './dialog-portal.svelte';
import Title from './dialog-title.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
import Trigger from './dialog-trigger.svelte';
import Close from './dialog-close.svelte';
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
Close,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,
Close as DialogClose
};
@@ -0,0 +1,16 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let {
ref = $bindable(null),
value = $bindable([]),
...restProps
}: DropdownMenuPrimitive.CheckboxGroupProps = $props();
</script>
<DropdownMenuPrimitive.CheckboxGroup
bind:ref
bind:value
data-slot="dropdown-menu-checkbox-group"
{...restProps}
/>
@@ -0,0 +1,44 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import MinusIcon from '@lucide/svelte/icons/minus';
import CheckIcon from '@lucide/svelte/icons/check';
import { cn, type WithoutChildrenOrChild } from '@evobgp/ui/lib/utils';
import type { Snippet } from 'svelte';
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
children: childrenProp,
...restProps
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
children?: Snippet;
} = $props();
</script>
<DropdownMenuPrimitive.CheckboxItem
bind:ref
bind:checked
bind:indeterminate
data-slot="dropdown-menu-checkbox-item"
class={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<span
class="pointer-events-none absolute right-2 flex items-center justify-center"
data-slot="dropdown-menu-checkbox-item-indicator"
>
{#if indeterminate}
<MinusIcon />
{:else if checked}
<CheckIcon />
{/if}
</span>
{@render childrenProp?.()}
{/snippet}
</DropdownMenuPrimitive.CheckboxItem>
@@ -0,0 +1,31 @@
<script lang="ts">
import { cn, type WithoutChildrenOrChild } from '@evobgp/ui/lib/utils';
import DropdownMenuPortal from './dropdown-menu-portal.svelte';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
sideOffset = 4,
align = 'start',
portalProps,
class: className,
...restProps
}: DropdownMenuPrimitive.ContentProps & {
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DropdownMenuPortal>>;
} = $props();
</script>
<DropdownMenuPortal {...portalProps}>
<DropdownMenuPrimitive.Content
bind:ref
data-slot="dropdown-menu-content"
{sideOffset}
{align}
class={cn(
'z-50 w-(--bits-dropdown-menu-anchor-width) min-w-32 overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...restProps}
/>
</DropdownMenuPortal>
@@ -0,0 +1,22 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.GroupHeading
bind:ref
data-slot="dropdown-menu-group-heading"
data-inset={inset}
class={cn('px-2 py-1.5 text-sm font-semibold data-[inset]:ps-8', className)}
{...restProps}
/>
@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
</script>
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
@@ -0,0 +1,27 @@
<script lang="ts">
import { cn } from '@evobgp/ui/lib/utils';
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let {
ref = $bindable(null),
class: className,
inset,
variant = 'default',
...restProps
}: DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
variant?: 'default' | 'destructive';
} = $props();
</script>
<DropdownMenuPrimitive.Item
bind:ref
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
class={cn(
"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-7 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",
className
)}
{...restProps}
/>
@@ -0,0 +1,27 @@
<script lang="ts">
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
inset?: boolean;
} = $props();
</script>
<div
bind:this={ref}
data-slot="dropdown-menu-label"
data-inset={inset}
class={cn(
'px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7 data-[inset]:pl-8',
className
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { ...restProps }: DropdownMenuPrimitive.PortalProps = $props();
</script>
<DropdownMenuPrimitive.Portal {...restProps} />
@@ -0,0 +1,16 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let {
ref = $bindable(null),
value = $bindable(),
...restProps
}: DropdownMenuPrimitive.RadioGroupProps = $props();
</script>
<DropdownMenuPrimitive.RadioGroup
bind:ref
bind:value
data-slot="dropdown-menu-radio-group"
{...restProps}
/>
@@ -0,0 +1,34 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import CheckIcon from '@lucide/svelte/icons/check';
import { cn, type WithoutChild } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children: childrenProp,
...restProps
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
</script>
<DropdownMenuPrimitive.RadioItem
bind:ref
data-slot="dropdown-menu-radio-item"
class={cn(
"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...restProps}
>
{#snippet children({ checked })}
<span
class="pointer-events-none absolute right-2 flex items-center justify-center"
data-slot="dropdown-menu-radio-item-indicator"
>
{#if checked}
<CheckIcon />
{/if}
</span>
{@render childrenProp?.({ checked })}
{/snippet}
</DropdownMenuPrimitive.RadioItem>
@@ -0,0 +1,17 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props();
</script>
<DropdownMenuPrimitive.Separator
bind:ref
data-slot="dropdown-menu-separator"
class={cn('-mx-1 my-1 h-px bg-border', className)}
{...restProps}
/>
@@ -0,0 +1,23 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
data-slot="dropdown-menu-shortcut"
class={cn(
'ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground',
className
)}
{...restProps}
>
{@render children?.()}
</span>
@@ -0,0 +1,20 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SubContentProps = $props();
</script>
<DropdownMenuPrimitive.SubContent
bind:ref
data-slot="dropdown-menu-sub-content"
class={cn(
'w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...restProps}
/>
@@ -0,0 +1,29 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: DropdownMenuPrimitive.SubTriggerProps & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.SubTrigger
bind:ref
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
class={cn(
"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...restProps}
>
{@render children?.()}
<ChevronRightIcon class="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.SubProps = $props();
</script>
<DropdownMenuPrimitive.Sub bind:open {...restProps} />
@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.TriggerProps = $props();
</script>
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
@@ -0,0 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.RootProps = $props();
</script>
<DropdownMenuPrimitive.Root bind:open {...restProps} />
@@ -0,0 +1,54 @@
import Root from './dropdown-menu.svelte';
import Sub from './dropdown-menu-sub.svelte';
import CheckboxGroup from './dropdown-menu-checkbox-group.svelte';
import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
import Content from './dropdown-menu-content.svelte';
import Group from './dropdown-menu-group.svelte';
import Item from './dropdown-menu-item.svelte';
import Label from './dropdown-menu-label.svelte';
import RadioGroup from './dropdown-menu-radio-group.svelte';
import RadioItem from './dropdown-menu-radio-item.svelte';
import Separator from './dropdown-menu-separator.svelte';
import Shortcut from './dropdown-menu-shortcut.svelte';
import Trigger from './dropdown-menu-trigger.svelte';
import SubContent from './dropdown-menu-sub-content.svelte';
import SubTrigger from './dropdown-menu-sub-trigger.svelte';
import GroupHeading from './dropdown-menu-group-heading.svelte';
import Portal from './dropdown-menu-portal.svelte';
export {
CheckboxGroup,
CheckboxItem,
Content,
Portal,
Root as DropdownMenu,
CheckboxGroup as DropdownMenuCheckboxGroup,
CheckboxItem as DropdownMenuCheckboxItem,
Content as DropdownMenuContent,
Portal as DropdownMenuPortal,
Group as DropdownMenuGroup,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
RadioGroup as DropdownMenuRadioGroup,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
Shortcut as DropdownMenuShortcut,
Sub as DropdownMenuSub,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
Trigger as DropdownMenuTrigger,
GroupHeading as DropdownMenuGroupHeading,
Group,
GroupHeading,
Item,
Label,
RadioGroup,
RadioItem,
Root,
Separator,
Shortcut,
Sub,
SubContent,
SubTrigger,
Trigger
};
@@ -0,0 +1,7 @@
<script lang="ts">
import { Button, type ButtonProps } from '@evobgp/ui/components/button/index.js';
let { ref = $bindable(null), ...restProps }: ButtonProps = $props();
</script>
<Button bind:ref type="submit" {...restProps} />
@@ -0,0 +1,17 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn, type WithoutChild } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChild<FormPrimitive.DescriptionProps> = $props();
</script>
<FormPrimitive.Description
bind:ref
data-slot="form-description"
class={cn('text-sm text-muted-foreground', className)}
{...restProps}
/>
@@ -0,0 +1,24 @@
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
import * as FormPrimitive from 'formsnap';
import type { FormPathLeaves } from 'sveltekit-superforms';
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef, type WithoutChildren } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
form,
name,
children: childrenProp,
...restProps
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> &
FormPrimitive.ElementFieldProps<T, U> = $props();
</script>
<FormPrimitive.ElementField {form} {name}>
{#snippet children({ constraints, errors, tainted, value })}
<div bind:this={ref} class={cn('space-y-2', className)} {...restProps}>
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
</div>
{/snippet}
</FormPrimitive.ElementField>
@@ -0,0 +1,30 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn, type WithoutChild } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
errorClasses,
children: childrenProp,
...restProps
}: WithoutChild<FormPrimitive.FieldErrorsProps> & {
errorClasses?: string | undefined | null;
} = $props();
</script>
<FormPrimitive.FieldErrors
bind:ref
class={cn('text-sm font-medium text-destructive', className)}
{...restProps}
>
{#snippet children({ errors, errorProps })}
{#if childrenProp}
{@render childrenProp({ errors, errorProps })}
{:else}
{#each errors as error (error)}
<div {...errorProps} class={cn(errorClasses)}>{error}</div>
{/each}
{/if}
{/snippet}
</FormPrimitive.FieldErrors>
@@ -0,0 +1,24 @@
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from 'formsnap';
import type { FormPath } from 'sveltekit-superforms';
import { cn, type WithElementRef, type WithoutChildren } from '@evobgp/ui/lib/utils';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
form,
name,
children: childrenProp,
...restProps
}: FormPrimitive.FieldProps<T, U> &
WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
</script>
<FormPrimitive.Field {form} {name}>
{#snippet children({ constraints, errors, tainted, value })}
<div bind:this={ref} data-slot="form-item" class={cn('space-y-2', className)} {...restProps}>
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
</div>
{/snippet}
</FormPrimitive.Field>
@@ -0,0 +1,15 @@
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from 'formsnap';
import type { FormPath } from 'sveltekit-superforms';
import { cn, type WithoutChild } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
form,
name,
...restProps
}: WithoutChild<FormPrimitive.FieldsetProps<T, U>> = $props();
</script>
<FormPrimitive.Fieldset bind:ref {form} {name} class={cn('space-y-2', className)} {...restProps} />
@@ -0,0 +1,24 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { Label } from '@evobgp/ui/components/label/index.js';
import { cn, type WithoutChild } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
children,
class: className,
...restProps
}: WithoutChild<FormPrimitive.LabelProps> = $props();
</script>
<FormPrimitive.Label {...restProps} bind:ref>
{#snippet child({ props })}
<Label
{...props}
data-slot="form-label"
class={cn('data-[fs-error]:text-destructive', className)}
>
{@render children?.()}
</Label>
{/snippet}
</FormPrimitive.Label>
@@ -0,0 +1,16 @@
<script lang="ts">
import * as FormPrimitive from 'formsnap';
import { cn, type WithoutChild } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: WithoutChild<FormPrimitive.LegendProps> = $props();
</script>
<FormPrimitive.Legend
bind:ref
class={cn('text-sm leading-none font-medium data-[fs-error]:text-destructive', className)}
{...restProps}
/>
+33
View File
@@ -0,0 +1,33 @@
import * as FormPrimitive from 'formsnap';
import Description from './form-description.svelte';
import Label from './form-label.svelte';
import FieldErrors from './form-field-errors.svelte';
import Field from './form-field.svelte';
import Fieldset from './form-fieldset.svelte';
import Legend from './form-legend.svelte';
import ElementField from './form-element-field.svelte';
import Button from './form-button.svelte';
const Control = FormPrimitive.Control;
export {
Field,
Control,
Label,
Button,
FieldErrors,
Description,
Fieldset,
Legend,
ElementField,
//
Field as FormField,
Control as FormControl,
Description as FormDescription,
Label as FormLabel,
FieldErrors as FormFieldErrors,
Fieldset as FormFieldset,
Legend as FormLegend,
ElementField as FormElementField,
Button as FormButton
};
@@ -0,0 +1,7 @@
import Root from './input.svelte';
export {
Root,
//
Root as Input
};
@@ -0,0 +1,48 @@
<script lang="ts">
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
type Props = WithElementRef<
Omit<HTMLInputAttributes, 'type'> &
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
>;
let {
ref = $bindable(null),
value = $bindable(),
type,
files = $bindable(),
class: className,
'data-slot': dataSlot = 'input',
...restProps
}: Props = $props();
</script>
{#if type === 'file'}
<input
bind:this={ref}
data-slot={dataSlot}
class={cn(
'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
className
)}
type="file"
bind:files
bind:value
{...restProps}
/>
{:else}
<input
bind:this={ref}
data-slot={dataSlot}
class={cn(
'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
className
)}
{type}
bind:value
{...restProps}
/>
{/if}
@@ -0,0 +1,7 @@
import Root from './label.svelte';
export {
Root,
//
Root as Label
};
@@ -0,0 +1,20 @@
<script lang="ts">
import { Label as LabelPrimitive } from 'bits-ui';
import { cn } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
...restProps
}: LabelPrimitive.RootProps = $props();
</script>
<LabelPrimitive.Root
bind:ref
data-slot="label"
class={cn(
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className
)}
{...restProps}
/>
@@ -0,0 +1,28 @@
import Root from './popover.svelte';
import Close from './popover-close.svelte';
import Content from './popover-content.svelte';
import Description from './popover-description.svelte';
import Header from './popover-header.svelte';
import Title from './popover-title.svelte';
import Trigger from './popover-trigger.svelte';
import Portal from './popover-portal.svelte';
export {
Root,
Content,
Description,
Header,
Title,
Trigger,
Close,
Portal,
//
Root as Popover,
Content as PopoverContent,
Description as PopoverDescription,
Header as PopoverHeader,
Title as PopoverTitle,
Trigger as PopoverTrigger,
Close as PopoverClose,
Portal as PopoverPortal
};
@@ -0,0 +1,7 @@
<script lang="ts">
import { Popover as PopoverPrimitive } from 'bits-ui';
let { ref = $bindable(null), ...restProps }: PopoverPrimitive.CloseProps = $props();
</script>
<PopoverPrimitive.Close bind:ref data-slot="popover-close" {...restProps} />
@@ -0,0 +1,31 @@
<script lang="ts">
import { Popover as PopoverPrimitive } from 'bits-ui';
import PopoverPortal from './popover-portal.svelte';
import { cn, type WithoutChildrenOrChild } from '@evobgp/ui/lib/utils';
import type { ComponentProps } from 'svelte';
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
align = 'center',
portalProps,
...restProps
}: PopoverPrimitive.ContentProps & {
portalProps?: WithoutChildrenOrChild<ComponentProps<typeof PopoverPortal>>;
} = $props();
</script>
<PopoverPortal {...portalProps}>
<PopoverPrimitive.Content
bind:ref
data-slot="popover-content"
{sideOffset}
{align}
class={cn(
'z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...restProps}
/>
</PopoverPortal>
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="popover-description"
class={cn('text-muted-foreground', className)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { cn, type WithElementRef } from '@evobgp/ui/lib/utils';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="popover-header"
class={cn('flex flex-col gap-0.5 text-sm', className)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,7 @@
<script lang="ts">
import { Popover as PopoverPrimitive } from 'bits-ui';
let { ...restProps }: PopoverPrimitive.PortalProps = $props();
</script>
<PopoverPrimitive.Portal {...restProps} />

Some files were not shown because too many files have changed in this diff Show More