milk-lib 0.0.17 → 0.0.18

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 (44) hide show
  1. package/README.md +4 -3
  2. package/dist/components/Icon/IconLoading.svelte +62 -0
  3. package/dist/components/Icon/IconLoading.svelte.d.ts +6 -0
  4. package/dist/components/Icon/index.d.ts +1 -0
  5. package/dist/components/Icon/index.js +1 -0
  6. package/dist/components/Modal/DialogContent.svelte +13 -0
  7. package/dist/components/Modal/DialogContent.svelte.d.ts +4 -0
  8. package/dist/components/Modal/DialogDescription.svelte +13 -0
  9. package/dist/components/Modal/DialogDescription.svelte.d.ts +4 -0
  10. package/dist/components/Modal/DialogFooter.svelte +16 -0
  11. package/dist/components/Modal/DialogFooter.svelte.d.ts +4 -0
  12. package/dist/components/Modal/DialogHeader.svelte +13 -0
  13. package/dist/components/Modal/DialogHeader.svelte.d.ts +4 -0
  14. package/dist/components/Modal/DialogTitle.svelte +15 -0
  15. package/dist/components/Modal/DialogTitle.svelte.d.ts +4 -0
  16. package/dist/components/Modal/Modal.svelte +122 -0
  17. package/dist/components/Modal/Modal.svelte.d.ts +4 -0
  18. package/dist/components/Modal/Modal.types.d.ts +30 -0
  19. package/dist/components/Modal/Modal.types.js +1 -0
  20. package/dist/components/Modal/ModalDialog.svelte +33 -0
  21. package/dist/components/Modal/ModalDialog.svelte.d.ts +4 -0
  22. package/dist/components/Modal/index.d.ts +7 -0
  23. package/dist/components/Modal/index.js +7 -0
  24. package/dist/components/Portal/Portal.svelte +37 -0
  25. package/dist/components/Portal/Portal.svelte.d.ts +7 -0
  26. package/dist/components/Portal/index.d.ts +1 -0
  27. package/dist/components/Portal/index.js +1 -0
  28. package/dist/components/Select/Select.svelte +258 -0
  29. package/dist/components/Select/Select.svelte.d.ts +4 -0
  30. package/dist/components/Select/Select.types.d.ts +19 -0
  31. package/dist/components/Select/Select.types.js +1 -0
  32. package/dist/components/Select/index.d.ts +2 -0
  33. package/dist/components/Select/index.js +2 -0
  34. package/dist/components/TextInput/TextInput.svelte +1 -0
  35. package/dist/components/TextInputBlock/TextInputBlock.svelte +21 -17
  36. package/dist/components/ThemeProvider/ThemeDefault.js +3 -0
  37. package/dist/components/ThemeProvider/ThemeProvider.svelte +11 -3
  38. package/dist/components/ThemeProvider/ThemeProvider.types.d.ts +3 -0
  39. package/dist/components/index.d.ts +4 -0
  40. package/dist/components/index.js +4 -0
  41. package/dist/styles/index.scss +6 -1
  42. package/dist/styles/root.scss +117 -0
  43. package/dist/styles/typography.scss +6 -1
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -10,12 +10,13 @@ Sveltekit library, powered by [`sv`](https://npmjs.com/package/sv).
10
10
  + TextInput Box
11
11
  + Command
12
12
 
13
+ + Icons
14
+ + Select
15
+ + Modal Dialog
16
+
13
17
  - Alert Dialog
14
18
  - Accordion
15
19
  - Sheet
16
20
  - Menu
17
21
  - Card
18
22
  - Checkbox
19
- - Select
20
- - Dialogs
21
- - Icons
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ let { size=16 }: { size?: number } = $props();
3
+ </script>
4
+
5
+ <div class="loading-indicator" style={`font-size: ${size}px;`}>
6
+ <span class="loading-indicator-item item-1"></span>
7
+ <span class="loading-indicator-item item-2"></span>
8
+ <span class="loading-indicator-item item-3"></span>
9
+ </div>
10
+
11
+ <style>.loading-indicator {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ width: 1em;
15
+ height: 1em;
16
+ padding: 0.375em 0;
17
+ gap: 0.125em;
18
+ }
19
+ .loading-indicator .loading-indicator-item {
20
+ width: 0.25em;
21
+ height: 0.25em;
22
+ background: var(--icon-base-placeholder);
23
+ border-radius: 50%;
24
+ transform-origin: center;
25
+ will-change: transform, opacity;
26
+ }
27
+ .loading-indicator .loading-indicator-item.item-1 {
28
+ animation: blink-1 1s ease-in-out 0ms infinite;
29
+ }
30
+ .loading-indicator .loading-indicator-item.item-2 {
31
+ animation: blink-1 1s ease-in-out 160ms infinite;
32
+ }
33
+ .loading-indicator .loading-indicator-item.item-3 {
34
+ animation: blink-1 1s ease-in-out 320ms infinite;
35
+ }
36
+
37
+ @keyframes blink-1 {
38
+ 0% {
39
+ opacity: 0.5;
40
+ transform: scale(0.5);
41
+ }
42
+ 20% {
43
+ opacity: 1;
44
+ transform: scale(1);
45
+ }
46
+ 40% {
47
+ opacity: 1;
48
+ transform: scale(1);
49
+ }
50
+ 60% {
51
+ opacity: 0.5;
52
+ transform: scale(0.5);
53
+ }
54
+ 80% {
55
+ opacity: 0;
56
+ transform: scale(0);
57
+ }
58
+ 100% {
59
+ opacity: 0;
60
+ transform: scale(0);
61
+ }
62
+ }</style>
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ size?: number;
3
+ };
4
+ declare const IconLoading: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type IconLoading = ReturnType<typeof IconLoading>;
6
+ export default IconLoading;
@@ -0,0 +1 @@
1
+ export { default as IconLoading } from './IconLoading.svelte';
@@ -0,0 +1 @@
1
+ export { default as IconLoading } from './IconLoading.svelte';
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import type { IDialogContentProps } from "./Modal.types";
3
+
4
+ let { children }: IDialogContentProps = $props();
5
+ </script>
6
+
7
+ <div class="DialogContent">
8
+ {@render children()}
9
+ </div>
10
+
11
+ <style>.DialogContent {
12
+ padding: 0.5rem 1.5rem;
13
+ }</style>
@@ -0,0 +1,4 @@
1
+ import type { IDialogContentProps } from "./Modal.types";
2
+ declare const DialogContent: import("svelte").Component<IDialogContentProps, {}, "">;
3
+ type DialogContent = ReturnType<typeof DialogContent>;
4
+ export default DialogContent;
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import type { IDialogDescriptionProps } from "./Modal.types";
3
+ let { children }: IDialogDescriptionProps = $props();
4
+ </script>
5
+
6
+ <div class="DialogDescription">
7
+ {@render children()}
8
+ </div>
9
+
10
+ <style>.DialogDescription {
11
+ color: var(--text-base-muted);
12
+ font-size: var(--font-size-md);
13
+ }</style>
@@ -0,0 +1,4 @@
1
+ import type { IDialogDescriptionProps } from "./Modal.types";
2
+ declare const DialogDescription: import("svelte").Component<IDialogDescriptionProps, {}, "">;
3
+ type DialogDescription = ReturnType<typeof DialogDescription>;
4
+ export default DialogDescription;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import type { IDialogFooterProps } from "./Modal.types";
3
+
4
+ let { children }: IDialogFooterProps = $props();
5
+ </script>
6
+
7
+ <div class="DialogFooter">
8
+ {@render children()}
9
+ </div>
10
+
11
+ <style>.DialogFooter {
12
+ padding: 0.5rem 1.5rem;
13
+ display: flex;
14
+ justify-content: flex-end;
15
+ gap: 0.5rem;
16
+ }</style>
@@ -0,0 +1,4 @@
1
+ import type { IDialogFooterProps } from "./Modal.types";
2
+ declare const DialogFooter: import("svelte").Component<IDialogFooterProps, {}, "">;
3
+ type DialogFooter = ReturnType<typeof DialogFooter>;
4
+ export default DialogFooter;
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import type { IDialogHeaderProps } from "./Modal.types";
3
+
4
+ let { children }: IDialogHeaderProps = $props();
5
+ </script>
6
+
7
+ <div class="DialogHeader">
8
+ {@render children()}
9
+ </div>
10
+
11
+ <style>.DialogHeader {
12
+ padding: 0.25rem 1.5rem;
13
+ }</style>
@@ -0,0 +1,4 @@
1
+ import type { IDialogHeaderProps } from "./Modal.types";
2
+ declare const DialogHeader: import("svelte").Component<IDialogHeaderProps, {}, "">;
3
+ type DialogHeader = ReturnType<typeof DialogHeader>;
4
+ export default DialogHeader;
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import type { IDialogTitleProps } from './Modal.types';
3
+ let { children } : IDialogTitleProps = $props();
4
+ </script>
5
+
6
+ <h2 class="DialogTitle">
7
+ {@render children()}
8
+ </h2>
9
+
10
+ <style>.DialogTitle {
11
+ font-size: var(--font-size-h2);
12
+ margin: 0 0 0.5em;
13
+ font-weight: 500;
14
+ margin: 0 0 0.25rem;
15
+ }</style>
@@ -0,0 +1,4 @@
1
+ import type { IDialogTitleProps } from './Modal.types';
2
+ declare const DialogTitle: import("svelte").Component<IDialogTitleProps, {}, "">;
3
+ type DialogTitle = ReturnType<typeof DialogTitle>;
4
+ export default DialogTitle;
@@ -0,0 +1,122 @@
1
+ <!--
2
+
3
+ Tech wrapper for Modal Window with Backdrop, Close Button and handlers
4
+
5
+ -->
6
+
7
+ <script lang="ts">
8
+
9
+ import Portal from '../Portal/Portal.svelte';
10
+ import { CloseFillSystem } from 'svelte-remix';
11
+ import type { IModalProps } from './Modal.types';
12
+
13
+ let { isVisible, hideModal, showCloseButton, hideOnEscape, blackout, closeOnBackdrop, children }: IModalProps= $props();
14
+
15
+ const handleKeyDown = (e: KeyboardEvent) => {
16
+ if (e.key === "Escape" && hideOnEscape && isVisible) {
17
+ hideModal?.();
18
+ }
19
+ };
20
+
21
+ $effect(() => {
22
+ if (document) {
23
+ if (isVisible) {
24
+ document.addEventListener("keydown", handleKeyDown);
25
+ document.body.style.overflow = 'hidden';
26
+ } else {
27
+ document.removeEventListener("keydown", handleKeyDown);
28
+ document.body.style.overflow = 'auto';
29
+ }
30
+ }
31
+
32
+ return () => {
33
+ document.removeEventListener("keydown", handleKeyDown);
34
+ document.body.style.overflow = 'auto';
35
+ }
36
+ })
37
+
38
+ const backdropClick = (e: MouseEvent) => {
39
+ if (closeOnBackdrop && (e.target as HTMLElement).classList.contains("Modal-content")) {
40
+ hideModal?.();
41
+ }
42
+ }
43
+
44
+ </script>
45
+
46
+
47
+ <Portal>
48
+ <div class="Modal">
49
+ <div class={`Modal-backdrop ${blackout ? "Modal-backdrop-blackout" : ""}`} ></div>
50
+
51
+ <div
52
+ class="Modal-content"
53
+ onmouseup={backdropClick}
54
+ role="dialog"
55
+ tabindex="-1"
56
+ aria-label="Close dialog"
57
+ >
58
+ {#if showCloseButton}
59
+ <div class="Modal-close-button-wrapper" role="button" tabindex="-1" onmouseup={() => hideModal?.()}>
60
+ <button class="Modal-close-button">
61
+ <CloseFillSystem/>
62
+ </button>
63
+ </div>
64
+ {/if}
65
+
66
+ {@render children()}
67
+
68
+ </div>
69
+ </div>
70
+ </Portal>
71
+
72
+
73
+ <style>.Modal-backdrop {
74
+ position: fixed;
75
+ display: flex;
76
+ z-index: var(--zindex-modal-backdrop);
77
+ top: 0;
78
+ left: 0;
79
+ right: 0;
80
+ bottom: 0;
81
+ background-color: var(--bg-backdrop);
82
+ }
83
+ .Modal-backdrop.Modal-backdrop-blackout {
84
+ background-color: var(--bg-backdrop-blackout);
85
+ }
86
+
87
+ .Modal-close-button-wrapper {
88
+ position: fixed;
89
+ right: 0.25rem;
90
+ top: 0.25rem;
91
+ }
92
+
93
+ .Modal-close-button {
94
+ background-color: transparent;
95
+ position: relative;
96
+ z-index: var(--zindex-modal);
97
+ font-size: 1rem;
98
+ height: 2em;
99
+ width: 2em;
100
+ cursor: pointer;
101
+ display: flex;
102
+ color: white;
103
+ border-radius: var(--border-radius-button);
104
+ transition: var(--transition-base);
105
+ padding: 0.25rem;
106
+ border: none;
107
+ }
108
+ .Modal-close-button:hover {
109
+ background: rgba(255, 255, 255, 0.1);
110
+ }
111
+
112
+ .Modal-content {
113
+ position: fixed;
114
+ z-index: var(--zindex-modal);
115
+ top: 0;
116
+ height: 100vh;
117
+ width: 100%;
118
+ overflow-y: scroll;
119
+ background: transparent;
120
+ display: flex;
121
+ padding: 2rem 0;
122
+ }</style>
@@ -0,0 +1,4 @@
1
+ import type { IModalProps } from './Modal.types';
2
+ declare const Modal: import("svelte").Component<IModalProps, {}, "">;
3
+ type Modal = ReturnType<typeof Modal>;
4
+ export default Modal;
@@ -0,0 +1,30 @@
1
+ import { type Snippet } from "svelte";
2
+ export interface IModalProps {
3
+ isVisible?: boolean;
4
+ hideModal?: () => unknown;
5
+ showCloseButton?: boolean;
6
+ hideOnEscape?: boolean;
7
+ blackout?: boolean;
8
+ closeOnBackdrop?: boolean;
9
+ children: Snippet;
10
+ }
11
+ export interface IModalDialogProps {
12
+ rounded?: boolean;
13
+ size?: 'sm' | 'md' | 'lg';
14
+ children: Snippet;
15
+ }
16
+ export interface IDialogHeaderProps {
17
+ children: Snippet;
18
+ }
19
+ export interface IDialogFooterProps {
20
+ children: Snippet;
21
+ }
22
+ export interface IDialogTitleProps {
23
+ children: Snippet;
24
+ }
25
+ export interface IDialogDescriptionProps {
26
+ children: Snippet;
27
+ }
28
+ export interface IDialogContentProps {
29
+ children: Snippet;
30
+ }
@@ -0,0 +1 @@
1
+ import {} from "svelte";
@@ -0,0 +1,33 @@
1
+ <!--
2
+
3
+ White panel for Modal Window
4
+
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import { type IModalDialogProps } from './Modal.types';
9
+ let { children, rounded, size='md' } : IModalDialogProps = $props();
10
+ </script>
11
+
12
+ <div class={`ModalDialog ModalDialog-${size}`} style={`border-radius: ${rounded ? 'var(--border-radius-window)' : '0'}`}>
13
+ {@render children()}
14
+ </div>
15
+
16
+ <style>.ModalDialog {
17
+ padding: 1rem 0;
18
+ background-color: white;
19
+ width: 440px;
20
+ max-width: 100%;
21
+ position: relative;
22
+ margin: auto;
23
+ z-index: var(--zindex-modal);
24
+ box-shadow: 0 0 200px rgba(0, 0, 0, 0.1);
25
+ display: flex;
26
+ flex-direction: column;
27
+ }
28
+ .ModalDialog.ModalDialog-sm {
29
+ width: 380px;
30
+ }
31
+ .ModalDialog.ModalDialog-lg {
32
+ width: 640px;
33
+ }</style>
@@ -0,0 +1,4 @@
1
+ import { type IModalDialogProps } from './Modal.types';
2
+ declare const ModalDialog: import("svelte").Component<IModalDialogProps, {}, "">;
3
+ type ModalDialog = ReturnType<typeof ModalDialog>;
4
+ export default ModalDialog;
@@ -0,0 +1,7 @@
1
+ export { default as Modal } from './Modal.svelte';
2
+ export { default as ModalDialog } from './ModalDialog.svelte';
3
+ export { default as DialogTitle } from './DialogTitle.svelte';
4
+ export { default as DialogHeader } from './DialogHeader.svelte';
5
+ export { default as DialogDescription } from './DialogDescription.svelte';
6
+ export { default as DialogContent } from './DialogContent.svelte';
7
+ export { default as DialogFooter } from './DialogFooter.svelte';
@@ -0,0 +1,7 @@
1
+ export { default as Modal } from './Modal.svelte';
2
+ export { default as ModalDialog } from './ModalDialog.svelte';
3
+ export { default as DialogTitle } from './DialogTitle.svelte';
4
+ export { default as DialogHeader } from './DialogHeader.svelte';
5
+ export { default as DialogDescription } from './DialogDescription.svelte';
6
+ export { default as DialogContent } from './DialogContent.svelte';
7
+ export { default as DialogFooter } from './DialogFooter.svelte';
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy, type Snippet } from 'svelte';
3
+ let ref: Node;
4
+ let portal: Element | null = null;
5
+
6
+ interface IPortalProps {
7
+ children: Snippet;
8
+ }
9
+
10
+ let { children } : IPortalProps = $props();
11
+
12
+ onMount(() => {
13
+ portal = document.createElement('div');
14
+ portal.className = 'portal';
15
+ document.body.appendChild(portal);
16
+ portal.appendChild(ref);
17
+ });
18
+
19
+ onDestroy(() => {
20
+ if (portal) {
21
+ document.body.removeChild(portal);
22
+ }
23
+ });
24
+ </script>
25
+
26
+
27
+ <div class="portal-clone">
28
+ <div bind:this={ref}>
29
+ {@render children?.()}
30
+ </div>
31
+ </div>
32
+
33
+ <style>
34
+ .portal-clone {
35
+ display: none;
36
+ }
37
+ </style>
@@ -0,0 +1,7 @@
1
+ import { type Snippet } from 'svelte';
2
+ interface IPortalProps {
3
+ children: Snippet;
4
+ }
5
+ declare const Portal: import("svelte").Component<IPortalProps, {}, "">;
6
+ type Portal = ReturnType<typeof Portal>;
7
+ export default Portal;
@@ -0,0 +1 @@
1
+ export * as Portal from './Portal.svelte';
@@ -0,0 +1 @@
1
+ export * as Portal from './Portal.svelte';
@@ -0,0 +1,258 @@
1
+ <!--
2
+ Select
3
+ -->
4
+
5
+ <script lang="ts">
6
+
7
+ import type {ISelectGroupData, ISelectItem, ISelectProps} from './Select.types';
8
+ import { ArrowDownSLineArrows } from 'svelte-remix';
9
+
10
+ import {
11
+ CommandRoot,
12
+ CommandList,
13
+ CommandGroup,
14
+ CommandItem,
15
+ CommandInput,
16
+ Menu,
17
+ TextInputBlock,
18
+ type TextInputInstance,
19
+ IconLoading
20
+ } from '../..';
21
+
22
+ let {
23
+ options,
24
+ value = $bindable(),
25
+ placeholder,
26
+ disabled,
27
+ fullWidthMenu,
28
+ minWidthMenu,
29
+ menuGap,
30
+ menuMaxHeight,
31
+ searchable=true
32
+ }: ISelectProps = $props();
33
+
34
+ const menuId = `select-menu-${crypto.randomUUID()}`;
35
+
36
+ let currentTitle = $derived<string>(value?.title || '');
37
+ // Нужен для того, чтоб позиционировать Menu относительно parent элемента
38
+ let menuParentElement = $state<HTMLDivElement | null>(null);
39
+ let isMenuVisible = $state<boolean>(false);
40
+
41
+ let textInputBlock: TextInputInstance;
42
+ let flatSelect = $state<boolean>(false);
43
+
44
+ // С помощью этой функции я различаю разные типы options
45
+ const isGroupArray = (options: ISelectItem[] | ISelectGroupData[]) =>
46
+ options.length > 0 && "heading" in options[0];
47
+
48
+ // Определяем тип options - Promise или значение
49
+ let innerGroupOptions = $state<ISelectGroupData[] | null>(null);
50
+ let innerOptions = $state<ISelectItem[] | null>(null);
51
+ let isLoadingOptions = $state(false);
52
+ let loadError = $state<unknown>(null);
53
+
54
+ const normalizeOptions = (opt: ISelectGroupData[] | ISelectItem[]) => {
55
+ if (isGroupArray(opt)) {
56
+ innerGroupOptions = opt as ISelectGroupData[];
57
+ innerOptions = null;
58
+ flatSelect = false;
59
+ } else {
60
+ innerOptions = opt as ISelectItem[];
61
+ innerGroupOptions = null;
62
+ flatSelect = true;
63
+ }
64
+ isLoadingOptions = false;
65
+ }
66
+
67
+ let requestId = 0;
68
+ $effect(() => {
69
+ const currentId = ++requestId;
70
+ isLoadingOptions = true;
71
+
72
+
73
+ (async () => {
74
+ try {
75
+ const resolved = await options as ISelectItem[] | ISelectGroupData[];
76
+ // если за это время options сменился — выходим
77
+ if (currentId !== requestId) return;
78
+ normalizeOptions(resolved);
79
+ } catch (err) {
80
+ if (currentId !== requestId) return;
81
+ loadError = err;
82
+ innerOptions = null;
83
+ innerGroupOptions = null;
84
+ } finally {
85
+ if (currentId === requestId) isLoadingOptions = false;
86
+ }
87
+ })();
88
+ });
89
+
90
+
91
+ const showMenu = () => (isMenuVisible = true);
92
+ const hideMenu = () => (isMenuVisible = false);
93
+
94
+ const focusHandler = () => {
95
+ if (!isMenuVisible) {
96
+ showMenu();
97
+ }
98
+ };
99
+
100
+ const updateValue = (newValue: ISelectItem) => {
101
+ value = newValue;
102
+ }
103
+
104
+ const handleItemClick = (item: ISelectItem) => {
105
+ updateValue({
106
+ title: item.title,
107
+ value: item.value
108
+ });
109
+ textInputBlock.focus();
110
+ hideMenu();
111
+ }
112
+
113
+ const handleCommandInputKeyDown = (e: KeyboardEvent) => {
114
+ if (disabled || isLoadingOptions) return;
115
+
116
+ if (e.key === "Tab") {
117
+ textInputBlock.focus();
118
+ hideMenu();
119
+ } else if (e.key === 'Escape') {
120
+ if (isMenuVisible) {
121
+ textInputBlock.focus();
122
+ hideMenu();
123
+ }
124
+ }
125
+ }
126
+
127
+ const handleControlKeyDown = (e: KeyboardEvent) => {
128
+ switch (e.key) {
129
+ case 'Enter':
130
+ case ' ':
131
+ case 'ArrowDown':
132
+ case 'ArrowUp': {
133
+ if (!isMenuVisible) {
134
+ showMenu();
135
+ e.preventDefault();
136
+ }
137
+ return;
138
+ }
139
+ }
140
+ }
141
+
142
+ const handleControlClick = () => {
143
+ if (!isMenuVisible) {
144
+ showMenu();
145
+ }
146
+ }
147
+
148
+ const handleClear = () => {
149
+ hideMenu();
150
+ value = null;
151
+ }
152
+
153
+ </script>
154
+
155
+ <div class={`dropdown-toggler ${isMenuVisible ? "dropdown-toggler-hover" : ""}`}
156
+ bind:this={menuParentElement}
157
+ >
158
+
159
+ <TextInputBlock
160
+ disabled={disabled || isLoadingOptions}
161
+ readonly
162
+ onFocus={focusHandler}
163
+ onKeyDown={handleControlKeyDown}
164
+ onClear={handleClear}
165
+ pseudoFocus={isMenuVisible}
166
+ onClick={handleControlClick}
167
+ variant="contained"
168
+ size="lg"
169
+ placeholder={placeholder}
170
+ bind:value={currentTitle}
171
+ bind:this={textInputBlock}
172
+
173
+ ariaHasPopup="listbox"
174
+ ariaExpanded={isMenuVisible}
175
+ ariaControls={menuId}
176
+ >
177
+ {#snippet suffix()}
178
+ <div class="suffix-wrapper">
179
+ {#if isLoadingOptions}
180
+ <IconLoading/>
181
+ {/if}
182
+ <ArrowDownSLineArrows size="1em" onclick={(e: MouseEvent) => { e.stopPropagation(); handleControlClick(); }} />
183
+ </div>
184
+ {/snippet}
185
+ </TextInputBlock>
186
+
187
+ <Menu
188
+ id={menuId}
189
+ parentElement={menuParentElement}
190
+ appearanceOnHover={false}
191
+ isVisible={isMenuVisible}
192
+ hideMenu={hideMenu}
193
+ minWidth={minWidthMenu}
194
+ fullWidth={fullWidthMenu}
195
+ {menuGap}
196
+ maxHeight={menuMaxHeight}
197
+ >
198
+ <div class="menu" role="listbox">
199
+ <CommandRoot maxHeight={menuMaxHeight} >
200
+ <CommandInput onKeyDown={handleCommandInputKeyDown} autoFocus={true} visible={searchable} placeholder={placeholder} ></CommandInput>
201
+ <CommandList>
202
+ {#if isLoadingOptions}
203
+ <div class="px-3 py-2">Loading...</div>
204
+ {:else if loadError}
205
+ <div class="px-3 py-2 text-danger">Load options error</div>
206
+ {:else}
207
+
208
+ {#if flatSelect}
209
+ {#if innerOptions}
210
+ <CommandGroup>
211
+ {#each innerOptions as option (option.value)}
212
+ <CommandItem onClick={ () => handleItemClick(option) }>{option.title}</CommandItem>
213
+ {/each}
214
+ </CommandGroup>
215
+ {/if}
216
+ {:else}
217
+ {#if innerGroupOptions}
218
+ {#each innerGroupOptions as option (option.heading)}
219
+ <CommandGroup heading={option.heading}>
220
+ {#each option.items as item (item.value)}
221
+ <CommandItem onClick={ () => handleItemClick(item) }>{item.title}</CommandItem>
222
+ {/each}
223
+ </CommandGroup>
224
+ {/each}
225
+ {/if}
226
+ {/if}
227
+
228
+ {/if}
229
+ </CommandList>
230
+
231
+ </CommandRoot>
232
+ </div>
233
+ </Menu>
234
+
235
+ </div>
236
+
237
+
238
+ <style>.dropdown-toggler {
239
+ color: var(--text-base-placeholder);
240
+ transition: color 0.3s ease-in-out;
241
+ padding: 0;
242
+ margin: 0 0 1rem;
243
+ display: flex;
244
+ width: 100%;
245
+ position: relative;
246
+ }
247
+ .dropdown-toggler:hover {
248
+ color: var(--text-base-main);
249
+ }
250
+ .dropdown-toggler.dropdown-toggler-hover {
251
+ color: var(--text-base-main);
252
+ }
253
+
254
+ .suffix-wrapper {
255
+ display: flex;
256
+ align-items: center;
257
+ gap: 0.5rem;
258
+ }</style>
@@ -0,0 +1,4 @@
1
+ import type { ISelectProps } from './Select.types';
2
+ declare const Select: import("svelte").Component<ISelectProps, {}, "value">;
3
+ type Select = ReturnType<typeof Select>;
4
+ export default Select;
@@ -0,0 +1,19 @@
1
+ export interface ISelectItem {
2
+ title: string;
3
+ value: string;
4
+ }
5
+ export interface ISelectGroupData {
6
+ heading: string;
7
+ items: ISelectItem[];
8
+ }
9
+ export interface ISelectProps {
10
+ searchable?: boolean;
11
+ options: ISelectGroupData[] | ISelectItem[] | Promise<(ISelectGroupData[] | ISelectItem[])>;
12
+ value: ISelectItem | null;
13
+ placeholder?: string;
14
+ disabled?: boolean;
15
+ fullWidthMenu?: boolean;
16
+ minWidthMenu?: number;
17
+ menuGap?: number;
18
+ menuMaxHeight?: number;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { default as Select } from './Select.svelte';
2
+ export * from './Select.types';
@@ -0,0 +1,2 @@
1
+ export { default as Select } from './Select.svelte';
2
+ export * from './Select.types';
@@ -128,6 +128,7 @@
128
128
  border-right-width: var(--border-right-width);
129
129
  border-left-width: var(--border-left-width);
130
130
  transition: var(--transition);
131
+ color: var(--text-color);
131
132
  }
132
133
  .TextInput:focus {
133
134
  border-color: var(--border-color-focus);
@@ -55,18 +55,20 @@
55
55
  />
56
56
 
57
57
 
58
- {#if onClear && !disabled && value !== ''}
59
- <!-- I deliberately excluded this button from tabindex -->
60
- <button tabindex="-1" type="button" class="clear-value" style={`right: ${suffixWidth + 12}px;`} onmouseup={handleClear}>
61
- <CloseCircleFillSystem size="1em" />
62
- </button>
63
- {/if}
58
+ <div class="suffix">
59
+ {#if onClear && !disabled && value !== ''}
60
+ <!-- I deliberately excluded this button from tabindex -->
61
+ <button tabindex="-1" type="button" class="clear-value" style={`right: ${suffixWidth + 12}px;`} onmouseup={handleClear}>
62
+ <CloseCircleFillSystem size="1em" />
63
+ </button>
64
+ {/if}
64
65
 
65
- {#if suffix}
66
- <div class="suffix" bind:this={suffixEl}>
67
- {@render suffix()}
68
- </div>
69
- {/if}
66
+ {#if suffix}
67
+ <div class="suffix-el" bind:this={suffixEl}>
68
+ {@render suffix()}
69
+ </div>
70
+ {/if}
71
+ </div>
70
72
  </div>
71
73
 
72
74
  <style>.TextInputBlock {
@@ -82,6 +84,7 @@
82
84
  height: 100%;
83
85
  display: flex;
84
86
  align-items: center;
87
+ gap: 0.25rem;
85
88
  }
86
89
  .TextInputBlock .prefix {
87
90
  left: 0;
@@ -92,18 +95,19 @@
92
95
  padding-right: 0.75em;
93
96
  }
94
97
  .TextInputBlock .clear-value {
95
- position: absolute;
96
- z-index: 1;
97
- top: 4px;
98
- bottom: 4px;
99
98
  height: calc(100% - 8px);
100
99
  cursor: pointer;
101
100
  border: 1px solid transparent;
102
101
  border-radius: 8px;
103
102
  padding: 0 4px;
103
+ background: transparent;
104
+ display: inline-flex;
105
+ align-items: center;
106
+ }
107
+ .TextInputBlock .clear-value:hover {
108
+ background: var(--bg-base-100);
104
109
  }
105
110
  .TextInputBlock .clear-value:focus {
106
111
  outline: none;
107
- background-color: #EDEEF0;
108
- border-color: #D3D5DC;
112
+ background: var(--bg-base-200);
109
113
  }</style>
@@ -1,4 +1,5 @@
1
1
  export const themeDefault = {
2
+ '--font-base': `"Onest", sans-serif`,
2
3
  '--panel-base': '#f3f5f6',
3
4
  '--panel-primary': '#FFF9E5',
4
5
  '--panel-success': '#DDF9E6',
@@ -13,6 +14,7 @@ export const themeDefault = {
13
14
  '--line-danger': '#FBC4BF',
14
15
  '--line-secondary': '#BEB6F9',
15
16
  '--bg-base': '#fff',
17
+ '--bg-backdrop': 'rgba(0,0,0,0.5)',
16
18
  '--bg-base-100': '#EDEEF0',
17
19
  '--bg-base-200': '#E2E4E8',
18
20
  '--bg-base-300': '#D3D5DC',
@@ -92,4 +94,5 @@ export const themeDefault = {
92
94
  '--line-height-md': '20px',
93
95
  '--font-size-lg': '16px',
94
96
  '--line-height-lg': '24px',
97
+ '--font-size-h2': '20px',
95
98
  };
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
-
2
+ import { browser } from '$app/environment';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { ITheme } from './ThemeProvider.types'
5
5
 
@@ -11,9 +11,17 @@
11
11
  .join(';');
12
12
  });
13
13
 
14
+ $effect(() => {
15
+ if (!browser || !theme) return;
16
+
17
+ for (const [k, v] of Object.entries(theme)) {
18
+ // ключи должны быть вида `--color-bg`
19
+ document.documentElement.style.setProperty(k, String(v));
20
+ }
21
+ });
14
22
  </script>
15
23
 
16
- <div style={cssVars} class="ThemeProvider">
24
+ <!-- <div style={cssVars} class="ThemeProvider"> -->
17
25
  {@render children()}
18
- </div>
26
+ <!-- </div> -->
19
27
 
@@ -1,5 +1,6 @@
1
1
  export type ThemeVars = Record<string, string | number>;
2
2
  export interface ITheme {
3
+ '--font-base': string;
3
4
  '--panel-base': string;
4
5
  '--panel-primary': string;
5
6
  '--panel-success': string;
@@ -14,6 +15,7 @@ export interface ITheme {
14
15
  '--line-danger': string;
15
16
  '--line-secondary': string;
16
17
  '--bg-base': string;
18
+ '--bg-backdrop': string;
17
19
  '--bg-base-100': string;
18
20
  '--bg-base-200': string;
19
21
  '--bg-base-300': string;
@@ -93,4 +95,5 @@ export interface ITheme {
93
95
  '--line-height-md': string;
94
96
  '--font-size-lg': string;
95
97
  '--line-height-lg': string;
98
+ '--font-size-h2': string;
96
99
  }
@@ -11,3 +11,7 @@ export * from './TextInput';
11
11
  export * from './TextInputMilk';
12
12
  export * from './TextInputBlock';
13
13
  export * from './Command';
14
+ export * from './Portal';
15
+ export * from './Modal';
16
+ export * from './Select';
17
+ export * from './Icon';
@@ -11,3 +11,7 @@ export * from './TextInput';
11
11
  export * from './TextInputMilk';
12
12
  export * from './TextInputBlock';
13
13
  export * from './Command';
14
+ export * from './Portal';
15
+ export * from './Modal';
16
+ export * from './Select';
17
+ export * from './Icon';
@@ -1 +1,6 @@
1
- @forward "./typography.scss";
1
+ @forward "./root.scss";
2
+ @forward "./typography.scss";
3
+
4
+ html, body {
5
+ color: var(--text-base-main);
6
+ }
@@ -0,0 +1,117 @@
1
+ // Please sync this with ThemeProvider
2
+
3
+ :root {
4
+ --font-base: "Geist", sans-serif;
5
+
6
+ --panel-base: #f3f5f6;
7
+ --panel-primary: #FFF9E5;
8
+ --panel-success: #DDF9E6;
9
+ --panel-danger: #FFEDEB;
10
+ --panel-secondary: #F6F5FF;
11
+
12
+ --line-base: #D3D5DC;
13
+ --line-control-100: #ABABB0;
14
+ --line-base-emp: #151617;
15
+ --line-primary: #FFE27B;
16
+ --line-primary-emp: #F0C013;
17
+ --line-success: #B6F7CB;
18
+ --line-danger: #FBC4BF;
19
+ --line-secondary: #BEB6F9;
20
+
21
+ --bg-base: #fff;
22
+ --bg-backdrop: rgba(0,0,0,0.5);
23
+
24
+ --bg-base-100: #EDEEF0;
25
+ --bg-base-200: #E2E4E8;
26
+ --bg-base-300: #D3D5DC;
27
+ --bg-base-emp-100: #28282B;
28
+ --bg-base-emp-200: #1E1F21;
29
+ --bg-base-emp-300: #151617;
30
+
31
+ --bg-primary-100: #FFF6D6;
32
+ --bg-primary-200: #FCF0C5;
33
+ --bg-primary-300: #FAEBB4;
34
+ --bg-primary-emp-100: #FFCC14;
35
+ --bg-primary-emp-200: #F7C614;
36
+ --bg-primary-emp-300: #F0C013;
37
+
38
+ --bg-success-100: #C3F6D4;
39
+ --bg-success-200: #B1F0C6;
40
+ --bg-success-300: #9FEBB9;
41
+ --bg-success-emp-100: #1BB44C;
42
+ --bg-success-emp-200: #12AA43;
43
+ --bg-success-emp-300: #0DA13C;
44
+
45
+ --bg-danger-100: #FCDFDC;
46
+ --bg-danger-200: #FAD4CF;
47
+ --bg-danger-300: #F7C7C1;
48
+ --bg-danger-emp-100: #F35749;
49
+ --bg-danger-emp-200: #ED4637;
50
+ --bg-danger-emp-300: #EB3A2A;
51
+
52
+ --bg-secondary-100: #E8E6FC;
53
+ --bg-secondary-200: #DCD9F9;
54
+ --bg-secondary-300: #D1CCF9;
55
+ --bg-secondary-emp-100: #5A49F3;
56
+ --bg-secondary-emp-200: #4F3DF2;
57
+ --bg-secondary-emp-300: #4431F5;
58
+
59
+ --text-base-main: #26272B;
60
+ --text-base-inv: #fff;
61
+ --text-base-muted: #515256;
62
+ --text-base-inv-muted: #C1C3CA;
63
+ --text-base-placeholder: #9C9EA3;
64
+ --text-base-link: #6846E1;
65
+ --text-secondary-main: #2F19FB;
66
+ --text-primary-main: #C29B09;
67
+ --text-success-main: #077129;
68
+ --text-danger-main: #C42A1A;
69
+
70
+ --icon-base-main: #26272B;
71
+ --icon-base-inv: #fff;
72
+ --icon-base-muted: #515256;
73
+ --icon-base-inv-muted: #C1C3CA;
74
+ --icon-base-placeholder: #9C9EA3;
75
+ --icon-base-link: #6846E1;
76
+ --icon-secondary-main: #2F19FB;
77
+ --icon-primary-main: #C29B09;
78
+ --icon-success-main: #077129;
79
+ --icon-danger-main: #C42A1A;
80
+
81
+ --border-radius-button-sm: 5px;
82
+ --border-radius-button: 8px;
83
+ --border-radius-panel: 12px;
84
+ --border-radius-window: 20px;
85
+
86
+ --stroke-base: 1px;
87
+ --stroke-button: var(--stroke-base);
88
+
89
+ --spacing-xs-1: 4;
90
+ --spacing-xs-1-5: 6;
91
+ --spacing-sm-2: 8;
92
+ --spacing-md-3: 12;
93
+ --spacing-lg-5: 20;
94
+ --spacing-xl-10: 40;
95
+
96
+ --zindex-dropdown: 1060;
97
+ --zindex-sticky: 1020;
98
+ --zindex-fixed: 1030;
99
+ --zindex-sheet: 1035;
100
+ --zindex-offcanvas-backdrop: 1040;
101
+ --zindex-offcanvas: 1045;
102
+ --zindex-modal-backdrop: 1050;
103
+ --zindex-modal: 1055;
104
+ --zindex-popover: 1070;
105
+ --zindex-tooltip: 1080;
106
+ --zindex-toast: 1090;
107
+
108
+ --font-size-sm: 12px;
109
+ --line-height-sm: 14px;
110
+ --font-size-md: 14px;
111
+ --line-height-md: 20px;
112
+ --font-size-lg: 16px;
113
+ --line-height-lg: 24px;
114
+
115
+ --font-size-h2: 20px;
116
+ }
117
+
@@ -1,6 +1,11 @@
1
1
  .onest-font-default {
2
- font-family: "Onest", sans-serif;
2
+ font-family: "Geist", sans-serif;
3
3
  font-optical-sizing: auto;
4
4
  font-weight: 400;
5
5
  font-style: normal;
6
+ }
7
+
8
+ html, body {
9
+ font-family: var(--font-base);
10
+ color: var(--text-base-main);
6
11
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "milk-lib",
3
3
  "license": "MIT",
4
- "version": "0.0.17",
4
+ "version": "0.0.18",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
7
7
  "build": "vite build && npm run prepack",