@koslibs/components 1.8.0 → 1.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @koslibs/components
2
2
 
3
+ ## 1.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#11](https://github.com/koslibs/components/pull/11) [`85e4c4b`](https://github.com/koslibs/components/commit/85e4c4b523847d339a773b63fcf7916098f28327) Thanks [@holypower777](https://github.com/holypower777)! - Добавлен Space для горизонтальной и вертикальной раскладки: обязательные size и children, единый положительный отступ из --size-\*, align, wrap и fullWidth. Компонент использует CSS gap, сохраняет прямые дочерние элементы и доступен через @koslibs/components/Space. Добавлены Storybook Docs и Playground.
8
+
9
+ ## 1.9.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#10](https://github.com/koslibs/components/pull/10) [`01aa734`](https://github.com/koslibs/components/commit/01aa734718eb2b649cc8ec361368b825fc8432d6) Thanks [@holypower777](https://github.com/holypower777)! - Добавлен Select по документации Figma: одиночный и множественный выбор, локальный поиск с внутренним состоянием запроса и сообщением при отсутствии совпадений, список на Popover, форма и примеры Storybook. Выбранные значения отображаются через запятую с однострочным многоточием. Выбранные строки отличаются от поиска и hover; внутренний label плавно перемещается по аналогии с Input. Очистка использует тонкий крестик 14×14 px с постоянным label «close».
14
+
3
15
  ## 1.8.0
4
16
 
5
17
  ### Minor Changes
