@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.
@@ -14,7 +14,7 @@
14
14
  let { dark = false, children }: Props = $props();
15
15
  </script>
16
16
 
17
- <div id="base">
17
+ <div id="hds-base">
18
18
  {@render children?.()}
19
19
  </div>
20
20
 
@@ -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
- ...rest
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
- [key: string]: any;
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
- <div class={'callout ' + type} {...rest}>
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 icon}
25
- <span
26
- class="title-icon
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 icon}
37
- <span class="title-icon">{@render icon()}</span>
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 icon && !title}
46
- <span class="icon">{@render icon()}</span>
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: 15px 25px;
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
- vertical-align: middle;
104
- margin-right: 8px;
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: 8px;
114
- font-size: 18px;
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
- [key: string]: any;
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
- <div class="header">
107
- <div class="title" id={titleId}>
108
- {#if typeof title === 'string'}
109
- <span>{title}</span>
110
- {:else}
111
- {@render title?.()}
112
- {/if}
113
- </div>
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
- <div class="close-wrap">
116
- {#if hasClose}
117
- <IconButton variant="invisible" on:click={handleCancel} aria-label="Close modal">
118
- <IconX size={25} />
119
- </IconButton>
120
- {/if}
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
- </div>
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.5);
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,4 @@
1
+ export declare function portal(node: HTMLElement, target?: string | HTMLElement | false): {
2
+ update: (t: string | HTMLElement | false) => void;
3
+ destroy(): void;
4
+ };
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyvor/design",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "repository": {