@koobiq/react-components 0.3.0 → 0.4.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.
Files changed (43) hide show
  1. package/dist/components/Button/Button.js +4 -0
  2. package/dist/components/FieldComponents/FieldContentGroup/FieldContentGroup.js +5 -6
  3. package/dist/components/FieldComponents/FieldContentGroup/FieldContentGroupContext.d.ts +2 -1
  4. package/dist/components/IconButton/IconButton.js +3 -0
  5. package/dist/components/List/List.d.ts +2 -1
  6. package/dist/components/List/List.js +16 -3
  7. package/dist/components/List/List.module.css.js +4 -1
  8. package/dist/components/Menu/Menu.js +1 -1
  9. package/dist/components/Menu/components/MenuInner/MenuInner.js +1 -1
  10. package/dist/components/Select/Select.d.ts +3 -2
  11. package/dist/components/Select/Select.js +83 -101
  12. package/dist/components/Select/Select.module.css.js +8 -2
  13. package/dist/components/Select/components/SelectList/SelectList.d.ts +8 -0
  14. package/dist/components/Select/components/SelectList/SelectList.js +51 -0
  15. package/dist/components/Select/components/SelectList/SelectList.module.css.js +11 -0
  16. package/dist/components/Select/components/SelectList/index.d.ts +1 -0
  17. package/dist/components/Select/components/SelectOption/SelectOption.d.ts +7 -0
  18. package/dist/components/Select/components/SelectOption/SelectOption.js +42 -0
  19. package/dist/components/Select/components/SelectOption/index.d.ts +1 -0
  20. package/dist/components/Select/components/Tag/Tag.d.ts +17 -0
  21. package/dist/components/Select/components/Tag/Tag.js +64 -0
  22. package/dist/components/Select/components/Tag/index.d.ts +1 -0
  23. package/dist/components/Select/components/Tag/intl.json.js +7 -0
  24. package/dist/components/Select/components/Tag/utils.d.ts +3 -0
  25. package/dist/components/Select/components/Tag/utils.js +9 -0
  26. package/dist/components/Select/components/TagGroup/TagGroup.d.ts +13 -0
  27. package/dist/components/Select/components/TagGroup/TagGroup.js +24 -0
  28. package/dist/components/Select/components/TagGroup/TagGroup.module.css.js +23 -0
  29. package/dist/components/Select/components/TagGroup/TagGroupMultiline.d.ts +3 -0
  30. package/dist/components/Select/components/TagGroup/TagGroupMultiline.js +47 -0
  31. package/dist/components/Select/components/TagGroup/TagGroupResponsive.d.ts +3 -0
  32. package/dist/components/Select/components/TagGroup/TagGroupResponsive.js +66 -0
  33. package/dist/components/Select/components/TagGroup/index.d.ts +1 -0
  34. package/dist/components/Select/components/TagGroup/utils.d.ts +1 -0
  35. package/dist/components/Select/components/TagGroup/utils.js +4 -0
  36. package/dist/components/Select/components/index.d.ts +3 -0
  37. package/dist/components/Select/intl.d.ts +2 -0
  38. package/dist/components/Select/intl.js +15 -0
  39. package/dist/components/Select/types.d.ts +35 -42
  40. package/dist/components/Select/types.js +7 -0
  41. package/dist/index.js +2 -0
  42. package/dist/style.css +230 -130
  43. package/package.json +5 -5