@@ -0,0 +1,7 @@
1
+ import type { SelectState } from '../hooks/use-select-state.js';
2
+ import type { SelectProps } from '../types.js';
3
+ type Props = Pick<SelectProps, 'multiple' | 'searchable' | 'searchPlaceholder' | 'noResultsText' | 'dataTestId'> & {
4
+ state: SelectState;
5
+ };
6
+ export declare function SelectDropdown({ state, multiple, searchable, searchPlaceholder, noResultsText, dataTestId, }: Props): import("react").JSX.Element;
7
+ export {};
@@ -0,0 +1,96 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { SearchIcon } from "@koslibs/icons/SearchIcon";
3
+ import { TickIcon } from "@koslibs/icons/TickIcon";
4
+ import classnames from "classnames";
5
+ import { useEffect, useRef } from "react";
6
+ import { useDataTestId } from "../../../hooks/use-data-test-id.js";
7
+ import { Input } from "../../Input/index.js";
8
+ import { TypographyText } from "../../Typography/text.js";
9
+ import index_module from "../index.module.js";
10
+ function SelectDropdown({ state, multiple, searchable, searchPlaceholder = 'Поиск по списку', noResultsText = 'Ничего не найдено', dataTestId }) {
11
+ const getDataTestId = useDataTestId(dataTestId);
12
+ const { options, selectedIds, search, activeId, setSearch, handleKeyDown, select } = state;
13
+ const activeOptionRef = useRef(null);
14
+ useEffect(()=>{
15
+ activeOptionRef.current?.scrollIntoView?.({
16
+ block: 'nearest'
17
+ });
18
+ }, [
19
+ activeId
20
+ ]);
21
+ return /*#__PURE__*/ jsxs(Fragment, {
22
+ children: [
23
+ searchable && /*#__PURE__*/ jsx(Input, {
24
+ block: true,
25
+ autoFocus: true,
26
+ size: 40,
27
+ view: "secondary",
28
+ value: search,
29
+ placeholder: searchPlaceholder,
30
+ leftAddon: /*#__PURE__*/ jsx(SearchIcon, {
31
+ width: 16,
32
+ height: 16,
33
+ "aria-hidden": "true"
34
+ }),
35
+ clear: true,
36
+ clearLabel: "close",
37
+ onChange: (_, { value })=>setSearch(value),
38
+ onClear: ()=>setSearch(''),
39
+ onKeyDown: handleKeyDown,
40
+ className: index_module.search,
41
+ dataTestId: getDataTestId('search')
42
+ }),
43
+ 0 === options.length && /*#__PURE__*/ jsxs("div", {
44
+ className: classnames(index_module.listState, index_module.listHint),
45
+ "data-test-id": getDataTestId('empty'),
46
+ children: [
47
+ /*#__PURE__*/ jsx(TypographyText, {
48
+ children: noResultsText
49
+ }),
50
+ search.trim() && /*#__PURE__*/ jsx(TypographyText, {
51
+ size: 12,
52
+ children: "Измените поисковый запрос"
53
+ })
54
+ ]
55
+ }),
56
+ options.length > 0 && /*#__PURE__*/ jsx("div", {
57
+ className: index_module.options,
58
+ "data-test-id": getDataTestId('options'),
59
+ children: options.map((option)=>{
60
+ const selected = selectedIds.includes(option.id);
61
+ return /*#__PURE__*/ jsxs("button", {
62
+ type: "button",
63
+ tabIndex: -1,
64
+ ref: option.id !== activeId || option.disabled ? void 0 : activeOptionRef,
65
+ className: classnames(index_module.option, {
66
+ [index_module.selected]: selected,
67
+ [index_module.active]: option.id === activeId && !option.disabled
68
+ }),
69
+ disabled: option.disabled,
70
+ onMouseDown: (event)=>event.preventDefault(),
71
+ onClick: (event)=>select(option, event),
72
+ "data-test-id": getDataTestId(`option-${option.id}`),
73
+ title: option.label,
74
+ children: [
75
+ /*#__PURE__*/ jsx(TypographyText, {
76
+ size: 13,
77
+ rowLimit: 1,
78
+ className: index_module.optionLabel,
79
+ children: option.label
80
+ }),
81
+ (multiple || selected) && /*#__PURE__*/ jsx("span", {
82
+ className: classnames(index_module.marker, multiple && index_module.checkbox),
83
+ children: selected && /*#__PURE__*/ jsx(TickIcon, {
84
+ width: multiple ? 14 : 18,
85
+ height: multiple ? 14 : 18,
86
+ "aria-hidden": "true"
87
+ })
88
+ })
89
+ ]
90
+ }, option.id);
91
+ })
92
+ })
93
+ ]
94
+ });
95
+ }
96
+ export { SelectDropdown };
@@ -0,0 +1,23 @@
1
+ import { type ButtonHTMLAttributes, type KeyboardEvent } from 'react';
2
+ import type { SelectChangeEvent, SelectOption, SelectProps } from '../types.js';
3
+ export declare function useSelectState(props: SelectProps): {
4
+ options: SelectOption[];
5
+ selectedIds: string[];
6
+ selectedOptions: SelectOption[];
7
+ search: string;
8
+ open: boolean;
9
+ blocked: boolean | undefined;
10
+ activeId: string | null;
11
+ raisedLabel: boolean;
12
+ buttonRef: import("react").RefObject<HTMLButtonElement>;
13
+ popupRef: import("react").RefObject<HTMLDivElement>;
14
+ anchor: HTMLDivElement | null;
15
+ setAnchor: import("react").Dispatch<import("react").SetStateAction<HTMLDivElement | null>>;
16
+ triggerProps: ButtonHTMLAttributes<HTMLButtonElement>;
17
+ close: () => void;
18
+ select: (option: SelectOption, event: SelectChangeEvent) => void;
19
+ handleKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
20
+ setSearch(value: string): void;
21
+ clear(event: Parameters<NonNullable<SelectProps["onClear"]>>[0]): void;
22
+ };
23
+ export type SelectState = ReturnType<typeof useSelectState>;
@@ -0,0 +1,183 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ function toIds(value) {
3
+ return [
4
+ ...new Set('string' == typeof value ? [
5
+ value
6
+ ] : value ?? [])
7
+ ];
8
+ }
9
+ function useSelectState(props) {
10
+ const [localIds, setLocalIds] = useState(()=>toIds(props.defaultValue));
11
+ const [localOpen, setLocalOpen] = useState(props.defaultOpen ?? false);
12
+ const [search, setSearch] = useState('');
13
+ const [activeId, setActiveId] = useState(null);
14
+ const [focused, setFocused] = useState(false);
15
+ const [anchor, setAnchor] = useState(null);
16
+ const buttonRef = useRef(null);
17
+ const popupRef = useRef(null);
18
+ const retainedOptions = useRef(new Map());
19
+ const ids = void 0 === props.value ? localIds : toIds(props.value);
20
+ const selectedIds = props.multiple ? ids : ids.slice(0, 1);
21
+ const optionMap = new Map(props.options.map((option)=>[
22
+ option.id,
23
+ option
24
+ ]));
25
+ const resolve = (id)=>optionMap.get(id) ?? retainedOptions.current.get(id) ?? {
26
+ id,
27
+ label: id
28
+ };
29
+ const selectedOptions = selectedIds.map(resolve);
30
+ const blocked = props.disabled || props.readOnly;
31
+ const open = !blocked && (props.open ?? localOpen);
32
+ const query = props.searchable ? search.trim().toLocaleLowerCase() : '';
33
+ const options = props.options.filter((option)=>!query || option.label.toLocaleLowerCase().includes(query));
34
+ useEffect(()=>{
35
+ retainedOptions.current = new Map(selectedOptions.map((option)=>[
36
+ option.id,
37
+ option
38
+ ]));
39
+ });
40
+ useEffect(()=>{
41
+ if (!open) setSearch('');
42
+ }, [
43
+ open
44
+ ]);
45
+ const resetRef = useRef();
46
+ resetRef.current = ()=>{
47
+ if (void 0 === props.value) setLocalIds(toIds(props.defaultValue));
48
+ if (void 0 === props.open) setLocalOpen(false);
49
+ setSearch('');
50
+ };
51
+ useEffect(()=>{
52
+ const form = buttonRef.current?.form;
53
+ let active = true;
54
+ const handleReset = (event)=>{
55
+ queueMicrotask(()=>{
56
+ if (active && !event.defaultPrevented) resetRef.current?.();
57
+ });
58
+ };
59
+ form?.addEventListener('reset', handleReset);
60
+ return ()=>{
61
+ active = false;
62
+ form?.removeEventListener('reset', handleReset);
63
+ };
64
+ }, [
65
+ props.form
66
+ ]);
67
+ function setOpen(next) {
68
+ if (next && blocked) return;
69
+ if (void 0 === props.open) setLocalOpen(next);
70
+ if (next !== open) props.onOpenChange?.(next);
71
+ }
72
+ function select(option, event) {
73
+ if (blocked || option.disabled) return;
74
+ let nextIds = [
75
+ option.id
76
+ ];
77
+ if (props.multiple) nextIds = selectedIds.includes(option.id) ? selectedIds.filter((id)=>id !== option.id) : [
78
+ ...selectedIds,
79
+ option.id
80
+ ];
81
+ if (void 0 === props.value) setLocalIds(nextIds);
82
+ if (props.multiple || selectedIds[0] !== option.id) {
83
+ const nextOptions = nextIds.map(resolve);
84
+ if (props.multiple) props.onChange?.(event, {
85
+ value: nextIds,
86
+ selectedOptions: nextOptions
87
+ });
88
+ else props.onChange?.(event, {
89
+ value: option.id,
90
+ selectedOptions: nextOptions
91
+ });
92
+ }
93
+ if (!props.multiple) {
94
+ setOpen(false);
95
+ buttonRef.current?.focus();
96
+ }
97
+ }
98
+ function close() {
99
+ const restoreFocus = popupRef.current?.contains(buttonRef.current?.ownerDocument.activeElement ?? null);
100
+ setOpen(false);
101
+ if (restoreFocus) buttonRef.current?.focus();
102
+ }
103
+ function handleKeyDown(event) {
104
+ if (event.defaultPrevented || blocked) return;
105
+ const { key } = event;
106
+ if ('Tab' === key) {
107
+ if (open) setOpen(false);
108
+ return;
109
+ }
110
+ const available = options.filter((option)=>!option.disabled);
111
+ if ([
112
+ 'ArrowDown',
113
+ 'ArrowUp',
114
+ 'Home',
115
+ 'End'
116
+ ].includes(key)) {
117
+ if ([
118
+ 'Home',
119
+ 'End'
120
+ ].includes(key) && (!open || 'INPUT' === event.currentTarget.tagName)) return;
121
+ event.preventDefault();
122
+ setOpen(true);
123
+ const current = available.findIndex((option)=>option.id === activeId);
124
+ let next = current <= 0 ? available.length - 1 : current - 1;
125
+ if ('Home' === key) next = 0;
126
+ if ('End' === key) next = available.length - 1;
127
+ if ('ArrowDown' === key) next = (current + 1) % available.length;
128
+ setActiveId(available[next]?.id ?? null);
129
+ } else if ('Enter' === key && open) {
130
+ event.preventDefault();
131
+ const active = available.find((option)=>option.id === activeId);
132
+ if (active) select(active, event);
133
+ }
134
+ }
135
+ const triggerProps = {
136
+ onFocus (event) {
137
+ setFocused(true);
138
+ props.onFocus?.(event);
139
+ },
140
+ onBlur (event) {
141
+ setFocused(false);
142
+ props.onBlur?.(event);
143
+ },
144
+ onClick (event) {
145
+ props.onClick?.(event);
146
+ if (event.defaultPrevented || blocked) return;
147
+ setActiveId(null);
148
+ setOpen(!open);
149
+ },
150
+ onKeyDown (event) {
151
+ props.onKeyDown?.(event);
152
+ handleKeyDown(event);
153
+ }
154
+ };
155
+ return {
156
+ options,
157
+ selectedIds,
158
+ selectedOptions,
159
+ search,
160
+ open,
161
+ blocked,
162
+ activeId: open ? activeId : null,
163
+ raisedLabel: selectedIds.length > 0 || open || focused && !blocked,
164
+ buttonRef,
165
+ popupRef,
166
+ anchor,
167
+ setAnchor,
168
+ triggerProps,
169
+ close,
170
+ select,
171
+ handleKeyDown,
172
+ setSearch (value) {
173
+ setActiveId(null);
174
+ setSearch(value);
175
+ },
176
+ clear (event) {
177
+ if (void 0 === props.value) setLocalIds([]);
178
+ props.onClear?.(event);
179
+ buttonRef.current?.focus();
180
+ }
181
+ };
182
+ }
183
+ export { useSelectState };
@@ -0,0 +1,2 @@
1
+ export { Select } from './select.js';
2
+ export type { SelectProps, SelectOption, SelectChangeEvent, SelectChangePayload } from './types.js';
@@ -0,0 +1 @@
1
+ export { Select } from "./select.js";
@@ -0,0 +1,44 @@
1
+ import "./index_module.css";
2
+ const index_module = {
3
+ component: "component-cM8DTg",
4
+ block: "block-EvThms",
5
+ secondary: "secondary-CnWByB",
6
+ readOnly: "readOnly-zwkIeK",
7
+ error: "error-TrKhzv",
8
+ disabled: "disabled-fZIeN_",
9
+ "size-40": "size-40-naTxHA",
10
+ size40: "size-40-naTxHA",
11
+ "size-48": "size-48-Lf4nSh",
12
+ size48: "size-48-Lf4nSh",
13
+ "size-56": "size-56-U3fog3",
14
+ size56: "size-56-U3fog3",
15
+ "size-64": "size-64-ZMZbdh",
16
+ size64: "size-64-ZMZbdh",
17
+ "size-72": "size-72-byhU5x",
18
+ size72: "size-72-byhU5x",
19
+ field: "field-jGXYY5",
20
+ open: "open-CCkiBd",
21
+ trigger: "trigger-nKdmww",
22
+ valueColumn: "valueColumn-IqzQmw",
23
+ value: "value-flrRUI",
24
+ placeholder: "placeholder-SlfxjN",
25
+ label: "label-rrakc7",
26
+ innerLabel: "innerLabel-njfkYe",
27
+ labelRaised: "labelRaised-iztTyX",
28
+ withInnerLabel: "withInnerLabel-a0m9CO",
29
+ chevron: "chevron-hiAxaW",
30
+ clear: "clear-x9k2FN",
31
+ message: "message-nYZGHP",
32
+ dropdown: "dropdown-ufLXzq",
33
+ search: "search-aVnGDP",
34
+ options: "options-ygwOHf",
35
+ option: "option-d9Lc3i",
36
+ selected: "selected-uq_Ki8",
37
+ active: "active-KEr7O6",
38
+ optionLabel: "optionLabel-g0AQIH",
39
+ marker: "marker-i_70bm",
40
+ checkbox: "checkbox-r7078O",
41
+ listState: "listState-_7We4G",
42
+ listHint: "listHint-C_Zt_z"
43
+ };
44
+ export default index_module;
@@ -0,0 +1,334 @@
1
+ @import "../../styles/theme.css";
2
+ @import "./vars.css";
3
+
4
+ .component-cM8DTg {
5
+ --field-background: var(--select-primary-background-color);
6
+ --field-color: var(--select-color);
7
+ --field-label-color: var(--select-label-color);
8
+ --field-icon-color: var(--select-icon-color);
9
+ --field-placeholder-color: var(--select-placeholder-color);
10
+ --field-border: transparent;
11
+ gap: var(--select-gap);
12
+ width: var(--select-width);
13
+ vertical-align: top;
14
+ min-width: 0;
15
+ max-width: 100%;
16
+ font-family: var(--font-primary);
17
+ flex-direction: column;
18
+ display: inline-flex;
19
+
20
+ &.block-EvThms {
21
+ width: 100%;
22
+ display: flex;
23
+ }
24
+
25
+ &.secondary-CnWByB {
26
+ --field-background: var(--select-secondary-background-color);
27
+ }
28
+
29
+ &.readOnly-zwkIeK {
30
+ --field-border: var(--select-read-only-border-color);
31
+ }
32
+
33
+ &.error-TrKhzv {
34
+ --field-border: var(--select-error-border-color);
35
+ }
36
+
37
+ &.disabled-fZIeN_ {
38
+ --field-background: var(--select-disabled-background-color);
39
+ --field-color: var(--select-disabled-color);
40
+ --field-label-color: var(--select-disabled-color);
41
+ --field-icon-color: var(--select-disabled-color);
42
+ --field-placeholder-color: var(--select-disabled-color);
43
+ --field-border: transparent;
44
+ }
45
+ }
46
+
47
+ .size-40-naTxHA {
48
+ --field-height: 40px;
49
+ --field-padding: var(--select-padding-40);
50
+ }
51
+
52
+ .size-48-Lf4nSh {
53
+ --field-height: 48px;
54
+ --field-padding: var(--select-padding-48);
55
+ }
56
+
57
+ .size-56-U3fog3 {
58
+ --field-height: 56px;
59
+ --field-padding: var(--select-padding-56);
60
+ }
61
+
62
+ .size-64-ZMZbdh {
63
+ --field-height: 64px;
64
+ --field-padding: var(--select-padding-64);
65
+ }
66
+
67
+ .size-72-byhU5x {
68
+ --field-height: 72px;
69
+ --field-padding: var(--select-padding-72);
70
+ }
71
+
72
+ .field-jGXYY5 {
73
+ box-sizing: border-box;
74
+ align-items: center;
75
+ gap: var(--select-gap);
76
+ height: var(--field-height);
77
+ padding: var(--size-0) var(--field-padding);
78
+ border-radius: var(--select-border-radius);
79
+ background: var(--field-background);
80
+ box-shadow: inset 0 0 0 1px var(--field-border);
81
+ color: var(--field-color);
82
+ display: flex;
83
+ position: relative;
84
+ }
85
+
86
+ @media (hover: hover) {
87
+ .component-cM8DTg:not(.disabled-fZIeN_, .readOnly-zwkIeK, .error-TrKhzv) .field-jGXYY5:hover {
88
+ box-shadow: inset 0 0 0 1px var(--select-hover-border-color);
89
+ }
90
+ }
91
+
92
+ .component-cM8DTg:not(.disabled-fZIeN_) .field-jGXYY5:focus-within, .component-cM8DTg.open-CCkiBd .field-jGXYY5 {
93
+ box-shadow: none;
94
+ outline: 2px solid var(--select-focus-border-color);
95
+ outline-offset: 0;
96
+ }
97
+
98
+ .component-cM8DTg.error-TrKhzv:not(.disabled-fZIeN_) .field-jGXYY5:focus-within, .component-cM8DTg.error-TrKhzv.open-CCkiBd .field-jGXYY5 {
99
+ outline-color: var(--select-error-border-color);
100
+ }
101
+
102
+ .trigger-nKdmww {
103
+ min-width: 0;
104
+ padding: var(--size-0);
105
+ color: inherit;
106
+ text-align: left;
107
+ cursor: pointer;
108
+ appearance: none;
109
+ background: none;
110
+ border: 0;
111
+ outline: none;
112
+ flex: 1;
113
+ align-self: stretch;
114
+
115
+ &:after {
116
+ content: "";
117
+ border-radius: inherit;
118
+ position: absolute;
119
+ inset: 0;
120
+ }
121
+ }
122
+
123
+ .valueColumn-IqzQmw {
124
+ justify-content: center;
125
+ gap: var(--size-2);
126
+ flex-direction: column;
127
+ min-width: 0;
128
+ height: 100%;
129
+ display: flex;
130
+ }
131
+
132
+ .value-flrRUI {
133
+ width: 100%;
134
+ min-width: 0;
135
+ color: var(--field-color);
136
+ }
137
+
138
+ .placeholder-SlfxjN {
139
+ color: var(--field-placeholder-color);
140
+ }
141
+
142
+ .label-rrakc7 {
143
+ min-width: 0;
144
+ color: var(--field-label-color);
145
+ overflow-wrap: anywhere;
146
+ }
147
+
148
+ .innerLabel-njfkYe {
149
+ color: var(--field-label-color);
150
+ transform: translateY(calc(50% + var(--size-2) / 2));
151
+ transition: transform .18s, font-size .18s;
152
+ }
153
+
154
+ .component-cM8DTg.labelRaised-iztTyX .innerLabel-njfkYe {
155
+ transform: translateY(0);
156
+ }
157
+
158
+ .component-cM8DTg.withInnerLabel-a0m9CO .value-flrRUI {
159
+ opacity: 0;
160
+ transition: opacity .18s;
161
+ }
162
+
163
+ .component-cM8DTg.labelRaised-iztTyX .value-flrRUI {
164
+ opacity: 1;
165
+ }
166
+
167
+ @media (prefers-reduced-motion: reduce) {
168
+ .innerLabel-njfkYe, .component-cM8DTg .chevron-hiAxaW, .component-cM8DTg.withInnerLabel-a0m9CO .value-flrRUI {
169
+ transition: none;
170
+ }
171
+ }
172
+
173
+ .chevron-hiAxaW {
174
+ color: var(--field-icon-color);
175
+ pointer-events: none;
176
+ flex-shrink: 0;
177
+ align-items: center;
178
+ transition: transform .18s;
179
+ display: flex;
180
+ transform: rotate(0);
181
+ }
182
+
183
+ .component-cM8DTg.open-CCkiBd .chevron-hiAxaW {
184
+ transform: rotate(180deg);
185
+ }
186
+
187
+ .component-cM8DTg .clear-x9k2FN {
188
+ z-index: 1;
189
+ width: 18px;
190
+ min-width: 0;
191
+ height: 24px;
192
+ min-height: 0;
193
+ margin: var(--size-0);
194
+ padding: var(--size-0);
195
+ color: var(--field-icon-color);
196
+ flex-shrink: 0;
197
+ justify-content: center;
198
+ align-items: center;
199
+ display: flex;
200
+ position: relative;
201
+
202
+ &:hover, &:active {
203
+ color: var(--field-icon-color);
204
+ }
205
+ }
206
+
207
+ .component-cM8DTg.disabled-fZIeN_ .trigger-nKdmww {
208
+ cursor: not-allowed;
209
+ }
210
+
211
+ .component-cM8DTg.readOnly-zwkIeK .trigger-nKdmww {
212
+ cursor: default;
213
+ }
214
+
215
+ .component-cM8DTg .message-nYZGHP {
216
+ color: var(--select-hint-color);
217
+ overflow-wrap: anywhere;
218
+ }
219
+
220
+ .component-cM8DTg.error-TrKhzv .message-nYZGHP {
221
+ color: var(--select-error-message-color);
222
+ }
223
+
224
+ .component-cM8DTg.disabled-fZIeN_ .message-nYZGHP {
225
+ color: var(--select-disabled-color);
226
+ }
227
+
228
+ .dropdown-ufLXzq {
229
+ --popover-shadow: var(--select-dropdown-shadow);
230
+ box-sizing: border-box;
231
+ gap: var(--select-gap);
232
+ padding: var(--size-8);
233
+ border-radius: var(--select-border-radius);
234
+ background: var(--select-primary-background-color);
235
+ color: var(--select-color);
236
+ }
237
+
238
+ .search-aVnGDP {
239
+ flex-shrink: 0;
240
+ }
241
+
242
+ .options-ygwOHf {
243
+ gap: var(--select-options-gap);
244
+ min-height: 0;
245
+ max-height: var(--select-options-max-height);
246
+ overscroll-behavior: contain;
247
+ scrollbar-width: thin;
248
+ scrollbar-color: var(--select-hover-border-color) transparent;
249
+ flex-direction: column;
250
+ display: flex;
251
+ overflow: auto;
252
+ }
253
+
254
+ .option-d9Lc3i {
255
+ box-sizing: border-box;
256
+ align-items: center;
257
+ gap: var(--select-gap);
258
+ width: 100%;
259
+ min-width: 0;
260
+ height: 40px;
261
+ padding: var(--size-0) var(--size-12);
262
+ border-radius: var(--select-option-border-radius);
263
+ color: var(--select-color);
264
+ text-align: left;
265
+ cursor: pointer;
266
+ background: none;
267
+ border: 0;
268
+ flex-shrink: 0;
269
+ display: flex;
270
+
271
+ &.selected-uq_Ki8 {
272
+ background: var(--select-option-selected-color);
273
+ }
274
+
275
+ &:not(:disabled):hover, &.active-KEr7O6 {
276
+ background: var(--select-option-hover-color);
277
+ }
278
+
279
+ &.selected-uq_Ki8:not(:disabled):hover, &.selected-uq_Ki8.active-KEr7O6 {
280
+ background: var(--select-option-selected-color);
281
+ }
282
+
283
+ &:disabled {
284
+ color: var(--select-disabled-color);
285
+ cursor: not-allowed;
286
+ }
287
+ }
288
+
289
+ .optionLabel-g0AQIH {
290
+ flex: 1;
291
+ min-width: 0;
292
+ }
293
+
294
+ .marker-i_70bm {
295
+ box-sizing: border-box;
296
+ width: 18px;
297
+ height: 18px;
298
+ color: var(--select-icon-color);
299
+ flex-shrink: 0;
300
+ justify-content: center;
301
+ align-items: center;
302
+ display: flex;
303
+ }
304
+
305
+ .checkbox-r7078O {
306
+ border: 1px solid var(--select-hover-border-color);
307
+ border-radius: var(--border-radius-4);
308
+ }
309
+
310
+ .option-d9Lc3i.selected-uq_Ki8 .checkbox-r7078O {
311
+ background: var(--select-marker-selected-color);
312
+ }
313
+
314
+ .option-d9Lc3i:disabled .marker-i_70bm {
315
+ opacity: .5;
316
+ }
317
+
318
+ .listState-_7We4G {
319
+ justify-content: center;
320
+ align-items: center;
321
+ gap: var(--select-gap);
322
+ min-height: 0;
323
+ padding: var(--size-24) var(--size-12);
324
+ text-align: center;
325
+ flex-direction: column;
326
+ flex: 1;
327
+ display: flex;
328
+ overflow: auto;
329
+ }
330
+
331
+ .listHint-C_Zt_z {
332
+ color: var(--select-hint-color);
333
+ }
334
+
@@ -0,0 +1,2 @@
1
+ import type { SelectProps } from './types.js';
2
+ export declare const Select: import("react").ForwardRefExoticComponent<SelectProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,161 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { ChevronDownIcon } from "@koslibs/icons/ChevronDownIcon";
3
+ import { CrossIcon } from "@koslibs/icons/CrossIcon";
4
+ import classnames from "classnames";
5
+ import { forwardRef, useId, useImperativeHandle } from "react";
6
+ import { useDataTestId } from "../../hooks/use-data-test-id.js";
7
+ import { Button } from "../Button/index.js";
8
+ import { Popover } from "../Popover/index.js";
9
+ import { TypographyText } from "../Typography/text.js";
10
+ import { SelectDropdown } from "./components/select-dropdown.js";
11
+ import { useSelectState } from "./hooks/use-select-state.js";
12
+ import index_module from "./index.module.js";
13
+ const textSizes = {
14
+ 40: 13,
15
+ 48: 13,
16
+ 56: 14,
17
+ 64: 14,
18
+ 72: 16
19
+ };
20
+ const dropdownOffset = [
21
+ 0,
22
+ 8
23
+ ];
24
+ const Select = /*#__PURE__*/ forwardRef((props, ref)=>{
25
+ const { view = 'primary', size = 40, label, labelView = 'outer', block = false, placeholder = 'Выберите значение', hint, error, clear = false, disabled = false, readOnly = false, className, style, labelClassName, messageClassName, dropdownClassName, dataTestId, id, name, form, onClear: _onClear, getPortalContainer, options: _options, multiple, value: _value, defaultValue: _defaultValue, onChange: _onChange, searchable, searchPlaceholder, open: _open, defaultOpen: _defaultOpen, onOpenChange: _onOpenChange, noResultsText, ...buttonProps } = props;
26
+ const generatedId = useId();
27
+ const triggerId = id ?? generatedId;
28
+ const getDataTestId = useDataTestId(dataTestId);
29
+ const state = useSelectState(props);
30
+ const { buttonRef, raisedLabel } = state;
31
+ const hasValue = state.selectedIds.length > 0;
32
+ const hasLabel = null != label && false !== label && '' !== label;
33
+ const innerLabel = 'inner' === labelView && hasLabel;
34
+ const selectedText = state.selectedOptions.map((option)=>option.label).join(', ');
35
+ const displayValue = hasValue ? selectedText : placeholder;
36
+ const message = (hint && 'boolean' != typeof error ? error : void 0) || hint;
37
+ const showClear = clear && hasValue && !state.blocked;
38
+ useImperativeHandle(ref, ()=>buttonRef.current, []);
39
+ return /*#__PURE__*/ jsxs("div", {
40
+ className: classnames(index_module.component, index_module[`size-${size}`], {
41
+ [index_module.secondary]: 'secondary' === view,
42
+ [index_module.block]: block,
43
+ [index_module.disabled]: disabled,
44
+ [index_module.readOnly]: readOnly,
45
+ [index_module.error]: Boolean(error),
46
+ [index_module.open]: state.open,
47
+ [index_module.withInnerLabel]: innerLabel,
48
+ [index_module.labelRaised]: innerLabel && raisedLabel
49
+ }, className),
50
+ style: style,
51
+ "data-test-id": getDataTestId('form-control'),
52
+ children: [
53
+ hasLabel && 'outer' === labelView && /*#__PURE__*/ jsx("label", {
54
+ htmlFor: triggerId,
55
+ className: classnames(index_module.label, labelClassName),
56
+ "data-test-id": getDataTestId('label'),
57
+ children: /*#__PURE__*/ jsx(TypographyText, {
58
+ size: 13,
59
+ weight: "medium",
60
+ children: label
61
+ })
62
+ }),
63
+ /*#__PURE__*/ jsxs("div", {
64
+ ref: state.setAnchor,
65
+ className: index_module.field,
66
+ "data-test-id": getDataTestId('field'),
67
+ onClick: (event)=>{
68
+ if (!state.blocked && !event.defaultPrevented && event.target === event.currentTarget) buttonRef.current?.click();
69
+ },
70
+ children: [
71
+ /*#__PURE__*/ jsx("button", {
72
+ ...buttonProps,
73
+ ...state.triggerProps,
74
+ ref: buttonRef,
75
+ id: triggerId,
76
+ type: "button",
77
+ form: form,
78
+ disabled: disabled,
79
+ className: index_module.trigger,
80
+ "data-test-id": dataTestId,
81
+ children: /*#__PURE__*/ jsxs("span", {
82
+ className: index_module.valueColumn,
83
+ children: [
84
+ innerLabel && /*#__PURE__*/ jsx(TypographyText, {
85
+ size: raisedLabel ? 11 : textSizes[size],
86
+ weight: "medium",
87
+ rowLimit: 1,
88
+ className: classnames(index_module.innerLabel, labelClassName),
89
+ dataTestId: getDataTestId('label'),
90
+ children: label
91
+ }),
92
+ /*#__PURE__*/ jsx(TypographyText, {
93
+ size: textSizes[size],
94
+ rowLimit: 1,
95
+ className: classnames(index_module.value, !hasValue && index_module.placeholder),
96
+ dataTestId: getDataTestId('value'),
97
+ children: displayValue
98
+ })
99
+ ]
100
+ })
101
+ }),
102
+ showClear && /*#__PURE__*/ jsx(Button, {
103
+ view: "text",
104
+ size: 32,
105
+ className: index_module.clear,
106
+ "aria-label": "close",
107
+ onMouseDown: (event)=>event.preventDefault(),
108
+ onClick: state.clear,
109
+ dataTestId: getDataTestId('clear'),
110
+ children: /*#__PURE__*/ jsx(CrossIcon, {
111
+ width: 14,
112
+ height: 14,
113
+ "aria-hidden": "true"
114
+ })
115
+ }),
116
+ /*#__PURE__*/ jsx("span", {
117
+ className: index_module.chevron,
118
+ children: /*#__PURE__*/ jsx(ChevronDownIcon, {
119
+ "aria-hidden": "true"
120
+ })
121
+ })
122
+ ]
123
+ }),
124
+ message && /*#__PURE__*/ jsx(TypographyText, {
125
+ tag: "div",
126
+ size: 12,
127
+ className: classnames(index_module.message, messageClassName),
128
+ dataTestId: getDataTestId('boolean' != typeof error && error ? 'error' : 'hint'),
129
+ children: message
130
+ }),
131
+ name && state.selectedIds.map((value)=>/*#__PURE__*/ jsx("input", {
132
+ type: "hidden",
133
+ name: name,
134
+ value: value,
135
+ disabled: disabled,
136
+ form: form
137
+ }, value)),
138
+ /*#__PURE__*/ jsx(Popover, {
139
+ ref: state.popupRef,
140
+ open: state.open,
141
+ anchorElement: state.anchor,
142
+ offset: dropdownOffset,
143
+ useAnchorWidth: true,
144
+ onClose: state.close,
145
+ getPortalContainer: getPortalContainer,
146
+ className: classnames(index_module.dropdown, dropdownClassName),
147
+ dataTestId: getDataTestId('dropdown'),
148
+ children: /*#__PURE__*/ jsx(SelectDropdown, {
149
+ state: state,
150
+ multiple: multiple,
151
+ searchable: searchable,
152
+ searchPlaceholder: searchPlaceholder,
153
+ noResultsText: noResultsText,
154
+ dataTestId: dataTestId
155
+ })
156
+ })
157
+ ]
158
+ });
159
+ });
160
+ Select.displayName = 'Select';
161
+ export { Select };
@@ -0,0 +1,145 @@
1
+ import type { ButtonHTMLAttributes, KeyboardEvent, MouseEvent, ReactNode } from 'react';
2
+ import type { InputProps } from '../Input/index.js';
3
+ import type { PopoverProps } from '../Popover/index.js';
4
+ export type SelectOption = {
5
+ /**
6
+ * Стабильный уникальный идентификатор варианта.
7
+ */
8
+ id: string;
9
+ /**
10
+ * Подпись в поле и в списке. Используется для локального поиска.
11
+ */
12
+ label: string;
13
+ /**
14
+ * Запрещает переключение строки, сохраняя уже выбранное значение.
15
+ * @default false
16
+ */
17
+ disabled?: boolean;
18
+ };
19
+ export type SelectChangeEvent = MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>;
20
+ export type SelectChangePayload<Value = string | null> = {
21
+ /**
22
+ * Значение выбора: id одиночного варианта либо массив id для multiple.
23
+ */
24
+ value: Value;
25
+ /**
26
+ * Выбранные варианты в порядке выбора, включая недоступные строки.
27
+ */
28
+ selectedOptions: SelectOption[];
29
+ };
30
+ type CommonProps = Pick<InputProps, 'view' | 'size' | 'block' | 'hint' | 'error' | 'clear' | 'className' | 'style' | 'labelClassName' | 'messageClassName'> & Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value' | 'defaultValue' | 'onChange' | 'children' | 'name' | 'type' | 'className' | 'style'> & {
31
+ /**
32
+ * Варианты с уникальными id. Порядок строк сохраняется при выборе.
33
+ */
34
+ options: readonly SelectOption[];
35
+ /**
36
+ * Название поля. Внешний label связан с кнопкой выбора через htmlFor и id.
37
+ */
38
+ label?: ReactNode;
39
+ /**
40
+ * Положение названия: снаружи или внутри. Внутренний label поднимается
41
+ * при фокусе, раскрытии или наличии выбранных значений.
42
+ * @default 'outer'
43
+ */
44
+ labelView?: 'outer' | 'inner';
45
+ /**
46
+ * Сохраняет читаемое значение и фокус, запрещая раскрытие, выбор и очистку.
47
+ * @default false
48
+ */
49
+ readOnly?: boolean;
50
+ /**
51
+ * Текст пустого поля.
52
+ * @default 'Выберите значение'
53
+ */
54
+ placeholder?: string;
55
+ /**
56
+ * Поиск внутри списка. По умолчанию фильтрует label без учёта регистра.
57
+ * @default false
58
+ */
59
+ searchable?: boolean;
60
+ /**
61
+ * Placeholder поискового Input.
62
+ * @default 'Поиск по списку'
63
+ */
64
+ searchPlaceholder?: string;
65
+ /**
66
+ * Управляемое раскрытие. Disabled и readOnly всегда скрывают список.
67
+ */
68
+ open?: boolean;
69
+ /**
70
+ * Начальное раскрытие неуправляемого списка.
71
+ * @default false
72
+ */
73
+ defaultOpen?: boolean;
74
+ /**
75
+ * Запрос изменения раскрытия, в том числе при выборе, Escape и клике снаружи.
76
+ */
77
+ onOpenChange?: (open: boolean) => void;
78
+ /**
79
+ * Текст отсутствия совпадений.
80
+ * @default 'Ничего не найдено'
81
+ */
82
+ noResultsText?: string;
83
+ /**
84
+ * Очистка, как у Input, вызывает только onClear, без onChange.
85
+ * Неуправляемое поле очищается само; для управляемого обновите value.
86
+ */
87
+ onClear?: (event: MouseEvent<HTMLButtonElement>) => void;
88
+ /**
89
+ * Имя поля формы. Multiple отправляет отдельное значение для каждого id.
90
+ */
91
+ name?: string;
92
+ /**
93
+ * Дополнительный класс выпадающего списка.
94
+ */
95
+ dropdownClassName?: string;
96
+ /**
97
+ * Контейнер портала. По умолчанию используется body документа поля.
98
+ */
99
+ getPortalContainer?: PopoverProps['getPortalContainer'];
100
+ /**
101
+ * Идентификатор кнопки выбора. Части получают суффиксы -form-control,
102
+ * -field, -label, -value, -hint/-error, -clear, -dropdown, -search,
103
+ * -options, -option-{id} и -empty.
104
+ */
105
+ dataTestId?: string;
106
+ };
107
+ type SingleProps = {
108
+ /**
109
+ * Выбор одного варианта.
110
+ * @default false
111
+ */
112
+ multiple?: false;
113
+ /**
114
+ * Id управляемого выбора. null означает отсутствие выбора.
115
+ */
116
+ value?: string | null;
117
+ /**
118
+ * Начальный id неуправляемого выбора.
119
+ */
120
+ defaultValue?: string | null;
121
+ /**
122
+ * Изменение выбора: событие и { value, selectedOptions }.
123
+ */
124
+ onChange?: (event: SelectChangeEvent, payload: SelectChangePayload) => void;
125
+ };
126
+ type MultipleProps = {
127
+ /**
128
+ * Множественный выбор. Строки переключаются без закрытия списка.
129
+ */
130
+ multiple: true;
131
+ /**
132
+ * Id управляемого выбора в порядке выбора. Пустой массив очищает поле.
133
+ */
134
+ value?: readonly string[];
135
+ /**
136
+ * Начальные id неуправляемого выбора.
137
+ */
138
+ defaultValue?: readonly string[];
139
+ /**
140
+ * Изменение выбора: событие и { value: string[], selectedOptions }.
141
+ */
142
+ onChange?: (event: SelectChangeEvent, payload: SelectChangePayload<string[]>) => void;
143
+ };
144
+ export type SelectProps = CommonProps & (SingleProps | MultipleProps);
145
+ export {};
File without changes
@@ -0,0 +1,34 @@
1
+ @import "../Input/vars.css";
2
+
3
+ :root {
4
+ --select-primary-background-color: var(--input-primary-background-color);
5
+ --select-secondary-background-color: var(--input-secondary-background-color);
6
+ --select-color: var(--input-color);
7
+ --select-placeholder-color: var(--input-placeholder-color);
8
+ --select-label-color: var(--input-label-color);
9
+ --select-hint-color: var(--input-hint-color);
10
+ --select-icon-color: var(--input-icon-color);
11
+ --select-hover-border-color: var(--input-hover-border-color);
12
+ --select-focus-border-color: var(--input-focus-border-color);
13
+ --select-error-border-color: var(--input-error-border-color);
14
+ --select-error-message-color: var(--input-error-message-color);
15
+ --select-disabled-background-color: var(--input-disabled-background-color);
16
+ --select-disabled-color: var(--input-disabled-color);
17
+ --select-read-only-border-color: var(--input-read-only-border-color);
18
+ --select-option-hover-color: var(--input-secondary-background-color);
19
+ --select-option-selected-color: #e0dbce;
20
+ --select-marker-selected-color: #e0dbce;
21
+ --select-border-radius: var(--input-border-radius);
22
+ --select-option-border-radius: var(--border-radius-6);
23
+ --select-gap: var(--input-gap);
24
+ --select-options-gap: var(--size-4);
25
+ --select-width: var(--input-width);
26
+ --select-options-max-height: 240px;
27
+ --select-dropdown-shadow: 0 4px 8px #0000001f;
28
+ --select-padding-40: var(--input-padding-40);
29
+ --select-padding-48: var(--input-padding-48);
30
+ --select-padding-56: var(--input-padding-56);
31
+ --select-padding-64: var(--input-padding-64);
32
+ --select-padding-72: var(--input-padding-72);
33
+ }
34
+
@@ -0,0 +1,2 @@
1
+ export { Space } from './space.js';
2
+ export type { SpaceProps, SpaceSize } from './types.js';
@@ -0,0 +1 @@
1
+ export { Space } from "./space.js";
@@ -0,0 +1,14 @@
1
+ import "./index_module.css";
2
+ const index_module = {
3
+ component: "component-DK13jr",
4
+ vertical: "vertical-MuaZ3s",
5
+ horizontal: "horizontal-GVxqs5",
6
+ start: "start-bYNz01",
7
+ center: "center-PS6vZJ",
8
+ end: "end-KbDlui",
9
+ stretch: "stretch-WhVrSc",
10
+ baseline: "baseline-mcTNmk",
11
+ wrap: "wrap-Ad0xGu",
12
+ fullWidth: "fullWidth-c7M8Hd"
13
+ };
14
+ export default index_module;
@@ -0,0 +1,46 @@
1
+ @import "../../styles/variables.css";
2
+
3
+ .component-DK13jr {
4
+ box-sizing: border-box;
5
+ gap: var(--space-gap);
6
+ min-width: 0;
7
+ display: inline-flex;
8
+ }
9
+
10
+ .vertical-MuaZ3s {
11
+ flex-direction: column;
12
+ }
13
+
14
+ .horizontal-GVxqs5 {
15
+ flex-direction: row;
16
+ }
17
+
18
+ .start-bYNz01 {
19
+ align-items: flex-start;
20
+ }
21
+
22
+ .center-PS6vZJ {
23
+ align-items: center;
24
+ }
25
+
26
+ .end-KbDlui {
27
+ align-items: flex-end;
28
+ }
29
+
30
+ .stretch-WhVrSc {
31
+ align-items: stretch;
32
+ }
33
+
34
+ .baseline-mcTNmk {
35
+ align-items: baseline;
36
+ }
37
+
38
+ .wrap-Ad0xGu {
39
+ flex-wrap: wrap;
40
+ }
41
+
42
+ .fullWidth-c7M8Hd {
43
+ width: 100%;
44
+ display: flex;
45
+ }
46
+
@@ -0,0 +1,9 @@
1
+ export declare const Space: import("react").ForwardRefExoticComponent<Omit<import("react").HTMLAttributes<HTMLDivElement>, "children"> & {
2
+ children: import("react").ReactNode;
3
+ direction?: "horizontal" | "vertical";
4
+ size: import("./types.js").SpaceSize;
5
+ align?: "start" | "center" | "end" | "stretch" | "baseline";
6
+ wrap?: boolean;
7
+ fullWidth?: boolean;
8
+ dataTestId?: string;
9
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,23 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import { Children, forwardRef } from "react";
4
+ import index_module from "./index.module.js";
5
+ const Space = /*#__PURE__*/ forwardRef(({ children, direction = 'vertical', size, align = 'start', wrap = false, fullWidth = false, className, style, dataTestId, ...restProps }, ref)=>{
6
+ if (0 === Children.toArray(children).length) return null;
7
+ return /*#__PURE__*/ jsx("div", {
8
+ ...restProps,
9
+ ref: ref,
10
+ className: classnames(index_module.component, index_module[direction], index_module[align], {
11
+ [index_module.wrap]: wrap,
12
+ [index_module.fullWidth]: fullWidth
13
+ }, className),
14
+ style: {
15
+ '--space-gap': `var(--size-${size})`,
16
+ ...style
17
+ },
18
+ "data-test-id": dataTestId,
19
+ children: children
20
+ });
21
+ });
22
+ Space.displayName = 'Space';
23
+ export { Space };
@@ -0,0 +1,40 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ /**
3
+ * Положительный отступ из общей шкалы --size-*.
4
+ */
5
+ export type SpaceSize = 2 | 4 | 6 | 8 | 12 | 16 | 20 | 22 | 24 | 28 | 30 | 32 | 36 | 40 | 44 | 48 | 56 | 64 | 72 | 80 | 96 | 128;
6
+ export type SpaceProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
7
+ /**
8
+ * Элементы раскладки. Отображаются без дополнительных обёрток.
9
+ */
10
+ children: ReactNode;
11
+ /**
12
+ * Направление раскладки.
13
+ * @default 'vertical'
14
+ */
15
+ direction?: 'horizontal' | 'vertical';
16
+ /**
17
+ * Единый отступ между элементами и строками.
18
+ * Обязательное положительное значение из глобальной шкалы --size-*.
19
+ */
20
+ size: SpaceSize;
21
+ /**
22
+ * Выравнивание по поперечной оси относительно direction.
23
+ * @default 'start'
24
+ */
25
+ align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline';
26
+ /**
27
+ * Разрешает перенос элементов на следующую строку или колонку.
28
+ * @default false
29
+ */
30
+ wrap?: boolean;
31
+ /**
32
+ * Растягивает контейнер на всю ширину родителя.
33
+ * @default false
34
+ */
35
+ fullWidth?: boolean;
36
+ /**
37
+ * Идентификатор корневого элемента для тестов.
38
+ */
39
+ dataTestId?: string;
40
+ };
File without changes
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { Button } from './components/Button/index.js';
2
2
  export type { ButtonProps } from './components/Button/index.js';
