@koslibs/components 1.11.1 → 1.13.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 (56) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/components/Button/index_module.css +4 -0
  3. package/dist/components/Collapse/collapse.d.ts +10 -0
  4. package/dist/components/Collapse/collapse.js +2 -1
  5. package/dist/components/Collapse/index_module.css +6 -5
  6. package/dist/components/Input/index_module.css +8 -7
  7. package/dist/components/Input/input.d.ts +1 -0
  8. package/dist/components/Input/input.js +2 -1
  9. package/dist/components/Input/types.d.ts +5 -0
  10. package/dist/components/Modal/components/modal-surface/index.js +1 -1
  11. package/dist/components/Select/components/select-dropdown.d.ts +2 -2
  12. package/dist/components/Select/components/select-dropdown.js +2 -1
  13. package/dist/components/Select/index_module.css +5 -4
  14. package/dist/components/Select/select.js +3 -1
  15. package/dist/components/Select/types.d.ts +1 -1
  16. package/dist/components/Sidepanel/components/content/index.d.ts +13 -0
  17. package/dist/components/Sidepanel/components/content/index.js +21 -0
  18. package/dist/components/Sidepanel/components/content/index.module.js +5 -0
  19. package/dist/components/Sidepanel/components/content/index_module.css +14 -0
  20. package/dist/components/Sidepanel/components/footer/index.d.ts +66 -0
  21. package/dist/components/Sidepanel/components/footer/index.js +25 -0
  22. package/dist/components/Sidepanel/components/footer/index.module.js +19 -0
  23. package/dist/components/Sidepanel/components/footer/index_module.css +57 -0
  24. package/dist/components/Sidepanel/components/header/index.d.ts +33 -0
  25. package/dist/components/Sidepanel/components/header/index.js +45 -0
  26. package/dist/components/Sidepanel/components/header/index.module.js +8 -0
  27. package/dist/components/Sidepanel/components/header/index_module.css +32 -0
  28. package/dist/components/Sidepanel/components/sidepanel-surface/index.d.ts +4 -0
  29. package/dist/components/Sidepanel/components/sidepanel-surface/index.js +92 -0
  30. package/dist/components/Sidepanel/context.d.ts +6 -0
  31. package/dist/components/Sidepanel/context.js +3 -0
  32. package/dist/components/Sidepanel/hooks/use-dialog-transition.d.ts +5 -0
  33. package/dist/components/Sidepanel/hooks/use-dialog-transition.js +83 -0
  34. package/dist/components/Sidepanel/index.d.ts +8 -0
  35. package/dist/components/Sidepanel/index.js +4 -0
  36. package/dist/components/Sidepanel/index.module.js +8 -0
  37. package/dist/components/Sidepanel/index_module.css +68 -0
  38. package/dist/components/Sidepanel/scrollbar.module.js +5 -0
  39. package/dist/components/Sidepanel/scrollbar_module.css +41 -0
  40. package/dist/components/Sidepanel/sidepanel.d.ts +9 -0
  41. package/dist/components/Sidepanel/sidepanel.js +28 -0
  42. package/dist/components/Sidepanel/types.d.ts +36 -0
  43. package/dist/components/Sidepanel/types.js +0 -0
  44. package/dist/components/Sidepanel/vars.css +12 -0
  45. package/dist/components/Tabs/index_module.css +1 -1
  46. package/dist/components/Textarea/index_module.css +3 -2
  47. package/dist/components/Textarea/textarea.d.ts +1 -0
  48. package/dist/components/Textarea/textarea.js +2 -1
  49. package/dist/components/Textarea/types.d.ts +5 -0
  50. package/dist/index.d.ts +2 -0
  51. package/dist/index.js +1 -0
  52. package/dist/utils/lock-scroll.d.ts +1 -0
  53. package/dist/utils/lock-scroll.js +49 -0
  54. package/package.json +1 -1
  55. package/dist/components/Modal/utils/lock-scroll.d.ts +0 -1
  56. package/dist/components/Modal/utils/lock-scroll.js +0 -31
