feat(ui): add InputColor and custom theme
This commit is contained in:
@@ -2,19 +2,19 @@
|
|||||||
import { colors } from '../graph/colors.svelte';
|
import { colors } from '../graph/colors.svelte';
|
||||||
|
|
||||||
const circleMaterial = new MeshBasicMaterial({
|
const circleMaterial = new MeshBasicMaterial({
|
||||||
color: colors.connection.clone(),
|
color: colors.outline.clone(),
|
||||||
toneMapped: false
|
toneMapped: false
|
||||||
});
|
});
|
||||||
|
|
||||||
let lineColor = $state(colors.connection.clone().convertSRGBToLinear());
|
let lineColor = $state(colors.outline.clone().convertSRGBToLinear());
|
||||||
|
|
||||||
$effect.root(() => {
|
$effect.root(() => {
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (appSettings.value.theme === undefined) {
|
if (appSettings.value.theme === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
circleMaterial.color = colors.connection.clone().convertSRGBToLinear();
|
circleMaterial.color = colors.outline.clone().convertSRGBToLinear();
|
||||||
lineColor = colors.connection.clone().convertSRGBToLinear();
|
lineColor = colors.outline.clone().convertSRGBToLinear();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
uniforms={{
|
uniforms={{
|
||||||
uColorBright: { value: colors['layer-2'] },
|
uColorBright: { value: colors['layer-2'] },
|
||||||
uColorDark: { value: colors['layer-1'] },
|
uColorDark: { value: colors['layer-1'] },
|
||||||
uStrokeColor: { value: colors.outline.clone() },
|
uStrokeColor: { value: colors['layer-2'] },
|
||||||
uStrokeWidth: { value: 1.0 },
|
uStrokeWidth: { value: 1.0 },
|
||||||
uWidth: { value: 20 },
|
uWidth: { value: 20 },
|
||||||
uHeight: { value: height }
|
uHeight: { value: height }
|
||||||
|
|||||||
@@ -87,8 +87,6 @@
|
|||||||
width: 30px;
|
width: 30px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
/* background: red; */
|
|
||||||
/* opacity: 0.2; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.click-target:hover + svg path {
|
.click-target:hover + svg path {
|
||||||
@@ -108,8 +106,10 @@
|
|||||||
|
|
||||||
svg path {
|
svg path {
|
||||||
stroke-width: 0.2px;
|
stroke-width: 0.2px;
|
||||||
transition: d 0.3s ease, fill 0.3s ease;
|
transition:
|
||||||
fill: var(--color-layer-2);
|
d 0.3s ease,
|
||||||
|
fill 0.3s ease;
|
||||||
|
fill: var(--color-outline);
|
||||||
stroke: var(--stroke);
|
stroke: var(--stroke);
|
||||||
stroke-width: var(--stroke-width);
|
stroke-width: var(--stroke-width);
|
||||||
d: var(--path);
|
d: var(--path);
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { NodeInput } from '@nodarium/types';
|
import type { NodeInput } from '@nodarium/types';
|
||||||
|
|
||||||
import { InputCheckbox, InputNumber, InputSelect, InputShape, InputVec3 } from './index';
|
import {
|
||||||
|
InputCheckbox,
|
||||||
|
InputColor,
|
||||||
|
InputNumber,
|
||||||
|
InputSelect,
|
||||||
|
InputShape,
|
||||||
|
InputVec3
|
||||||
|
} from './index';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
input: NodeInput;
|
input: NodeInput;
|
||||||
@@ -21,8 +28,15 @@
|
|||||||
/>
|
/>
|
||||||
{:else if input.type === 'shape'}
|
{:else if input.type === 'shape'}
|
||||||
<InputShape bind:value={value as number[]} />
|
<InputShape bind:value={value as number[]} />
|
||||||
|
{:else if input.type === 'color'}
|
||||||
|
<InputColor bind:value={value as number[]} />
|
||||||
{:else if input.type === 'integer'}
|
{:else if input.type === 'integer'}
|
||||||
<InputNumber bind:value={value as number} min={input?.min} max={input?.max} step={1} />
|
<InputNumber
|
||||||
|
bind:value={value as number}
|
||||||
|
min={input?.min}
|
||||||
|
max={input?.max}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
{:else if input.type === 'boolean'}
|
{:else if input.type === 'boolean'}
|
||||||
<InputCheckbox bind:value={value as boolean} {id} />
|
<InputCheckbox bind:value={value as boolean} {id} />
|
||||||
{:else if input.type === 'select'}
|
{:else if input.type === 'select'}
|
||||||
|
|||||||
@@ -140,12 +140,12 @@ html.theme-catppuccin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
html.theme-high-contrast {
|
html.theme-high-contrast {
|
||||||
--color-text: #ffffff;
|
--color-text: white;
|
||||||
--color-outline: white;
|
--color-outline: white;
|
||||||
--color-layer-0: #000000;
|
--color-layer-0: black;
|
||||||
--color-layer-1: black;
|
--color-layer-1: black;
|
||||||
--color-layer-2: #222222;
|
--color-layer-2: #ababab;
|
||||||
--color-layer-3: #ffffff;
|
--color-layer-3: white;
|
||||||
--color-connection: #fff;
|
--color-connection: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export { default as Input } from './Input.svelte';
|
export { default as Input } from './Input.svelte';
|
||||||
export { default as InputCheckbox } from './inputs/InputCheckbox.svelte';
|
export { default as InputCheckbox } from './inputs/InputCheckbox.svelte';
|
||||||
|
export { default as InputColor } from './inputs/InputColor.svelte';
|
||||||
export { default as InputNumber } from './inputs/InputNumber.svelte';
|
export { default as InputNumber } from './inputs/InputNumber.svelte';
|
||||||
export { default as InputSelect } from './inputs/InputSelect.svelte';
|
export { default as InputSelect } from './inputs/InputSelect.svelte';
|
||||||
export { default as InputShape } from './inputs/InputShape.svelte';
|
export { default as InputShape } from './inputs/InputShape.svelte';
|
||||||
|
|||||||
85
packages/ui/src/lib/inputs/InputColor.svelte
Normal file
85
packages/ui/src/lib/inputs/InputColor.svelte
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
value?: [number, number, number];
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
value = $bindable([255, 255, 255] as [number, number, number]),
|
||||||
|
id
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let hexValue = $derived(
|
||||||
|
`#${value.map((c) => c.toString(16).padStart(2, '0')).join('')}`
|
||||||
|
);
|
||||||
|
|
||||||
|
let rValue = $state(value[0]);
|
||||||
|
let gValue = $state(value[1]);
|
||||||
|
let bValue = $state(value[2]);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
rValue = value[0];
|
||||||
|
gValue = value[1];
|
||||||
|
bValue = value[2];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleHexInput(e: Event) {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
let val = target.value.replace(/[^0-9a-fA-F]/g, '');
|
||||||
|
if (val.length > 6) val = val.slice(0, 6);
|
||||||
|
if (val.length === 3) {
|
||||||
|
val = val
|
||||||
|
.split('')
|
||||||
|
.map((c) => c + c)
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
if (val.length === 6) {
|
||||||
|
value = [
|
||||||
|
parseInt(val.slice(0, 2), 16),
|
||||||
|
parseInt(val.slice(2, 4), 16),
|
||||||
|
parseInt(val.slice(4, 6), 16)
|
||||||
|
] as [number, number, number];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHexBlur() {
|
||||||
|
rValue = value[0];
|
||||||
|
gValue = value[1];
|
||||||
|
bValue = value[2];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex overflow-hidden rounded-sm border border-outline bg-layer-2 w-min">
|
||||||
|
<label
|
||||||
|
class="-ml-px w-8 shrink-0 overflow-hidden"
|
||||||
|
style={`background-color: ${hexValue}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
bind:value={hexValue}
|
||||||
|
{id}
|
||||||
|
oninput={handleHexInput}
|
||||||
|
class="h-full w-8 cursor-pointer appearance-none p-0"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center gap-1 px-2 py-1">
|
||||||
|
<span class="pointer-events-none text-text opacity-30">#</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={hexValue.slice(1)}
|
||||||
|
{id}
|
||||||
|
oninput={handleHexInput}
|
||||||
|
onblur={handleHexBlur}
|
||||||
|
maxlength={6}
|
||||||
|
class="w-15 bg-transparent text-text outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
input[type="color"] {
|
||||||
|
margin-top: -1px;
|
||||||
|
margin-right: -1px;
|
||||||
|
height: calc(100% + 2px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import {
|
import {
|
||||||
Details,
|
Details,
|
||||||
InputCheckbox,
|
InputCheckbox,
|
||||||
|
InputColor,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
InputSelect,
|
InputSelect,
|
||||||
InputShape,
|
InputShape,
|
||||||
@@ -10,6 +11,8 @@
|
|||||||
ShortCut
|
ShortCut
|
||||||
} from '$lib';
|
} from '$lib';
|
||||||
import Section from './Section.svelte';
|
import Section from './Section.svelte';
|
||||||
|
import Theme from './Theme.svelte';
|
||||||
|
import ThemeSelector from './ThemeSelector.svelte';
|
||||||
|
|
||||||
let intValue = $state(0);
|
let intValue = $state(0);
|
||||||
let floatValue = $state(0.2);
|
let floatValue = $state(0.2);
|
||||||
@@ -19,61 +22,22 @@
|
|||||||
let selectValue = $state(0);
|
let selectValue = $state(0);
|
||||||
const d = $derived(options[selectValue]);
|
const d = $derived(options[selectValue]);
|
||||||
let checked = $state(false);
|
let checked = $state(false);
|
||||||
|
let colorValue = $state<[number, number, number]>([59, 130, 246]);
|
||||||
let mirrorShape = $state(true);
|
let mirrorShape = $state(true);
|
||||||
let detailsOpen = $state(false);
|
let detailsOpen = $state(false);
|
||||||
|
|
||||||
let points = $state([]);
|
let points = $state([]);
|
||||||
|
let theme = $state('dark');
|
||||||
const themes = [
|
|
||||||
'dark',
|
|
||||||
'light',
|
|
||||||
'solarized',
|
|
||||||
'catppuccin',
|
|
||||||
'high-contrast',
|
|
||||||
'nord',
|
|
||||||
'dracula'
|
|
||||||
];
|
|
||||||
let themeIndex = $state(0);
|
|
||||||
$effect(() => {
|
|
||||||
const classList = document.documentElement.classList;
|
|
||||||
for (const c of classList) {
|
|
||||||
if (c.startsWith('theme-')) document.documentElement.classList.remove(c);
|
|
||||||
}
|
|
||||||
document.documentElement.classList.add(`theme-${themes[themeIndex]}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const colors = [
|
|
||||||
'layer-0',
|
|
||||||
'layer-1',
|
|
||||||
'layer-2',
|
|
||||||
'layer-3',
|
|
||||||
'active',
|
|
||||||
'selected',
|
|
||||||
'outline',
|
|
||||||
'connection',
|
|
||||||
'text'
|
|
||||||
];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="flex flex-col gap-8 py-8">
|
<main class="flex flex-col gap-8 py-8">
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<h1 class="text-4xl">@nodarium/ui</h1>
|
<h1 class="text-4xl">@nodarium/ui</h1>
|
||||||
<InputSelect bind:value={themeIndex} options={themes}></InputSelect>
|
<ThemeSelector bind:theme />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Section title="Colors">
|
<Section title="InputNumber">
|
||||||
<table>
|
<Theme theme />
|
||||||
<tbody>
|
|
||||||
{#each colors as color (color)}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="w-6 h-6 mr-2 my-1 rounded-sm outline-1 bg-{color}"></div>
|
|
||||||
</td>
|
|
||||||
<td>{color}</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section title="InputNumber">
|
<Section title="InputNumber">
|
||||||
@@ -99,6 +63,10 @@
|
|||||||
<InputCheckbox bind:value={checked} />
|
<InputCheckbox bind:value={checked} />
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Color" value={colorValue}>
|
||||||
|
<InputColor bind:value={colorValue} />
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Section title="Shape">
|
<Section title="Shape">
|
||||||
{#snippet header()}
|
{#snippet header()}
|
||||||
<label class="flex gap-2">
|
<label class="flex gap-2">
|
||||||
|
|||||||
80
packages/ui/src/routes/Theme.svelte
Normal file
80
packages/ui/src/routes/Theme.svelte
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { InputColor } from '$lib';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
theme?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { theme }: Props = $props();
|
||||||
|
|
||||||
|
const colors = [
|
||||||
|
'layer-0',
|
||||||
|
'layer-1',
|
||||||
|
'layer-2',
|
||||||
|
'layer-3',
|
||||||
|
'active',
|
||||||
|
'selected',
|
||||||
|
'outline',
|
||||||
|
'connection',
|
||||||
|
'text'
|
||||||
|
];
|
||||||
|
|
||||||
|
let customColors = $state<CustomColors>({
|
||||||
|
text: [205, 214, 244],
|
||||||
|
outline: [62, 62, 79],
|
||||||
|
'layer-0': [6, 6, 27],
|
||||||
|
'layer-1': [23, 23, 46],
|
||||||
|
'layer-2': [49, 50, 68],
|
||||||
|
'layer-3': [168, 170, 200],
|
||||||
|
active: [0, 0, 0],
|
||||||
|
selected: [38, 139, 210],
|
||||||
|
connection: [131, 148, 150]
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeCss = $derived.by(() => {
|
||||||
|
return `<style>html.theme-custom{
|
||||||
|
${
|
||||||
|
Object.keys(customColors)
|
||||||
|
.map((v) => {
|
||||||
|
return `--color-${v}: rgb(${customColors[v].join(',')});`;
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
</style>`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
{@html themeCss}
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Color</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Custom</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each colors as color (color)}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="w-6 h-6 mr-2 my-1 rounded-sm outline-1 bg-{color}"></div>
|
||||||
|
</td>
|
||||||
|
<td>{color}</td>
|
||||||
|
<td>
|
||||||
|
<InputColor bind:value={customColors[color]} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
border-spacing: 5px;
|
||||||
|
border-collapse: separate;
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
27
packages/ui/src/routes/ThemeSelector.svelte
Normal file
27
packages/ui/src/routes/ThemeSelector.svelte
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { InputSelect } from '$lib';
|
||||||
|
const themes = [
|
||||||
|
'dark',
|
||||||
|
'light',
|
||||||
|
'solarized',
|
||||||
|
'catppuccin',
|
||||||
|
'high-contrast',
|
||||||
|
'nord',
|
||||||
|
'dracula',
|
||||||
|
'custom'
|
||||||
|
];
|
||||||
|
|
||||||
|
let { theme = $bindable() } = $props();
|
||||||
|
|
||||||
|
let themeIndex = $state(0);
|
||||||
|
$effect(() => {
|
||||||
|
theme = themes[themeIndex];
|
||||||
|
const classList = document.documentElement.classList;
|
||||||
|
for (const c of classList) {
|
||||||
|
if (c.startsWith('theme-')) document.documentElement.classList.remove(c);
|
||||||
|
}
|
||||||
|
document.documentElement.classList.add(`theme-${themes[themeIndex]}`);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<InputSelect bind:value={themeIndex} options={themes}></InputSelect>
|
||||||
Reference in New Issue
Block a user