@the_dissidents/svelte-ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Svelte UI components by @the-dissidents
2
+
3
+ ## Features
4
+
5
+ All controls are consistently styled using a set of configurable SASS parameters.
6
+
7
+ Styled native controls:
8
+
9
+ - `<button>`
10
+ - `<input>` type=`text|number|checkbox|radio`
11
+ - `<textarea>`
12
+ - `<select>` (single-selection only)
13
+ - `<ol role=listbox>` (displays as a listbox)
14
+
15
+ Components:
16
+
17
+ - `<TabView>` and `<TabPage>`
18
+ - `<ListView>` (with configurable columns)
19
+ - `<OrderableList>` (each row has a grab handle)
20
+ - `<Colorpicker>` (uses the tree-shakable API of `colorjs.io`)
21
+ - `<Resizer>`
22
+ - `<Collapsible>`
23
+ - `<NumberInput>` (a wrapper on `<input type=number>`)
24
+ - `<Popup>` (generic base component)
25
+ - `<Tooltip>` (a specialized `<Popup>`)
26
+ - `<Banner>`
27
+ - An overlay menu with an imperative API: `await overlayMenu(...)`
28
+
29
+ ## Usage
30
+
31
+ 1. Install the library.
32
+
33
+ ```sh
34
+ pnpm add @the_dissidents/svelte-ui
35
+ ```
36
+
37
+ 2. Configure the main stylesheet. You must enable preprocessing of SASS/SCSS files, and you need to `@use` the main stylesheet in somewhere that has global scope (e.g. in a `<style lang='scss' global>` block or in a stylesheet `import`ed in your entrance point's JS/TS script):
38
+
39
+ ```scss
40
+ // The author cannot find a reliable method to resolve package paths in
41
+ // SASS/SCSS except specifying the whole relative path to it in node_modules
42
+ @use "../node_modules/@the_dissidents/svelte-ui/dist/main";
43
+
44
+ // Uncomment this line to access the uchu palette which is used in the
45
+ // default values:
46
+ // @use "../node_modules/@the_dissidents/svelte-ui/dist/uchu";
47
+
48
+ @include main.configure(
49
+ // $ui-font-family: system-ui,
50
+ // $mono-font-family: monospace,
51
+ // $input-font-size: 0.8rem,
52
+ // $text-font-size: 0.85rem,
53
+
54
+ // $page-background-light: #fafafa,
55
+ // $page-background-medium: uchu.$yin-2,
56
+ // $page-background-dark: uchu.$yin-9,
57
+
58
+ // $text-light: uchu.$yin,
59
+ // $text-dark: uchu.$yang,
60
+ // $h5-text-light: uchu.$yin-6,
61
+ // $h5-text-dark: uchu.$yin-3,
62
+
63
+ // $disabled-text-light: uchu.$gray-9,
64
+ // $disabled-text-dark: uchu.$gray-9,
65
+ // $disabled-back-light: uchu.$gray-1,
66
+ // $disabled-back-dark: uchu.$yin-7,
67
+
68
+ // $box-border-light: uchu.$gray-4,
69
+ // $box-border-dark: uchu.$yin-8,
70
+ // $box-back-light: white,
71
+ // $box-back-dark: uchu.$yin-8,
72
+
73
+ // $small-border-light: uchu.$gray-4,
74
+ // $small-border-dark: uchu.$yin-7,
75
+
76
+ // $button-back-light: white,
77
+ // $button-back-dark: uchu.$yin-7,
78
+ // $button-disabled-light: uchu.$gray-1,
79
+ // $button-disabled-dark: uchu.$yin-7,
80
+
81
+ // $accent1-back-light: uchu.$red-1,
82
+ // $accent1-back-dark: Teal,
83
+ // $accent1-border-light: uchu.$red-2,
84
+ // $accent1-border-dark: LightSeaGreen,
85
+
86
+ // $accent2-back-light: uchu.$pink-7,
87
+ // $accent2-back-dark: uchu.$blue-4,
88
+ // $accent2-border-light: uchu.$pink-8,
89
+ // $accent2-border-dark: uchu.$blue-8,
90
+
91
+ // $slider-track-light: uchu.$yin-1,
92
+ // $slider-track-dark: uchu.$yin-8,
93
+
94
+ // $header-back-light: uchu.$gray-1,
95
+ // $header-back-dark: uchu.$yin-8,
96
+ // $header-border-light: none,
97
+ // $header-border-dark: uchu.$yin-9,
98
+
99
+ // $separator-light: uchu.$gray-4,
100
+ // $separator-dark: uchu.$yin-7,
101
+ // $separator-minor-light: uchu.$gray-2,
102
+ // $separator-minor-dark: uchu.$yin-7,
103
+
104
+ // $shadow-light: rgba(128, 128, 128, 0.5),
105
+ // $shadow-dark: rgba(128, 128, 128, 0.5),
106
+
107
+ // $tab-accent-light: uchu.$blue-4,
108
+ // $tab-accent-dark: slategray,
109
+
110
+ // $list-header-light: uchu.$gray-1,
111
+ // $list-header-dark: uchu.$yin-8,
112
+ // $list-border-light: uchu.$gray-4,
113
+ // $list-border-dark: uchu.$yin-8,
114
+ // $list-selection-light: uchu.$red-1,
115
+ // $list-selection-dark: LightSeaGreen,
116
+
117
+ // $border-radius-large: 3px,
118
+ // $border-radius-small: 2px,
119
+ );
120
+ ```
121
+
122
+ In the template above, commented lines contain default values for the parameters. Uncomment and modify to override.
123
+
124
+ It is important to know that `@include`ing `main.configure` is mandatory to get the library working. You should include this call even if you don't override any of the parameters.
125
+
126
+ 3. Now you can use the styled native controls and import the Svelte components as usual:
127
+
128
+ ```typescript
129
+ import { TabView, TabPage, Resizer } from '@the_dissidents/svelte-ui';
130
+ ```
@@ -0,0 +1,145 @@
1
+ <script lang="ts" module>"use strict";
2
+ const dialogs = new SvelteMap();
3
+ const setChanged = new EventHost();
4
+ </script>
5
+
6
+ <script lang="ts">import { Debug } from "./Debug.js";
7
+ import { EventHost } from "./EventHost.js";
8
+ import { XIcon } from "@lucide/svelte";
9
+ import { onDestroy, untrack } from "svelte";
10
+ import { SvelteMap } from "svelte/reactivity";
11
+ let dialog = $state();
12
+ let closed = false;
13
+ let bottomMargin = $state('2em');
14
+ let transform = $state('translateX(200%)');
15
+ let id = 0;
16
+ let me = {};
17
+ onDestroy(() => EventHost.unbind(me));
18
+ setChanged.bind(me, () => {
19
+ const diags = [...dialogs.entries()].filter((x) => x[0] < id);
20
+ const sum = diags.reduce((a, b) => a + b[1].offsetHeight, 0);
21
+ const result = `calc(${sum}px + ${diags.length + 2}em)`;
22
+ bottomMargin = result;
23
+ });
24
+ let { onSubmit, open = $bindable(), style = 'normal', buttons = [], text } = $props();
25
+ $effect(() => {
26
+ if (open) {
27
+ untrack(() => {
28
+ Debug.assert(dialog !== undefined);
29
+ id = dialogs.size;
30
+ closed = false;
31
+ dialogs.set(id, dialog);
32
+ setChanged.dispatch();
33
+ transform = 'none';
34
+ dialog.show();
35
+ });
36
+ }
37
+ });
38
+ </script>
39
+
40
+ <dialog
41
+ bind:this={dialog}
42
+ {open}
43
+ class={[style]}
44
+ style:bottom={bottomMargin}
45
+ style:transform={transform}
46
+ onclose={() => {
47
+ if (!closed) onSubmit?.('');
48
+ dialogs.delete(id);
49
+ setChanged.dispatch();
50
+ transform = 'translateX(200%)';
51
+ open = false;
52
+ }}
53
+ >
54
+ <form class='vlayout' method="dialog">
55
+ <p>{text}</p>
56
+ {#if buttons?.length}
57
+ <div class="buttons">
58
+ {#each buttons as btn (btn)}
59
+ <button type="submit" class="noborder"
60
+ disabled={typeof btn.disabled == 'function'
61
+ ? btn.disabled()
62
+ : (btn.disabled ?? false)}
63
+ onclick={() => {closed = true; onSubmit?.(btn.name)}}
64
+ >
65
+ {btn.localizedName()}
66
+ </button>
67
+ {/each}
68
+ </div>
69
+ {:else}
70
+ <button type="submit" class="close noborder" onclick={() => {closed = true; onSubmit?.('')}}>
71
+ <XIcon />
72
+ </button>
73
+ {/if}
74
+ </form>
75
+ </dialog>
76
+
77
+ <style>/* https://github.com/NeverCease/uchu/blob/primary/css/color_expanded.css */
78
+ /*** gray ***/
79
+ /*** red ***/
80
+ /*** pink ***/
81
+ /*** purple ***/
82
+ /*** blue ***/
83
+ /*** green ***/
84
+ /*** yellow ***/
85
+ /*** orange ***/
86
+ /*** general ***/
87
+ @media (prefers-color-scheme: light) {
88
+ dialog {
89
+ box-shadow: 2px 4px 10px gray;
90
+ }
91
+ }
92
+ @media (prefers-color-scheme: dark) {
93
+ dialog {
94
+ box-shadow: 2px 4px 10px black;
95
+ background-color: oklch(52.79% 0.005 271.32deg);
96
+ color: oklch(99.4% 0 0deg);
97
+ }
98
+ }
99
+ dialog {
100
+ position: absolute;
101
+ margin-right: 2em;
102
+ padding: 1.2em 1.7em;
103
+ border-radius: 0.3em;
104
+ border: none;
105
+ z-index: 100;
106
+ transition: transform 0.15s ease-in, bottom 0.1s ease-in, display 0.5s allow-discrete, overlay 0.5s allow-discrete;
107
+ }
108
+
109
+ p {
110
+ margin: 0;
111
+ }
112
+
113
+ .error p {
114
+ font-weight: bold;
115
+ }
116
+
117
+ .buttons {
118
+ text-align: right;
119
+ transform: translate(0.5em, 0.5em);
120
+ }
121
+
122
+ button {
123
+ box-shadow: none;
124
+ text-transform: uppercase;
125
+ padding: 0.5em 0.8em;
126
+ font-size: 90%;
127
+ }
128
+
129
+ .error {
130
+ background-color: oklch(62.73% 0.209 12.37deg);
131
+ color: oklch(99.4% 0 0deg);
132
+ }
133
+ .error button {
134
+ color: oklch(99.4% 0 0deg);
135
+ }
136
+ .error button:hover {
137
+ background-color: oklch(69.86% 0.162 7.82deg) !important;
138
+ }
139
+
140
+ .close {
141
+ position: absolute;
142
+ right: 0.2em;
143
+ top: 0.2em;
144
+ padding: 0.3em;
145
+ }</style>
@@ -0,0 +1,15 @@
1
+ export type BannerButton = {
2
+ name: string;
3
+ localizedName: () => string;
4
+ disabled?: boolean | (() => boolean);
5
+ };
6
+ interface Props {
7
+ text: string;
8
+ open?: boolean;
9
+ style?: 'normal' | 'error';
10
+ buttons?: BannerButton[];
11
+ onSubmit?: (x: string) => void;
12
+ }
13
+ declare const Banner: import("svelte").Component<Props, {}, "open">;
14
+ type Banner = ReturnType<typeof Banner>;
15
+ export default Banner;
@@ -0,0 +1,93 @@
1
+ <script lang="ts">import Tooltip from "./Tooltip.svelte";
2
+ let { header = "", helpText = "", active = $bindable(false), showCheck = false, checked = $bindable(false), onActiveChanged, onCheckedChanged, children } = $props();
3
+ </script>
4
+
5
+ <button type="button" class="collapsible hlayout"
6
+ class:active class:checked
7
+ onclick={() => { active = !active; onActiveChanged?.(active) }}
8
+ >
9
+ <span class='caret flexgrow'>{header}</span>
10
+
11
+ {#if helpText != ""}
12
+ <Tooltip position='left' text={helpText} />
13
+ {/if}
14
+
15
+ <span class='check' class:hidden={!showCheck}>
16
+ <input type='checkbox' bind:checked
17
+ onchange={() => onCheckedChanged?.(checked)}
18
+ onclick={(e) => e.stopPropagation()}/>
19
+ </span>
20
+ </button>
21
+ <div class='content' class:active={active}>
22
+ {@render children?.()}
23
+ </div>
24
+
25
+ <style>@charset "UTF-8";
26
+ /* https://github.com/NeverCease/uchu/blob/primary/css/color_expanded.css */
27
+ /*** gray ***/
28
+ /*** red ***/
29
+ /*** pink ***/
30
+ /*** purple ***/
31
+ /*** blue ***/
32
+ /*** green ***/
33
+ /*** yellow ***/
34
+ /*** orange ***/
35
+ /*** general ***/
36
+ @media (prefers-color-scheme: light) {
37
+ button.collapsible {
38
+ background-color: var(--header-back-light);
39
+ outline: 1px solid var(--header-border-light);
40
+ }
41
+ button.checked {
42
+ background-color: oklch(88.98% 0.052 3.28deg) !important;
43
+ }
44
+ .content {
45
+ /* background-color: uchu.$gray-1; */
46
+ border-left: 1px solid oklch(88.28% 0.003 286.34deg);
47
+ }
48
+ }
49
+ @media (prefers-color-scheme: dark) {
50
+ button.collapsible {
51
+ outline: 1px solid var(--header-border-dark);
52
+ background-color: var(--header-back-dark);
53
+ }
54
+ button.checked {
55
+ background-color: oklch(52.77% 0.138 145.41deg) !important;
56
+ }
57
+ }
58
+ .check {
59
+ display: inline-block;
60
+ text-align: right;
61
+ }
62
+
63
+ .hidden {
64
+ display: none;
65
+ }
66
+
67
+ button.collapsible {
68
+ margin-top: 5px;
69
+ width: 100%;
70
+ border: none;
71
+ text-align: left;
72
+ box-shadow: none;
73
+ }
74
+
75
+ button.collapsible.active .caret::before {
76
+ transform: rotate(90deg);
77
+ }
78
+
79
+ .caret::before {
80
+ content: "▶";
81
+ display: inline-block;
82
+ margin-right: 6px;
83
+ }
84
+
85
+ .content {
86
+ padding: 2px 0 2px 4px;
87
+ border-radius: 2px;
88
+ /* transition: max-height 1s ease-out; */
89
+ }
90
+
91
+ .content:not(.active) {
92
+ display: none;
93
+ }</style>
@@ -0,0 +1,14 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ header?: string;
4
+ helpText?: string;
5
+ active?: boolean;
6
+ showCheck?: boolean;
7
+ checked?: boolean;
8
+ onActiveChanged?: (v: boolean) => void;
9
+ onCheckedChanged?: (v: boolean) => void;
10
+ children?: Snippet;
11
+ }
12
+ declare const Collapsible: import("svelte").Component<Props, {}, "active" | "checked">;
13
+ type Collapsible = ReturnType<typeof Collapsible>;
14
+ export default Collapsible;