@@ -0,0 +1,64 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { useLocalizedStringFormatter, mergeProps, clsx, isNotNil } from "@koobiq/react-core";
4
+ import { IconXmarkS16 } from "@koobiq/react-icons";
5
+ import { utilClasses } from "../../../../styles/utility.js";
6
+ import s from "../../../TagGroup/components/Tag/Tag.module.css.js";
7
+ import intlMessages from "./intl.json.js";
8
+ import { matchVariantToCloseButton } from "./utils.js";
9
+ import { IconButton } from "../../../IconButton/IconButton.js";
10
+ const textNormalMedium = utilClasses.typography["text-normal-medium"];
11
+ const Tag = forwardRef((props, ref) => {
12
+ const {
13
+ variant = "theme-fade",
14
+ icon,
15
+ className,
16
+ style,
17
+ isDisabled,
18
+ children,
19
+ onRemove
20
+ } = props;
21
+ const stringFormatter = useLocalizedStringFormatter(intlMessages);
22
+ const rootProps = mergeProps({
23
+ className: clsx(
24
+ s.base,
25
+ s[variant],
26
+ isDisabled && s.disabled,
27
+ textNormalMedium,
28
+ className
29
+ ),
30
+ style
31
+ });
32
+ const removeButtonProps = {
33
+ isCompact: true,
34
+ isDisabled,
35
+ className: s.cancelIcon,
36
+ variant: matchVariantToCloseButton[variant],
37
+ "aria-label": stringFormatter.format("remove")
38
+ };
39
+ const contentProps = mergeProps({
40
+ className: s.content
41
+ });
42
+ const iconProps = mergeProps({
43
+ className: s.icon
44
+ });
45
+ return /* @__PURE__ */ jsxs("div", { ref, ...rootProps, children: [
46
+ isNotNil(icon) && /* @__PURE__ */ jsx("span", { ...iconProps, children: icon }),
47
+ isNotNil(children) && /* @__PURE__ */ jsx("span", { ...contentProps, children }),
48
+ /* @__PURE__ */ jsx(
49
+ IconButton,
50
+ {
51
+ as: "div",
52
+ size: "l",
53
+ ...removeButtonProps,
54
+ tabIndex: void 0,
55
+ onPress: onRemove,
56
+ children: /* @__PURE__ */ jsx(IconXmarkS16, {})
57
+ }
58
+ )
59
+ ] });
60
+ });
61
+ Tag.displayName = "SelectTag";
62
+ export {
63
+ Tag
64
+ };
@@ -0,0 +1 @@
1
+ export * from './Tag';
@@ -0,0 +1,7 @@
1
+ const intlMessages = {
2
+ "ru-RU": { "remove": "Удалить" },
3
+ "en-US": { "remove": "Remove" }
4
+ };
5
+ export {
6
+ intlMessages as default
7
+ };
@@ -0,0 +1,3 @@
1
+ import type { IconButtonPropVariant } from '../../../IconButton';
2
+ import type { TagGroupPropVariant } from '../../../TagGroup';
3
+ export declare const matchVariantToCloseButton: Record<TagGroupPropVariant, IconButtonPropVariant>;
@@ -0,0 +1,9 @@
1
+ const matchVariantToCloseButton = {
2
+ "theme-fade": "theme",
3
+ "contrast-fade": "fade-contrast",
4
+ "error-fade": "error",
5
+ "warning-fade": "warning"
6
+ };
7
+ export {
8
+ matchVariantToCloseButton
9
+ };
@@ -0,0 +1,13 @@
1
+ import type { FC } from 'react';
2
+ import type { MultiSelectState } from '@koobiq/react-primitives';
3
+ import type { SelectPropSelectedTagsOverflow } from '../../types';
4
+ export type TagGroupProps<T> = {
5
+ state: MultiSelectState<T>;
6
+ states: {
7
+ isInvalid?: boolean;
8
+ isDisabled?: boolean;
9
+ isRequired?: boolean;
10
+ };
11
+ selectedTagsOverflow?: SelectPropSelectedTagsOverflow;
12
+ };
13
+ export declare const TagGroup: FC<TagGroupProps<unknown>>;
@@ -0,0 +1,24 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { logger } from "@koobiq/logger";
3
+ import { TagGroupMultiline } from "./TagGroupMultiline.js";
4
+ import { TagGroupResponsive } from "./TagGroupResponsive.js";
5
+ function assertNever(x) {
6
+ logger.error(`Unhandled selectedTagsOverflow variant: ${x}`);
7
+ return null;
8
+ }
9
+ const TagGroup = ({
10
+ selectedTagsOverflow = "responsive",
11
+ ...rest
12
+ }) => {
13
+ switch (selectedTagsOverflow) {
14
+ case "responsive":
15
+ return /* @__PURE__ */ jsx(TagGroupResponsive, { ...rest });
16
+ case "multiline":
17
+ return /* @__PURE__ */ jsx(TagGroupMultiline, { ...rest });
18
+ default:
19
+ return assertNever(selectedTagsOverflow);
20
+ }
21
+ };
22
+ export {
23
+ TagGroup
24
+ };
@@ -0,0 +1,23 @@
1
+ const container = "kbq-taggroup-container-c4d544";
2
+ const hasStartAddon = "kbq-taggroup-hasStartAddon-40d1f9";
3
+ const base = "kbq-taggroup-8253a5";
4
+ const tag = "kbq-taggroup-tag-b9306f";
5
+ const more = "kbq-taggroup-more-b2d6b9";
6
+ const hidden = "kbq-taggroup-hidden-eadb46";
7
+ const s = {
8
+ container,
9
+ hasStartAddon,
10
+ base,
11
+ tag,
12
+ more,
13
+ hidden
14
+ };
15
+ export {
16
+ base,
17
+ container,
18
+ s as default,
19
+ hasStartAddon,
20
+ hidden,
21
+ more,
22
+ tag
23
+ };
@@ -0,0 +1,3 @@
1
+ import type { FC } from 'react';
2
+ import type { TagGroupProps } from './TagGroup';
3
+ export declare const TagGroupMultiline: FC<TagGroupProps<unknown>>;
@@ -0,0 +1,47 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useLocalizedStringFormatter, clsx } from "@koobiq/react-core";
3
+ import intlMessages from "../../intl.js";
4
+ import s from "./TagGroup.module.css.js";
5
+ import { Tag } from "../Tag/Tag.js";
6
+ import { useFieldInputGroup } from "../../../FieldComponents/FieldContentGroup/FieldContentGroupContext.js";
7
+ const TagGroupMultiline = ({
8
+ state,
9
+ states
10
+ }) => {
11
+ const { isDisabled, isInvalid } = states;
12
+ const t = useLocalizedStringFormatter(intlMessages);
13
+ const { hasStartAddon } = useFieldInputGroup();
14
+ return /* @__PURE__ */ jsx(
15
+ "div",
16
+ {
17
+ className: clsx(s.container, hasStartAddon && s.hasStartAddon),
18
+ "aria-hidden": true,
19
+ children: /* @__PURE__ */ jsx(
20
+ "div",
21
+ {
22
+ className: s.base,
23
+ "data-limit-tags": "multiline",
24
+ "aria-label": t.format("selected items"),
25
+ children: state.selectedItems?.map((item) => /* @__PURE__ */ jsx(
26
+ Tag,
27
+ {
28
+ className: s.tag,
29
+ isDisabled,
30
+ variant: isInvalid ? "error-fade" : "contrast-fade",
31
+ onRemove: () => {
32
+ if (state.selectionManager.isSelected(item.key)) {
33
+ state.selectionManager.toggleSelection(item.key);
34
+ }
35
+ },
36
+ children: item.textValue
37
+ },
38
+ item.key
39
+ ))
40
+ }
41
+ )
42
+ }
43
+ );
44
+ };
45
+ export {
46
+ TagGroupMultiline
47
+ };
@@ -0,0 +1,3 @@
1
+ import type { FC } from 'react';
2
+ import type { TagGroupProps } from './TagGroup';
3
+ export declare const TagGroupResponsive: FC<TagGroupProps<unknown>>;
@@ -0,0 +1,66 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useHideOverflowItems, useLocalizedStringFormatter, clsx } from "@koobiq/react-core";
3
+ import intlMessages from "../../intl.js";
4
+ import s from "./TagGroup.module.css.js";
5
+ import { getHiddenCount } from "./utils.js";
6
+ import { useFieldInputGroup } from "../../../FieldComponents/FieldContentGroup/FieldContentGroupContext.js";
7
+ import { Tag } from "../Tag/Tag.js";
8
+ const TagGroupResponsive = ({
9
+ state,
10
+ states
11
+ }) => {
12
+ const { isDisabled, isInvalid } = states;
13
+ const length = state?.selectedItems?.length || 0;
14
+ const { parentRef, visibleMap, itemsRefs } = useHideOverflowItems({
15
+ length: length + 1,
16
+ pinnedIndex: 0
17
+ });
18
+ const hiddenCount = getHiddenCount(visibleMap);
19
+ const t = useLocalizedStringFormatter(intlMessages);
20
+ const { hasStartAddon } = useFieldInputGroup();
21
+ return /* @__PURE__ */ jsxs(
22
+ "div",
23
+ {
24
+ className: clsx(s.container, hasStartAddon && s.hasStartAddon),
25
+ ref: parentRef,
26
+ "aria-hidden": true,
27
+ children: [
28
+ /* @__PURE__ */ jsx(
29
+ "div",
30
+ {
31
+ className: s.base,
32
+ "data-limit-tags": "responsive",
33
+ "aria-label": t.format("selected items"),
34
+ children: state.selectedItems?.map((item, i) => /* @__PURE__ */ jsx(
35
+ Tag,
36
+ {
37
+ ref: itemsRefs[i],
38
+ className: clsx(s.tag, !visibleMap[i] && s.hidden),
39
+ isDisabled,
40
+ variant: isInvalid ? "error-fade" : "contrast-fade",
41
+ onRemove: () => {
42
+ if (state.selectionManager.isSelected(item.key)) {
43
+ state.selectionManager.toggleSelection(item.key);
44
+ }
45
+ },
46
+ children: item.textValue
47
+ },
48
+ item.key
49
+ ))
50
+ }
51
+ ),
52
+ /* @__PURE__ */ jsx(
53
+ "div",
54
+ {
55
+ ref: itemsRefs[itemsRefs.length - 1],
56
+ className: clsx(s.more, !visibleMap[length] && s.hidden),
57
+ children: t.format("more", { count: hiddenCount })
58
+ }
59
+ )
60
+ ]
61
+ }
62
+ );
63
+ };
64
+ export {
65
+ TagGroupResponsive
66
+ };
@@ -0,0 +1 @@
1
+ export * from './TagGroup';
@@ -0,0 +1 @@
1
+ export declare const getHiddenCount: (map: boolean[]) => number;
@@ -0,0 +1,4 @@
1
+ const getHiddenCount = (map) => map.filter((item) => !item).length;
2
+ export {
3
+ getHiddenCount
4
+ };
@@ -0,0 +1,3 @@
1
+ export * from './SelectList';
2
+ export * from './Tag';
3
+ export * from './TagGroup';
@@ -0,0 +1,2 @@
1
+ declare const _default: Record<string, Record<string, string>>;
2
+ export default _default;
@@ -0,0 +1,15 @@
1
+ const intlMessages = {
2
+ "ru-RU": {
3
+ "selected items": "Выбранные элементы",
4
+ clear: "Очистить",
5
+ more: ({ count }) => `еще ${count}`
6
+ },
7
+ "en-US": {
8
+ "selected items": "Selected items",
9
+ clear: "Clear",
10
+ more: ({ count }) => `${count} more`
11
+ }
12
+ };
13
+ export {
14
+ intlMessages as default
15
+ };
@@ -1,49 +1,38 @@
1
1
  import type { ComponentRef, CSSProperties, ReactElement, ReactNode, Ref } from 'react';
