initial rewrite of base UI

This commit is contained in:
Chris Bisset 2025-01-15 14:38:49 +11:00
parent 64b6c5dc75
commit 074cf4752e
76 changed files with 4873 additions and 1084 deletions

View file

@ -1,13 +1,4 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

View file

@ -1,7 +1,16 @@
{
"useTabs": true,
"tabWidth": 2,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 400
"printWidth": 1000,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

1
archive/.npmrc Normal file
View file

@ -0,0 +1 @@
engine-strict=true

13
archive/.prettierignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

7
archive/.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"useTabs": true,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 400
}

3499
archive/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

35
archive/package.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "headscale-ui",
"version": "2024.10.10",
"scripts": {
"dev": "vite dev --port 8080 --host 0.0.0.0",
"build": "vite build",
"package": "vite package",
"preview": "vite preview --https --port 443 --host 0.0.0.0",
"stage": "/usr/bin/caddy run --adapter caddyfile --config ./Caddyfile",
"check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. .",
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3",
"@sveltejs/adapter-static": "^3",
"@sveltejs/kit": "^2",
"@tailwindcss/typography": "^0",
"@vitejs/plugin-basic-ssl": "^1",
"autoprefixer": "^10",
"daisyui": "^4",
"fuse.js": "^7",
"postcss": "^8",
"postcss-load-config": "^5",
"prettier": "^3",
"prettier-plugin-svelte": "^3",
"svelte": "^4",
"svelte-check": "^3",
"svelte-preprocess": "^5",
"tailwindcss": "^3",
"typescript": "^5"
},
"type": "module"
}

24
archive/src/app.css Normal file
View file

@ -0,0 +1,24 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;
.form-input {
@apply shadow appearance-none border rounded w-full bg-base-100 py-2 px-3 text-base-content text-sm mb-3 leading-tight focus:outline-none;
}
.card-primary {
@apply grid grid-cols-1 divide-y p-2 max-w-screen-lg mx-4 border-base-content rounded-md text-sm text-base-content shadow
}
.card-pending {
@apply flex justify-between p-1 mb-4 max-w-screen-lg border border-dashed mx-4 border-base-content rounded-md text-sm text-base-content shadow
}
.card-input {
@apply shadow appearance-none border rounded w-64 py-1 px-3 bg-base-100 text-base-content text-sm leading-tight focus:outline-none;
}
.card-select {
@apply shadow border rounded w-64 py-1 px-3 bg-base-100 text-base-content text-sm leading-tight focus:outline-2;
}

10
archive/src/app.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
// interface Locals {}
// interface Platform {}
// interface Session {}
// interface Stuff {}
}

12
archive/src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,30 @@
<script lang="ts">
import '../app.css';
import Nav from '$lib/common/nav.svelte';
import Alert from '$lib/common/Alert.svelte';
import Stores from '$lib/common/Stores.svelte';
import { themeStore } from '$lib/common/stores.js'
// NOTE: the element that is using one of the theme attributes must be in the DOM on mount
</script>
<main data-theme={$themeStore} class="flex flex-col">
<!-- initialize localStorage -->
<Stores></Stores>
<div class="flex">
<!-- sidebar -->
<Nav />
<!-- main window -->
<div class="flex flex-1 min-w-0 flex-col bg-base-100">
<Alert />
<!-- header -->
<!-- <div class="flex bg-gray-100 h-12 p-4">Header</div> -->
<!-- content -->
<div>
<slot />
</div>
</div>
</div>
<!-- <div class="flex">Footer</div> -->
</main>

View file

@ -0,0 +1,9 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { onMount } from 'svelte';
onMount(async () => {
goto(`${base}/users.html`);
});
</script>

26
archive/svelte.config.js Normal file
View file

@ -0,0 +1,26 @@
import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
fallback: 'index.html',
precompress: false
}),
paths: {
base: "/web"
},
csp: {
mode: "hash",
directives: { "script-src": ["self"] },
}
},
preprocess: [
preprocess({
postcss: true
})
]
};
export default config;

