feat(ui): migrate to svelte-5 and storybook
This commit is contained in:
parent
9ba26374db
commit
a87add30ff
@ -27,6 +27,7 @@ Currently this visual programming language is used to develop https://nodes.max-
|
|||||||
- [Node.js](https://nodejs.org/en/download)
|
- [Node.js](https://nodejs.org/en/download)
|
||||||
- [pnpm](https://pnpm.io/installation)
|
- [pnpm](https://pnpm.io/installation)
|
||||||
- [rust](https://www.rust-lang.org/tools/install)
|
- [rust](https://www.rust-lang.org/tools/install)
|
||||||
|
- wasm-pack
|
||||||
|
|
||||||
### Install dependencies
|
### Install dependencies
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"build:app": "BASE_PATH=/ui pnpm -r --filter 'ui' build && pnpm -r --filter 'app' build",
|
"build:app": "BASE_PATH=/ui pnpm -r --filter 'ui' build && pnpm -r --filter 'app' build",
|
||||||
"build:nodes": "pnpm -r --filter './nodes/**' build",
|
"build:nodes": "pnpm -r --filter './nodes/**' build",
|
||||||
"dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev",
|
"dev:nodes": "pnpm -r --parallel --filter './nodes/**' dev",
|
||||||
"build:deploy": "pnpm build && cp -r ./packages/ui/build ./app/build/ui",
|
"build:deploy": "pnpm build && cp -r ./packages/ui/storybook-static ./app/build/ui",
|
||||||
"dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev"
|
"dev": "pnpm -r --filter 'app' --filter './packages/node-registry' dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
"eslint:recommended",
|
||||||
'plugin:@typescript-eslint/recommended',
|
"plugin:@typescript-eslint/recommended",
|
||||||
'plugin:svelte/recommended',
|
"plugin:svelte/recommended",
|
||||||
'prettier'
|
"prettier",
|
||||||
|
"plugin:storybook/recommended"
|
||||||
],
|
],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
plugins: ['@typescript-eslint'],
|
plugins: ['@typescript-eslint'],
|
||||||
|
3
packages/ui/.gitignore
vendored
3
packages/ui/.gitignore
vendored
@ -2,6 +2,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
/build
|
/build
|
||||||
/dist
|
/dist
|
||||||
|
/storybook-static
|
||||||
/.svelte-kit
|
/.svelte-kit
|
||||||
/package
|
/package
|
||||||
.env
|
.env
|
||||||
@ -9,3 +10,5 @@ node_modules
|
|||||||
!.env.example
|
!.env.example
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
|
21
packages/ui/.storybook/main.ts
Normal file
21
packages/ui/.storybook/main.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/sveltekit';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
"stories": [
|
||||||
|
"../src/**/*.stories.@(js|ts|svelte)"
|
||||||
|
],
|
||||||
|
|
||||||
|
"addons": [
|
||||||
|
"@storybook/addon-svelte-csf",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
"@storybook/addon-themes",
|
||||||
|
],
|
||||||
|
|
||||||
|
"framework": {
|
||||||
|
"name": "@storybook/sveltekit",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
|
||||||
|
docs: {}
|
||||||
|
};
|
||||||
|
export default config;
|
12
packages/ui/.storybook/manager-head.html
Normal file
12
packages/ui/.storybook/manager-head.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<style>
|
||||||
|
.sidebar-header {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
#downshift-0-label {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#downshift-0-label ~ div {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
29
packages/ui/.storybook/preview.ts
Normal file
29
packages/ui/.storybook/preview.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { withThemeByClassName } from "@storybook/addon-themes";
|
||||||
|
import type { Preview } from '@storybook/svelte';
|
||||||
|
|
||||||
|
import "../src/lib/app.css";
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [withThemeByClassName({
|
||||||
|
themes: {
|
||||||
|
dark: 'theme-dark',
|
||||||
|
light: 'theme-light',
|
||||||
|
catppuccin: 'theme-catppuccin',
|
||||||
|
solarized: 'theme-solarized',
|
||||||
|
high: 'theme-high-contrast',
|
||||||
|
nord: 'theme-nord',
|
||||||
|
dracula: 'theme-dracula',
|
||||||
|
},
|
||||||
|
defaultTheme: 'light',
|
||||||
|
})],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
@ -1,12 +0,0 @@
|
|||||||
import { defineConfig } from 'histoire'
|
|
||||||
import { HstSvelte } from '@histoire/plugin-svelte'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
setupFile: '/src/histoire.setup.ts',
|
|
||||||
storyMatch: [
|
|
||||||
'./src/lib/**/*.story.svelte',
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
HstSvelte(),
|
|
||||||
],
|
|
||||||
})
|
|
@ -11,9 +11,8 @@
|
|||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"story:dev": "histoire dev",
|
"story:dev": "storybook dev -p 6006",
|
||||||
"story:build": "histoire build",
|
"story:build": "storybook build"
|
||||||
"story:preview": "histoire preview"
|
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
@ -31,24 +30,28 @@
|
|||||||
"svelte": "^4.0.0"
|
"svelte": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@histoire/plugin-svelte": "^0.17.17",
|
"@storybook/addon-essentials": "^8.4.1",
|
||||||
"@sveltejs/adapter-auto": "^3.2.0",
|
"@storybook/addon-svelte-csf": "^5.0.0-next.10",
|
||||||
|
"@storybook/addon-themes": "^8.4.1",
|
||||||
|
"@storybook/svelte": "^8.4.1",
|
||||||
|
"@storybook/sveltekit": "^8.4.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
"@sveltejs/adapter-static": "^3.0.1",
|
||||||
"@sveltejs/kit": "^2.5.7",
|
"@sveltejs/kit": "^2.5.27",
|
||||||
"@sveltejs/package": "^2.3.1",
|
"@sveltejs/package": "^2.3.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@types/eslint": "^8.56.10",
|
"@types/eslint": "^8.56.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.7.1",
|
"@typescript-eslint/eslint-plugin": "^7.7.1",
|
||||||
"@typescript-eslint/parser": "^7.7.1",
|
"@typescript-eslint/parser": "^7.7.1",
|
||||||
"eslint": "^9.1.1",
|
"eslint": "^9.1.1",
|
||||||
"eslint-plugin-svelte": "^2.38.0",
|
"eslint-plugin-storybook": "^0.10.1",
|
||||||
"histoire": "^0.17.17",
|
"eslint-plugin-svelte": "^2.45.1",
|
||||||
"publint": "^0.2.7",
|
"publint": "^0.2.7",
|
||||||
"svelte": "^4.2.15",
|
"storybook": "^8.4.1",
|
||||||
"svelte-check": "^3.7.0",
|
"svelte": "^5.0.0",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.5.0",
|
||||||
"vite": "^5.2.10",
|
"vite": "^5.4.4",
|
||||||
"vitest": "^1.5.2"
|
"vitest": "^1.5.2"
|
||||||
},
|
},
|
||||||
"svelte": "./dist/index.js",
|
"svelte": "./dist/index.js",
|
||||||
|
@ -1 +0,0 @@
|
|||||||
import "./lib/app.css"
|
|
@ -1,12 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let title = "Details";
|
interface Props {
|
||||||
export let transparent = false;
|
title?: string;
|
||||||
|
transparent?: boolean;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title = "Details", transparent = false, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<details class:transparent>
|
<details class:transparent>
|
||||||
<summary>{title}</summary>
|
<summary>{title}</summary>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<slot />
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
@ -7,9 +7,13 @@
|
|||||||
import type { NodeInput } from '@nodes/types';
|
import type { NodeInput } from '@nodes/types';
|
||||||
import Vec3 from './elements/Vec3.svelte';
|
import Vec3 from './elements/Vec3.svelte';
|
||||||
|
|
||||||
export let input: NodeInput;
|
interface Props {
|
||||||
export let value: any;
|
input: NodeInput;
|
||||||
export let id: string;
|
value: any;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { input, value = $bindable(), id }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if input.type === 'float'}
|
{#if input.type === 'float'}
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let ctrl = false;
|
interface Props {
|
||||||
export let shift = false;
|
ctrl?: boolean;
|
||||||
export let alt = false;
|
shift?: boolean;
|
||||||
export let key: string;
|
alt?: boolean;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
ctrl = false,
|
||||||
|
shift = false,
|
||||||
|
alt = false,
|
||||||
|
key
|
||||||
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="command">
|
<div class="command">
|
||||||
|
@ -38,9 +38,7 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
html {
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
--neutral-100: #E7E7E7;
|
--neutral-100: #E7E7E7;
|
||||||
--neutral-200: #CECECE;
|
--neutral-200: #CECECE;
|
||||||
--neutral-300: #7C7C7C;
|
--neutral-300: #7C7C7C;
|
||||||
@ -65,6 +63,11 @@ body {
|
|||||||
--edge: var(--connection, var(--outline));
|
--edge: var(--connection, var(--outline));
|
||||||
|
|
||||||
--text-color: var(--neutral-200);
|
--text-color: var(--neutral-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
background-color: var(--layer-0);
|
background-color: var(--layer-0);
|
||||||
@ -74,7 +77,7 @@ body * {
|
|||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-light {
|
html.theme-light {
|
||||||
--text-color: var(--neutral-800);
|
--text-color: var(--neutral-800);
|
||||||
--outline: var(--neutral-300);
|
--outline: var(--neutral-300);
|
||||||
--layer-0: var(--neutral-100);
|
--layer-0: var(--neutral-100);
|
||||||
@ -86,7 +89,7 @@ body.theme-light {
|
|||||||
--connection: #888;
|
--connection: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-solarized {
|
html.theme-solarized {
|
||||||
--text-color: #425055;
|
--text-color: #425055;
|
||||||
--outline: #93a1a1;
|
--outline: #93a1a1;
|
||||||
--layer-0: #fdf6e3;
|
--layer-0: #fdf6e3;
|
||||||
@ -98,7 +101,7 @@ body.theme-solarized {
|
|||||||
--connection: #839496;
|
--connection: #839496;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-catppuccin {
|
html.theme-catppuccin {
|
||||||
--text-color: #CDD6F4;
|
--text-color: #CDD6F4;
|
||||||
--outline: #3e3e4f;
|
--outline: #3e3e4f;
|
||||||
--layer-3: #a8aac8;
|
--layer-3: #a8aac8;
|
||||||
@ -108,7 +111,7 @@ body.theme-catppuccin {
|
|||||||
--connection: #444459;
|
--connection: #444459;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-high-contrast {
|
html.theme-high-contrast {
|
||||||
--text-color: #FFFFFF;
|
--text-color: #FFFFFF;
|
||||||
--outline: white;
|
--outline: white;
|
||||||
--layer-0: #000000;
|
--layer-0: #000000;
|
||||||
@ -118,7 +121,7 @@ body.theme-high-contrast {
|
|||||||
--connection: #FFF;
|
--connection: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-nord {
|
html.theme-nord {
|
||||||
--text-color: #D8DEE9;
|
--text-color: #D8DEE9;
|
||||||
--outline: #4C566A;
|
--outline: #4C566A;
|
||||||
--layer-0: #2E3440;
|
--layer-0: #2E3440;
|
||||||
@ -130,7 +133,7 @@ body.theme-nord {
|
|||||||
--connection: #4C566A;
|
--connection: #4C566A;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.theme-dracula {
|
html.theme-dracula {
|
||||||
--text-color: #F8F8F2;
|
--text-color: #F8F8F2;
|
||||||
--outline: #6272A4;
|
--outline: #6272A4;
|
||||||
--layer-0: #282A36;
|
--layer-0: #282A36;
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let value: boolean;
|
import { run } from 'svelte/legacy';
|
||||||
|
|
||||||
$: if (typeof value === 'string') {
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value: boolean;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable(), id = '' }: Props = $props();
|
||||||
|
run(() => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
value = value === 'true';
|
value = value === 'true';
|
||||||
} else if (typeof value === 'number') {
|
} else if (typeof value === 'number') {
|
||||||
value = value === 1;
|
value = value === 1;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
export let id = '';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<input {id} type="checkbox" bind:checked={value} />
|
<input {id} type="checkbox" bind:checked={value} />
|
||||||
|
11
packages/ui/src/lib/elements/Float.stories.svelte
Normal file
11
packages/ui/src/lib/elements/Float.stories.svelte
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import FloatComp from './Float.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Inputs/Float',
|
||||||
|
component: FloatComp
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Float" />
|
@ -1,17 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Hst } from '@histoire/plugin-svelte';
|
|
||||||
export let Hst: Hst;
|
|
||||||
import Float from './Float.svelte';
|
|
||||||
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
|
||||||
import StorySettings from '$lib/helpers/StorySettings.svelte';
|
|
||||||
let theme = 'dark';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Hst.Story>
|
|
||||||
<StoryContent {theme}>
|
|
||||||
<Float value={0} min={0} max={6.9} />
|
|
||||||
</StoryContent>
|
|
||||||
<svelte:fragment slot="controls">
|
|
||||||
<StorySettings bind:theme />
|
|
||||||
</svelte:fragment>
|
|
||||||
</Hst.Story>
|
|
@ -1,9 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let value = 0.5;
|
interface Props {
|
||||||
export let step = 0.01;
|
value?: number;
|
||||||
export let min = 0;
|
step?: number;
|
||||||
export let max = 1;
|
min?: number;
|
||||||
export let id = '';
|
max?: number;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
value = $bindable(0.5),
|
||||||
|
step = 0.01,
|
||||||
|
min = $bindable(0),
|
||||||
|
max = $bindable(1),
|
||||||
|
id = ''
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
if (min > max) {
|
if (min > max) {
|
||||||
[min, max] = [max, min];
|
[min, max] = [max, min];
|
||||||
@ -16,23 +26,15 @@
|
|||||||
return +parseFloat(input + '').toPrecision(2);
|
return +parseFloat(input + '').toPrecision(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl: HTMLInputElement | undefined = $state();
|
||||||
|
|
||||||
$: if ((value || 0).toString().length > 5) {
|
|
||||||
value = strip(value || 0);
|
|
||||||
}
|
|
||||||
$: value !== undefined && handleChange();
|
|
||||||
let oldValue: number;
|
let oldValue: number;
|
||||||
function handleChange() {
|
function handleChange() {
|
||||||
if (value === oldValue) return;
|
if (value === oldValue) return;
|
||||||
oldValue = value;
|
oldValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: width = Number.isFinite(value)
|
let isMouseDown = $state(false);
|
||||||
? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px'
|
|
||||||
: '20px';
|
|
||||||
|
|
||||||
let isMouseDown = false;
|
|
||||||
let downV = 0;
|
let downV = 0;
|
||||||
let vx = 0;
|
let vx = 0;
|
||||||
let rect: DOMRect;
|
let rect: DOMRect;
|
||||||
@ -40,6 +42,8 @@
|
|||||||
function handleMouseDown(ev: MouseEvent) {
|
function handleMouseDown(ev: MouseEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!inputEl) return;
|
||||||
|
|
||||||
inputEl.focus();
|
inputEl.focus();
|
||||||
|
|
||||||
isMouseDown = true;
|
isMouseDown = true;
|
||||||
@ -57,7 +61,7 @@
|
|||||||
isMouseDown = false;
|
isMouseDown = false;
|
||||||
|
|
||||||
if (downV === value) {
|
if (downV === value) {
|
||||||
inputEl.focus();
|
inputEl?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value > max) {
|
if (value > max) {
|
||||||
@ -76,7 +80,7 @@
|
|||||||
function handleKeyDown(ev: KeyboardEvent) {
|
function handleKeyDown(ev: KeyboardEvent) {
|
||||||
if (ev.key === 'Escape' || ev.key === 'Enter') {
|
if (ev.key === 'Escape' || ev.key === 'Enter') {
|
||||||
handleMouseUp();
|
handleMouseUp();
|
||||||
inputEl.blur();
|
inputEl?.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +94,15 @@
|
|||||||
value = Math.max(Math.min(min + (max - min) * vx, max), min);
|
value = Math.max(Math.min(min + (max - min) * vx, max), min);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$effect(() => {
|
||||||
|
if ((value || 0).toString().length > 5) {
|
||||||
|
value = strip(value || 0);
|
||||||
|
}
|
||||||
|
value !== undefined && handleChange();
|
||||||
|
});
|
||||||
|
let width = $derived(
|
||||||
|
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px' : '20px'
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="component-wrapper" class:is-down={isMouseDown}>
|
<div class="component-wrapper" class:is-down={isMouseDown}>
|
||||||
@ -101,9 +114,9 @@
|
|||||||
{step}
|
{step}
|
||||||
{max}
|
{max}
|
||||||
{min}
|
{min}
|
||||||
on:keydown={handleKeyDown}
|
onkeydown={handleKeyDown}
|
||||||
on:mousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
on:mouseup={handleMouseUp}
|
onmouseup={handleMouseUp}
|
||||||
type="number"
|
type="number"
|
||||||
style={`width:${width};`}
|
style={`width:${width};`}
|
||||||
/>
|
/>
|
||||||
|
11
packages/ui/src/lib/elements/Integer.stories.svelte
Normal file
11
packages/ui/src/lib/elements/Integer.stories.svelte
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import IntegerComp from './Integer.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Inputs/Integer',
|
||||||
|
component: IntegerComp
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Integer" />
|
@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Hst } from '@histoire/plugin-svelte';
|
|
||||||
export let Hst: Hst;
|
|
||||||
import Integer from './Integer.svelte';
|
|
||||||
import StorySettings from '../helpers/StorySettings.svelte';
|
|
||||||
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
|
||||||
|
|
||||||
let theme = 'dark';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Hst.Story>
|
|
||||||
<StoryContent {theme}>
|
|
||||||
<Integer value={5} min={0} max={42} />
|
|
||||||
</StoryContent>
|
|
||||||
<svelte:fragment slot="controls">
|
|
||||||
<StorySettings bind:theme />
|
|
||||||
</svelte:fragment>
|
|
||||||
</Hst.Story>
|
|
@ -2,20 +2,28 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
// Styling
|
interface Props {
|
||||||
export let min: number | undefined = undefined;
|
min?: number | undefined;
|
||||||
export let max: number | undefined = undefined;
|
max?: number | undefined;
|
||||||
export let step = 1;
|
step?: number;
|
||||||
export let value = 0;
|
value?: number;
|
||||||
export let id = '';
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
min = undefined,
|
||||||
|
max = undefined,
|
||||||
|
step = 1,
|
||||||
|
value = $bindable(0),
|
||||||
|
id = ''
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = 0;
|
value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputEl: HTMLInputElement;
|
let inputEl: HTMLInputElement | undefined = $state();
|
||||||
let wrapper: HTMLDivElement;
|
let wrapper: HTMLDivElement | undefined = $state();
|
||||||
$: value !== undefined && update();
|
|
||||||
|
|
||||||
let prev = -1;
|
let prev = -1;
|
||||||
function update() {
|
function update() {
|
||||||
@ -24,10 +32,6 @@
|
|||||||
dispatch('change', parseFloat(value + ''));
|
dispatch('change', parseFloat(value + ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
$: width = Number.isFinite(value)
|
|
||||||
? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px'
|
|
||||||
: '20px';
|
|
||||||
|
|
||||||
function handleChange(change: number) {
|
function handleChange(change: number) {
|
||||||
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
|
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
|
||||||
}
|
}
|
||||||
@ -70,6 +74,13 @@
|
|||||||
value = downV + Math.round(vx / 10);
|
value = downV + Math.round(vx / 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
value !== undefined && update();
|
||||||
|
});
|
||||||
|
let width = $derived(
|
||||||
|
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px' : '20px'
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -78,13 +89,14 @@
|
|||||||
role="slider"
|
role="slider"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-valuenow={value}
|
aria-valuenow={value}
|
||||||
on:mousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
on:mouseup={handleMouseUp}
|
onmouseup={handleMouseUp}
|
||||||
>
|
>
|
||||||
{#if typeof min !== 'undefined' && typeof max !== 'undefined'}
|
{#if typeof min !== 'undefined' && typeof max !== 'undefined'}
|
||||||
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`} />
|
<span class="overlay" style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
|
||||||
|
></span>
|
||||||
{/if}
|
{/if}
|
||||||
<button on:click={() => handleChange(-step)}>-</button>
|
<button onclick={() => handleChange(-step)}>-</button>
|
||||||
<input
|
<input
|
||||||
bind:value
|
bind:value
|
||||||
bind:this={inputEl}
|
bind:this={inputEl}
|
||||||
@ -96,7 +108,7 @@
|
|||||||
style={`width:${width};`}
|
style={`width:${width};`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button on:click={() => handleChange(+step)}>+</button>
|
<button onclick={() => handleChange(+step)}>+</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -159,4 +171,3 @@
|
|||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
23
packages/ui/src/lib/elements/Select.stories.svelte
Normal file
23
packages/ui/src/lib/elements/Select.stories.svelte
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import SelectComp from '$lib/elements/Select.svelte';
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Inputs/Select',
|
||||||
|
component: SelectComp,
|
||||||
|
|
||||||
|
argTypes: {
|
||||||
|
options: {
|
||||||
|
control: {
|
||||||
|
type: 'select'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
actions: {
|
||||||
|
handles: ['change']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Select" args={{ options: ['strawberry', 'raspberry', 'chickpeas'] }} />
|
@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Hst } from '@histoire/plugin-svelte';
|
|
||||||
export let Hst: Hst;
|
|
||||||
import Select from './Select.svelte';
|
|
||||||
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
|
||||||
import StorySettings from '$lib/helpers/StorySettings.svelte';
|
|
||||||
|
|
||||||
let theme = 'dark';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Hst.Story>
|
|
||||||
<StoryContent {theme}>
|
|
||||||
<Select id="" options={['strawberry', 'apple', 'banana']} />
|
|
||||||
</StoryContent>
|
|
||||||
<svelte:fragment slot="controls">
|
|
||||||
<StorySettings bind:theme />
|
|
||||||
</svelte:fragment>
|
|
||||||
</Hst.Story>
|
|
@ -1,7 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let options: string[] = [];
|
interface Props {
|
||||||
export let value: number = 0;
|
options?: string[];
|
||||||
export let id = '';
|
value?: number;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { options = [], value = $bindable(0), id = '' }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<select {id} bind:value>
|
<select {id} bind:value>
|
||||||
|
10
packages/ui/src/lib/elements/Vec3.stories.svelte
Normal file
10
packages/ui/src/lib/elements/Vec3.stories.svelte
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Vec3Comp from './Vec3.svelte';
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Inputs/Vec3',
|
||||||
|
component: Vec3Comp
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Vec3" />
|
@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Hst } from '@histoire/plugin-svelte';
|
|
||||||
export let Hst: Hst;
|
|
||||||
import Vec3 from './Vec3.svelte';
|
|
||||||
import StoryContent from '$lib/helpers/StoryContent.svelte';
|
|
||||||
import StorySettings from '$lib/helpers/StorySettings.svelte';
|
|
||||||
|
|
||||||
let theme = 'dark';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Hst.Story>
|
|
||||||
<StoryContent {theme}>
|
|
||||||
<Vec3 value={[0.2, 0.4, 0.6]} />
|
|
||||||
</StoryContent>
|
|
||||||
<svelte:fragment slot="controls">
|
|
||||||
<StorySettings bind:theme />
|
|
||||||
</svelte:fragment>
|
|
||||||
</Hst.Story>
|
|
@ -1,8 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Float from './Float.svelte';
|
import Float from './Float.svelte';
|
||||||
|
|
||||||
export let value = [0, 0, 0];
|
interface Props {
|
||||||
export let id = '';
|
value?: any;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { value = $bindable([0, 0, 0]), id = '' }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let theme = 'dark';
|
|
||||||
|
|
||||||
$: if (theme !== undefined) {
|
|
||||||
const classes = document.body.classList;
|
|
||||||
const newClassName = `theme-${theme}`;
|
|
||||||
for (const className of classes) {
|
|
||||||
if (className.startsWith('theme-') && className !== newClassName) {
|
|
||||||
classes.remove(className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.body.classList.add(newClassName);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Select } from '$lib/index.js';
|
|
||||||
|
|
||||||
const themes = ['dark', 'light', 'catppuccin', 'solarized', 'high-contrast', 'nord', 'dracula'];
|
|
||||||
|
|
||||||
let value = 0;
|
|
||||||
export let theme = themes[value];
|
|
||||||
$: theme = themes[value];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="theme-select"> Select Theme </label>
|
|
||||||
<Select id="" bind:value options={themes} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1em;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -4,9 +4,9 @@
|
|||||||
import Integer from '$lib/elements/Integer.svelte';
|
import Integer from '$lib/elements/Integer.svelte';
|
||||||
import Vec3 from '$lib/elements/Vec3.svelte';
|
import Vec3 from '$lib/elements/Vec3.svelte';
|
||||||
|
|
||||||
let intValue = 0;
|
let intValue = $state(0);
|
||||||
let floatValue = 0.2;
|
let floatValue = $state(0.2);
|
||||||
let vecValue = [0.2, 0.3, 0.4];
|
let vecValue = $state([0.2, 0.3, 0.4]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
6575
pnpm-lock.yaml
6575
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user