poe-svelte-ui-lib 1.17.6 → 1.18.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.
- package/dist/Accordion/Accordion.svelte +25 -45
- package/dist/Button/Button.svelte +82 -77
- package/dist/ComponentExample.svelte +3 -8
- package/dist/CopiedChip.svelte +20 -0
- package/dist/CopiedChip.svelte.d.ts +7 -0
- package/dist/Divider/Divider.svelte +7 -3
- package/dist/Input/Input.svelte +63 -103
- package/dist/Map/Map.svelte +2 -9
- package/dist/Modal.svelte +22 -15
- package/dist/PeriodChart/PeriodChart.svelte +41 -30
- package/dist/ProgressBar/ProgressBar.svelte +30 -33
- package/dist/Select/Select.svelte +84 -65
- package/dist/Slider/Slider.svelte +134 -156
- package/dist/Switch/Switch.svelte +47 -48
- package/dist/Table/Table.svelte +42 -55
- package/dist/Tabs/Tabs.svelte +26 -41
- package/dist/TextField/TextField.svelte +11 -7
- package/dist/Tooltip.svelte +54 -0
- package/dist/Tooltip.svelte.d.ts +9 -0
- package/dist/Widget/Widget.svelte +112 -126
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<!-- $lib/Accordion/Accordion.svelte — заголовок больше не увеличивается при раскрытии,
|
|
2
|
+
тень на hover заменена фоном, радиус 14. Логика toggle не тронута. -->
|
|
1
3
|
<script lang="ts">
|
|
2
4
|
import { slide } from "svelte/transition"
|
|
3
5
|
import type { IAccordionProps } from "../types"
|
|
@@ -16,64 +18,42 @@
|
|
|
16
18
|
const toggle = () => (isOpen = !isOpen)
|
|
17
19
|
</script>
|
|
18
20
|
|
|
19
|
-
<div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge(`w-full
|
|
20
|
-
<div class="
|
|
21
|
-
<button
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
<div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge(`w-full`, wrapperClass)} transition:slide={{ duration: 200 }}>
|
|
22
|
+
<div class="overflow-hidden rounded-[14px] border border-(--hairline-color) bg-(--back-color)">
|
|
23
|
+
<button
|
|
24
|
+
class="flex w-full cursor-pointer items-center justify-between gap-3 px-4 py-3 text-left transition-colors duration-150 hover:bg-(--container-color)"
|
|
25
|
+
onclick={toggle}
|
|
26
|
+
>
|
|
27
|
+
<div class="flex min-w-0 flex-1 items-center gap-3">
|
|
28
|
+
{#if label?.icon}
|
|
29
|
+
<span class="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-(--muted-color) [&_svg]:h-full [&_svg]:w-full">
|
|
25
30
|
{#if typeof label?.icon === "string"}
|
|
26
31
|
{@html label.icon}
|
|
27
32
|
{:else}
|
|
28
33
|
{@const IconComponent = label?.icon}
|
|
29
34
|
<IconComponent />
|
|
30
35
|
{/if}
|
|
31
|
-
{/if}
|
|
32
|
-
</span>
|
|
33
|
-
|
|
34
|
-
<div
|
|
35
|
-
class="{twMerge(
|
|
36
|
-
'flex w-full justify-start ',
|
|
37
|
-
label.class?.split(' ').find((cls: string) => cls.startsWith('justify-')),
|
|
38
|
-
)} "
|
|
39
|
-
>
|
|
40
|
-
<span
|
|
41
|
-
class="{twMerge(
|
|
42
|
-
`m-0 origin-top-left cursor-pointer justify-end px-3 font-semibold transition duration-200 ${isOpen ? 'scale-110' : ''}`,
|
|
43
|
-
label.class,
|
|
44
|
-
)} text-lg"
|
|
45
|
-
>
|
|
46
|
-
{label?.name}
|
|
47
36
|
</span>
|
|
48
|
-
|
|
37
|
+
{/if}
|
|
38
|
+
<span class={twMerge(`min-w-0 truncate text-[14px] font-semibold`, label.class)}>{label?.name}</span>
|
|
49
39
|
</div>
|
|
50
40
|
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fill="none"
|
|
60
|
-
stroke="currentColor"
|
|
61
|
-
stroke-linecap="round"
|
|
62
|
-
stroke-linejoin="round"
|
|
63
|
-
stroke-width="2"
|
|
64
|
-
d="M18 12.5s-4.419 6-6 6s-6-6-6-6m12-7s-4.419 6-6 6s-6-6-6-6"
|
|
65
|
-
color="currentColor"
|
|
66
|
-
/>
|
|
67
|
-
</svg>
|
|
68
|
-
</div>
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
class="h-[14px] w-[14px] shrink-0 text-(--faint-color) transition-transform duration-200"
|
|
44
|
+
style="transform: rotate({isOpen ? 180 : 0}deg)"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
>
|
|
47
|
+
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 9l6 6 6-6" />
|
|
48
|
+
</svg>
|
|
69
49
|
</button>
|
|
70
50
|
|
|
71
51
|
{#if isOpen}
|
|
72
52
|
<div
|
|
73
|
-
class="grid w-full
|
|
74
|
-
transition:slide={{ duration:
|
|
75
|
-
style="grid-template-columns: repeat({size.width ||
|
|
76
|
-
grid-template-rows: repeat({size.height ||
|
|
53
|
+
class="grid w-full gap-3 border-t border-(--hairline-color) bg-(--container-color) p-4"
|
|
54
|
+
transition:slide={{ duration: 200 }}
|
|
55
|
+
style="grid-template-columns: repeat({size.width || 1}, minmax(0, 1fr));
|
|
56
|
+
grid-template-rows: repeat({size.height || 1}, {image ? 'minmax(6.5rem, auto)' : 'auto'});
|
|
77
57
|
{image ? `background-image: url(${image}); background-size: cover; background-position: center;` : ''}"
|
|
78
58
|
>
|
|
79
59
|
{@render children?.()}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
<!-- $lib/
|
|
1
|
+
<!-- $lib/Button/Button.svelte — шаг 3 плана.
|
|
2
|
+
API не изменён: id, wrapperClass, componentClass, disabled, content, keyBind, onClick.
|
|
3
|
+
Что изменилось внутри:
|
|
4
|
+
• роль определяется значением bg-* (намерение), а не фактом его наличия;
|
|
5
|
+
• убрано автоподчёркивание текста — кнопка больше не выглядит ссылкой;
|
|
6
|
+
• единый радиус 10px (иконка без подписи — круг), высота 36px, кегль 14/600;
|
|
7
|
+
• кольцо фокуса вместо размытой тени;
|
|
8
|
+
• disabled через токены, а не opacity: 0.5;
|
|
9
|
+
• иконка встала в поток (flex + gap) вместо absolute left-3. -->
|
|
2
10
|
<script lang="ts">
|
|
3
11
|
import { onMount } from "svelte"
|
|
4
12
|
import type { IButtonProps } from "../types"
|
|
5
|
-
import { fly } from "svelte/transition"
|
|
6
13
|
import { twMerge } from "tailwind-merge"
|
|
14
|
+
import Tooltip from "../Tooltip.svelte"
|
|
7
15
|
|
|
8
16
|
let {
|
|
9
17
|
id = crypto.randomUUID(),
|
|
@@ -21,31 +29,45 @@
|
|
|
21
29
|
|
|
22
30
|
let showInfo = $state(false)
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
/* ─── Роль кнопки по последнему классу bg-* ─────────────────────────
|
|
33
|
+
bg-blue / bg-green / bg-cyan / bg-purple / bg-orange / bg-yellow → primary (заливка + белый текст)
|
|
34
|
+
bg-red / bg-pink → destructive
|
|
35
|
+
bg-max / bg-gray / bg-*-soft → secondary (поверхность + контур)
|
|
36
|
+
bg-transparent / без bg-* → plain (текст акцентом) */
|
|
37
|
+
const SURFACE_BG = ["bg-max", "bg-gray"]
|
|
38
|
+
const DANGER_BG = ["bg-red", "bg-pink"]
|
|
39
|
+
|
|
40
|
+
const role = $derived.by(() => {
|
|
41
|
+
const bg = componentClass.split(/\s+/).filter((c) => c.startsWith("bg-")).at(-1) ?? ""
|
|
42
|
+
if (!bg || bg === "bg-transparent") return "plain"
|
|
43
|
+
if (SURFACE_BG.includes(bg) || bg.endsWith("-soft")) return "secondary"
|
|
44
|
+
if (DANGER_BG.includes(bg)) return "destructive"
|
|
45
|
+
return "primary"
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const isIconOnly = $derived(!!content?.icon && !content.name?.trim())
|
|
49
|
+
|
|
50
|
+
/* bg-(--bg-color) намеренно не входит в roleClass: он приклеивается к готовому
|
|
51
|
+
className вне twMerge (см. разметку ниже) — иначе twMerge видит его и явный
|
|
52
|
+
bg-* из componentClass (bg-red, bg-max…) как конфликтующие утилиты одной
|
|
53
|
+
группы и вырезает один из них, из-за чего заливка кнопки пропадает. */
|
|
54
|
+
const roleBg = $derived.by(() => {
|
|
55
|
+
if (disabled) return "bg-(--gray-color)"
|
|
56
|
+
if (role === "plain") return "bg-transparent"
|
|
57
|
+
return "bg-(--bg-color)"
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const roleClass = $derived.by(() => {
|
|
61
|
+
if (disabled) return "text-(--faint-color) border border-transparent cursor-not-allowed"
|
|
62
|
+
switch (role) {
|
|
63
|
+
case "primary":
|
|
64
|
+
case "destructive":
|
|
65
|
+
return "text-(--on-accent) border border-transparent shadow-(--elevation-1) hover:brightness-90 dark:hover:brightness-115"
|
|
66
|
+
case "secondary":
|
|
67
|
+
return "text-(--font-color) border border-(--border-color) hover:bg-(--container-color)"
|
|
68
|
+
default:
|
|
69
|
+
return "text-(--accent-color) border border-transparent hover:bg-(--accent-soft)"
|
|
70
|
+
}
|
|
49
71
|
})
|
|
50
72
|
|
|
51
73
|
const svgSize = $derived(() => {
|
|
@@ -60,30 +82,25 @@
|
|
|
60
82
|
return `${widthClass} ${heightClass}`
|
|
61
83
|
})
|
|
62
84
|
|
|
63
|
-
/* Обработчик клика */
|
|
64
85
|
const handleClick = (event: Event) => {
|
|
65
86
|
if (disabled || !onClick) return
|
|
66
87
|
event?.stopPropagation()
|
|
67
88
|
onClick()
|
|
68
89
|
}
|
|
69
90
|
|
|
70
|
-
/* Обработчик горячих клавиш */
|
|
71
91
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
72
92
|
if (disabled || !keyBind.key || !onClick) return
|
|
73
|
-
|
|
74
93
|
const isKeyMatch = event.key === keyBind.key
|
|
75
94
|
const isCtrlMatch = keyBind.ctrlKey === undefined || event.ctrlKey === keyBind.ctrlKey
|
|
76
95
|
const isShiftMatch = keyBind.shiftKey === undefined || event.shiftKey === keyBind.shiftKey
|
|
77
96
|
const isAltMatch = keyBind.altKey === undefined || event.altKey === keyBind.altKey
|
|
78
97
|
const isMetaMatch = keyBind.metaKey === undefined || event.metaKey === keyBind.metaKey
|
|
79
|
-
|
|
80
98
|
if (isKeyMatch && isCtrlMatch && isShiftMatch && isAltMatch && isMetaMatch) {
|
|
81
99
|
event.preventDefault()
|
|
82
100
|
onClick()
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
|
|
86
|
-
/* Подписка на события клавиатуры */
|
|
87
104
|
onMount(() => {
|
|
88
105
|
if (keyBind) window.addEventListener("keydown", handleKeyDown)
|
|
89
106
|
return () => {
|
|
@@ -92,20 +109,21 @@
|
|
|
92
109
|
})
|
|
93
110
|
</script>
|
|
94
111
|
|
|
95
|
-
<div class={twMerge(`relative flex w-full flex-col items-center
|
|
112
|
+
<div class={twMerge(`relative flex w-full flex-col items-center`, wrapperClass)}>
|
|
96
113
|
<button
|
|
97
|
-
|
|
98
|
-
class=
|
|
99
|
-
`relative inline-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
{id}
|
|
115
|
+
class={`${twMerge(
|
|
116
|
+
`relative inline-flex w-full items-center justify-center gap-2 leading-none font-semibold text-[14px] select-none
|
|
117
|
+
transition-[background-color,filter,box-shadow] duration-150
|
|
118
|
+
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--accent-color)
|
|
119
|
+
${isIconOnly ? "aspect-square rounded-full p-0" : "min-h-9 rounded-[10px] px-4 py-2"}
|
|
120
|
+
${disabled ? "" : "cursor-pointer active:scale-[0.97]"}`,
|
|
121
|
+
roleClass,
|
|
103
122
|
componentClass,
|
|
104
|
-
)}
|
|
105
|
-
style={!content.name?.trim() && content.name && content.icon ? `padding-inline: ${content.name.length / 2}rem` : ""}
|
|
123
|
+
)} ${roleBg}`}
|
|
106
124
|
onclick={(event) => handleClick(event)}
|
|
107
125
|
{disabled}
|
|
108
|
-
aria-label={content.name}
|
|
126
|
+
aria-label={content.name || undefined}
|
|
109
127
|
onmouseenter={() => {
|
|
110
128
|
if (content.info?.text) showInfo = true
|
|
111
129
|
}}
|
|
@@ -113,43 +131,30 @@
|
|
|
113
131
|
if (content.info?.text) showInfo = false
|
|
114
132
|
}}
|
|
115
133
|
>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
{#if content?.icon}
|
|
135
|
+
<span
|
|
136
|
+
class={`flex shrink-0 items-center justify-center overflow-visible
|
|
137
|
+
${content.name?.trim() ? "h-[18px] w-[18px]" : `${svgSize()}`}
|
|
138
|
+
[&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full`}
|
|
139
|
+
>
|
|
140
|
+
{#if typeof content?.icon === "string"}
|
|
141
|
+
{@html content.icon}
|
|
142
|
+
{:else}
|
|
143
|
+
{@const IconComponent = content?.icon}
|
|
144
|
+
<IconComponent />
|
|
145
|
+
{/if}
|
|
146
|
+
</span>
|
|
147
|
+
{/if}
|
|
130
148
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
</div>
|
|
138
|
-
{/if}
|
|
139
|
-
</div>
|
|
149
|
+
{#if content.name?.trim()}
|
|
150
|
+
<span class="truncate">{content.name}</span>
|
|
151
|
+
{#if keyBind.key && keyBind.showHint}
|
|
152
|
+
<span class="ml-1 text-[11px] font-medium tabular-nums opacity-60">
|
|
153
|
+
{keyBind.ctrlKey ? "Ctrl+" : ""}{keyBind.shiftKey ? "Shift+" : ""}{keyBind.altKey ? "Alt+" : ""}{keyBind.key}
|
|
154
|
+
</span>
|
|
140
155
|
{/if}
|
|
141
|
-
|
|
156
|
+
{/if}
|
|
142
157
|
</button>
|
|
143
158
|
|
|
144
|
-
{
|
|
145
|
-
{@const config = tooltipConfig[content.info?.side]}
|
|
146
|
-
<div
|
|
147
|
-
transition:fly={{ [config.off.split(":")[0]]: parseInt(config.off.split(":")[1]), duration: 300 }}
|
|
148
|
-
class={`absolute z-50 w-max max-w-xs rounded-md bg-(--container-color) px-3 py-1 text-sm shadow-lg ${config.pos}`}
|
|
149
|
-
style={`transform: ${config.tr};`}
|
|
150
|
-
>
|
|
151
|
-
{content.info?.text}
|
|
152
|
-
<div class={`absolute ${config.arr} h-2 w-2 transform bg-(--container-color)`}></div>
|
|
153
|
-
</div>
|
|
154
|
-
{/if}
|
|
159
|
+
<Tooltip show={showInfo} text={content.info?.text ?? ""} side={content.info?.side ?? "top"} />
|
|
155
160
|
</div>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Select, Switch, T, type IOption } from "./"
|
|
3
3
|
import type { Snippet } from "svelte"
|
|
4
4
|
import { fade } from "svelte/transition"
|
|
5
|
+
import CopiedChip from "./CopiedChip.svelte"
|
|
5
6
|
|
|
6
7
|
let {
|
|
7
8
|
component,
|
|
@@ -65,14 +66,7 @@
|
|
|
65
66
|
aria-label="Копировать текст"
|
|
66
67
|
>
|
|
67
68
|
<div class="size-6 text-sm [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full">
|
|
68
|
-
{#if isCopied}
|
|
69
|
-
<div
|
|
70
|
-
class="right-1..5 absolute top-1/2 -translate-y-1/2 transform rounded-md bg-(--green-color) px-1.5 py-1 shadow-lg"
|
|
71
|
-
transition:fade={{ duration: 200 }}
|
|
72
|
-
>
|
|
73
|
-
✓
|
|
74
|
-
</div>
|
|
75
|
-
{:else}
|
|
69
|
+
{#if !isCopied}
|
|
76
70
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
|
77
71
|
<g fill="none" stroke="currentColor" stroke-width="1.5">
|
|
78
72
|
<path
|
|
@@ -82,6 +76,7 @@
|
|
|
82
76
|
</g>
|
|
83
77
|
</svg>
|
|
84
78
|
{/if}
|
|
79
|
+
<CopiedChip show={isCopied} class="right-1.5 top-1/2 -translate-y-1/2" />
|
|
85
80
|
</div>
|
|
86
81
|
</button>
|
|
87
82
|
<pre class="overflow-x-auto">{codeText}</pre>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- $lib/CopiedChip.svelte — единая всплывающая метка "Скопировано" после клика
|
|
2
|
+
на кнопку копирования. Раньше в каждом месте (Input, Table, Map,
|
|
3
|
+
ComponentExample) была своя версия: где-то текст, где-то просто "✓",
|
|
4
|
+
разные цвета (--green-color вместо --green-soft) и радиусы, а в
|
|
5
|
+
ComponentExample — опечатка в классе позиционирования (right-1..5,
|
|
6
|
+
невалидный Tailwind-класс, физически ничего не позиционировал). -->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { fade } from "svelte/transition"
|
|
9
|
+
|
|
10
|
+
let { show = false, class: className = "" }: { show?: boolean; class?: string } = $props()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
{#if show}
|
|
14
|
+
<span
|
|
15
|
+
class="{className} absolute flex items-center gap-1 rounded-lg bg-(--green-soft) px-2 py-1 text-[11px] font-bold whitespace-nowrap text-(--green-color)"
|
|
16
|
+
transition:fade={{ duration: 150 }}
|
|
17
|
+
>
|
|
18
|
+
Скопировано
|
|
19
|
+
</span>
|
|
20
|
+
{/if}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
<!-- $lib/
|
|
1
|
+
<!-- $lib/Divider/Divider.svelte — волосяная линия вместо скруглённой полосы 2px. -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { twMerge } from "tailwind-merge"
|
|
4
4
|
import type { IDividerProps } from "../types"
|
|
5
5
|
|
|
6
|
-
let { id = crypto.randomUUID(), wrapperClass = "", type = "line", width =
|
|
6
|
+
let { id = crypto.randomUUID(), wrapperClass = "", type = "line", width = 1, color = "", anchor = "center" }: IDividerProps = $props()
|
|
7
7
|
|
|
8
8
|
const anchorClass = { top: "justify-start", center: "justify-center", bottom: "justify-end" } as const
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge(`flex h-full w-full flex-col ${anchorClass[anchor]}`, wrapperClass)}>
|
|
12
12
|
{#if type === "line"}
|
|
13
|
-
|
|
13
|
+
<!-- Без bg-* класса берём --hairline-color; явный bg-* по-прежнему уважается через --bg-color -->
|
|
14
|
+
<div
|
|
15
|
+
class={twMerge("w-full", color)}
|
|
16
|
+
style="height: {width}px; background: {color ? 'var(--bg-color)' : 'var(--hairline-color)'};"
|
|
17
|
+
></div>
|
|
14
18
|
{/if}
|
|
15
19
|
</div>
|