1716
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,35 +1,33 @@
{
"name": "headscale-ui",
"version": "2024.10.10",
"name": "base-frontend",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev --port 8080 --host 0.0.0.0",
"dev": "vite dev --host 0.0.0.0 --port 8080",
"build": "vite build",
"package": "vite package",
"preview": "vite preview --https --port 443 --host 0.0.0.0",
"stage": "/usr/bin/caddy run --adapter caddyfile --config ./Caddyfile",
"check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. .",
"format": "prettier --write --plugin-search-dir=. ."
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3",
"@sveltejs/adapter-static": "^3",
"@sveltejs/kit": "^2",
"@tailwindcss/typography": "^0",
"@vitejs/plugin-basic-ssl": "^1",
"autoprefixer": "^10",
"daisyui": "^4",
"fuse.js": "^7",
"postcss": "^8",
"postcss-load-config": "^5",
"prettier": "^3",
"prettier-plugin-svelte": "^3",
"svelte": "^4",
"svelte-check": "^3",
"svelte-preprocess": "^5",
"tailwindcss": "^3",
"typescript": "^5"
},
"type": "module"
}
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.15",
"autoprefixer": "^10.4.20",
"daisyui": "^4.12.14",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.5",
"svelecte": "^5.1.1",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.9",
"typescript": "^5.0.0",
"vite": "^6.0.3"
}
}

6
postcss.config.js Normal file
View file

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View file