2
- import type { ExtendableProps, Node } from '@koobiq/react-core';
3
- import type { AriaSelectProps } from '@koobiq/react-primitives';
2
+ import type { ExtendableProps } from '@koobiq/react-core';
3
+ import type { AriaSelectProps, MultiSelectState, useMultiSelectState } from '@koobiq/react-primitives';
4
4
  import type { FieldErrorProps, FieldLabelProps, FieldSelectProps, FieldCaptionProps, FieldInputGroupProps } from '../FieldComponents';
5
+ import type { IconButtonProps } from '../IconButton';
5
6
  import type { ListProps } from '../List';
6
7
  import type { PopoverProps } from '../Popover';
7
- export type SelectKey = string | number;
8
- type SelectDeprecatedProps = {
9
- /**
10
- * If `true`, the component is disabled.
11
- * @default false
12
- * @deprecated
13
- * The "disabled" prop is deprecated. Use "isDisabled" prop to replace it.
14
- */
15
- disabled?: boolean;
16
- /**
17
- * If `true`, the input will indicate an error.
18
- * @default false
19
- * @deprecated
20
- * The "error" prop is deprecated. Use "isInvalid" prop to replace it.
21
- */
22
- error?: boolean;
23
- /**
24
- * If `true`, the label is displayed as required and the input element is required.
25
- * @default false
26
- * @deprecated
27
- * The "required" prop is deprecated. Use "isRequired" prop to replace it.
28
- */
29
- required?: boolean;
8
+ export declare const selectPropSelectedTagsOverflow: readonly ["multiline", "responsive"];
9
+ export type SelectPropSelectedTagsOverflow = (typeof selectPropSelectedTagsOverflow)[number];
10
+ export type SelectProps<T> = ExtendableProps<{
30
11
  /**
31
- * Sets the open state of the menu.
32
- * @deprecated
33
- * The "open" prop is deprecated. Use "isOpen" prop to replace it.
12
+ * Defines how selected tags are displayed when they exceed the available space.
13
+ *
14
+ *- `"multiline"` tags wrap to multiple lines.
15
+ *- `"responsive"` — tags collapse into a summary (e.g., "3 more").
16
+ * @default "responsive"
34
17
  */
35
- open?: boolean;
18
+ selectedTagsOverflow?: SelectPropSelectedTagsOverflow;
19
+ /** Handler that is called when the clear button is clicked. */
20
+ onClear?: () => void;
21
+ /** Sets the CSS [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. */
22
+ className?: string;
23
+ /** The initial selected keys in the collection (uncontrolled). */
24
+ defaultSelectedKeys?: Parameters<typeof useMultiSelectState>['0']['defaultSelectedKeys'];
25
+ /** Whether the field can be emptied. */
26
+ isClearable?: boolean;
27
+ /** Handler that is called when the selection changes. */
28
+ onSelectionChange?: Parameters<typeof useMultiSelectState>['0']['onSelectionChange'];
29
+ /** The currently selected keys in the collection (controlled). */
30
+ selectedKeys?: Parameters<typeof useMultiSelectState>['0']['selectedKeys'];
36
31
  /**
37
- * If `true`, the label is hidden. Be sure to add aria-label to the input element.
38
- * @default false
39
- * @deprecated
40
- * The "hiddenLabel" prop is deprecated. Use "isLabelHidden" prop to replace it.
32
+ * The type of selection that is allowed in the collection.
33
+ * @default 'single'
41
34
  */
42
- hiddenLabel?: boolean;
43
- };
44
- export type SelectProps<T> = ExtendableProps<{
45
- /** Additional CSS-classes. */
46
- className?: string;
35
+ selectionMode?: 'single' | 'multiple';
47
36
  /** Addon placed before the children. */
48
37
  startAddon?: ReactNode;
49
38
  /** Addon placed after the children. */
@@ -69,18 +58,22 @@ export type SelectProps<T> = ExtendableProps<{
69
58
  /** Ref to the control */
70
59
  ref?: Ref<HTMLDivElement>;
71
60
  /** A render function for displaying the selected value. */
72
- renderValue?: (props: Node<T> | null) => ReactElement;
61
+ renderValue?: (state: MultiSelectState<T>, states: {
62
+ isInvalid?: boolean;
63
+ isDisabled?: boolean;
64
+ isRequired?: boolean;
65
+ }) => ReactNode;
73
66
  /** The props used for each slot inside. */
74
67
  slotProps?: {
75
68
  popover?: PopoverProps;
76
69
  label?: FieldLabelProps;
77
70
  list?: ListProps<T>;
78
- control?: FieldSelectProps;
71
+ control?: FieldSelectProps<'div'>;
79
72
  caption?: FieldCaptionProps;
80
73
  group?: FieldInputGroupProps;
81
74
  errorMessage?: FieldErrorProps;
75
+ clearButton?: IconButtonProps;
82
76
  };
83
- } & SelectDeprecatedProps, Omit<AriaSelectProps<T>, 'description' | 'validationState'>>;
77
+ }, Omit<AriaSelectProps<T>, 'description' | 'validate' | 'validationBehavior' | 'validationState' | 'selectedKey' | 'onSelectionChange' | 'defaultSelectedKey'>>;
84
78
  export type SelectComponent = <T>(props: SelectProps<T>) => ReactElement | null;
85
79
  export type SelectRef = ComponentRef<'div'>;
86
- export {};
@@ -0,0 +1,7 @@
1
+ const selectPropSelectedTagsOverflow = [
2
+ "multiline",
3
+ "responsive"
4
+ ];
5
+ export {
6
+ selectPropSelectedTagsOverflow
7
+ };
package/dist/index.js CHANGED
@@ -62,6 +62,7 @@ import { List, ListInner } from "./components/List/List.js";
62
62
  import { ListItemText } from "./components/List/components/ListItemText/ListItemText.js";
63
63
  import { AnimatedIcon } from "./components/AnimatedIcon/AnimatedIcon.js";
64
64
  import { Select } from "./components/Select/Select.js";
65
+ import { selectPropSelectedTagsOverflow } from "./components/Select/types.js";
65
66
  import { Divider } from "./components/Divider/Divider.js";
66
67
  import { dividerPropDisplay } from "./components/Divider/types.js";
67
68
  import { Menu } from "./components/Menu/Menu.js";
@@ -177,6 +178,7 @@ export {
177
178
  radioGroupPropSize,
178
179
  radioPropLabelPlacement,
179
180
  radioPropSize,
181
+ selectPropSelectedTagsOverflow,
180
182
  sidePanelPropPlacement,
181
183
  sidePanelPropPosition,
182
184
  sidePanelPropSize,