@koslibs/components 1.2.0 → 1.3.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,25 @@
1
1
  # @koslibs/components
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#4](https://github.com/koslibs/components/pull/4) [`b37f3b0`](https://github.com/koslibs/components/commit/b37f3b008be8ffea80908557c13ea77d6d55c792) Thanks [@holypower777](https://github.com/holypower777)! - Перенесён Collapse из core-components GullEye с отдельным импортом
8
+ `@koslibs/components/Collapse`, внутренним и внешним управлением, сохранением
9
+ содержимого, анимацией и поддержкой reduced motion. Проп `colors` убран:
10
+ оформление кнопки настраивается через CSS-переменные. Добавлены документация
11
+ с тремя вкладками, Playground и история компонента. Стрелка подключается
12
+ отдельным импортом из `@koslibs/icons`.
13
+
14
+ Подписи `collapsedLabel` и `expandedLabel` не имеют дефолтных значений.
15
+ Добавлены `showRightChevron` (по умолчанию `true`) и `showLeftChevron`
16
+ (по умолчанию `false`): можно независимо показывать шевроны или скрыть оба.
17
+ Левый шеврон смотрит вправо в закрытом состоянии и вниз в раскрытом,
18
+ правый сохраняет поворот вниз → вверх.
19
+
20
+ Комментарии публичных пропсов приведены к единому многострочному формату JSDoc
21
+ с отдельной строкой для `@default`.
22
+
3
23
  ## 1.2.0
4
24
 
5
25
  ### Minor Changes
