@svadmin/ui 0.75.0 → 0.76.0

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.
Files changed (41) hide show
  1. package/dist/app.css +14 -0
  2. package/dist/app.theme.css +14 -0
  3. package/dist/components/AppFooter.svelte +137 -0
  4. package/dist/components/AppFooter.svelte.d.ts +25 -0
  5. package/dist/components/CodeEditor.svelte +145 -0
  6. package/dist/components/CodeEditor.svelte.d.ts +26 -0
  7. package/dist/components/HighlightText.svelte +46 -0
  8. package/dist/components/HighlightText.svelte.d.ts +13 -0
  9. package/dist/components/JsonEditor.svelte +182 -0
  10. package/dist/components/JsonEditor.svelte.d.ts +22 -0
  11. package/dist/components/Marquee.svelte +104 -0
  12. package/dist/components/Marquee.svelte.d.ts +20 -0
  13. package/dist/components/MediaPlayer.svelte +104 -0
  14. package/dist/components/MediaPlayer.svelte.d.ts +21 -0
  15. package/dist/components/PhoneInput.svelte +157 -0
  16. package/dist/components/PhoneInput.svelte.d.ts +26 -0
  17. package/dist/components/QRCode.svelte +88 -0
  18. package/dist/components/QRCode.svelte.d.ts +22 -0
  19. package/dist/components/ResizableGrid.svelte +252 -0
  20. package/dist/components/ResizableGrid.svelte.d.ts +34 -0
  21. package/dist/components/TopProgressBar.svelte +90 -0
  22. package/dist/components/TopProgressBar.svelte.d.ts +15 -0
  23. package/dist/components/browser.svelte.d.ts +65 -0
  24. package/dist/components/browser.svelte.js +228 -0
  25. package/dist/components/charts/AreaChart.svelte +78 -0
  26. package/dist/components/charts/AreaChart.svelte.d.ts +17 -0
  27. package/dist/components/charts/ScatterChart.svelte +82 -0
  28. package/dist/components/charts/ScatterChart.svelte.d.ts +17 -0
  29. package/dist/components/charts/index.d.ts +2 -0
  30. package/dist/components/charts/index.js +2 -0
  31. package/dist/components/code-editor-presets.d.ts +14 -0
  32. package/dist/components/code-editor-presets.js +14 -0
  33. package/dist/components/highlight.d.ts +16 -0
  34. package/dist/components/highlight.js +68 -0
  35. package/dist/components/media.svelte.d.ts +27 -0
  36. package/dist/components/media.svelte.js +60 -0
  37. package/dist/components/phone-dial-codes.d.ts +19 -0
  38. package/dist/components/phone-dial-codes.js +112 -0
  39. package/dist/index.d.ts +19 -1
  40. package/dist/index.js +14 -1
  41. package/package.json +64 -4
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../utils.js';
4
+
5
+ interface Props {
6
+ /** Plain text shortcut; ignored when `children` is provided. */
7
+ text?: string;
8
+ children?: Snippet;
9
+ /** Seconds for one full loop. */
10
+ duration?: number;
11
+ direction?: 'left' | 'right';
12
+ vertical?: boolean;
13
+ /** Copies of the content used to build a seamless loop. */
14
+ repeat?: number;
15
+ pauseOnHover?: boolean;
16
+ /** Fades the leading and trailing edges. */
17
+ fade?: boolean;
18
+ ariaLabel?: string;
19
+ class?: string;
20
+ }
21
+
22
+ let {
23
+ text,
24
+ children,
25
+ duration = 14,
26
+ direction = 'left',
27
+ vertical = false,
28
+ repeat = 2,
29
+ pauseOnHover = true,
30
+ fade = true,
31
+ ariaLabel,
32
+ class: className = '',
33
+ }: Props = $props();
34
+
35
+ const copies = $derived(Math.max(2, Math.round(repeat)));
36
+ const safeDuration = $derived(duration > 0 ? duration : 14);
37
+ </script>
38
+
39
+ <div
40
+ class={cn('svadmin-marquee', className)}
41
+ data-slot="marquee"
42
+ data-direction={direction}
43
+ data-vertical={vertical ? 'true' : 'false'}
44
+ data-pause={pauseOnHover ? 'true' : 'false'}
45
+ data-fade={fade ? 'true' : 'false'}
46
+ role="marquee"
47
+ aria-label={ariaLabel}
48
+ >
49
+ <div
50
+ class="svadmin-marquee__track"
51
+ style={`--svadmin-marquee-duration: ${safeDuration}s; --svadmin-marquee-repeat: ${copies};`}
52
+ >
53
+ {#each Array.from({ length: copies }) as _, index (index)}
54
+ <div class="svadmin-marquee__item" aria-hidden={index > 0 ? 'true' : undefined}>
55
+ {#if children}{@render children()}{:else}{text}{/if}
56
+ </div>
57
+ {/each}
58
+ </div>
59
+ </div>
60
+
61
+ <style>
62
+ .svadmin-marquee {
63
+ position: relative;
64
+ overflow: hidden;
65
+ color: inherit;
66
+ }
67
+
68
+ .svadmin-marquee[data-fade='true'] {
69
+ mask-image: linear-gradient(to right, transparent, currentColor 8%, currentColor 92%, transparent);
70
+ }
71
+
72
+ .svadmin-marquee__track {
73
+ display: flex;
74
+ width: max-content;
75
+ animation: svadmin-marquee-scroll var(--svadmin-marquee-duration, 14s) linear infinite;
76
+ will-change: transform;
77
+ }
78
+
79
+ .svadmin-marquee[data-direction='right'] .svadmin-marquee__track { animation-direction: reverse; }
80
+ .svadmin-marquee[data-vertical='true'] .svadmin-marquee__track { flex-direction: column; }
81
+ .svadmin-marquee[data-pause='true']:hover .svadmin-marquee__track,
82
+ .svadmin-marquee[data-pause='true']:focus-within .svadmin-marquee__track { animation-play-state: paused; }
83
+
84
+ .svadmin-marquee__item {
85
+ display: flex;
86
+ flex: none;
87
+ align-items: center;
88
+ padding-inline-end: 2rem;
89
+ }
90
+
91
+ .svadmin-marquee[data-vertical='true'] .svadmin-marquee__item {
92
+ padding-inline-end: 0;
93
+ padding-block-end: 1rem;
94
+ }
95
+
96
+ @keyframes svadmin-marquee-scroll {
97
+ from { transform: translate(0, 0); }
98
+ to { transform: translate(calc(-100% / var(--svadmin-marquee-repeat, 2)), 0); }
99
+ }
100
+
101
+ @media (prefers-reduced-motion: reduce) {
102
+ .svadmin-marquee__track { animation: none; }
103
+ }
104
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** Plain text shortcut; ignored when `children` is provided. */
4
+ text?: string;
5
+ children?: Snippet;
6
+ /** Seconds for one full loop. */
7
+ duration?: number;
8
+ direction?: 'left' | 'right';
9
+ vertical?: boolean;
10
+ /** Copies of the content used to build a seamless loop. */
11
+ repeat?: number;
12
+ pauseOnHover?: boolean;
13
+ /** Fades the leading and trailing edges. */
14
+ fade?: boolean;
15
+ ariaLabel?: string;
16
+ class?: string;
17
+ }
18
+ declare const Marquee: import("svelte").Component<Props, {}, "">;
19
+ type Marquee = ReturnType<typeof Marquee>;
20
+ export default Marquee;
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ import { cn } from '../utils.js';
3
+
4
+ interface Props {
5
+ src: string;
6
+ media?: 'video' | 'audio';
7
+ poster?: string;
8
+ title?: string;
9
+ description?: string;
10
+ controls?: boolean;
11
+ autoplay?: boolean;
12
+ loop?: boolean;
13
+ muted?: boolean;
14
+ playsinline?: boolean;
15
+ preload?: 'none' | 'metadata' | 'auto';
16
+ /** CSS aspect ratio for video, e.g. `16 / 9`. */
17
+ aspectRatio?: string;
18
+ class?: string;
19
+ ontimeupdate?: (event: Event) => void;
20
+ onended?: (event: Event) => void;
21
+ }
22
+
23
+ let {
24
+ src,
25
+ media = 'video',
26
+ poster,
27
+ title,
28
+ description,
29
+ controls = true,
30
+ autoplay = false,
31
+ loop = false,
32
+ muted = false,
33
+ playsinline = true,
34
+ preload = 'metadata',
35
+ aspectRatio = '16 / 9',
36
+ class: className = '',
37
+ ontimeupdate,
38
+ onended,
39
+ }: Props = $props();
40
+ </script>
41
+
42
+ <figure class={cn('svadmin-media-player', className)} data-slot="media-player" data-media={media}>
43
+ {#if media === 'video'}
44
+ <div class="svadmin-media-player__stage" style={`aspect-ratio: ${aspectRatio};`}>
45
+ <video
46
+ class="svadmin-media-player__video"
47
+ {src}
48
+ {poster}
49
+ {controls}
50
+ autoplay={autoplay}
51
+ {loop}
52
+ {muted}
53
+ {playsinline}
54
+ {preload}
55
+ aria-label={title}
56
+ ontimeupdate={ontimeupdate}
57
+ onended={onended}
58
+ ></video>
59
+ </div>
60
+ {:else}
61
+ <div class="svadmin-media-player__audio-stage">
62
+ <audio
63
+ class="svadmin-media-player__audio"
64
+ {src}
65
+ {controls}
66
+ autoplay={autoplay}
67
+ {loop}
68
+ {muted}
69
+ {preload}
70
+ aria-label={title}
71
+ ontimeupdate={ontimeupdate}
72
+ onended={onended}
73
+ ></audio>
74
+ </div>
75
+ {/if}
76
+
77
+ {#if title || description}
78
+ <figcaption class="svadmin-media-player__caption">
79
+ {#if title}<span class="svadmin-media-player__title">{title}</span>{/if}
80
+ {#if description}<span class="svadmin-media-player__description">{description}</span>{/if}
81
+ </figcaption>
82
+ {/if}
83
+ </figure>
84
+
85
+ <style>
86
+ .svadmin-media-player {
87
+ display: grid;
88
+ gap: 0.5rem;
89
+ margin: 0;
90
+ border: 1px solid var(--border, currentColor);
91
+ border-radius: min(var(--radius, 0.5rem), 0.75rem);
92
+ padding: 0.5rem;
93
+ background: var(--card, transparent);
94
+ color: var(--card-foreground, var(--foreground, currentColor));
95
+ }
96
+
97
+ .svadmin-media-player__stage { overflow: hidden; border-radius: min(var(--radius, 0.5rem), 0.5rem); background: var(--muted, transparent); }
98
+ .svadmin-media-player__video { display: block; width: 100%; height: 100%; object-fit: contain; }
99
+ .svadmin-media-player__audio-stage { padding: 0.25rem; }
100
+ .svadmin-media-player__audio { width: 100%; }
101
+ .svadmin-media-player__caption { display: grid; gap: 0.125rem; padding-inline: 0.25rem; }
102
+ .svadmin-media-player__title { font-size: 0.875rem; font-weight: 600; }
103
+ .svadmin-media-player__description { color: var(--muted-foreground, currentColor); font-size: 0.8125rem; }
104
+ </style>
@@ -0,0 +1,21 @@
1
+ interface Props {
2
+ src: string;
3
+ media?: 'video' | 'audio';
4
+ poster?: string;
5
+ title?: string;
6
+ description?: string;
7
+ controls?: boolean;
8
+ autoplay?: boolean;
9
+ loop?: boolean;
10
+ muted?: boolean;
11
+ playsinline?: boolean;
12
+ preload?: 'none' | 'metadata' | 'auto';
13
+ /** CSS aspect ratio for video, e.g. `16 / 9`. */
14
+ aspectRatio?: string;
15
+ class?: string;
16
+ ontimeupdate?: (event: Event) => void;
17
+ onended?: (event: Event) => void;
18
+ }
19
+ declare const MediaPlayer: import("svelte").Component<Props, {}, "">;
20
+ type MediaPlayer = ReturnType<typeof MediaPlayer>;
21
+ export default MediaPlayer;
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ import { cn } from '../utils.js';
3
+ import {
4
+ findDialCodeByCountry,
5
+ PHONE_DIAL_CODES,
6
+ toE164,
7
+ type PhoneDialCode,
8
+ } from './phone-dial-codes.js';
9
+
10
+ interface Props {
11
+ /** ISO 3166-1 alpha-2 country code. */
12
+ country?: string;
13
+ /** National number without the dialling prefix. */
14
+ nationalNumber?: string;
15
+ countries?: readonly PhoneDialCode[];
16
+ id?: string;
17
+ name?: string;
18
+ placeholder?: string;
19
+ disabled?: boolean;
20
+ readonly?: boolean;
21
+ required?: boolean;
22
+ invalid?: boolean;
23
+ describedby?: string;
24
+ ariaLabel?: string;
25
+ class?: string;
26
+ onchange?: (payload: { country: string; nationalNumber: string; e164: string }) => void;
27
+ }
28
+
29
+ let {
30
+ country = $bindable('CN'),
31
+ nationalNumber = $bindable(''),
32
+ countries = PHONE_DIAL_CODES,
33
+ id,
34
+ name,
35
+ placeholder = 'Phone number',
36
+ disabled = false,
37
+ readonly = false,
38
+ required = false,
39
+ invalid = false,
40
+ describedby,
41
+ ariaLabel = 'Phone number',
42
+ class: className = '',
43
+ onchange,
44
+ }: Props = $props();
45
+
46
+ const entry = $derived(findDialCodeByCountry(country, countries));
47
+ const dial = $derived(entry?.dial ?? '');
48
+
49
+ function emit(): void {
50
+ onchange?.({
51
+ country,
52
+ nationalNumber,
53
+ e164: toE164(country, nationalNumber, countries),
54
+ });
55
+ }
56
+ </script>
57
+
58
+ <div
59
+ class={cn('svadmin-phone-input', className)}
60
+ data-slot="phone-input"
61
+ data-invalid={invalid ? 'true' : undefined}
62
+ data-disabled={disabled ? 'true' : undefined}
63
+ >
64
+ <select
65
+ class="svadmin-phone-input__country"
66
+ bind:value={country}
67
+ {disabled}
68
+ aria-label={`${ariaLabel}: country code`}
69
+ onchange={emit}
70
+ >
71
+ {#each countries as option (option.country)}
72
+ <option value={option.country}>{option.name} (+{option.dial})</option>
73
+ {/each}
74
+ </select>
75
+
76
+ <div class="svadmin-phone-input__control">
77
+ {#if dial}<span class="svadmin-phone-input__dial" aria-hidden="true">+{dial}</span>{/if}
78
+ <input
79
+ class="svadmin-phone-input__field"
80
+ type="tel"
81
+ inputmode="tel"
82
+ autocomplete="tel-national"
83
+ {id}
84
+ {name}
85
+ bind:value={nationalNumber}
86
+ {placeholder}
87
+ {disabled}
88
+ {readonly}
89
+ {required}
90
+ aria-label={ariaLabel}
91
+ aria-invalid={invalid ? 'true' : undefined}
92
+ aria-describedby={describedby}
93
+ oninput={emit}
94
+ />
95
+ </div>
96
+ </div>
97
+
98
+ <style>
99
+ .svadmin-phone-input {
100
+ display: flex;
101
+ align-items: stretch;
102
+ border: 1px solid var(--border, currentColor);
103
+ border-radius: min(var(--radius, 0.5rem), 0.5rem);
104
+ background: var(--background, transparent);
105
+ color: var(--foreground, currentColor);
106
+ }
107
+
108
+ .svadmin-phone-input:focus-within { outline: 2px solid var(--ring, currentColor); outline-offset: 1px; }
109
+ .svadmin-phone-input[data-invalid='true'] { border-color: var(--destructive, currentColor); }
110
+ .svadmin-phone-input[data-disabled='true'] { cursor: not-allowed; opacity: 0.6; }
111
+
112
+ .svadmin-phone-input__country {
113
+ flex: none;
114
+ max-width: 11rem;
115
+ border: 0;
116
+ border-inline-end: 1px solid var(--border, currentColor);
117
+ border-start-start-radius: inherit;
118
+ border-end-start-radius: inherit;
119
+ padding: 0.5rem 0.625rem;
120
+ background: var(--muted, transparent);
121
+ color: inherit;
122
+ font: inherit;
123
+ font-size: 0.8125rem;
124
+ cursor: pointer;
125
+ }
126
+
127
+ .svadmin-phone-input__country:focus-visible { outline: none; }
128
+
129
+ .svadmin-phone-input__control {
130
+ display: flex;
131
+ min-width: 0;
132
+ flex: 1;
133
+ align-items: center;
134
+ gap: 0.375rem;
135
+ padding-inline: 0.625rem;
136
+ }
137
+
138
+ .svadmin-phone-input__dial {
139
+ flex: none;
140
+ color: var(--muted-foreground, currentColor);
141
+ font-size: 0.8125rem;
142
+ font-variant-numeric: tabular-nums;
143
+ }
144
+
145
+ .svadmin-phone-input__field {
146
+ width: 100%;
147
+ min-width: 0;
148
+ border: 0;
149
+ padding-block: 0.5rem;
150
+ background: transparent;
151
+ color: inherit;
152
+ font: inherit;
153
+ font-size: 0.875rem;
154
+ }
155
+
156
+ .svadmin-phone-input__field:focus { outline: none; }
157
+ </style>
@@ -0,0 +1,26 @@
1
+ import { type PhoneDialCode } from './phone-dial-codes.js';
2
+ interface Props {
3
+ /** ISO 3166-1 alpha-2 country code. */
4
+ country?: string;
5
+ /** National number without the dialling prefix. */
6
+ nationalNumber?: string;
7
+ countries?: readonly PhoneDialCode[];
8
+ id?: string;
9
+ name?: string;
10
+ placeholder?: string;
11
+ disabled?: boolean;
12
+ readonly?: boolean;
13
+ required?: boolean;
14
+ invalid?: boolean;
15
+ describedby?: string;
16
+ ariaLabel?: string;
17
+ class?: string;
18
+ onchange?: (payload: {
19
+ country: string;
20
+ nationalNumber: string;
21
+ e164: string;
22
+ }) => void;
23
+ }
24
+ declare const PhoneInput: import("svelte").Component<Props, {}, "country" | "nationalNumber">;
25
+ type PhoneInput = ReturnType<typeof PhoneInput>;
26
+ export default PhoneInput;
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ import QRCodeLib from 'qrcode';
3
+ import { cn } from '../utils.js';
4
+
5
+ /**
6
+ * Renders a QR code from the Svelte ecosystem `qrcode` package as a crisp
7
+ * SVG. No canvas is required, so it is safe under SSR and test DOMs.
8
+ */
9
+ interface Props {
10
+ /** Content to encode. Empty content renders a neutral placeholder. */
11
+ value: string;
12
+ /** Rendered size in pixels. */
13
+ size?: number;
14
+ /** Quiet-zone width in modules. Keep at least 4 for reliable scanning. */
15
+ margin?: number;
16
+ errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
17
+ /** Module color; defaults to the semantic foreground token. */
18
+ color?: string;
19
+ /** Background color; defaults to the semantic background token. */
20
+ background?: string;
21
+ ariaLabel?: string;
22
+ class?: string;
23
+ }
24
+
25
+ let {
26
+ value,
27
+ size = 160,
28
+ margin = 4,
29
+ errorCorrectionLevel = 'M',
30
+ color = 'var(--foreground, black)',
31
+ background = 'var(--background, white)',
32
+ ariaLabel = 'QR code',
33
+ class: className = '',
34
+ }: Props = $props();
35
+
36
+ const matrix = $derived.by(() => {
37
+ if (!value) return null;
38
+ try {
39
+ const qr = QRCodeLib.create(value, { errorCorrectionLevel });
40
+ const moduleCount = qr.modules.size;
41
+ const data = qr.modules.data;
42
+ const total = moduleCount + margin * 2;
43
+ let path = '';
44
+ for (let y = 0; y < moduleCount; y += 1) {
45
+ for (let x = 0; x < moduleCount; x += 1) {
46
+ if (data[y * moduleCount + x]) path += `M${x + margin} ${y + margin}h1v1h-1z`;
47
+ }
48
+ }
49
+ return { total, path };
50
+ } catch {
51
+ return null;
52
+ }
53
+ });
54
+ </script>
55
+
56
+ <figure class={cn('svadmin-qr-code', className)} data-slot="qr-code">
57
+ {#if matrix}
58
+ <svg
59
+ class="svadmin-qr-code__svg"
60
+ width={size}
61
+ height={size}
62
+ viewBox={`0 0 ${matrix.total} ${matrix.total}`}
63
+ role="img"
64
+ aria-label={ariaLabel}
65
+ shape-rendering="crispEdges"
66
+ >
67
+ <rect width={matrix.total} height={matrix.total} fill={background} />
68
+ <path d={matrix.path} fill={color} />
69
+ </svg>
70
+ {:else}
71
+ <p class="svadmin-qr-code__empty" role="status">No QR content.</p>
72
+ {/if}
73
+ </figure>
74
+
75
+ <style>
76
+ .svadmin-qr-code {
77
+ display: inline-flex;
78
+ margin: 0;
79
+ border: 1px solid var(--border, currentColor);
80
+ border-radius: min(var(--radius, 0.5rem), 0.75rem);
81
+ padding: 0.5rem;
82
+ background: var(--card, var(--background, transparent));
83
+ color: var(--card-foreground, var(--foreground, currentColor));
84
+ }
85
+
86
+ .svadmin-qr-code__svg { display: block; border-radius: calc(min(var(--radius, 0.5rem), 0.75rem) - 0.25rem); }
87
+ .svadmin-qr-code__empty { margin: 0; padding: 1rem; color: var(--muted-foreground, currentColor); font-size: 0.8125rem; }
88
+ </style>
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Renders a QR code from the Svelte ecosystem `qrcode` package as a crisp
3
+ * SVG. No canvas is required, so it is safe under SSR and test DOMs.
4
+ */
5
+ interface Props {
6
+ /** Content to encode. Empty content renders a neutral placeholder. */
7
+ value: string;
8
+ /** Rendered size in pixels. */
9
+ size?: number;
10
+ /** Quiet-zone width in modules. Keep at least 4 for reliable scanning. */
11
+ margin?: number;
12
+ errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H';
13
+ /** Module color; defaults to the semantic foreground token. */
14
+ color?: string;
15
+ /** Background color; defaults to the semantic background token. */
16
+ background?: string;
17
+ ariaLabel?: string;
18
+ class?: string;
19
+ }
20
+ declare const QRCode: import("svelte").Component<Props, {}, "">;
21
+ type QRCode = ReturnType<typeof QRCode>;
22
+ export default QRCode;