@ -1,24 +1,41 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
.form-input {
@apply shadow appearance-none border rounded w-full bg-base-100 py-2 px-3 text-base-content text-sm mb-3 leading-tight focus:outline-none;
}
.card-primary {
@apply grid grid-cols-1 divide-y p-2 max-w-screen-lg mx-4 border-base-content rounded-md text-sm text-base-content shadow
}
.card-pending {
@apply flex justify-between p-1 mb-4 max-w-screen-lg border border-dashed mx-4 border-base-content rounded-md text-sm text-base-content shadow
}
.card-input {
@apply shadow appearance-none border rounded w-64 py-1 px-3 bg-base-100 text-base-content text-sm leading-tight focus:outline-none;
}
.card-select {
@apply shadow border rounded w-64 py-1 px-3 bg-base-100 text-base-content text-sm leading-tight focus:outline-2;
/* css settings for svelecte dependency */
@layer components {
.svelecte {
--sv-min-height: 40px;
--sv-bg: oklch(var(--b1));
--sv-disabled-bg: oklch(var(--nc));
--sv-border: 1px solid #626262;
--sv-border-radius: 4px;
--sv-general-padding: 4px;
--sv-control-bg: var(--sv-bg);
--sv-item-wrap-padding: 3px 3px 3px 6px;
--sv-item-selected-bg: #626262;
--sv-item-btn-color: #ccc;
--sv-item-btn-color-hover: #ccc;
--sv-item-btn-bg: #626262;
--sv-item-btn-bg-hover: #bc6063;
--sv-icon-color: #bbb;
--sv-icon-color-hover: #ccc;
--sv-icon-bg: transparent;
--sv-icon-size: 20px;
--sv-separator-bg: #626262;
--sv-btn-border: 0;
--sv-placeholder-color: #ccccd6;
--sv-dropdown-bg: var(--sv-bg);
--sv-dropdown-border: var(--sv-border);
--sv-dropdown-offset: 1px;
--sv-dropdown-width: auto;
--sv-dropdown-shadow: 0 1px 3px #555;
--sv-dropdown-height: 320px;
--sv-dropdown-active-bg: oklch(var(--b3));
--sv-dropdown-selected-bg: #754545;
--sv-create-kbd-border: 1px solid #626262;
--sv-create-kbd-bg: #626262;
--sv-create-disabled-bg: #fcbaba;
--sv-loader-border: 2px solid #626262;
}
}

19
src/app.d.ts vendored
View file

@ -1,10 +1,13 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare namespace App {
// interface Locals {}
// interface Platform {}
// interface Session {}
// interface Stuff {}
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export { };

View file

@ -1,12 +1,15 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/site/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

29
src/lib/classes.svelte.ts Normal file
View file

@ -0,0 +1,29 @@
export class PersistentAppSettingsObject {
daisyUITheme = "" // for setting the UI theme. See https://daisyui.com/docs/themes/
public constructor(init?: Partial<PersistentAppSettingsObject>) {
Object.assign(this, init);
}
}
export class AppSettingsObject {
navbarTitle = "" // for setting the title of the page
appLoaded = false // for hiding the screen until hydration has completed
sidebarDrawerOpen = false // for determining if the sidebar is open when on a small screen
toastAlerts: toastAlert[] = [] // for adding or removing alerts
public constructor(init?: Partial<AppSettingsObject>) {
Object.assign(this, init);
}
}
// alert used for populating toasts in the layout
export class toastAlert {
message = "" //message to display
notificationType = "alert" //to style the toast
id = "" //UUID generated to reference the toast
public constructor(init?: Partial<toastAlert>) {
Object.assign(this, init);
}
}

View file

@ -0,0 +1,17 @@
<!-- nav bar content here -->
<script lang="ts">
import { appSettings } from '$lib/state.svelte';
</script>
<div class="flex-1">
<button
aria-label="open sidebar"
onclick={() => {
appSettings.sidebarDrawerOpen = true;
}}
class="btn-square btn-ghost mask drawer-button mask-squircle btn-sm lg:hidden"
>
<svg xmlns="http://www.w3.org/2000/svg" class="m-1 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" /></svg>
</button>
<div class="p-1"><h1 class="bold text-xl text-primary">{appSettings.navbarTitle}</h1></div>
</div>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,41 @@
<!-- used to generate alerts or messages -->
<script lang="ts">
import { appSettings } from '$lib/state.svelte';
let { toastAlert } = $props();
let progress = $state(100);
// sets a timer and removes the alert after totalTime miliseconds
const intervalTime = 20; // 20ms
const totalTime = 4000; // 4 seconds
const decrement = 100 / (totalTime / intervalTime);
const interval = setInterval(() => {
progress -= decrement;
if (progress <= 0) {
progress = 0;
removeAlert();
}
}, intervalTime);
// Removes alert early on button press
function removeAlert() {
clearInterval(interval); // Stop the interval at 0
appSettings.toastAlerts = appSettings.toastAlerts.filter(function (returnObj) {
return returnObj.id !== toastAlert.id;
});
}
</script>
<div class="alert flex justify-between min-w-60 text-wrap">
<div>{toastAlert.message}</div>
<div>
<button aria-label="close notification" class="mask mask-circle hover:bg-base-300" onclick={() => removeAlert()}>
<div class="radial-progress" style="--value:{progress}; --size:2rem; --thickness: 2px;" role="progressbar">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</button>
</div>
</div>

1
src/lib/index.ts Normal file
View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

16
src/lib/state.svelte Normal file
View file

@ -0,0 +1,16 @@
<script module lang="ts">
import { AppSettingsObject, PersistentAppSettingsObject } from './classes.svelte';
// defines global state objects for the application
export let appSettings = $state({
navbarTitle: '',
appLoaded: false,
sidebarDrawerOpen: false,
toastAlerts: []
} as AppSettingsObject);
// defines global state objects that get written to localStorage for persistence
export let persistentAppSettings = $state({
daisyUITheme: ''
} as PersistentAppSettingsObject);
</script>

View file

@ -1,30 +1,79 @@
<script lang="ts">
import { toastAlert, type PersistentAppSettingsObject } from '$lib/classes.svelte';
import Toast from '$lib/components/layout/toast.svelte';
import Navbar from '$lib/components/layout/navbar.svelte';
import Sidebar from '$lib/components/layout/sidebar.svelte';
import { appSettings, persistentAppSettings } from '$lib/state.svelte';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import '../app.css';
import Nav from '$lib/common/nav.svelte';
import Alert from '$lib/common/Alert.svelte';
import Stores from '$lib/common/Stores.svelte';
import { themeStore } from '$lib/common/stores.js'
let { children } = $props();
// NOTE: the element that is using one of the theme attributes must be in the DOM on mount
onMount(async () => {
// monitor the persistent app store and read/write to local storage as required
// load existing persistentAppSettings from localStorage
let localStorageAppSettings = JSON.parse(localStorage.getItem('persistentAppSettings') || '{}') as PersistentAppSettingsObject;
Object.assign(persistentAppSettings, localStorageAppSettings);
// track when persistentAppSettings changes and write it back into localStorage
$effect(() => {
localStorage.setItem('persistentAppSettings', JSON.stringify(persistentAppSettings));
});
// delay load until page is hydrated
appSettings.appLoaded = true;
// alert test
// appSettings.toastAlerts.push(
// new toastAlert({
// message: 'this is a test message',
// id: crypto.randomUUID()
// })
// );
// appSettings.toastAlerts.push(
// new toastAlert({
// message: 'this is a test message too and super long and long and long',
// id: crypto.randomUUID()
// })
// );
});
</script>
<main data-theme={$themeStore} class="flex flex-col">
<!-- initialize localStorage -->
<Stores></Stores>
<div class="flex">
<!-- sidebar -->
<Nav />
<!-- main window -->
<div class="flex flex-1 min-w-0 flex-col bg-base-100">
<Alert />
<!-- header -->
<!-- <div class="flex bg-gray-100 h-12 p-4">Header</div> -->
<!-- content -->
<div>
<slot />
{#if appSettings.appLoaded}
<!-- daisy UI theme and menu settings -->
<main data-theme={persistentAppSettings.daisyUITheme} in:fade={{ duration: 200 }} class="drawer lg:drawer-open">
<input id="drawer-toggle" type="checkbox" class="drawer-toggle" checked={appSettings.sidebarDrawerOpen ? true : null} />
<div class="drawer-content flex flex-col">
<!-- toast content -->
<div class="toast toast-center toast-top z-40">
{#each appSettings.toastAlerts as toast}
<div transition:fade={{ duration: 200 }}>
<Toast toastAlert={toast}></Toast>
</div>
{/each}
</div>
<!-- Navbar Content -->
<div class="navbar h-10 min-h-0 border-b-2 bg-base-100">
<Navbar></Navbar>
</div>
<!-- Page Content -->
<div class="ml-5 mr-5 mt-5 min-h-screen items-center justify-center">
{@render children()}
</div>
</div>
</div>
<!-- <div class="flex">Footer</div> -->
</main>
<!-- Sidebar Content -->
<div class="drawer-side">
<button
aria-label="close sidebar"
class="drawer-overlay lg:hidden"
onclick={() => {
appSettings.sidebarDrawerOpen = false;
}}
></button>
<ul class="menu min-h-full w-44 border-e-2 bg-base-100 p-1">
<Sidebar></Sidebar>
</ul>
</div>
</main>
{/if}

1
src/routes/+layout.ts Normal file
View file

@ -0,0 +1 @@
export const prerender = true;

View file

@ -4,6 +4,6 @@
import { onMount } from 'svelte';
onMount(async () => {
goto(`${base}/users.html`);
goto(`${base}/users`);
});
</script>

View file

@ -0,0 +1,12 @@
<script lang="ts">
import { appSettings, persistentAppSettings } from '$lib/state.svelte';
import Svelecte from 'svelecte';
appSettings.navbarTitle = 'Settings';
appSettings.sidebarDrawerOpen = false;
</script>
<div class="max-w-96">
<h1 class="mb-4 text-primary">Application Theme</h1>
<Svelecte name="daisyUITheme" options={['hsui', 'light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden', 'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black', 'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade', 'night', 'coffee', 'winter', 'dim', 'nord', 'sunset']} bind:value={persistentAppSettings.daisyUITheme} />
</div>

View file

@ -0,0 +1,8 @@
<script lang="ts">
import { appSettings } from '$lib/state.svelte';
appSettings.navbarTitle = 'Users';
appSettings.sidebarDrawerOpen = false;
</script>
Users

View file

@ -1,26 +1,26 @@
import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
fallback: 'index.html',
precompress: false
// default options are shown. On some platforms
// these options are set automatically — see below
pages: 'build',
assets: 'build',
fallback: undefined,
precompress: false,
strict: true
}),
paths: {
base: "/web"
},
csp: {
mode: "hash",
directives: { "script-src": ["self"] },
base: '/web'
}
},
preprocess: [
preprocess({
postcss: true
})
]
}
};
export default config;

63
tailwind.config.ts Normal file
View file

@ -0,0 +1,63 @@
import containerQueries from '@tailwindcss/container-queries';
import typography from '@tailwindcss/typography';
import type { Config } from 'tailwindcss';
import daisyui from 'daisyui';
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
daisyui: {
themes: [
{
hsui: {
"primary": "#065f46",
"secondary": "#0369a1",
"accent": "#6b21a8",
"neutral": "#78716c",
"base-100": "#FFFFFF",
"info": "#3ABFF8",
"success": "#36D399",
"warning": "#FBBD23",
"error": "#F87272",
},
},
"light",
"dark",
"cupcake",
"bumblebee",
"emerald",
"corporate",
"synthwave",
"retro",
"cyberpunk",
"valentine",
"halloween",
"garden",
"forest",
"aqua",
"lofi",
"pastel",
"fantasy",
"wireframe",
"black",
"luxury",
"dracula",
"cmyk",
"autumn",
"business",
"acid",
"lemonade",
"night",
"coffee",
"winter",
"dim",
"nord",
"sunset"
]
},
plugins: [typography, containerQueries, daisyui]
} satisfies Config;

19
tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

6
vite.config.ts Normal file
View file

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});