@@ -0,0 +1,91 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type CollapseProps = Omit<HTMLAttributes<HTMLDivElement>, 'onTransitionEnd'> & {
3
+ /**
4
+ * Состояние controlled-компонента.
5
+ */
6
+ expanded?: boolean;
7
+ /**
8
+ * Начальное состояние uncontrolled-компонента.
9
+ * @default false
10
+ */
11
+ defaultExpanded?: boolean;
12
+ /**
13
+ * Подпись кнопки, когда содержимое скрыто.
14
+ */
15
+ collapsedLabel?: ReactNode;
16
+ /**
17
+ * Подпись кнопки, когда содержимое раскрыто.
18
+ */
19
+ expandedLabel?: ReactNode;
20
+ /**
21
+ * Показывать шеврон справа от подписи: вниз при закрытии, вверх при раскрытии.
22
+ * @default true
23
+ */
24
+ showRightChevron?: boolean;
25
+ /**
26
+ * Показывать шеврон слева от подписи: вправо при закрытии, вниз при раскрытии.
27
+ * @default false
28
+ */
29
+ showLeftChevron?: boolean;
30
+ /**
31
+ * Запрос изменения состояния. В controlled-режиме обновите expanded.
32
+ */
33
+ onExpandedChange?: (expanded: boolean) => void;
34
+ /**
35
+ * Завершение раскрытия/сворачивания; без анимации вызывается сразу.
36
+ */
37
+ onTransitionEnd?: (expanded: boolean) => void;
38
+ /**
39
+ * Дополнительный класс внутреннего контейнера содержимого.
40
+ */
41
+ expandedContentClassName?: string;
42
+ /**
43
+ * Идентификатор корневого элемента для автоматизированных тестов.
44
+ */
45
+ dataTestId?: string;
46
+ };
47
+ export declare const Collapse: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "onTransitionEnd"> & {
48
+ /**
49
+ * Состояние controlled-компонента.
50
+ */
51
+ expanded?: boolean;
52
+ /**
53
+ * Начальное состояние uncontrolled-компонента.
54
+ * @default false
55
+ */
56
+ defaultExpanded?: boolean;
57
+ /**
58
+ * Подпись кнопки, когда содержимое скрыто.
59
+ */
60
+ collapsedLabel?: ReactNode;
61
+ /**
62
+ * Подпись кнопки, когда содержимое раскрыто.
63
+ */
64
+ expandedLabel?: ReactNode;
65
+ /**
66
+ * Показывать шеврон справа от подписи: вниз при закрытии, вверх при раскрытии.
67
+ * @default true
68
+ */
69
+ showRightChevron?: boolean;
70
+ /**
71
+ * Показывать шеврон слева от подписи: вправо при закрытии, вниз при раскрытии.
72
+ * @default false
73
+ */
74
+ showLeftChevron?: boolean;
75
+ /**
76
+ * Запрос изменения состояния. В controlled-режиме обновите expanded.
77
+ */
78
+ onExpandedChange?: (expanded: boolean) => void;
79
+ /**
80
+ * Завершение раскрытия/сворачивания; без анимации вызывается сразу.
81
+ */
82
+ onTransitionEnd?: (expanded: boolean) => void;
83
+ /**
84
+ * Дополнительный класс внутреннего контейнера содержимого.
85
+ */
86
+ expandedContentClassName?: string;
87
+ /**
88
+ * Идентификатор корневого элемента для автоматизированных тестов.
89
+ */
90
+ dataTestId?: string;
91
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,103 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { ChevronDownIcon } from "@koslibs/icons/ChevronDownIcon";
3
+ import classnames from "classnames";
4
+ import { forwardRef, useId, useLayoutEffect, useRef, useState } from "react";
5
+ import index_module from "./index.module.js";
6
+ const Collapse = /*#__PURE__*/ forwardRef(({ expanded, defaultExpanded = false, collapsedLabel, expandedLabel, showRightChevron = true, showLeftChevron = false, onExpandedChange, onTransitionEnd, expandedContentClassName, dataTestId, className, children, ...restProps }, ref)=>{
7
+ const [expandedState, setExpandedState] = useState(defaultExpanded);
8
+ const isExpanded = expanded ?? expandedState;
9
+ const contentId = `collapse-${useId()}`;
10
+ const contentRef = useRef(null);
11
+ const innerRef = useRef(null);
12
+ const toggleRef = useRef(null);
13
+ const previousExpanded = useRef(isExpanded);
14
+ const pendingTransition = useRef(false);
15
+ const transitionCallback = useRef(onTransitionEnd);
16
+ const label = isExpanded ? expandedLabel || collapsedLabel : collapsedLabel || expandedLabel;
17
+ useLayoutEffect(()=>{
18
+ transitionCallback.current = onTransitionEnd;
19
+ }, [
20
+ onTransitionEnd
21
+ ]);
22
+ useLayoutEffect(()=>{
23
+ const content = contentRef.current;
24
+ if (!content) return;
25
+ if (!isExpanded && content.contains(content.ownerDocument.activeElement)) toggleRef.current?.focus();
26
+ content.inert = !isExpanded;
27
+ if (previousExpanded.current === isExpanded) return;
28
+ previousExpanded.current = isExpanded;
29
+ pendingTransition.current = true;
30
+ const transitionDuration = content.ownerDocument.defaultView?.getComputedStyle(content).transitionDuration;
31
+ if (innerRef.current?.scrollHeight === 0 || 0 === parseFloat(transitionDuration ?? '0')) {
32
+ pendingTransition.current = false;
33
+ transitionCallback.current?.(isExpanded);
34
+ }
35
+ }, [
36
+ isExpanded
37
+ ]);
38
+ const handleToggle = ()=>{
39
+ const nextExpanded = !isExpanded;
40
+ if (void 0 === expanded) setExpandedState(nextExpanded);
41
+ onExpandedChange?.(nextExpanded);
42
+ };
43
+ const handleTransitionEnd = (event)=>{
44
+ if (event.target === event.currentTarget && 'grid-template-rows' === event.propertyName && pendingTransition.current) {
45
+ pendingTransition.current = false;
46
+ transitionCallback.current?.(isExpanded);
47
+ }
48
+ };
49
+ return /*#__PURE__*/ jsxs("div", {
50
+ ...restProps,
51
+ ref: ref,
52
+ className: classnames(index_module.component, className),
53
+ "data-test-id": dataTestId,
54
+ children: [
55
+ label && /*#__PURE__*/ jsxs("button", {
56
+ ref: toggleRef,
57
+ type: "button",
58
+ className: index_module.toggle,
59
+ "aria-expanded": isExpanded,
60
+ "aria-controls": contentId,
61
+ onClick: handleToggle,
62
+ "data-test-id": dataTestId ? `${dataTestId}-toggle` : void 0,
63
+ children: [
64
+ showLeftChevron && /*#__PURE__*/ jsx(ChevronDownIcon, {
65
+ className: classnames(index_module.icon, index_module.leftIcon, {
66
+ [index_module.rotated]: isExpanded
67
+ }),
68
+ "aria-hidden": "true"
69
+ }),
70
+ /*#__PURE__*/ jsx("span", {
71
+ children: label
72
+ }),
73
+ showRightChevron && /*#__PURE__*/ jsx(ChevronDownIcon, {
74
+ className: classnames(index_module.icon, {
75
+ [index_module.rotated]: isExpanded
76
+ }),
77
+ "aria-hidden": "true"
78
+ })
79
+ ]
80
+ }),
81
+ /*#__PURE__*/ jsx("div", {
82
+ ref: contentRef,
83
+ id: contentId,
84
+ className: classnames(index_module.content, {
85
+ [index_module.expanded]: isExpanded
86
+ }),
87
+ "aria-hidden": !isExpanded,
88
+ "data-test-id": dataTestId ? `${dataTestId}-content` : void 0,
89
+ onTransitionEnd: handleTransitionEnd,
90
+ children: /*#__PURE__*/ jsx("div", {
91
+ className: index_module.clip,
92
+ children: /*#__PURE__*/ jsx("div", {
93
+ ref: innerRef,
94
+ className: expandedContentClassName,
95
+ children: children
96
+ })
97
+ })
98
+ })
99
+ ]
100
+ });
101
+ });
102
+ Collapse.displayName = 'Collapse';
103
+ export { Collapse };
@@ -0,0 +1,2 @@
1
+ export { Collapse } from './collapse.js';
2
+ export type { CollapseProps } from './collapse.js';
@@ -0,0 +1 @@
1
+ export { Collapse } from "./collapse.js";
@@ -0,0 +1,12 @@
1
+ import "./index_module.css";
2
+ const index_module = {
3
+ component: "component-tPiInR",
4
+ content: "content-D8E_z9",
5
+ expanded: "expanded-rqGMOG",
6
+ clip: "clip-D3ydtQ",
7
+ toggle: "toggle-YA8uJB",
8
+ icon: "icon-wFeaQN",
9
+ rotated: "rotated-sRw5l2",
10
+ leftIcon: "leftIcon-PGZrVM"
11
+ };
12
+ export default index_module;
@@ -0,0 +1,82 @@
1
+ @import "../../styles/theme.css";
2
+ @import "./vars.css";
3
+
4
+ .component-tPiInR {
5
+ min-width: 0;
6
+ font-family: var(--font-primary);
7
+ }
8
+
9
+ .content-D8E_z9 {
10
+ visibility: hidden;
11
+ grid-template-rows: 0fr;
12
+ transition: grid-template-rows .2s, visibility 0s linear .2s;
13
+ display: grid;
14
+ }
15
+
16
+ .expanded-rqGMOG {
17
+ visibility: visible;
18
+ grid-template-rows: 1fr;
19
+ transition-delay: 0s;
20
+ }
21
+
22
+ .clip-D3ydtQ {
23
+ min-height: 0;
24
+ overflow: hidden;
25
+ }
26
+
27
+ .toggle-YA8uJB {
28
+ align-items: center;
29
+ gap: var(--size-4, 4px);
30
+ max-width: 100%;
31
+ padding: var(--size-8, 8px) 0;
32
+ color: var(--collapse-color);
33
+ font: inherit;
34
+ text-align: left;
35
+ overflow-wrap: anywhere;
36
+ cursor: pointer;
37
+ background: none;
38
+ border: 0;
39
+ margin: 0;
40
+ font-size: 13px;
41
+ line-height: 20px;
42
+ display: inline-flex;
43
+
44
+ &:hover {
45
+ color: var(--collapse-hover-color);
46
+ }
47
+
48
+ &:hover span {
49
+ text-underline-offset: 3px;
50
+ text-decoration: underline;
51
+ }
52
+
53
+ &:focus-visible {
54
+ outline: 2px solid var(--collapse-focus-color);
55
+ outline-offset: 2px;
56
+ border-radius: var(--border-radius-4, 4px);
57
+ }
58
+ }
59
+
60
+ .icon-wFeaQN {
61
+ flex-shrink: 0;
62
+ transition: transform .2s;
63
+ }
64
+
65
+ .rotated-sRw5l2 {
66
+ transform: rotate(180deg);
67
+ }
68
+
69
+ .leftIcon-PGZrVM {
70
+ transform: rotate(-90deg);
71
+
72
+ &.rotated-sRw5l2 {
73
+ transform: rotate(0);
74
+ }
75
+ }
76
+
77
+ @media (prefers-reduced-motion: reduce) {
78
+ .content-D8E_z9, .icon-wFeaQN {
79
+ transition: none;
80
+ }
81
+ }
82
+
@@ -0,0 +1,6 @@
1
+ :root {
2
+ --collapse-color: #1c1a18;
3
+ --collapse-hover-color: #686d63;
4
+ --collapse-focus-color: #7967f2;
5
+ }
6
+
@@ -2,7 +2,9 @@ import type { Placement } from '@popperjs/core';
2
2
  import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
3
3
  export type PopoverPosition = Exclude<Placement, 'auto' | 'auto-start' | 'auto-end'>;
4
4
  export type PopoverProps = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'className' | 'style'> & {
5
- /** Содержимое поповера. Оформление и прокрутка задаются вызывающим компонентом. */
5
+ /**
6
+ * Содержимое поповера. Оформление и прокрутка задаются вызывающим компонентом.
7
+ */
6
8
  children?: ReactNode;
7
9
  /**
8
10
  * Дополнительный класс внешнего контейнера.
@@ -2,11 +2,17 @@ import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
2
2
  export type TypographyWeight = 'regular' | 'medium' | 'semibold' | 'bold';
3
3
  export type TypographyColor = 'inherit' | 'primary' | 'secondary' | 'inverse' | 'positive' | 'negative';
4
4
  export type BaseProps = Omit<HTMLAttributes<HTMLElement>, 'color' | 'children' | 'className' | 'style'> & {
5
- /** Содержимое текста или заголовка. */
5
+ /**
6
+ * Содержимое текста или заголовка.
7
+ */
6
8
  children?: ReactNode;
7
- /** Дополнительный CSS-класс корневого элемента. */
9
+ /**
10
+ * Дополнительный CSS-класс корневого элемента.
11
+ */
8
12
  className?: string;
9
- /** Инлайн-стили корневого элемента. */
13
+ /**
14
+ * Инлайн-стили корневого элемента.
15
+ */
10
16
  style?: CSSProperties;
11
17
  /**
12
18
  * Цвет текста. По умолчанию наследуется от родителя.
package/dist/index.d.ts CHANGED
@@ -6,3 +6,5 @@ export { Typography, TypographyText, TypographyTitle, Text, Title } from './comp
6
6
  export type { TextProps, TitleProps, TypographyColor, TypographyWeight, } from './components/Typography/index.js';
7
7
  export { Popover } from './components/Popover/index.js';
8
8
  export type { PopoverProps, PopoverPosition } from './components/Popover/index.js';
9
+ export { Collapse } from './components/Collapse/index.js';
10
+ export type { CollapseProps } from './components/Collapse/index.js';
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export { Button } from "./components/Button/index.js";
2
2
  export { Spinner } from "./components/Spinner/index.js";
3
3
  export { Text, Title, Typography, TypographyText, TypographyTitle } from "./components/Typography/index.js";
4
4
  export { Popover } from "./components/Popover/index.js";
5
+ 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.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Shared React components for koslibs projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -80,6 +80,7 @@
80
80
  "react-dom": "^18.3.1 || ^19.0.0"
81
81
  },
82
82
  "dependencies": {
83
+ "@koslibs/icons": "1.0.1",
83
84
  "@popperjs/core": "2.11.8",
84
85
  "classnames": "2.5.1"
85
86
  }