@hyvor/design 2.1.0 → 2.1.2
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/components/Base/Base.svelte +1 -1
- package/dist/components/Callout/Callout.svelte +72 -20
- package/dist/components/Callout/Callout.svelte.d.ts +6 -1
- package/dist/components/Modal/Modal.svelte +44 -18
- package/dist/components/Modal/Modal.svelte.d.ts +4 -0
- package/dist/components/Modal/portal.d.ts +4 -0
- package/dist/components/Modal/portal.js +25 -0
- package/package.json +1 -1
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import IconCheckCircle from '@hyvor/icons/IconCheckCircle';
|
|
4
|
+
import IconExclamationCircle from '@hyvor/icons/IconExclamationCircle';
|
|
5
|
+
import IconExclamationTriangle from '@hyvor/icons/IconExclamationTriangle';
|
|
6
|
+
import IconInfoCircle from '@hyvor/icons/IconInfoCircle';
|
|
7
|
+
|
|
8
|
+
const defaultIcons = {
|
|
9
|
+
soft: undefined,
|
|
10
|
+
info: IconInfoCircle,
|
|
11
|
+
success: IconCheckCircle,
|
|
12
|
+
warning: IconExclamationCircle,
|
|
13
|
+
danger: IconExclamationTriangle
|
|
14
|
+
};
|
|
3
15
|
|
|
4
16
|
const {
|
|
5
17
|
type = 'soft',
|
|
@@ -7,34 +19,59 @@
|
|
|
7
19
|
children = undefined,
|
|
8
20
|
text = undefined,
|
|
9
21
|
icon = undefined,
|
|
10
|
-
|
|
22
|
+
showIcon = true,
|
|
23
|
+
fg = undefined,
|
|
24
|
+
bg = undefined
|
|
11
25
|
}: {
|
|
12
26
|
type?: 'info' | 'success' | 'warning' | 'danger' | 'soft';
|
|
13
27
|
title?: string | Snippet;
|
|
14
28
|
icon?: Snippet;
|
|
29
|
+
/** Whether to show an icon. Defaults to the icon matching `type`, unless a custom `icon` snippet is given. */
|
|
30
|
+
showIcon?: boolean;
|
|
15
31
|
text?: string | Snippet;
|
|
16
32
|
children?: Snippet;
|
|
17
|
-
|
|
33
|
+
/** Custom text color. Overrides `type`. If `bg` is not set, a matching background is derived from it. */
|
|
34
|
+
fg?: string;
|
|
35
|
+
/** Custom background color. Overrides `type`. If `fg` is not set, a matching text color is derived from it. */
|
|
36
|
+
bg?: string;
|
|
18
37
|
} = $props();
|
|
38
|
+
|
|
39
|
+
const DefaultIcon = $derived(defaultIcons[type]);
|
|
40
|
+
|
|
41
|
+
const style = $derived.by(() => {
|
|
42
|
+
if (fg === undefined && bg === undefined) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const finalFg = fg ?? `color-mix(in srgb, ${bg} 55%, black)`;
|
|
47
|
+
const finalBg = bg ?? `color-mix(in srgb, ${fg} 15%, white)`;
|
|
48
|
+
|
|
49
|
+
return `color: ${finalFg}; background-color: ${finalBg};`;
|
|
50
|
+
});
|
|
19
51
|
</script>
|
|
20
52
|
|
|
21
|
-
|
|
53
|
+
{#snippet iconContent()}
|
|
54
|
+
{#if icon}
|
|
55
|
+
{@render icon()}
|
|
56
|
+
{:else}
|
|
57
|
+
<DefaultIcon />
|
|
58
|
+
{/if}
|
|
59
|
+
{/snippet}
|
|
60
|
+
|
|
61
|
+
<div class={'callout ' + type} {style}>
|
|
22
62
|
{#if typeof title === 'string'}
|
|
23
63
|
<div class="title-wrap">
|
|
24
|
-
{#if
|
|
25
|
-
<span
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
>
|
|
29
|
-
{@render icon()}
|
|
64
|
+
{#if showIcon}
|
|
65
|
+
<span class="title-icon">
|
|
66
|
+
{@render iconContent()}
|
|
30
67
|
</span>
|
|
31
68
|
{/if}
|
|
32
69
|
<div class="title">{title}</div>
|
|
33
70
|
</div>
|
|
34
71
|
{:else if title !== undefined}
|
|
35
72
|
<div class="title-wrap">
|
|
36
|
-
{#if
|
|
37
|
-
<span class="title-icon">{@render
|
|
73
|
+
{#if showIcon}
|
|
74
|
+
<span class="title-icon">{@render iconContent()}</span>
|
|
38
75
|
{/if}
|
|
39
76
|
|
|
40
77
|
<div class="title">{@render title?.()}</div>
|
|
@@ -42,8 +79,8 @@
|
|
|
42
79
|
{/if}
|
|
43
80
|
|
|
44
81
|
<div class="text-wrap">
|
|
45
|
-
{#if
|
|
46
|
-
<span class="icon">{@render
|
|
82
|
+
{#if showIcon && !title}
|
|
83
|
+
<span class="icon">{@render iconContent()}</span>
|
|
47
84
|
{/if}
|
|
48
85
|
|
|
49
86
|
<div class="text">
|
|
@@ -61,7 +98,7 @@
|
|
|
61
98
|
|
|
62
99
|
<style>
|
|
63
100
|
.callout {
|
|
64
|
-
padding:
|
|
101
|
+
padding: 10px 20px;
|
|
65
102
|
border-radius: var(--box-radius);
|
|
66
103
|
line-height: var(--line-height-content);
|
|
67
104
|
}
|
|
@@ -92,16 +129,16 @@
|
|
|
92
129
|
}
|
|
93
130
|
|
|
94
131
|
.title-wrap {
|
|
95
|
-
margin-bottom: 4px;
|
|
96
132
|
display: flex;
|
|
97
133
|
align-items: center;
|
|
98
134
|
font-weight: 600;
|
|
99
|
-
font-size: 18px;
|
|
100
135
|
}
|
|
101
136
|
|
|
102
137
|
.title-icon {
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
margin-right: 6px;
|
|
139
|
+
display: inline-flex;
|
|
140
|
+
height: var(--line-height-content);
|
|
141
|
+
align-items: center;
|
|
105
142
|
}
|
|
106
143
|
|
|
107
144
|
.text-wrap {
|
|
@@ -110,7 +147,22 @@
|
|
|
110
147
|
}
|
|
111
148
|
|
|
112
149
|
.icon {
|
|
113
|
-
margin-right:
|
|
114
|
-
font-size:
|
|
150
|
+
margin-right: 6px;
|
|
151
|
+
font-size: 16px;
|
|
152
|
+
display: inline-flex;
|
|
153
|
+
height: var(--line-height-content);
|
|
154
|
+
align-items: center;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.text :global(p) {
|
|
158
|
+
margin: 0.5em 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.text :global(p:first-child) {
|
|
162
|
+
margin-top: 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.text :global(p:last-child) {
|
|
166
|
+
margin-bottom: 0;
|
|
115
167
|
}
|
|
116
168
|
</style>
|
|
@@ -3,9 +3,14 @@ type $$ComponentProps = {
|
|
|
3
3
|
type?: 'info' | 'success' | 'warning' | 'danger' | 'soft';
|
|
4
4
|
title?: string | Snippet;
|
|
5
5
|
icon?: Snippet;
|
|
6
|
+
/** Whether to show an icon. Defaults to the icon matching `type`, unless a custom `icon` snippet is given. */
|
|
7
|
+
showIcon?: boolean;
|
|
6
8
|
text?: string | Snippet;
|
|
7
9
|
children?: Snippet;
|
|
8
|
-
|
|
10
|
+
/** Custom text color. Overrides `type`. If `bg` is not set, a matching background is derived from it. */
|
|
11
|
+
fg?: string;
|
|
12
|
+
/** Custom background color. Overrides `type`. If `fg` is not set, a matching text color is derived from it. */
|
|
13
|
+
bg?: string;
|
|
9
14
|
};
|
|
10
15
|
declare const Callout: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
16
|
type Callout = ReturnType<typeof Callout>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import ModalFooter from './ModalFooter.svelte';
|
|
3
3
|
import type { Footer } from './modal-types.js';
|
|
4
|
+
import { portal } from './portal.js';
|
|
4
5
|
import IconX from '@hyvor/icons/IconX';
|
|
5
6
|
import IconButton from './../IconButton/IconButton.svelte';
|
|
6
7
|
import { fade, scale } from 'svelte/transition';
|
|
@@ -12,6 +13,10 @@
|
|
|
12
13
|
show?: boolean;
|
|
13
14
|
title?: string | Snippet;
|
|
14
15
|
size?: 'small' | 'medium' | 'large';
|
|
16
|
+
width?: string;
|
|
17
|
+
height?: string;
|
|
18
|
+
bare?: boolean;
|
|
19
|
+
appendToBody?: boolean;
|
|
15
20
|
id?: string;
|
|
16
21
|
role?: 'dialog' | 'alertdialog';
|
|
17
22
|
closeOnOutsideClick?: boolean;
|
|
@@ -28,6 +33,10 @@
|
|
|
28
33
|
show = $bindable(false),
|
|
29
34
|
title,
|
|
30
35
|
size = 'medium',
|
|
36
|
+
width,
|
|
37
|
+
height,
|
|
38
|
+
bare = false,
|
|
39
|
+
appendToBody = false,
|
|
31
40
|
id = 'modal',
|
|
32
41
|
role = 'alertdialog',
|
|
33
42
|
closeOnOutsideClick = true,
|
|
@@ -44,6 +53,10 @@
|
|
|
44
53
|
const titleId = id + '-title';
|
|
45
54
|
const descId = id + '-desc';
|
|
46
55
|
|
|
56
|
+
const innerStyle = $derived(
|
|
57
|
+
[width ? `width: ${width}` : '', height ? `height: ${height}` : ''].filter(Boolean).join('; ')
|
|
58
|
+
);
|
|
59
|
+
|
|
47
60
|
let wrapEl: HTMLDivElement | undefined = $state();
|
|
48
61
|
let innerEl: HTMLDivElement | undefined = $state();
|
|
49
62
|
|
|
@@ -91,41 +104,46 @@
|
|
|
91
104
|
role="presentation"
|
|
92
105
|
class="wrap"
|
|
93
106
|
bind:this={wrapEl}
|
|
107
|
+
use:portal={appendToBody ? '#hds-base' : false}
|
|
94
108
|
transition:fade|global={{ duration: 100 }}
|
|
95
109
|
onclick={(e) => handleClose(e)}
|
|
96
110
|
>
|
|
97
111
|
<div
|
|
98
112
|
class="inner {size}"
|
|
113
|
+
class:bare
|
|
114
|
+
style={innerStyle || undefined}
|
|
99
115
|
transition:scale|global={{ duration: 100, start: 0.9, opacity: 0.9 }}
|
|
100
116
|
bind:this={innerEl}
|
|
101
117
|
{role}
|
|
102
118
|
aria-modal="true"
|
|
103
|
-
aria-labelledby={titleId}
|
|
119
|
+
aria-labelledby={bare ? undefined : titleId}
|
|
104
120
|
aria-describedby={descId}
|
|
105
121
|
>
|
|
106
|
-
|
|
107
|
-
<div class="
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
{#if !bare}
|
|
123
|
+
<div class="header">
|
|
124
|
+
<div class="title" id={titleId}>
|
|
125
|
+
{#if typeof title === 'string'}
|
|
126
|
+
<span>{title}</span>
|
|
127
|
+
{:else}
|
|
128
|
+
{@render title?.()}
|
|
129
|
+
{/if}
|
|
130
|
+
</div>
|
|
114
131
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
<div class="close-wrap">
|
|
133
|
+
{#if hasClose}
|
|
134
|
+
<IconButton variant="invisible" on:click={handleCancel} aria-label="Close modal">
|
|
135
|
+
<IconX size={25} />
|
|
136
|
+
</IconButton>
|
|
137
|
+
{/if}
|
|
138
|
+
</div>
|
|
121
139
|
</div>
|
|
122
|
-
|
|
140
|
+
{/if}
|
|
123
141
|
|
|
124
142
|
<div class="content" id={descId}>
|
|
125
143
|
{@render children?.()}
|
|
126
144
|
</div>
|
|
127
145
|
|
|
128
|
-
{#if footer}
|
|
146
|
+
{#if footer && !bare}
|
|
129
147
|
<div class="footer">
|
|
130
148
|
{#if typeof footer === 'object'}
|
|
131
149
|
<ModalFooter {footer} bind:show on:cancel on:confirm />
|
|
@@ -168,7 +186,7 @@
|
|
|
168
186
|
z-index: 10000000;
|
|
169
187
|
width: 100%;
|
|
170
188
|
height: 100%;
|
|
171
|
-
background: rgba(0, 0, 0, 0.
|
|
189
|
+
background: rgba(0, 0, 0, 0.15);
|
|
172
190
|
display: flex;
|
|
173
191
|
align-items: center;
|
|
174
192
|
justify-content: center;
|
|
@@ -209,6 +227,14 @@
|
|
|
209
227
|
.content {
|
|
210
228
|
padding: 20px 25px;
|
|
211
229
|
}
|
|
230
|
+
.inner.bare {
|
|
231
|
+
display: flex;
|
|
232
|
+
flex-direction: column;
|
|
233
|
+
}
|
|
234
|
+
.inner.bare .content {
|
|
235
|
+
padding: 0;
|
|
236
|
+
flex: 1;
|
|
237
|
+
}
|
|
212
238
|
|
|
213
239
|
.footer {
|
|
214
240
|
padding: 20px 25px;
|
|
@@ -4,6 +4,10 @@ interface Props {
|
|
|
4
4
|
show?: boolean;
|
|
5
5
|
title?: string | Snippet;
|
|
6
6
|
size?: 'small' | 'medium' | 'large';
|
|
7
|
+
width?: string;
|
|
8
|
+
height?: string;
|
|
9
|
+
bare?: boolean;
|
|
10
|
+
appendToBody?: boolean;
|
|
7
11
|
id?: string;
|
|
8
12
|
role?: 'dialog' | 'alertdialog';
|
|
9
13
|
closeOnOutsideClick?: boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function portal(node, target = false) {
|
|
2
|
+
const originalParent = node.parentNode;
|
|
3
|
+
const originalNextSibling = node.nextSibling;
|
|
4
|
+
function resolveTarget(t) {
|
|
5
|
+
if (typeof t !== 'string')
|
|
6
|
+
return t;
|
|
7
|
+
return document.querySelector(t) ?? document.body;
|
|
8
|
+
}
|
|
9
|
+
function place(t) {
|
|
10
|
+
if (t === false) {
|
|
11
|
+
if (originalParent && node.parentNode !== originalParent) {
|
|
12
|
+
originalParent.insertBefore(node, originalNextSibling);
|
|
13
|
+
}
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
resolveTarget(t).appendChild(node);
|
|
17
|
+
}
|
|
18
|
+
place(target);
|
|
19
|
+
return {
|
|
20
|
+
update: place,
|
|
21
|
+
destroy() {
|
|
22
|
+
node.remove();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|