3
3
  export { Input } from './components/Input/index.js';
4
4
  export type { InputProps } from './components/Input/index.js';
5
+ export { Select } from './components/Select/index.js';
6
+ export type { SelectProps, SelectOption, SelectChangeEvent, SelectChangePayload, } from './components/Select/index.js';
5
7
  export { Textarea } from './components/Textarea/index.js';
6
8
  export type { TextareaProps } from './components/Textarea/index.js';
7
9
  export { Tab, Tabs } from './components/Tabs/index.js';
@@ -10,6 +12,8 @@ export { Spinner } from './components/Spinner/index.js';
10
12
  export type { SpinnerProps } from './components/Spinner/index.js';
11
13
  export { Status } from './components/Status/index.js';
12
14
  export type { StatusProps } from './components/Status/index.js';
15
+ export { Space } from './components/Space/index.js';
16
+ export type { SpaceProps, SpaceSize } from './components/Space/index.js';
13
17
  export { Typography, TypographyText, TypographyTitle, Text, Title } from './components/Typography/index.js';
14
18
  export type { TextProps, TitleProps, TypographyColor, TypographyWeight, } from './components/Typography/index.js';
15
19
  export { Popover } from './components/Popover/index.js';
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  export { Button } from "./components/Button/index.js";
2
2
  export { Input } from "./components/Input/index.js";
3
+ export { Select } from "./components/Select/index.js";
3
4
  export { Textarea } from "./components/Textarea/index.js";
4
5
  export { Tab, Tabs } from "./components/Tabs/index.js";
5
6
  export { Spinner } from "./components/Spinner/index.js";
6
7
  export { Status } from "./components/Status/index.js";
8
+ export { Space } from "./components/Space/index.js";
7
9
  export { Text, Title, Typography, TypographyText, TypographyTitle } from "./components/Typography/index.js";
8
10
  export { Popover } from "./components/Popover/index.js";
9
11
  export { Collapse } from "./components/Collapse/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koslibs/components",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Shared React components for koslibs projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",