@hyvor/design 2.0.12 → 2.0.16
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/dist/cloud/HyvorBar/BarSupport.svelte +0 -22
- package/dist/components/ConsoleLoader/ConsoleLoader.svelte +1 -1
- package/dist/components/Kbd/Kbd.svelte +108 -0
- package/dist/components/Kbd/Kbd.svelte.d.ts +8 -0
- package/dist/components/Modal/Modal.svelte +9 -7
- package/dist/components/Modal/Modal.svelte.d.ts +1 -0
- package/dist/components/NavLink/NavLink.svelte +51 -14
- package/dist/components/NavLink/NavLink.svelte.d.ts +10 -0
- package/dist/components/NavLink/NavLinkGroup.svelte +189 -0
- package/dist/components/NavLink/NavLinkGroup.svelte.d.ts +12 -0
- package/dist/components/TernaryStatus/TernaryStatus.svelte +70 -0
- package/dist/components/TernaryStatus/TernaryStatus.svelte.d.ts +12 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +3 -0
- package/dist/variables.scss +3 -3
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import IconDiscord from '@hyvor/icons/IconDiscord';
|
|
13
13
|
import IconInfoCircle from '@hyvor/icons/IconInfoCircle';
|
|
14
14
|
import IconFileEarmark from '@hyvor/icons/IconFileEarmark';
|
|
15
|
-
import IconChatDots from '@hyvor/icons/IconChatDots';
|
|
16
15
|
import IconBluesky from '@hyvor/icons/IconBluesky';
|
|
17
16
|
import IconLinkedin from '@hyvor/icons/IconLinkedin';
|
|
18
17
|
import type { BarConfig } from './bar.js';
|
|
@@ -34,14 +33,6 @@
|
|
|
34
33
|
|
|
35
34
|
const cloudContext = $derived(getCloudContext());
|
|
36
35
|
|
|
37
|
-
function openLiveChat(e: any) {
|
|
38
|
-
e.preventDefault();
|
|
39
|
-
if ((window as any).$crisp) {
|
|
40
|
-
(window as any).$crisp.push(['do', 'chat:open']);
|
|
41
|
-
}
|
|
42
|
-
supportDropdown = false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
36
|
function getName() {
|
|
46
37
|
if (config.name) {
|
|
47
38
|
return config.name;
|
|
@@ -107,19 +98,6 @@
|
|
|
107
98
|
</ActionListItem>
|
|
108
99
|
</a>
|
|
109
100
|
{/if}
|
|
110
|
-
{#if config.chat}
|
|
111
|
-
<a href="/chat" onclick={openLiveChat}>
|
|
112
|
-
<ActionListItem>
|
|
113
|
-
Live Chat
|
|
114
|
-
{#snippet description()}
|
|
115
|
-
<div>Chat with our team</div>
|
|
116
|
-
{/snippet}
|
|
117
|
-
{#snippet start()}
|
|
118
|
-
<IconChatDots />
|
|
119
|
-
{/snippet}
|
|
120
|
-
</ActionListItem>
|
|
121
|
-
</a>
|
|
122
|
-
{/if}
|
|
123
101
|
|
|
124
102
|
<ActionListGroup title="Social">
|
|
125
103
|
<a href={SOCIAL_LINKS.discord} target="_blank">
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
|
|
4
|
+
type ModKey = 'mod' | 'ctrl' | 'alt' | 'shift' | 'meta';
|
|
5
|
+
|
|
6
|
+
const PLATFORM_LABELS: Record<ModKey, { mac: string; other: string }> = {
|
|
7
|
+
mod: { mac: '⌘', other: 'Ctrl' },
|
|
8
|
+
ctrl: { mac: '⌃', other: 'Ctrl' },
|
|
9
|
+
alt: { mac: '⌥', other: 'Alt' },
|
|
10
|
+
shift: { mac: '⇧', other: 'Shift' },
|
|
11
|
+
meta: { mac: '⌘', other: 'Win' }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const MAC_ARIA: Record<ModKey, string> = {
|
|
15
|
+
mod: 'Command',
|
|
16
|
+
ctrl: 'Control',
|
|
17
|
+
alt: 'Option',
|
|
18
|
+
shift: 'Shift',
|
|
19
|
+
meta: 'Command'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Canonical Mac modifier order: Control → Option → Shift → Command
|
|
23
|
+
const MOD_ORDER: ModKey[] = ['ctrl', 'alt', 'shift', 'meta', 'mod'];
|
|
24
|
+
const MOD_SET = new Set<string>(MOD_ORDER);
|
|
25
|
+
|
|
26
|
+
interface Props {
|
|
27
|
+
keys: string | string[];
|
|
28
|
+
size?: 'small' | 'medium' | 'large' | (string & {});
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let { keys, size = 'small', ...rest }: Props = $props();
|
|
33
|
+
|
|
34
|
+
const PREDEFINED_SIZES = ['small', 'medium', 'large'];
|
|
35
|
+
let sizeClass = $derived(PREDEFINED_SIZES.includes(size) ? `size-${size}` : '');
|
|
36
|
+
let sizeStyle = $derived(!PREDEFINED_SIZES.includes(size) ? `font-size: ${size};` : undefined);
|
|
37
|
+
|
|
38
|
+
let isMac = $state(false);
|
|
39
|
+
|
|
40
|
+
onMount(() => {
|
|
41
|
+
const platform = (navigator as any).userAgentData?.platform ?? navigator.platform ?? '';
|
|
42
|
+
isMac = /mac/i.test(platform);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function keyLabel(key: string, mac: boolean): string {
|
|
46
|
+
const lower = key.toLowerCase();
|
|
47
|
+
if (MOD_SET.has(lower)) {
|
|
48
|
+
return mac ? PLATFORM_LABELS[lower as ModKey].mac : PLATFORM_LABELS[lower as ModKey].other;
|
|
49
|
+
}
|
|
50
|
+
return key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function keyAria(key: string, mac: boolean): string {
|
|
54
|
+
const lower = key.toLowerCase();
|
|
55
|
+
if (MOD_SET.has(lower)) {
|
|
56
|
+
return mac ? MAC_ARIA[lower as ModKey] : PLATFORM_LABELS[lower as ModKey].other;
|
|
57
|
+
}
|
|
58
|
+
return key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function sortKeys(keyArr: string[]): string[] {
|
|
62
|
+
const mods = MOD_ORDER.filter((m) => keyArr.some((k) => k.toLowerCase() === m));
|
|
63
|
+
const regular = keyArr.filter((k) => !MOD_SET.has(k.toLowerCase()));
|
|
64
|
+
return [...mods, ...regular];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let keyArray = $derived(Array.isArray(keys) ? keys : [keys]);
|
|
68
|
+
let sorted = $derived(sortKeys(keyArray));
|
|
69
|
+
|
|
70
|
+
let display = $derived(
|
|
71
|
+
isMac
|
|
72
|
+
? sorted.map((k) => keyLabel(k, true)).join('')
|
|
73
|
+
: sorted.map((k) => keyLabel(k, false)).join(' + ')
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
let ariaLabel = $derived(sorted.map((k) => keyAria(k, isMac)).join(' plus '));
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<kbd aria-label={ariaLabel} class={sizeClass} style={sizeStyle} {...rest}>{display}</kbd>
|
|
80
|
+
|
|
81
|
+
<style>kbd {
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
font-family: inherit;
|
|
86
|
+
line-height: 1;
|
|
87
|
+
background-color: var(--hover-dark);
|
|
88
|
+
border: 1px solid var(--border);
|
|
89
|
+
border-bottom-width: 2px;
|
|
90
|
+
border-radius: 4px;
|
|
91
|
+
color: var(--text);
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
}
|
|
94
|
+
kbd.size-small {
|
|
95
|
+
font-size: 0.7em;
|
|
96
|
+
min-width: 1.6em;
|
|
97
|
+
padding: 2px 5px;
|
|
98
|
+
}
|
|
99
|
+
kbd.size-medium {
|
|
100
|
+
font-size: 0.85em;
|
|
101
|
+
min-width: 1.8em;
|
|
102
|
+
padding: 3px 7px;
|
|
103
|
+
}
|
|
104
|
+
kbd.size-large {
|
|
105
|
+
font-size: 1em;
|
|
106
|
+
min-width: 2em;
|
|
107
|
+
padding: 4px 9px;
|
|
108
|
+
}</style>
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
closeOnEscape?: boolean;
|
|
19
19
|
loading?: boolean | string;
|
|
20
20
|
onclose?: () => void;
|
|
21
|
+
hasClose?: boolean | string;
|
|
21
22
|
|
|
22
23
|
footer?: Footer | Snippet;
|
|
23
24
|
children?: Snippet;
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
closeOnEscape = true,
|
|
34
35
|
loading = false,
|
|
35
36
|
onclose,
|
|
37
|
+
hasClose = true,
|
|
36
38
|
footer,
|
|
37
39
|
children
|
|
38
40
|
}: Props = $props();
|
|
@@ -89,14 +91,12 @@
|
|
|
89
91
|
role="presentation"
|
|
90
92
|
class="wrap"
|
|
91
93
|
bind:this={wrapEl}
|
|
92
|
-
|
|
93
|
-
out:fade={{ duration: 100 }}
|
|
94
|
+
transition:fade|global={{ duration: 100 }}
|
|
94
95
|
onclick={(e) => handleClose(e)}
|
|
95
96
|
>
|
|
96
97
|
<div
|
|
97
98
|
class="inner {size}"
|
|
98
|
-
|
|
99
|
-
out:scale={{ duration: 100, start: 0.9, opacity: 0.9 }}
|
|
99
|
+
transition:scale|global={{ duration: 100, start: 0.9, opacity: 0.9 }}
|
|
100
100
|
bind:this={innerEl}
|
|
101
101
|
{role}
|
|
102
102
|
aria-modal="true"
|
|
@@ -113,9 +113,11 @@
|
|
|
113
113
|
</div>
|
|
114
114
|
|
|
115
115
|
<div class="close-wrap">
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
116
|
+
{#if hasClose}
|
|
117
|
+
<IconButton variant="invisible" on:click={handleCancel} aria-label="Close modal">
|
|
118
|
+
<IconX size={25} />
|
|
119
|
+
</IconButton>
|
|
120
|
+
{/if}
|
|
119
121
|
</div>
|
|
120
122
|
</div>
|
|
121
123
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { legacyHandlers } from '../../legacy.js';
|
|
3
|
+
import { getContext } from 'svelte';
|
|
2
4
|
import { createBubbler } from 'svelte/legacy';
|
|
3
5
|
|
|
4
6
|
const bubble = createBubbler();
|
|
@@ -10,27 +12,61 @@
|
|
|
10
12
|
start?: import('svelte').Snippet;
|
|
11
13
|
children?: import('svelte').Snippet;
|
|
12
14
|
end?: import('svelte').Snippet;
|
|
15
|
+
|
|
16
|
+
onkeyup?: (event: KeyboardEvent) => void;
|
|
17
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
18
|
+
onkeypress?: (event: KeyboardEvent) => void;
|
|
19
|
+
onfocus?: (event: FocusEvent) => void;
|
|
20
|
+
onblur?: (event: FocusEvent) => void;
|
|
21
|
+
onclick?: (event: MouseEvent) => void;
|
|
22
|
+
onmouseover?: (event: MouseEvent) => void;
|
|
23
|
+
onmouseenter?: (event: MouseEvent) => void;
|
|
24
|
+
onmouseleave?: (event: MouseEvent) => void;
|
|
25
|
+
onchange?: (event: Event) => void;
|
|
26
|
+
|
|
13
27
|
[key: string]: any;
|
|
14
28
|
};
|
|
15
29
|
|
|
16
|
-
|
|
30
|
+
const isNavLinkGroup = !!getContext('navlink-group');
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
href,
|
|
34
|
+
active = false,
|
|
35
|
+
disabled = false,
|
|
36
|
+
start,
|
|
37
|
+
children,
|
|
38
|
+
end,
|
|
39
|
+
onkeyup,
|
|
40
|
+
onkeydown,
|
|
41
|
+
onkeypress,
|
|
42
|
+
onfocus,
|
|
43
|
+
onblur,
|
|
44
|
+
onclick,
|
|
45
|
+
onmouseover,
|
|
46
|
+
onmouseenter,
|
|
47
|
+
onmouseleave,
|
|
48
|
+
onchange,
|
|
49
|
+
...rest
|
|
50
|
+
}: Props = $props();
|
|
17
51
|
</script>
|
|
18
52
|
|
|
19
53
|
<a
|
|
20
54
|
{href}
|
|
21
55
|
{...rest}
|
|
56
|
+
class="nav-link"
|
|
22
57
|
class:active
|
|
23
58
|
class:disabled
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
class:group={isNavLinkGroup}
|
|
60
|
+
onkeyup={legacyHandlers(onkeyup, bubble('keyup'))}
|
|
61
|
+
onkeydown={legacyHandlers(onkeydown, bubble('keydown'))}
|
|
62
|
+
onkeypress={legacyHandlers(onkeypress, bubble('keypress'))}
|
|
63
|
+
onfocus={legacyHandlers(onfocus, bubble('focus'))}
|
|
64
|
+
onblur={legacyHandlers(onblur, bubble('blur'))}
|
|
65
|
+
onclick={legacyHandlers(onclick, bubble('click'))}
|
|
66
|
+
onmouseover={legacyHandlers(onmouseover, bubble('mouseover'))}
|
|
67
|
+
onmouseenter={legacyHandlers(onmouseenter, bubble('mouseenter'))}
|
|
68
|
+
onmouseleave={legacyHandlers(onmouseleave, bubble('mouseleave'))}
|
|
69
|
+
onchange={legacyHandlers(onchange, bubble('change'))}
|
|
34
70
|
>
|
|
35
71
|
{#if start}
|
|
36
72
|
<span class="start">
|
|
@@ -52,6 +88,7 @@
|
|
|
52
88
|
<style>
|
|
53
89
|
a {
|
|
54
90
|
display: flex;
|
|
91
|
+
width: 100%;
|
|
55
92
|
padding: 12px 29px;
|
|
56
93
|
font-size: 14px;
|
|
57
94
|
letter-spacing: 0.3px;
|
|
@@ -60,16 +97,16 @@
|
|
|
60
97
|
align-items: center;
|
|
61
98
|
}
|
|
62
99
|
|
|
63
|
-
a:hover {
|
|
100
|
+
a:hover:not(.group) {
|
|
64
101
|
background-color: var(--hover);
|
|
65
102
|
}
|
|
66
103
|
|
|
67
|
-
a.active {
|
|
104
|
+
a.active:not(.group) {
|
|
68
105
|
background-color: var(--accent-lightest);
|
|
69
106
|
border-left: 3px solid var(--accent);
|
|
70
107
|
}
|
|
71
108
|
|
|
72
|
-
:global(:root.dark) a.active {
|
|
109
|
+
:global(:root.dark) a.active:not(.group) {
|
|
73
110
|
background-color: color-mix(in srgb, var(--accent-lightest), transparent 80%);
|
|
74
111
|
}
|
|
75
112
|
|
|
@@ -5,6 +5,16 @@ type Props = {
|
|
|
5
5
|
start?: import('svelte').Snippet;
|
|
6
6
|
children?: import('svelte').Snippet;
|
|
7
7
|
end?: import('svelte').Snippet;
|
|
8
|
+
onkeyup?: (event: KeyboardEvent) => void;
|
|
9
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
10
|
+
onkeypress?: (event: KeyboardEvent) => void;
|
|
11
|
+
onfocus?: (event: FocusEvent) => void;
|
|
12
|
+
onblur?: (event: FocusEvent) => void;
|
|
13
|
+
onclick?: (event: MouseEvent) => void;
|
|
14
|
+
onmouseover?: (event: MouseEvent) => void;
|
|
15
|
+
onmouseenter?: (event: MouseEvent) => void;
|
|
16
|
+
onmouseleave?: (event: MouseEvent) => void;
|
|
17
|
+
onchange?: (event: Event) => void;
|
|
8
18
|
[key: string]: any;
|
|
9
19
|
};
|
|
10
20
|
declare const NavLink: import("svelte").Component<Props, {}, "">;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext, onMount, type Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: Snippet;
|
|
7
|
+
activeBackground?: string;
|
|
8
|
+
activeBorderColor?: string;
|
|
9
|
+
hoverBackground?: string;
|
|
10
|
+
wrapProps?: HTMLAttributes<HTMLElement>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
children,
|
|
15
|
+
activeBackground = 'var(--accent-lightest)',
|
|
16
|
+
activeBorderColor = 'var(--accent)',
|
|
17
|
+
hoverBackground = 'var(--hover)',
|
|
18
|
+
wrapProps = {}
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
setContext('navlink-group', true);
|
|
22
|
+
|
|
23
|
+
let container: HTMLDivElement;
|
|
24
|
+
let indicator: HTMLDivElement;
|
|
25
|
+
let hoverIndicator: HTMLDivElement;
|
|
26
|
+
let visible = $state(false);
|
|
27
|
+
let hoverVisible = $state(false);
|
|
28
|
+
let firstRender = true;
|
|
29
|
+
let hoverFirstRender = true;
|
|
30
|
+
let hoverTarget: HTMLElement | null = null;
|
|
31
|
+
|
|
32
|
+
function positionIndicator(el: HTMLDivElement, target: HTMLElement, instant: boolean) {
|
|
33
|
+
const rect = target.getBoundingClientRect();
|
|
34
|
+
|
|
35
|
+
el.style.transition = instant ? 'none' : '';
|
|
36
|
+
el.style.top = `${rect.top}px`;
|
|
37
|
+
el.style.left = `${rect.left}px`;
|
|
38
|
+
el.style.width = `${rect.width}px`;
|
|
39
|
+
el.style.height = `${rect.height}px`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function updateIndicator(instant = false) {
|
|
43
|
+
if (!container || !indicator) return;
|
|
44
|
+
|
|
45
|
+
const activeLink = container.querySelector<HTMLElement>('.nav-link.active');
|
|
46
|
+
|
|
47
|
+
if (!activeLink) {
|
|
48
|
+
visible = false;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
positionIndicator(indicator, activeLink, firstRender || instant);
|
|
53
|
+
visible = true;
|
|
54
|
+
|
|
55
|
+
if (firstRender) {
|
|
56
|
+
firstRender = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function updateHoverIndicator(target: HTMLElement | null, instant = false) {
|
|
61
|
+
if (!hoverIndicator) return;
|
|
62
|
+
|
|
63
|
+
if (!target || target.classList.contains('active') || target.classList.contains('disabled')) {
|
|
64
|
+
hoverVisible = false;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
positionIndicator(hoverIndicator, target, hoverFirstRender || instant);
|
|
69
|
+
hoverVisible = true;
|
|
70
|
+
|
|
71
|
+
if (hoverFirstRender) {
|
|
72
|
+
hoverFirstRender = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onMouseOver(event: MouseEvent) {
|
|
77
|
+
hoverTarget = (event.target as HTMLElement).closest<HTMLElement>('.nav-link');
|
|
78
|
+
updateHoverIndicator(hoverTarget);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function onMouseLeave() {
|
|
82
|
+
hoverTarget = null;
|
|
83
|
+
hoverVisible = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function onScrollOrResize() {
|
|
87
|
+
updateIndicator(true);
|
|
88
|
+
|
|
89
|
+
if (hoverVisible && hoverTarget) {
|
|
90
|
+
updateHoverIndicator(hoverTarget, true);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onMount(() => {
|
|
95
|
+
updateIndicator();
|
|
96
|
+
|
|
97
|
+
const observer = new MutationObserver(() => updateIndicator());
|
|
98
|
+
observer.observe(container, {
|
|
99
|
+
attributes: true,
|
|
100
|
+
attributeFilter: ['class'],
|
|
101
|
+
subtree: true
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
container.addEventListener('mouseover', onMouseOver);
|
|
105
|
+
container.addEventListener('mouseleave', onMouseLeave);
|
|
106
|
+
|
|
107
|
+
window.addEventListener('resize', onScrollOrResize);
|
|
108
|
+
window.addEventListener('scroll', onScrollOrResize, true);
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
observer.disconnect();
|
|
112
|
+
container.removeEventListener('mouseover', onMouseOver);
|
|
113
|
+
container.removeEventListener('mouseleave', onMouseLeave);
|
|
114
|
+
window.removeEventListener('resize', onScrollOrResize);
|
|
115
|
+
window.removeEventListener('scroll', onScrollOrResize, true);
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<div
|
|
121
|
+
class="nav-link-group"
|
|
122
|
+
bind:this={container}
|
|
123
|
+
style:--nlg-active-bg={activeBackground}
|
|
124
|
+
style:--nlg-active-border={activeBorderColor}
|
|
125
|
+
style:--nlg-hover-bg={hoverBackground}
|
|
126
|
+
{...wrapProps}
|
|
127
|
+
>
|
|
128
|
+
<div
|
|
129
|
+
class="nav-link-group-indicator hover"
|
|
130
|
+
class:visible={hoverVisible}
|
|
131
|
+
bind:this={hoverIndicator}
|
|
132
|
+
></div>
|
|
133
|
+
<div class="nav-link-group-indicator active" class:visible bind:this={indicator}></div>
|
|
134
|
+
{@render children?.()}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<style>
|
|
138
|
+
.nav-link-group {
|
|
139
|
+
position: relative;
|
|
140
|
+
z-index: 0;
|
|
141
|
+
width: 100%;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.nav-link-group-indicator {
|
|
145
|
+
position: fixed;
|
|
146
|
+
top: 0;
|
|
147
|
+
left: 0;
|
|
148
|
+
width: 0;
|
|
149
|
+
height: 0;
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
opacity: 0;
|
|
152
|
+
z-index: 0;
|
|
153
|
+
transition:
|
|
154
|
+
top 0.2s ease,
|
|
155
|
+
left 0.2s ease,
|
|
156
|
+
width 0.2s ease,
|
|
157
|
+
height 0.2s ease,
|
|
158
|
+
opacity 0.2s ease;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.nav-link-group-indicator.visible {
|
|
162
|
+
opacity: 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.nav-link-group-indicator.active {
|
|
166
|
+
background-color: var(--nlg-active-bg);
|
|
167
|
+
border-left: 3px solid var(--nlg-active-border);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.nav-link-group-indicator.hover {
|
|
171
|
+
z-index: 0;
|
|
172
|
+
background-color: var(--nlg-hover-bg);
|
|
173
|
+
transition:
|
|
174
|
+
top 0.1s ease,
|
|
175
|
+
left 0.1s ease,
|
|
176
|
+
width 0.1s ease,
|
|
177
|
+
height 0.1s ease,
|
|
178
|
+
opacity 0.1s ease;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
:global(:root.dark) .nav-link-group-indicator.active {
|
|
182
|
+
background-color: color-mix(in srgb, var(--nlg-active-bg), transparent 80%);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
:global(.nav-link.group) {
|
|
186
|
+
position: relative;
|
|
187
|
+
z-index: 1;
|
|
188
|
+
}
|
|
189
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
children: Snippet;
|
|
5
|
+
activeBackground?: string;
|
|
6
|
+
activeBorderColor?: string;
|
|
7
|
+
hoverBackground?: string;
|
|
8
|
+
wrapProps?: HTMLAttributes<HTMLElement>;
|
|
9
|
+
}
|
|
10
|
+
declare const NavLinkGroup: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type NavLinkGroup = ReturnType<typeof NavLinkGroup>;
|
|
12
|
+
export default NavLinkGroup;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import IconCheckCircleFill from '@hyvor/icons/IconCheckCircleFill';
|
|
4
|
+
import IconXCircleFill from '@hyvor/icons/IconXCircleFill';
|
|
5
|
+
import IconDashCircleFill from '@hyvor/icons/IconDashCircleFill';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
status: 'positive' | 'negative' | 'neutral';
|
|
9
|
+
text?: string;
|
|
10
|
+
iconSize?: number;
|
|
11
|
+
fontSize?: string;
|
|
12
|
+
color?: string;
|
|
13
|
+
icon?: Snippet | false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
status,
|
|
18
|
+
text = undefined,
|
|
19
|
+
iconSize = 20,
|
|
20
|
+
fontSize = undefined,
|
|
21
|
+
color = undefined,
|
|
22
|
+
icon = undefined
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
let resolvedText = $derived(
|
|
26
|
+
text ?? (status === 'positive' ? 'Yes' : status === 'negative' ? 'No' : '')
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
let resolvedColor = $derived(
|
|
30
|
+
color ??
|
|
31
|
+
(status === 'positive'
|
|
32
|
+
? 'var(--green)'
|
|
33
|
+
: status === 'negative'
|
|
34
|
+
? 'var(--red)'
|
|
35
|
+
: 'var(--text-light)')
|
|
36
|
+
);
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<span class="ternary-status" style:color={resolvedColor} style:font-size={fontSize}>
|
|
40
|
+
{#if icon !== false}
|
|
41
|
+
<span class="icon">
|
|
42
|
+
{#if icon}
|
|
43
|
+
{@render icon()}
|
|
44
|
+
{:else if status === 'positive'}
|
|
45
|
+
<IconCheckCircleFill size={iconSize} />
|
|
46
|
+
{:else if status === 'negative'}
|
|
47
|
+
<IconXCircleFill size={iconSize} />
|
|
48
|
+
{:else}
|
|
49
|
+
<IconDashCircleFill size={iconSize} />
|
|
50
|
+
{/if}
|
|
51
|
+
</span>
|
|
52
|
+
{/if}
|
|
53
|
+
{#if resolvedText}
|
|
54
|
+
<span class="text">{resolvedText}</span>
|
|
55
|
+
{/if}
|
|
56
|
+
</span>
|
|
57
|
+
|
|
58
|
+
<style>
|
|
59
|
+
.ternary-status {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 8px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.icon {
|
|
66
|
+
display: inline-flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
status: 'positive' | 'negative' | 'neutral';
|
|
4
|
+
text?: string;
|
|
5
|
+
iconSize?: number;
|
|
6
|
+
fontSize?: string;
|
|
7
|
+
color?: string;
|
|
8
|
+
icon?: Snippet | false;
|
|
9
|
+
}
|
|
10
|
+
declare const TernaryStatus: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type TernaryStatus = ReturnType<typeof TernaryStatus>;
|
|
12
|
+
export default TernaryStatus;
|
|
@@ -30,12 +30,14 @@ export { default as Label } from './FormControl/Label.svelte';
|
|
|
30
30
|
export { default as Validation } from './FormControl/Validation.svelte';
|
|
31
31
|
export { uploadFile, type FileUploaderConfig, type UploadedFile as FileUploaderUploadedFile, type SelectedFile as FileUploaderSelectedFile } from './FileUploader/file-uploader.js';
|
|
32
32
|
export { default as IconButton } from './IconButton/IconButton.svelte';
|
|
33
|
+
export { default as Kbd } from './Kbd/Kbd.svelte';
|
|
33
34
|
export { default as Link } from './Link/Link.svelte';
|
|
34
35
|
export { default as Loader } from './Loader/Loader.svelte';
|
|
35
36
|
export { default as LoadButton } from './Loader/LoadButton.svelte';
|
|
36
37
|
export { default as Modal } from './Modal/Modal.svelte';
|
|
37
38
|
export { confirm } from './Modal/confirm.js';
|
|
38
39
|
export { default as NavLink } from './NavLink/NavLink.svelte';
|
|
40
|
+
export { default as NavLinkGroup } from './NavLink/NavLinkGroup.svelte';
|
|
39
41
|
export { default as Radio } from './Radio/Radio.svelte';
|
|
40
42
|
export { default as Slider } from './Slider/Slider.svelte';
|
|
41
43
|
export { default as SplitControl } from './SplitControl/SplitControl.svelte';
|
|
@@ -47,6 +49,7 @@ export { default as TableCell } from './Table/TableCell.svelte';
|
|
|
47
49
|
export { default as TabNav } from './TabNav/TabNav.svelte';
|
|
48
50
|
export { default as TabNavItem } from './TabNav/TabNavItem.svelte';
|
|
49
51
|
export { default as Tag } from './Tag/Tag.svelte';
|
|
52
|
+
export { default as TernaryStatus } from './TernaryStatus/TernaryStatus.svelte';
|
|
50
53
|
export { default as Textarea } from './Textarea/Textarea.svelte';
|
|
51
54
|
export { default as Text } from './Text/Text.svelte';
|
|
52
55
|
export { default as TextInput } from './TextInput/TextInput.svelte';
|
package/dist/components/index.js
CHANGED
|
@@ -30,12 +30,14 @@ export { default as Label } from './FormControl/Label.svelte';
|
|
|
30
30
|
export { default as Validation } from './FormControl/Validation.svelte';
|
|
31
31
|
export { uploadFile } from './FileUploader/file-uploader.js';
|
|
32
32
|
export { default as IconButton } from './IconButton/IconButton.svelte';
|
|
33
|
+
export { default as Kbd } from './Kbd/Kbd.svelte';
|
|
33
34
|
export { default as Link } from './Link/Link.svelte';
|
|
34
35
|
export { default as Loader } from './Loader/Loader.svelte';
|
|
35
36
|
export { default as LoadButton } from './Loader/LoadButton.svelte';
|
|
36
37
|
export { default as Modal } from './Modal/Modal.svelte';
|
|
37
38
|
export { confirm } from './Modal/confirm.js';
|
|
38
39
|
export { default as NavLink } from './NavLink/NavLink.svelte';
|
|
40
|
+
export { default as NavLinkGroup } from './NavLink/NavLinkGroup.svelte';
|
|
39
41
|
export { default as Radio } from './Radio/Radio.svelte';
|
|
40
42
|
export { default as Slider } from './Slider/Slider.svelte';
|
|
41
43
|
export { default as SplitControl } from './SplitControl/SplitControl.svelte';
|
|
@@ -47,6 +49,7 @@ export { default as TableCell } from './Table/TableCell.svelte';
|
|
|
47
49
|
export { default as TabNav } from './TabNav/TabNav.svelte';
|
|
48
50
|
export { default as TabNavItem } from './TabNav/TabNavItem.svelte';
|
|
49
51
|
export { default as Tag } from './Tag/Tag.svelte';
|
|
52
|
+
export { default as TernaryStatus } from './TernaryStatus/TernaryStatus.svelte';
|
|
50
53
|
export { default as Textarea } from './Textarea/Textarea.svelte';
|
|
51
54
|
export { default as Text } from './Text/Text.svelte';
|
|
52
55
|
export { default as TextInput } from './TextInput/TextInput.svelte';
|
package/dist/variables.scss
CHANGED
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
* In most cases, no need to extend these colors
|
|
23
23
|
*/
|
|
24
24
|
--green-light: #cadfca;
|
|
25
|
-
--green: #
|
|
25
|
+
--green: #4b874b;
|
|
26
26
|
--green-dark: #335c33;
|
|
27
27
|
|
|
28
28
|
--red-dark: #ab2525;
|
|
29
|
-
--red: #
|
|
29
|
+
--red: #c05959;
|
|
30
30
|
--red-light: #ffdfdf;
|
|
31
31
|
|
|
32
32
|
--blue-dark: #334b81;
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
--blue-light: #dae5ff;
|
|
35
35
|
|
|
36
36
|
--orange-dark: #8b5b04;
|
|
37
|
-
--orange: #
|
|
37
|
+
--orange: #a98526;
|
|
38
38
|
--orange-light: #efe3b4;
|
|
39
39
|
|
|
40
40
|
--gray-light: #ddd;
|