mirror of
https://github.com/wireadmin/wireadmin
synced 2025-06-04 03:36:55 +00:00
add shadcn
, badge, and few more new components
This commit is contained in:
parent
5d4a2c3d51
commit
af3f37b14f
31
web/src/lib/components/copiable-text/CopiableText.svelte
Normal file
31
web/src/lib/components/copiable-text/CopiableText.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/utils.js';
|
||||||
|
|
||||||
|
export let showInHover: boolean = false;
|
||||||
|
export let rootClass: string | undefined = undefined;
|
||||||
|
export let value: string;
|
||||||
|
let className: string | undefined = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn('group flex items-center', rootClass)}>
|
||||||
|
<slot />
|
||||||
|
<i
|
||||||
|
aria-roledescription="Copy to clipboard"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
class={cn(
|
||||||
|
'ml-2 mb-0.5 far fa-copy cursor-pointer text-gray-400/80 hover:text-primary',
|
||||||
|
showInHover && 'group-hover:opacity-100 opacity-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
on:click={handleCopy}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') handleCopy();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
7
web/src/lib/components/copiable-text/index.ts
Normal file
7
web/src/lib/components/copiable-text/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Root from './CopiableText.svelte';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as CopiableText,
|
||||||
|
};
|
73
web/src/lib/components/editable-text/EditableText.svelte
Normal file
73
web/src/lib/components/editable-text/EditableText.svelte
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/utils';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import type { ZodEffects, ZodString } from 'zod';
|
||||||
|
|
||||||
|
export let editMode: boolean = false;
|
||||||
|
export let schema: ZodString | ZodEffects<any>;
|
||||||
|
export let rootClass: string | undefined = undefined;
|
||||||
|
export let inputClass: string | undefined = undefined;
|
||||||
|
export let value: string;
|
||||||
|
export let error: boolean = false;
|
||||||
|
export let asChild: boolean = false;
|
||||||
|
let className: string | undefined = undefined;
|
||||||
|
export { className as class };
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const handleEnterEditMode = () => {
|
||||||
|
editMode = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExitEditMode = async () => {
|
||||||
|
editMode = false;
|
||||||
|
dispatch('change', { value });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn('group flex items-center leading-none gap-x-3', rootClass)}>
|
||||||
|
{#if asChild}
|
||||||
|
<slot {editMode} />
|
||||||
|
{:else}
|
||||||
|
<span class={cn(editMode ? 'hidden' : 'flex items-center gap-x-2', 'leading-none', className)}>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class={cn(
|
||||||
|
editMode ? 'block' : 'hidden',
|
||||||
|
'w-full ring-2 ring-neutral-800 ring-offset-2 rounded transition-colors duration-200 ease-in-out outline-transparent',
|
||||||
|
inputClass,
|
||||||
|
error && 'ring-red-500 rounded',
|
||||||
|
)}
|
||||||
|
{value}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
// @ts-ignore
|
||||||
|
let val = e.target.value ?? '';
|
||||||
|
if (!schema.safeParse(val).success) {
|
||||||
|
editMode = true;
|
||||||
|
error = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = val;
|
||||||
|
handleExitEditMode();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
editMode = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<i
|
||||||
|
class="fal fa-pen-to-square text-sm opacity-0 group-hover:opacity-100 text-neutral-400 hover:text-primary cursor-pointer"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-roledescription="Edit"
|
||||||
|
on:click={handleEnterEditMode}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') handleEnterEditMode();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
7
web/src/lib/components/editable-text/index.ts
Normal file
7
web/src/lib/components/editable-text/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Root from './EditableText.svelte';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as EditableText
|
||||||
|
}
|
@ -11,7 +11,7 @@
|
|||||||
<h1>WireAdmin</h1>
|
<h1>WireAdmin</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={'flex items-center gap-x-2'}>
|
<div class={'hidden md:flex items-center gap-x-2'}>
|
||||||
<a href={'https://github.com/shahradelahi/wireadmin'} title={'Giv me a star on Github'}>
|
<a href={'https://github.com/shahradelahi/wireadmin'} title={'Giv me a star on Github'}>
|
||||||
<img
|
<img
|
||||||
src={'https://img.shields.io/github/stars/shahradelahi/wireadmin.svg?style=social&label=Star'}
|
src={'https://img.shields.io/github/stars/shahradelahi/wireadmin.svg?style=social&label=Star'}
|
||||||
@ -20,7 +20,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
{#if showLogout}
|
{#if showLogout}
|
||||||
<a href={'/logout'} class={''} title="logout">
|
<a href={'/logout'} title="logout">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-6 w-6 text-red-500 hover:text-red-700"
|
class="h-6 w-6 text-red-500 hover:text-red-700"
|
||||||
|
13
web/src/lib/components/ui/badge/badge.svelte
Normal file
13
web/src/lib/components/ui/badge/badge.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from '$lib/utils';
|
||||||
|
import { badgeVariants, type Variant } from '.';
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export let href: string | undefined = undefined;
|
||||||
|
export let variant: Variant = 'default';
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={href ? 'a' : 'span'} {href} class={cn(badgeVariants({ variant, className }))} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</svelte:element>
|
20
web/src/lib/components/ui/badge/index.ts
Normal file
20
web/src/lib/components/ui/badge/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { tv, type VariantProps } from 'tailwind-variants';
|
||||||
|
export { default as Badge } from './badge.svelte';
|
||||||
|
|
||||||
|
export const badgeVariants = tv({
|
||||||
|
base: 'inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none select-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-primary hover:bg-primary/80 border-transparent text-primary-foreground',
|
||||||
|
secondary: 'bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground',
|
||||||
|
success: 'bg-success hover:bg-success/80 border-transparent text-success-foreground',
|
||||||
|
destructive: 'bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground',
|
||||||
|
outline: 'text-foreground',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Variant = VariantProps<typeof badgeVariants>['variant'];
|
92
web/src/routes/CreateServerDialog.svelte
Normal file
92
web/src/routes/CreateServerDialog.svelte
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { CreateServerSchema, type CreateServerSchemaType } from './schema';
|
||||||
|
import type { SuperValidated } from 'sveltekit-superforms';
|
||||||
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '$lib/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormButton,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormInput,
|
||||||
|
FormLabel,
|
||||||
|
FormValidation,
|
||||||
|
} from '$lib/components/ui/form';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { FormItem } from '$lib/components/ui/form/index.js';
|
||||||
|
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
|
||||||
|
|
||||||
|
let form: SuperValidated<CreateServerSchemaType>;
|
||||||
|
export let isOpen = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog open={isOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Server</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<SuperDebug data={form} />
|
||||||
|
<Form
|
||||||
|
{form}
|
||||||
|
schema={CreateServerSchema}
|
||||||
|
class="space-y-3.5"
|
||||||
|
action="?/create"
|
||||||
|
method={'POST'}
|
||||||
|
let:config
|
||||||
|
options={{
|
||||||
|
onResult: ({ result }) => {
|
||||||
|
if (result.type === 'success') {
|
||||||
|
goto('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormField {config} name={'name'}>
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Name</FormLabel>
|
||||||
|
<FormInput placeholder={'e.g. CuteHub'} type={'text'} />
|
||||||
|
<FormValidation />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField {config} name={'address'}>
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Address</FormLabel>
|
||||||
|
<FormInput placeholder={'e.g. 10.8.0.1'} type={'text'} />
|
||||||
|
<FormDescription>This is the Private IP Address of the server.</FormDescription>
|
||||||
|
<FormValidation />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField {config} name={'port'}>
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Port</FormLabel>
|
||||||
|
<FormInput placeholder={'e.g. 51820'} type={'text'} />
|
||||||
|
<FormDescription>This is the port that the WireGuard server will listen on.</FormDescription>
|
||||||
|
<FormValidation />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField {config} name={'dns'}>
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>DNS</FormLabel>
|
||||||
|
<FormInput placeholder={'e.g. 1.1.1.1'} type={'text'} />
|
||||||
|
<FormDescription>Optional. This is the DNS server that will be pushed to clients.</FormDescription>
|
||||||
|
<FormValidation />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField {config} name={'mtu'}>
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>MTU</FormLabel>
|
||||||
|
<FormInput placeholder={'1350'} type={'text'} />
|
||||||
|
<FormDescription>Optional. Recommended to leave this blank.</FormDescription>
|
||||||
|
<FormValidation />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<FormButton type="submit">Create</FormButton>
|
||||||
|
</DialogFooter>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
13
web/src/routes/[serverId]/CreatePeerDialog.svelte
Normal file
13
web/src/routes/[serverId]/CreatePeerDialog.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Dialog, DialogContent } from '$lib/components/ui/dialog';
|
||||||
|
|
||||||
|
export let serverId: string;
|
||||||
|
export let open: boolean = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog {open}>
|
||||||
|
<DialogContent>
|
||||||
|
<h2>Create Peer</h2>
|
||||||
|
<p>Server Id: {serverId}</p>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
Loading…
Reference in New Issue
Block a user