@@ -0,0 +1,32 @@
1
+ .component-pnHhxm {
2
+ box-sizing: border-box;
3
+ justify-content: space-between;
4
+ align-items: center;
5
+ gap: var(--size-16);
6
+ min-height: var(--size-80);
7
+ padding: var(--sidepanel-padding);
8
+ flex-shrink: 0;
9
+ display: flex;
10
+ }
11
+
12
+ .title-nolsR9 {
13
+ overflow-wrap: anywhere;
14
+ flex: 1;
15
+ min-width: 0;
16
+ }
17
+
18
+ .component-pnHhxm > .closer-PhFI6O {
19
+ width: var(--size-24);
20
+ min-width: var(--size-24);
21
+ height: var(--size-32);
22
+ min-height: var(--size-32);
23
+ padding: var(--size-0);
24
+ flex-shrink: 0;
25
+ }
26
+
27
+ .closerLabel-q4m2AK {
28
+ justify-content: center;
29
+ align-items: center;
30
+ display: flex;
31
+ }
32
+
@@ -0,0 +1,4 @@
1
+ import type { SidepanelProps } from '../../types.js';
2
+ export declare const SidepanelSurface: import("react").ForwardRefExoticComponent<Omit<SidepanelProps, "getPortalContainer"> & {
3
+ onExited: VoidFunction;
4
+ } & import("react").RefAttributes<HTMLDialogElement>>;
@@ -0,0 +1,92 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import { forwardRef, useCallback, useLayoutEffect, useMemo, useRef } from "react";
4
+ import { lockScroll } from "../../../../utils/lock-scroll.js";
5
+ import { SidepanelContext } from "../../context.js";
6
+ import { useDialogTransition } from "../../hooks/use-dialog-transition.js";
7
+ import index_module from "../../index.module.js";
8
+ import scrollbar_module from "../../scrollbar.module.js";
9
+ const SidepanelSurface = /*#__PURE__*/ forwardRef(({ open, onClose, onExited, escapeKeyDown = true, backdropClick = true, lockScroll: shouldLockScroll = true, className, dataTestId, children, onPointerDown, onClick, onTransitionEnd, ...restProps }, ref)=>{
10
+ const dialogRef = useRef(null);
11
+ const backdropPointerDown = useRef(false);
12
+ const mounted = useRef(false);
13
+ useLayoutEffect(()=>{
14
+ mounted.current = true;
15
+ return ()=>{
16
+ mounted.current = false;
17
+ };
18
+ }, []);
19
+ const { active, finishClosing } = useDialogTransition(dialogRef, open, onExited);
20
+ const requestClose = useCallback(()=>{
21
+ if (open) onClose?.();
22
+ }, [
23
+ onClose,
24
+ open
25
+ ]);
26
+ const mergedRef = useCallback((element)=>{
27
+ dialogRef.current = element;
28
+ if ('function' == typeof ref) ref(element);
29
+ else if (ref) ref.current = element;
30
+ }, [
31
+ ref
32
+ ]);
33
+ const context = useMemo(()=>({
34
+ onClose: requestClose,
35
+ dataTestId
36
+ }), [
37
+ dataTestId,
38
+ requestClose
39
+ ]);
40
+ useLayoutEffect(()=>{
41
+ const dialog = dialogRef.current;
42
+ if (active && shouldLockScroll && dialog) return lockScroll(dialog.ownerDocument, true);
43
+ }, [
44
+ active,
45
+ shouldLockScroll
46
+ ]);
47
+ const isBackdrop = (event)=>{
48
+ const bounds = event.currentTarget.getBoundingClientRect();
49
+ return event.target === event.currentTarget && (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom);
50
+ };
51
+ const handleCancel = (event)=>{
52
+ event.preventDefault();
53
+ event.stopPropagation();
54
+ if (escapeKeyDown) requestClose();
55
+ };
56
+ const handleClose = (event)=>{
57
+ event.stopPropagation();
58
+ if (mounted.current && !event.currentTarget.open) requestClose();
59
+ };
60
+ const handlePointerDown = (event)=>{
61
+ onPointerDown?.(event);
62
+ backdropPointerDown.current = !event.defaultPrevented && isBackdrop(event);
63
+ };
64
+ const handleClick = (event)=>{
65
+ onClick?.(event);
66
+ const close = backdropPointerDown.current && isBackdrop(event);
67
+ backdropPointerDown.current = false;
68
+ if (backdropClick && close && !event.defaultPrevented) requestClose();
69
+ };
70
+ const handleTransitionEnd = (event)=>{
71
+ onTransitionEnd?.(event);
72
+ if (event.target === event.currentTarget && 'transform' === event.propertyName) finishClosing();
73
+ };
74
+ return /*#__PURE__*/ jsx("dialog", {
75
+ ...restProps,
76
+ ref: mergedRef,
77
+ className: classnames(index_module.component, scrollbar_module.scrollbar, className),
78
+ "data-test-id": dataTestId,
79
+ "data-state": "closed",
80
+ onCancel: handleCancel,
81
+ onClose: handleClose,
82
+ onPointerDown: handlePointerDown,
83
+ onClick: handleClick,
84
+ onTransitionEnd: handleTransitionEnd,
85
+ children: /*#__PURE__*/ jsx(SidepanelContext.Provider, {
86
+ value: context,
87
+ children: children
88
+ })
89
+ });
90
+ });
91
+ SidepanelSurface.displayName = 'SidepanelSurface';
92
+ export { SidepanelSurface };
@@ -0,0 +1,6 @@
1
+ type SidepanelContextValue = {
2
+ onClose?: VoidFunction;
3
+ dataTestId?: string;
4
+ };
5
+ export declare const SidepanelContext: import("react").Context<SidepanelContextValue>;
6
+ export {};
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+ const SidepanelContext = createContext({});
3
+ export { SidepanelContext };
@@ -0,0 +1,5 @@
1
+ import { type RefObject } from 'react';
2
+ export declare function useDialogTransition(dialogRef: RefObject<HTMLDialogElement>, open: boolean, onExited: VoidFunction): {
3
+ active: boolean;
4
+ finishClosing: () => void;
5
+ };
@@ -0,0 +1,83 @@
1
+ import { useCallback, useLayoutEffect, useRef, useState } from "react";
2
+ function milliseconds(value) {
3
+ return (parseFloat(value) || 0) * (value.trim().endsWith('ms') ? 1 : 1000);
4
+ }
5
+ function transitionTime(dialog) {
6
+ const style = dialog.ownerDocument.defaultView?.getComputedStyle(dialog);
7
+ const durations = style?.transitionDuration.split(',').map(milliseconds) ?? [
8
+ 0
9
+ ];
10
+ const delays = style?.transitionDelay.split(',').map(milliseconds) ?? [
11
+ 0
12
+ ];
13
+ return Math.max(0, ...durations.map((duration, index)=>duration + delays[index % delays.length]));
14
+ }
15
+ function useDialogTransition(dialogRef, open, onExited) {
16
+ const [active, setActive] = useState(open);
17
+ const latest = useRef({
18
+ open,
19
+ onExited
20
+ });
21
+ useLayoutEffect(()=>{
22
+ latest.current = {
23
+ open,
24
+ onExited
25
+ };
26
+ }, [
27
+ open,
28
+ onExited
29
+ ]);
30
+ const finishClosing = useCallback(()=>{
31
+ const dialog = dialogRef.current;
32
+ if (!dialog || latest.current.open) return;
33
+ if (dialog.open) dialog.close();
34
+ setActive(false);
35
+ latest.current.onExited();
36
+ }, [
37
+ dialogRef
38
+ ]);
39
+ useLayoutEffect(()=>{
40
+ const dialog = dialogRef.current;
41
+ const view = dialog?.ownerDocument.defaultView;
42
+ if (!dialog || !view) return;
43
+ let frame;
44
+ let timeout;
45
+ if (open) {
46
+ if (!dialog.open) {
47
+ dialog.dataset.state = 'closed';
48
+ dialog.showModal();
49
+ dialog.getBoundingClientRect();
50
+ }
51
+ setActive(true);
52
+ frame = view.requestAnimationFrame(()=>{
53
+ dialog.dataset.state = 'open';
54
+ });
55
+ } else if (dialog.open && 'open' === dialog.dataset.state) {
56
+ dialog.dataset.state = 'closed';
57
+ const duration = transitionTime(dialog);
58
+ if (0 === duration) finishClosing();
59
+ else timeout = view.setTimeout(finishClosing, duration + 50);
60
+ } else finishClosing();
61
+ return ()=>{
62
+ if (void 0 !== frame) view.cancelAnimationFrame(frame);
63
+ if (void 0 !== timeout) view.clearTimeout(timeout);
64
+ };
65
+ }, [
66
+ dialogRef,
67
+ finishClosing,
68
+ open
69
+ ]);
70
+ useLayoutEffect(()=>{
71
+ const dialog = dialogRef.current;
72
+ return ()=>{
73
+ if (dialog?.open) dialog.close();
74
+ };
75
+ }, [
76
+ dialogRef
77
+ ]);
78
+ return {
79
+ active,
80
+ finishClosing
81
+ };
82
+ }
83
+ export { useDialogTransition };
@@ -0,0 +1,8 @@
1
+ export { Sidepanel } from './sidepanel.js';
2
+ export type { SidepanelProps } from './types.js';
3
+ export { Header } from './components/header/index.js';
4
+ export type { HeaderProps } from './components/header/index.js';
5
+ export { Content } from './components/content/index.js';
6
+ export type { ContentProps } from './components/content/index.js';
7
+ export { Footer } from './components/footer/index.js';
8
+ export type { FooterProps } from './components/footer/index.js';
@@ -0,0 +1,4 @@
1
+ export { Sidepanel } from "./sidepanel.js";
2
+ export { Header } from "./components/header/index.js";
3
+ export { Content } from "./components/content/index.js";
4
+ export { Footer } from "./components/footer/index.js";
@@ -0,0 +1,8 @@
1
+ import "./index_module.css";
2
+ const index_module = {
3
+ component: "component-kIdcz9",
4
+ nonStickyFooter: "nonStickyFooter-rXnHkK",
5
+ header: "header-UlW3Pm",
6
+ content: "content-RhY7Xk"
7
+ };
8
+ export default index_module;
@@ -0,0 +1,68 @@
1
+ @import "../../styles/theme.css";
2
+ @import "./vars.css";
3
+
4
+ .component-kIdcz9 {
5
+ box-sizing: border-box;
6
+ width: 500px;
7
+ min-width: 0;
8
+ max-width: 100vw;
9
+ height: 100dvh;
10
+ max-height: none;
11
+ margin: var(--size-0);
12
+ padding: var(--size-0);
13
+ border-radius: var(--border-radius-0);
14
+ background: var(--sidepanel-background-color);
15
+ color: var(--sidepanel-color);
16
+ font-family: var(--font-primary);
17
+ transition: transform var(--sidepanel-animation-duration) ease-in-out;
18
+ border: 0;
19
+ outline: none;
20
+ flex-direction: column;
21
+ position: fixed;
22
+ inset: 0 0 0 auto;
23
+ overflow: hidden;
24
+ transform: translateX(100%);
25
+
26
+ &[open] {
27
+ display: flex;
28
+ }
29
+
30
+ &:has( > .nonStickyFooter-rXnHkK) {
31
+ overscroll-behavior: contain;
32
+ overflow: auto;
33
+
34
+ & > .header-UlW3Pm {
35
+ z-index: 1;
36
+ background: var(--sidepanel-background-color);
37
+ position: sticky;
38
+ top: 0;
39
+ }
40
+
41
+ & > .content-RhY7Xk {
42
+ flex: 1 0 auto;
43
+ overflow: visible;
44
+ }
45
+ }
46
+
47
+ &[data-state="open"] {
48
+ transform: translateX(0);
49
+ }
50
+
51
+ &::backdrop {
52
+ background: var(--sidepanel-backdrop-color);
53
+ backdrop-filter: blur(var(--sidepanel-backdrop-blur));
54
+ opacity: 0;
55
+ transition: opacity var(--sidepanel-animation-duration) ease-in-out;
56
+ }
57
+
58
+ &[data-state="open"]::backdrop {
59
+ opacity: 1;
60
+ }
61
+ }
62
+
63
+ @media (prefers-reduced-motion: reduce) {
64
+ .component-kIdcz9, .component-kIdcz9::backdrop {
65
+ transition-duration: 0s;
66
+ }
67
+ }
68
+
@@ -0,0 +1,5 @@
1
+ import "./scrollbar_module.css";
2
+ const scrollbar_module = {
3
+ scrollbar: "scrollbar-haYNj_"
4
+ };
5
+ export default scrollbar_module;
@@ -0,0 +1,41 @@
1
+ .scrollbar-haYNj_ {
2
+ scrollbar-color: auto;
3
+ scrollbar-width: auto;
4
+
5
+ &::-webkit-scrollbar {
6
+ width: var(--size-6);
7
+ height: var(--size-6);
8
+ background: none;
9
+ }
10
+
11
+ &::-webkit-scrollbar-button {
12
+ width: 0;
13
+ height: 0;
14
+ display: none;
15
+ }
16
+
17
+ &::-webkit-scrollbar-track {
18
+ background: none;
19
+ }
20
+
21
+ &::-webkit-scrollbar-track-piece {
22
+ background: none;
23
+ }
24
+
25
+ &::-webkit-scrollbar-corner {
26
+ background: none;
27
+ }
28
+
29
+ &::-webkit-scrollbar-thumb {
30
+ border-radius: var(--border-radius-pill);
31
+ background: var(--sidepanel-scrollbar-color);
32
+ }
33
+ }
34
+
35
+ @supports not selector(::-webkit-scrollbar) {
36
+ .scrollbar-haYNj_ {
37
+ scrollbar-width: thin;
38
+ scrollbar-color: var(--sidepanel-scrollbar-color) transparent;
39
+ }
40
+ }
41
+
@@ -0,0 +1,9 @@
1
+ export declare const Sidepanel: import("react").ForwardRefExoticComponent<Omit<import("react").DialogHTMLAttributes<HTMLDialogElement>, "open" | "onClose" | "onCancel"> & {
2
+ open: boolean;
3
+ onClose?: VoidFunction;
4
+ escapeKeyDown?: boolean;
5
+ backdropClick?: boolean;
6
+ lockScroll?: boolean;
7
+ getPortalContainer?: import("../Portal/index.js").PortalProps["getPortalContainer"];
8
+ dataTestId?: string;
9
+ } & import("react").RefAttributes<HTMLDialogElement>>;
@@ -0,0 +1,28 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef, useCallback, useLayoutEffect, useState } from "react";
3
+ import { Portal } from "../Portal/index.js";
4
+ import { SidepanelSurface } from "./components/sidepanel-surface/index.js";
5
+ const Sidepanel = /*#__PURE__*/ forwardRef(({ open, getPortalContainer, ...restProps }, ref)=>{
6
+ const [mounted, setMounted] = useState(open);
7
+ useLayoutEffect(()=>{
8
+ if (open) setMounted(true);
9
+ }, [
10
+ open
11
+ ]);
12
+ const handleExited = useCallback(()=>{
13
+ if (!open) setMounted(false);
14
+ }, [
15
+ open
16
+ ]);
17
+ return mounted ? /*#__PURE__*/ jsx(Portal, {
18
+ getPortalContainer: getPortalContainer,
19
+ children: /*#__PURE__*/ jsx(SidepanelSurface, {
20
+ ...restProps,
21
+ ref: ref,
22
+ open: open,
23
+ onExited: handleExited
24
+ })
25
+ }) : null;
26
+ });
27
+ Sidepanel.displayName = 'Sidepanel';
28
+ export { Sidepanel };
@@ -0,0 +1,36 @@
1
+ import type { DialogHTMLAttributes } from 'react';
2
+ import type { PortalProps } from '../Portal/index.js';
3
+ export type SidepanelProps = Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClose' | 'onCancel'> & {
4
+ /**
5
+ * Видимость панели. Для закрытия обновите open в обработчике onClose.
6
+ * Панель остаётся открытой до завершения анимации ухода вправо.
7
+ */
8
+ open: boolean;
9
+ /**
10
+ * Запрос закрытия по крестику, Escape или бекдропу.
11
+ */
12
+ onClose?: VoidFunction;
13
+ /**
14
+ * Разрешает запрос закрытия по Escape.
15
+ * @default true
16
+ */
17
+ escapeKeyDown?: boolean;
18
+ /**
19
+ * Разрешает запрос закрытия по бекдропу.
20
+ * @default true
21
+ */
22
+ backdropClick?: boolean;
23
+ /**
24
+ * Блокирует прокрутку страницы до завершения закрытия.
25
+ * @default true
26
+ */
27
+ lockScroll?: boolean;
28
+ /**
29
+ * Контейнер портала. По умолчанию используется body текущего документа.
30
+ */
31
+ getPortalContainer?: PortalProps['getPortalContainer'];
32
+ /**
33
+ * Идентификатор панели. Части получают суффиксы -header, -closer, -content и -footer.
34
+ */
35
+ dataTestId?: string;
36
+ };
File without changes
@@ -0,0 +1,12 @@
1
+ @import "../Modal/vars.css";
2
+
3
+ :root {
4
+ --sidepanel-background-color: var(--modal-background-color);
5
+ --sidepanel-color: var(--modal-color);
6
+ --sidepanel-backdrop-color: var(--modal-backdrop-color);
7
+ --sidepanel-backdrop-blur: var(--modal-backdrop-blur);
8
+ --sidepanel-padding: var(--size-24);
9
+ --sidepanel-scrollbar-color: #a49b89;
10
+ --sidepanel-animation-duration: .2s;
11
+ }
12
+
@@ -83,7 +83,7 @@
83
83
 
