@koslibs/components 1.16.0 → 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/CHANGELOG.md +12 -0
- package/dist/components/Chips/chips.d.ts +2 -0
- package/dist/components/Chips/chips.js +48 -0
- package/dist/components/Chips/hooks/use-chips-state.d.ts +7 -0
- package/dist/components/Chips/hooks/use-chips-state.js +56 -0
- package/dist/components/Chips/index.d.ts +2 -0
- package/dist/components/Chips/index.js +1 -0
- package/dist/components/Chips/index.module.js +9 -0
- package/dist/components/Chips/index_module.css +99 -0
- package/dist/components/Chips/types.d.ts +86 -0
- package/dist/components/Chips/types.js +0 -0
- package/dist/components/Chips/utils/normalize-value.d.ts +2 -0
- package/dist/components/Chips/utils/normalize-value.js +9 -0
- package/dist/components/Chips/vars.css +19 -0
- package/dist/components/Select/index.d.ts +1 -1
- package/dist/components/Select/types.d.ts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @koslibs/components
|
|
2
2
|
|
|
3
|
+
## 1.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#21](https://github.com/koslibs/components/pull/21) [`0c010e3`](https://github.com/koslibs/components/commit/0c010e36657f39517053465e5d4864936b11f164) Thanks [@holypower777](https://github.com/holypower777)! - Export `SelectSingleProps` and `SelectMultipleProps` from the package root and the `Select` entry point so consumers can type change handlers for either selection mode directly.
|
|
8
|
+
|
|
9
|
+
## 1.17.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#20](https://github.com/koslibs/components/pull/20) [`ea31f0a`](https://github.com/koslibs/components/commit/ea31f0a1ece96d62c21f943de86459946fb1a5fe) Thanks [@holypower777](https://github.com/holypower777)! - Добавлен Chips: одиночный и множественный выбор, управляемые и внутренние значения, блокировка группы и пунктов, состояния из Figma с плавными переходами, перенос полных подписей, нативные формы, Storybook и публичные типы. All обрабатывается как обычный пункт.
|
|
14
|
+
|
|
3
15
|
## 1.16.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import { forwardRef, useId, useImperativeHandle } from "react";
|
|
4
|
+
import { useDataTestId } from "../../hooks/use-data-test-id.js";
|
|
5
|
+
import { TypographyText } from "../Typography/text.js";
|
|
6
|
+
import { useChipsState } from "./hooks/use-chips-state.js";
|
|
7
|
+
import index_module from "./index.module.js";
|
|
8
|
+
const Chips = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
9
|
+
const { items, multiselect = false, value: _value, defaultValue: _defaultValue, onChange: _onChange, disabled = false, form, className, listClassName, itemClassName, dataTestId, ...restProps } = props;
|
|
10
|
+
const groupName = useId();
|
|
11
|
+
const getId = useDataTestId(dataTestId);
|
|
12
|
+
const state = useChipsState(props);
|
|
13
|
+
useImperativeHandle(ref, ()=>state.groupRef.current, []);
|
|
14
|
+
return /*#__PURE__*/ jsx("fieldset", {
|
|
15
|
+
...restProps,
|
|
16
|
+
ref: state.groupRef,
|
|
17
|
+
form: form,
|
|
18
|
+
disabled: disabled,
|
|
19
|
+
className: classnames(index_module.component, className),
|
|
20
|
+
"data-test-id": dataTestId,
|
|
21
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
22
|
+
className: classnames(index_module.list, listClassName),
|
|
23
|
+
children: items.map((item)=>/*#__PURE__*/ jsxs("label", {
|
|
24
|
+
className: index_module.item,
|
|
25
|
+
children: [
|
|
26
|
+
/*#__PURE__*/ jsx("input", {
|
|
27
|
+
type: multiselect ? 'checkbox' : 'radio',
|
|
28
|
+
name: groupName,
|
|
29
|
+
form: groupName,
|
|
30
|
+
value: item.value,
|
|
31
|
+
checked: state.selected.has(item.value),
|
|
32
|
+
disabled: disabled || item.disabled,
|
|
33
|
+
onChange: state.handleChange,
|
|
34
|
+
className: index_module.input,
|
|
35
|
+
"data-test-id": getId(item.value)
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ jsx(TypographyText, {
|
|
38
|
+
size: 14,
|
|
39
|
+
className: classnames(index_module.chip, itemClassName),
|
|
40
|
+
children: item.label
|
|
41
|
+
})
|
|
42
|
+
]
|
|
43
|
+
}, item.value))
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
Chips.displayName = 'Chips';
|
|
48
|
+
export { Chips };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ChangeEvent } from 'react';
|
|
2
|
+
import type { ChipsProps } from '../types.js';
|
|
3
|
+
export declare function useChipsState(props: ChipsProps): {
|
|
4
|
+
groupRef: import("react").RefObject<HTMLFieldSetElement>;
|
|
5
|
+
selected: Set<string>;
|
|
6
|
+
handleChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
7
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { normalizeValue } from "../utils/normalize-value.js";
|
|
3
|
+
function useChipsState(props) {
|
|
4
|
+
const { items, multiselect = false, defaultValue } = props;
|
|
5
|
+
const controlled = void 0 !== props.value;
|
|
6
|
+
const groupRef = useRef(null);
|
|
7
|
+
const [localValue, setLocalValue] = useState(defaultValue);
|
|
8
|
+
const value = normalizeValue(items, multiselect, controlled ? props.value : localValue);
|
|
9
|
+
const selected = new Set(Array.isArray(value) ? value : [
|
|
10
|
+
value
|
|
11
|
+
].filter((item)=>null !== item));
|
|
12
|
+
useEffect(()=>{
|
|
13
|
+
const form = groupRef.current?.form;
|
|
14
|
+
let active = true;
|
|
15
|
+
const handleReset = (event)=>{
|
|
16
|
+
queueMicrotask(()=>{
|
|
17
|
+
if (active && !event.defaultPrevented && !controlled) setLocalValue(defaultValue);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
form?.addEventListener('reset', handleReset);
|
|
21
|
+
return ()=>{
|
|
22
|
+
active = false;
|
|
23
|
+
form?.removeEventListener('reset', handleReset);
|
|
24
|
+
};
|
|
25
|
+
}, [
|
|
26
|
+
props.form,
|
|
27
|
+
controlled,
|
|
28
|
+
defaultValue
|
|
29
|
+
]);
|
|
30
|
+
const handleChange = (event)=>{
|
|
31
|
+
const item = items.find((candidate)=>candidate.value === event.currentTarget.value);
|
|
32
|
+
if (props.disabled || !item || item.disabled) return;
|
|
33
|
+
if (props.multiselect) {
|
|
34
|
+
const current = Array.isArray(value) ? value : [];
|
|
35
|
+
const next = event.currentTarget.checked ? [
|
|
36
|
+
...current,
|
|
37
|
+
item.value
|
|
38
|
+
] : current.filter((candidate)=>candidate !== item.value);
|
|
39
|
+
if (!controlled) setLocalValue(next);
|
|
40
|
+
props.onChange?.(event, {
|
|
41
|
+
value: next
|
|
42
|
+
});
|
|
43
|
+
} else if (value !== item.value) {
|
|
44
|
+
if (!controlled) setLocalValue(item.value);
|
|
45
|
+
props.onChange?.(event, {
|
|
46
|
+
value: item.value
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
groupRef,
|
|
52
|
+
selected,
|
|
53
|
+
handleChange
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export { useChipsState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Chips } from "./chips.js";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
@import "../../styles/theme.css";
|
|
2
|
+
@import "./vars.css";
|
|
3
|
+
|
|
4
|
+
.component-YzqK3S {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
color: var(--chips-color);
|
|
8
|
+
font-family: var(--font-primary);
|
|
9
|
+
border: 0;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.list-Dlab5I {
|
|
15
|
+
gap: var(--chips-gap);
|
|
16
|
+
flex-wrap: wrap;
|
|
17
|
+
align-items: flex-start;
|
|
18
|
+
display: flex;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.item-Q7WaXH {
|
|
22
|
+
min-width: 0;
|
|
23
|
+
max-width: 100%;
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.input-UTMIEW {
|
|
29
|
+
z-index: 1;
|
|
30
|
+
opacity: 0;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
margin: 0;
|
|
35
|
+
position: absolute;
|
|
36
|
+
inset: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.chip-jqwYZi {
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
min-width: 0;
|
|
42
|
+
min-height: var(--chips-height);
|
|
43
|
+
padding: var(--chips-padding-y) var(--chips-padding-x);
|
|
44
|
+
border-radius: var(--chips-radius);
|
|
45
|
+
background: var(--chips-background);
|
|
46
|
+
color: var(--chips-color);
|
|
47
|
+
text-align: center;
|
|
48
|
+
overflow-wrap: anywhere;
|
|
49
|
+
line-height: 20px;
|
|
50
|
+
transition: background-color .1s ease-in-out, color .1s ease-in-out;
|
|
51
|
+
display: block;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.input-UTMIEW:checked + .chip-jqwYZi {
|
|
55
|
+
background: var(--chips-selected-background);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@media (hover: hover) {
|
|
59
|
+
.input-UTMIEW:not(:disabled):hover + .chip-jqwYZi {
|
|
60
|
+
background: var(--chips-hover-background);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.input-UTMIEW:not(:disabled):checked:hover + .chip-jqwYZi {
|
|
64
|
+
background: var(--chips-selected-hover-background);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.input-UTMIEW:not(:disabled):active + .chip-jqwYZi {
|
|
69
|
+
background: var(--chips-active-background);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.input-UTMIEW:not(:disabled):checked:active + .chip-jqwYZi {
|
|
73
|
+
background: var(--chips-selected-active-background);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.input-UTMIEW:focus-visible + .chip-jqwYZi {
|
|
77
|
+
outline: 2px solid var(--chips-focus-color);
|
|
78
|
+
outline-offset: 2px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.input-UTMIEW:disabled {
|
|
82
|
+
cursor: not-allowed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.input-UTMIEW:disabled + .chip-jqwYZi {
|
|
86
|
+
background: var(--chips-disabled-background);
|
|
87
|
+
color: var(--chips-disabled-color);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.input-UTMIEW:disabled:checked + .chip-jqwYZi {
|
|
91
|
+
background: var(--chips-disabled-selected-background);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@media (prefers-reduced-motion: reduce) {
|
|
95
|
+
.chip-jqwYZi {
|
|
96
|
+
transition: none;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ChangeEvent, FieldsetHTMLAttributes } from 'react';
|
|
2
|
+
export type ChipsItem = {
|
|
3
|
+
/**
|
|
4
|
+
* Уникальное значение пункта.
|
|
5
|
+
*/
|
|
6
|
+
value: string;
|
|
7
|
+
/**
|
|
8
|
+
* Полная текстовая подпись пункта.
|
|
9
|
+
*/
|
|
10
|
+
label: string;
|
|
11
|
+
/**
|
|
12
|
+
* Блокирует переключение пункта, сохраняя его выбранное состояние.
|
|
13
|
+
* @default false
|
|
14
|
+
*/
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type ChipsChangePayload<Value = string | null> = {
|
|
18
|
+
/**
|
|
19
|
+
* Выбранное значение или массив значений в порядке выбора.
|
|
20
|
+
*/
|
|
21
|
+
value: Value;
|
|
22
|
+
};
|
|
23
|
+
type CommonProps = Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'onChange' | 'defaultValue' | 'children' | 'name' | 'disabled'> & {
|
|
24
|
+
/**
|
|
25
|
+
* Пункты с уникальными value. All обрабатывается как обычный пункт.
|
|
26
|
+
*/
|
|
27
|
+
items: readonly ChipsItem[];
|
|
28
|
+
/**
|
|
29
|
+
* Блокирует все пункты группы, сохраняя выбор.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Дополнительный CSS-класс списка пунктов.
|
|
35
|
+
*/
|
|
36
|
+
listClassName?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Дополнительный CSS-класс каждого chip.
|
|
39
|
+
*/
|
|
40
|
+
itemClassName?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Идентификатор группы для тестов. Пункты получают суффикс с их value.
|
|
43
|
+
*/
|
|
44
|
+
dataTestId?: string;
|
|
45
|
+
};
|
|
46
|
+
type SingleProps = {
|
|
47
|
+
/**
|
|
48
|
+
* Выбирает один пункт. Повторное нажатие сохраняет выбор.
|
|
49
|
+
* @default false
|
|
50
|
+
*/
|
|
51
|
+
multiselect?: false;
|
|
52
|
+
/**
|
|
53
|
+
* Управляемое значение; null означает пустой выбор.
|
|
54
|
+
*/
|
|
55
|
+
value?: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Начальное значение неуправляемой группы.
|
|
58
|
+
* @default null
|
|
59
|
+
*/
|
|
60
|
+
defaultValue?: string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Изменение пользователем выбранного значения.
|
|
63
|
+
*/
|
|
64
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>, payload: ChipsChangePayload) => void;
|
|
65
|
+
};
|
|
66
|
+
type MultipleProps = {
|
|
67
|
+
/**
|
|
68
|
+
* Независимое переключение нескольких пунктов.
|
|
69
|
+
*/
|
|
70
|
+
multiselect: true;
|
|
71
|
+
/**
|
|
72
|
+
* Управляемый массив выбранных значений.
|
|
73
|
+
*/
|
|
74
|
+
value?: readonly string[];
|
|
75
|
+
/**
|
|
76
|
+
* Начальный массив значений неуправляемой группы.
|
|
77
|
+
* @default []
|
|
78
|
+
*/
|
|
79
|
+
defaultValue?: readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* Изменение пользователем набора выбранных значений.
|
|
82
|
+
*/
|
|
83
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>, payload: ChipsChangePayload<string[]>) => void;
|
|
84
|
+
};
|
|
85
|
+
export type ChipsProps = CommonProps & (SingleProps | MultipleProps);
|
|
86
|
+
export {};
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function normalizeValue(items, multiselect, value) {
|
|
2
|
+
const available = new Set(items.map((item)=>item.value));
|
|
3
|
+
if (!multiselect) return 'string' == typeof value && available.has(value) ? value : null;
|
|
4
|
+
if (!Array.isArray(value)) return [];
|
|
5
|
+
return [
|
|
6
|
+
...new Set(value.filter((item)=>'string' == typeof item && available.has(item)))
|
|
7
|
+
];
|
|
8
|
+
}
|
|
9
|
+
export { normalizeValue };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--chips-gap: var(--size-8);
|
|
3
|
+
--chips-height: var(--size-32);
|
|
4
|
+
--chips-padding-x: var(--size-12);
|
|
5
|
+
--chips-padding-y: var(--size-6);
|
|
6
|
+
--chips-radius: var(--border-radius-pill);
|
|
7
|
+
--chips-color: #1c1a18;
|
|
8
|
+
--chips-background: transparent;
|
|
9
|
+
--chips-hover-background: #e9e4d9;
|
|
10
|
+
--chips-active-background: #dcd4c5;
|
|
11
|
+
--chips-selected-background: #fdde9b;
|
|
12
|
+
--chips-selected-hover-background: #f4d38d;
|
|
13
|
+
--chips-selected-active-background: #e9c479;
|
|
14
|
+
--chips-disabled-background: #cfd1cb;
|
|
15
|
+
--chips-disabled-selected-background: #f7e6c2;
|
|
16
|
+
--chips-disabled-color: #858981;
|
|
17
|
+
--chips-focus-color: #ff6b0a;
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Select } from './select.js';
|
|
2
|
-
export type { SelectProps, SelectOption, SelectChangeEvent, SelectChangePayload } from './types.js';
|
|
2
|
+
export type { SelectProps, SelectSingleProps, SelectMultipleProps, SelectOption, SelectChangeEvent, SelectChangePayload, } from './types.js';
|
|
@@ -141,5 +141,7 @@ type MultipleProps = {
|
|
|
141
141
|
*/
|
|
142
142
|
onChange?: (event: SelectChangeEvent, payload: SelectChangePayload<string[]>) => void;
|
|
143
143
|
};
|
|
144
|
-
export type
|
|
144
|
+
export type SelectSingleProps = CommonProps & SingleProps;
|
|
145
|
+
export type SelectMultipleProps = CommonProps & MultipleProps;
|
|
146
|
+
export type SelectProps = SelectSingleProps | SelectMultipleProps;
|
|
145
147
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { Button } from './components/Button/index.js';
|
|
2
2
|
export type { ButtonProps } from './components/Button/index.js';
|
|
3
|
+
export { Chips } from './components/Chips/index.js';
|
|
4
|
+
export type { ChipsProps, ChipsItem, ChipsChangePayload } from './components/Chips/index.js';
|
|
3
5
|
export { Input } from './components/Input/index.js';
|
|
4
6
|
export type { InputProps } from './components/Input/index.js';
|
|
5
7
|
export { Datepicker, DatepickerPanel } from './components/Datepicker/index.js';
|
|
6
8
|
export type { DatepickerProps, DatepickerPanelProps, DatepickerRange, DatepickerChangePayload, } from './components/Datepicker/index.js';
|
|
7
9
|
export { Select } from './components/Select/index.js';
|
|
8
|
-
export type { SelectProps, SelectOption, SelectChangeEvent, SelectChangePayload, } from './components/Select/index.js';
|
|
10
|
+
export type { SelectProps, SelectSingleProps, SelectMultipleProps, SelectOption, SelectChangeEvent, SelectChangePayload, } from './components/Select/index.js';
|
|
9
11
|
export { Textarea } from './components/Textarea/index.js';
|
|
10
12
|
export type { TextareaProps } from './components/Textarea/index.js';
|
|
11
13
|
export { Tab, Tabs } from './components/Tabs/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Button } from "./components/Button/index.js";
|
|
2
|
+
export { Chips } from "./components/Chips/index.js";
|
|
2
3
|
export { Input } from "./components/Input/index.js";
|
|
3
4
|
export { Datepicker, DatepickerPanel } from "./components/Datepicker/index.js";
|
|
4
5
|
export { Select } from "./components/Select/index.js";
|