84
84
  .tab-yKhKqB:disabled, .tab-yKhKqB:disabled .counter-Tokn9i {
85
85
  color: var(--tabs-disabled-color);
86
- cursor: default;
86
+ cursor: not-allowed;
87
87
  }
88
88
 
89
89
  .tab-yKhKqB:disabled .counter-Tokn9i {
@@ -42,6 +42,7 @@
42
42
  --field-label-color: var(--input-disabled-color);
43
43
  --field-placeholder-color: var(--input-disabled-color);
44
44
  --field-icon-color: var(--input-disabled-color);
45
+ cursor: not-allowed;
45
46
  }
46
47
  }
47
48
 
@@ -64,13 +65,13 @@
64
65
  }
65
66
  }
66
67
 
67
- .component-K9LhZN:not([data-disabled]) .field-GADSqb:focus-within {
68
+ .component-K9LhZN[data-has-outline]:not([data-disabled]) .field-GADSqb:focus-within {
68
69
  box-shadow: none;
69
70
  outline: 2px solid var(--input-focus-border-color);
70
71
  outline-offset: 0;
71
72
  }
72
73
 
73
- .component-K9LhZN[data-error]:not([data-disabled]) .field-GADSqb:focus-within {
74
+ .component-K9LhZN[data-has-outline][data-error]:not([data-disabled]) .field-GADSqb:focus-within {
74
75
  outline-color: var(--input-error-border-color);
75
76
  }
76
77
 
@@ -6,6 +6,7 @@ export declare const Textarea: import("react").ForwardRefExoticComponent<Omit<im
6
6
  showValueLength?: boolean;
7
7
  view?: "primary" | "secondary";
8
8
  block?: boolean;
9
+ hasOutline?: boolean;
9
10
  label?: import("react").ReactNode;
10
11
  labelView?: "outer" | "inner";
11
12
  hint?: import("react").ReactNode;
@@ -7,7 +7,7 @@ import { Button } from "../Button/index.js";
7
7
  import { TypographyText } from "../Typography/text.js";
8
8
  import { useAutosize } from "./hooks/use-autosize.js";
9
9
  import index_module from "./index.module.js";
10
- const Textarea = /*#__PURE__*/ forwardRef(({ view = 'primary', block = false, label, labelView = 'outer', hint, error, minRows = 3, maxRows, autosize = false, resize = false, clear = false, value, defaultValue, maxLength, showValueLength = false, onChange, onClear, disabled = false, readOnly = false, id, className, style, fieldClassName, textareaClassName, labelClassName, messageClassName, dataTestId, ...restProps }, ref)=>{
10
+ const Textarea = /*#__PURE__*/ forwardRef(({ view = 'primary', block = false, hasOutline = false, label, labelView = 'outer', hint, error, minRows = 3, maxRows, autosize = false, resize = false, clear = false, value, defaultValue, maxLength, showValueLength = false, onChange, onClear, disabled = false, readOnly = false, id, className, style, fieldClassName, textareaClassName, labelClassName, messageClassName, dataTestId, ...restProps }, ref)=>{
11
11
  const generatedId = useId();
12
12
  const textareaId = id ?? generatedId;
13
13
  const textareaRef = useRef(null);
@@ -96,6 +96,7 @@ const Textarea = /*#__PURE__*/ forwardRef(({ view = 'primary', block = false, la
96
96
  },
97
97
  "data-view": view,
98
98
  "data-block": block || void 0,
99
+ "data-has-outline": hasOutline || void 0,
99
100
  "data-disabled": disabled || void 0,
100
101
  "data-read-only": readOnly || void 0,
101
102
  "data-error": Boolean(error) || void 0,
@@ -28,6 +28,11 @@ export type TextareaProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'v
28
28
  * @default false
29
29
  */
30
30
  block?: boolean;
31
+ /**
32
+ * Показывает outline поля при фокусе.
33
+ * @default false
34
+ */
35
+ hasOutline?: boolean;
31
36
  /**
32
37
  * Название поля. Клик переводит фокус в textarea.
33
38
  */
package/dist/index.d.ts CHANGED
@@ -25,4 +25,6 @@ export type { CollapseProps } from './components/Collapse/index.js';
25
25
  export { Modal, ModalComponent, ModalHeader, ModalContent, ModalFooter, ModalControls, } from './components/Modal/index.js';
26
26
  export { Portal } from './components/Portal/index.js';
27
27
  export type { PortalProps } from './components/Portal/index.js';
28
+ export { Sidepanel, Header as SidepanelHeader, Content as SidepanelContent, Footer as SidepanelFooter, } from './components/Sidepanel/index.js';
29
+ export type { SidepanelProps, HeaderProps as SidepanelHeaderProps, ContentProps as SidepanelContentProps, FooterProps as SidepanelFooterProps, } from './components/Sidepanel/index.js';
28
30
  export type { ModalProps, HeaderProps as ModalHeaderProps, ContentProps as ModalContentProps, FooterProps as ModalFooterProps, ControlsProps as ModalControlsProps, } from './components/Modal/index.js';
package/dist/index.js CHANGED
@@ -12,3 +12,4 @@ export { Popover } from "./components/Popover/index.js";
12
12
  export { Collapse } from "./components/Collapse/index.js";
13
13
  export { Modal, ModalComponent, ModalContent, ModalControls, ModalFooter, ModalHeader } from "./components/Modal/index.js";
14
14
  export { Portal } from "./components/Portal/index.js";
15
+ export { Content as SidepanelContent, Footer as SidepanelFooter, Header as SidepanelHeader, Sidepanel } from "./components/Sidepanel/index.js";
@@ -0,0 +1 @@
1
+ export declare function lockScroll(document: Document, removeScrollbarGutter?: boolean): () => void;
@@ -0,0 +1,49 @@
1
+ const locks = new WeakMap();
2
+ function lockScroll(document, removeScrollbarGutter = false) {
3
+ const { body, documentElement, defaultView } = document;
4
+ const existing = locks.get(document);
5
+ if (existing) existing.count += 1;
6
+ else {
7
+ locks.set(document, {
8
+ count: 1,
9
+ overflow: body.style.overflow,
10
+ paddingRight: body.style.paddingRight,
11
+ paddingLeft: body.style.paddingLeft
12
+ });
13
+ const scrollbarWidth = (defaultView?.innerWidth ?? 0) - documentElement.clientWidth;
14
+ const hasStableGutter = defaultView?.getComputedStyle(documentElement).scrollbarGutter?.split(/\s+/).includes('stable');
15
+ if (scrollbarWidth > 0 && defaultView && !hasStableGutter) {
16
+ const paddingRight = parseFloat(defaultView.getComputedStyle(body).paddingRight) || 0;
17
+ body.style.paddingRight = `${paddingRight + scrollbarWidth}px`;
18
+ }
19
+ body.style.overflow = 'hidden';
20
+ }
21
+ const gutterLock = locks.get(document);
22
+ if (removeScrollbarGutter && !gutterLock.gutter && defaultView?.getComputedStyle(documentElement).scrollbarGutter?.includes('stable')) {
23
+ const before = documentElement.getBoundingClientRect();
24
+ const paddingLeft = parseFloat(defaultView.getComputedStyle(body).paddingLeft) || 0;
25
+ const paddingRight = parseFloat(defaultView.getComputedStyle(body).paddingRight) || 0;
26
+ gutterLock.gutter = {
27
+ value: documentElement.style.getPropertyValue('scrollbar-gutter'),
28
+ priority: documentElement.style.getPropertyPriority('scrollbar-gutter')
29
+ };
30
+ documentElement.style.setProperty('scrollbar-gutter', 'auto', 'important');
31
+ const after = documentElement.getBoundingClientRect();
32
+ body.style.paddingLeft = `${paddingLeft + Math.max(0, before.left - after.left)}px`;
33
+ body.style.paddingRight = `${paddingRight + Math.max(0, after.right - before.right)}px`;
34
+ }
35
+ return ()=>{
36
+ const lock = locks.get(document);
37
+ if (!lock) return;
38
+ lock.count -= 1;
39
+ if (0 === lock.count) {
40
+ body.style.overflow = lock.overflow;
41
+ body.style.paddingRight = lock.paddingRight;
42
+ body.style.paddingLeft = lock.paddingLeft;
43
+ if (lock.gutter) if (lock.gutter.value) documentElement.style.setProperty('scrollbar-gutter', lock.gutter.value, lock.gutter.priority);
44
+ else documentElement.style.removeProperty('scrollbar-gutter');
45
+ locks.delete(document);
46
+ }
47
+ };
48
+ }
49
+ export { lockScroll };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koslibs/components",
3
- "version": "1.11.1",
3
+ "version": "1.13.0",
4
4
  "description": "Shared React components for koslibs projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- export declare function lockScroll(document: Document): () => void;
@@ -1,31 +0,0 @@
1
- const locks = new WeakMap();
2
- function lockScroll(document) {
3
- const { body, documentElement, defaultView } = document;
4
- const existing = locks.get(document);
5
- if (existing) existing.count += 1;
6
- else {
7
- locks.set(document, {
8
- count: 1,
9
- overflow: body.style.overflow,
10
- paddingRight: body.style.paddingRight
11
- });
12
- const scrollbarWidth = (defaultView?.innerWidth ?? 0) - documentElement.clientWidth;
13
- const hasStableGutter = defaultView?.getComputedStyle(documentElement).scrollbarGutter?.split(/\s+/).includes('stable');
14
- if (scrollbarWidth > 0 && defaultView && !hasStableGutter) {
15
- const paddingRight = parseFloat(defaultView.getComputedStyle(body).paddingRight) || 0;
16
- body.style.paddingRight = `${paddingRight + scrollbarWidth}px`;
17
- }
18
- body.style.overflow = 'hidden';
19
- }
20
- return ()=>{
21
- const lock = locks.get(document);
22
- if (!lock) return;
23
- lock.count -= 1;
24
- if (0 === lock.count) {
25
- body.style.overflow = lock.overflow;
26
- body.style.paddingRight = lock.paddingRight;
27
- locks.delete(document);
28
- }
29
- };
30
- }
31
- export { lockScroll };