@koslibs/components 0.0.0 → 1.0.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,3 +1,27 @@
1
1
  # @koslibs/components
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#1](https://github.com/koslibs/components/pull/1) [`8954b64`](https://github.com/koslibs/components/commit/8954b64becb19165000479731114463317438bcd) Thanks [@holypower777](https://github.com/holypower777)! - Первый стабильный выпуск 1.0.0 с Button и Spinner, перенесёнными из core-components GullEye.
8
+
9
+ Компонент доступен через `import { Button } from '@koslibs/components/Button'` вместе с типом ButtonProps. Поддерживает шесть вариантов оформления, размеры 32–72, формы rectangular и rounded, левый и правый аддоны, подпись, полную ширину, disabled и loading. Сохранены ref, стандартные HTML-атрибуты и минимальное время отображения загрузки 500 мс.
10
+
11
+ Spinner доступен отдельно через `import { Spinner } from '@koslibs/components/Spinner'` вместе с типом SpinnerProps. Поддерживает размеры 16, 24 и 48, управление видимостью, настройку цвета через style.color, className, id и dataTestId. Button использует этот же публичный Spinner для отображения загрузки.
12
+
13
+ CSS Modules подключаются вместе с используемыми компонентами. Добавлены значения по умолчанию для CSS-переменных размеров, скруглений и шрифтов, чтобы компоненты работали без глобальных стилей GullEye. Отдельный импорт Spinner не включает Button и его стили. Для обоих компонентов в Storybook доступны Docs и Playground.
14
+
15
+ Docs каждого компонента разделены на вкладки «Документация», «Разработчику» и «Обновления». Во вкладке разработчика доступны публичный импорт, Props и CSS-переменные из исходных стилей; обновления отображают CHANGELOG конкретного компонента.
16
+
17
+ Проверка отдельных импортов разделена на общий fixture и декларативные сценарии: JS и типы всех публичных компонентов проверяются автоматически, библиотека собирается один раз, а выбранные production-потребители — одним запуском builder в независимых environments с общей установленной копией пакета и отдельными выходными папками.
18
+
19
+ ## 0.1.0
20
+
21
+ ### Minor Changes
22
+
23
+ - [`ee39a5b`](https://github.com/koslibs/components/commit/ee39a5bb32f94a3a76e4367ec6b14d9cbe4d41b2) Thanks [@holypower777](https://github.com/holypower777)! - Подготовлен каркас @koslibs/components для React 18/19 и TypeScript: сборка ESM, деклараций и CSS Modules через @koslibs/builder@1.0.0, общие настройки и release-инструменты @koslibs/configs@1.0.0, Storybook и workflow деплоя на GitHub Pages.
24
+
25
+ Настроены отдельные импорты вида @koslibs/components/Button. Тест production-сборки проверяет, что в бандл потребителя входят выбранный компонент и его CSS, а независимые соседние компоненты исключаются. Добавлены CI, Changesets и документация по переносу компонентов и ведению их историй изменений. Runtime-компоненты будут перенесены отдельно.
26
+
3
27
  Опубликованных версий пока нет. После первого релиза Changesets добавит сюда историю пакета.
package/README.md CHANGED
@@ -20,18 +20,18 @@ src/components/Button/
20
20
  также добавить реэкспорт в `src/index.ts`; отдельный импорт компонента его не использует.
21
21
  3. Общий шаблон в `package.json` → `exports` уже связывает каждый компонент с его файлами:
22
22
 
23
- ```json
24
- {
25
- "./*": {
26
- "types": "./dist/components/*/index.d.ts",
27
- "import": "./dist/components/*/index.js"
28
- }
29
- }
30
- ```
23
+ ```json
24
+ {
25
+ "./*": {
26
+ "types": "./dist/components/*/index.d.ts",
27
+ "import": "./dist/components/*/index.js"
28
+ }
29
+ }
30
+ ```
31
31
 
32
32
  4. Добавьте Docs, Playground и `CHANGELOG.md` по [примеру документации](https://github.com/koslibs/components/blob/main/docs/component-changelogs.md).
33
- 5. Добавьте тесты поведения. Unit-тесты можно запускать через `koslibs-builder lib:test`;
34
- в каркасе unit-suite отсутствует, потому что runtime-компонентов пока нет.
33
+ 5. Добавьте тесты поведения. `npm run test:unit` запускает unit-тесты через
34
+ `koslibs-builder lib:test`; `npm test` также проверяет отдельные импорты.
35
35
  6. Выполните сборку и проверьте реальные пути JS, деклараций и CSS в `dist`.
36
36
 
37
37
  После публикации предполагается такой импорт:
@@ -53,10 +53,11 @@ Builder сохраняет отдельные JS-модули и отдельн
53
53
 
54
54
  Тест `tests/component-imports.test.mjs` запускается через Node.js test runner командой
55
55
  `npm test` (или отдельно `npm run test:imports`) и проверяет схему через builder:
56
- собирает временную библиотеку с Button
57
- и Spinner, затем приложение с импортом только `@koslibs/components/Button`.
58
- Проверка подтверждает наличие Button и его CSS и отсутствие Spinner и его CSS
59
- в production-бандле. Временные компоненты находятся в `.cache` и не публикуются.
56
+ собирает временную библиотеку с настоящим Button и независимым тестовым Spinner,
57
+ затем приложение с импортом только `@koslibs/components/Button`.
58
+ Проверка подтверждает наличие Button, его внутреннего спиннера загрузки и их CSS
59
+ и отсутствие независимого Spinner в production-бандле.
60
+ Временная библиотека находится в `.cache` и не публикуется.
60
61
 
61
62
  Пакет выпускается в ESM. CSS помечен как side effect, чтобы оптимизация потребителя
62
63
  не удаляла стили. Builder сохраняет структуру модулей; Docs, stories и Markdown
@@ -0,0 +1,67 @@
1
+ import React, { type ButtonHTMLAttributes, type ReactNode } from 'react';
2
+ type ButtonOwnProps = {
3
+ /**
4
+ * Тип кнопки
5
+ * @default 'primary'
6
+ */
7
+ view?: 'primary' | 'secondary' | 'accent' | 'outlined' | 'transparent' | 'text';
8
+ /**
9
+ * Скругление кнопки
10
+ * @default 'rectangular'
11
+ */
12
+ shape?: 'rectangular' | 'rounded';
13
+ /**
14
+ * Размер кнопки
15
+ * @default 40
16
+ */
17
+ size?: 32 | 40 | 48 | 56 | 64 | 72;
18
+ /**
19
+ * Аддон слева от лейбла
20
+ */
21
+ leftAddon?: ReactNode;
22
+ /**
23
+ * Аддон справа от лейбла
24
+ */
25
+ rightAddon?: ReactNode;
26
+ /**
27
+ * Подпись под основным текстом
28
+ */
29
+ hint?: string;
30
+ /**
31
+ * Заставляет кнопку растянуться на ширину контейнера.
32
+ * @default false
33
+ */
34
+ block?: boolean;
35
+ /**
36
+ * Показать лоадер
37
+ * @default false
38
+ */
39
+ loading?: boolean;
40
+ /**
41
+ * Айди для автотестов.
42
+ */
43
+ dataTestId?: string;
44
+ /**
45
+ * Дополнительный класс
46
+ */
47
+ className?: string;
48
+ /**
49
+ * Дополнительный класс для label
50
+ */
51
+ labelClassName?: string;
52
+ /**
53
+ * Дополнительный класс для hint
54
+ */
55
+ hintClassName?: string;
56
+ /**
57
+ * Дополнительный класс для спиннера
58
+ */
59
+ spinnerClassName?: string;
60
+ /**
61
+ * Текст кнопки
62
+ */
63
+ children?: ReactNode;
64
+ };
65
+ export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & ButtonOwnProps;
66
+ export declare const Button: React.ForwardRefExoticComponent<React.ButtonHTMLAttributes<HTMLButtonElement> & ButtonOwnProps & React.RefAttributes<HTMLButtonElement>>;
67
+ export {};
@@ -0,0 +1,88 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import react, { forwardRef, useEffect, useRef, useState } from "react";
4
+ import { Spinner } from "../Spinner/index.js";
5
+ import colors_module from "./colors.module.js";
6
+ import index_module from "./index.module.js";
7
+ const Button = /*#__PURE__*/ forwardRef(({ view = 'primary', shape = 'rectangular', size = 40, leftAddon, rightAddon, hint, block = false, loading = false, dataTestId, className, labelClassName, hintClassName, spinnerClassName, children, onClick, ...restProps }, ref)=>{
8
+ const [loaderTimePassed, setLoaderTimePassed] = useState(true);
9
+ const timerId = useRef(0);
10
+ const showLoader = loading || !loaderTimePassed;
11
+ const showHint = hint && [
12
+ 56,
13
+ 64,
14
+ 72
15
+ ].includes(size);
16
+ const { disabled, type = 'button', ...restButtonProps } = restProps;
17
+ const buttonChildren = /*#__PURE__*/ jsxs(react.Fragment, {
18
+ children: [
19
+ leftAddon && /*#__PURE__*/ jsx("span", {
20
+ className: index_module.addons,
21
+ children: leftAddon
22
+ }),
23
+ children && /*#__PURE__*/ jsxs("span", {
24
+ className: classnames(index_module.label, labelClassName, {
25
+ [index_module.stretchText]: !(leftAddon || rightAddon)
26
+ }),
27
+ children: [
28
+ children,
29
+ showHint && /*#__PURE__*/ jsx("span", {
30
+ className: classnames(index_module.hint, colors_module.hint, hintClassName),
31
+ children: hint
32
+ })
33
+ ]
34
+ }),
35
+ showLoader && /*#__PURE__*/ jsx(Spinner, {
36
+ size: 24,
37
+ dataTestId: dataTestId ? `${dataTestId}-loader` : void 0,
38
+ visible: true,
39
+ className: classnames(index_module.loader, colors_module.loader, spinnerClassName)
40
+ }),
41
+ rightAddon && /*#__PURE__*/ jsx("span", {
42
+ className: index_module.addons,
43
+ children: rightAddon
44
+ })
45
+ ]
46
+ });
47
+ useEffect(()=>{
48
+ if (loading) {
49
+ window.clearTimeout(timerId.current);
50
+ setLoaderTimePassed(false);
51
+ timerId.current = window.setTimeout(()=>{
52
+ setLoaderTimePassed(true);
53
+ }, 500);
54
+ }
55
+ }, [
56
+ loading
57
+ ]);
58
+ useEffect(()=>()=>{
59
+ window.clearTimeout(timerId.current);
60
+ }, []);
61
+ const handleClick = (e)=>{
62
+ if (disabled || showLoader) {
63
+ e.preventDefault();
64
+ e.stopPropagation();
65
+ return;
66
+ }
67
+ onClick?.(e);
68
+ };
69
+ return /*#__PURE__*/ jsx("button", {
70
+ ...restButtonProps,
71
+ className: classnames(index_module.component, index_module[view], index_module[`size-${size}`], index_module[shape], colors_module.component, colors_module[view], {
72
+ [index_module.block]: block,
73
+ [index_module.loading]: showLoader,
74
+ [index_module.withRightAddon]: Boolean(rightAddon),
75
+ [index_module.withLeftAddon]: Boolean(leftAddon),
76
+ [colors_module.loading]: showLoader
77
+ }, className),
78
+ "data-test-id": dataTestId,
79
+ type: type,
80
+ disabled: disabled || showLoader,
81
+ "aria-busy": showLoader,
82
+ onClick: handleClick,
83
+ ref: ref,
84
+ children: buttonChildren
85
+ });
86
+ });
87
+ Button.displayName = 'Button';
88
+ export { Button };
@@ -0,0 +1,14 @@
1
+ import "./colors_module.css";
2
+ const colors_module = {
3
+ primary: "primary-jF1frT",
4
+ secondary: "secondary-em6CEW",
5
+ accent: "accent-cr9M8g",
6
+ transparent: "transparent-osSlaN",
7
+ text: "text-fLOhKS",
8
+ loader: "loader-wrwZSN",
9
+ hint: "hint-JAXcv5",
10
+ outlined: "outlined-xzRrKO",
11
+ component: "component-GmKJmC",
12
+ loading: "loading-LzTYPE"
13
+ };
14
+ export default colors_module;
@@ -0,0 +1,164 @@
1
+ @import "./vars.css";
2
+
3
+ .primary-jF1frT, .secondary-em6CEW, .accent-cr9M8g, .transparent-osSlaN, .text-fLOhKS {
4
+ box-shadow: none;
5
+ }
6
+
7
+ .primary-jF1frT {
8
+ color: var(--button-inverse-color);
9
+ background-color: var(--button-primary-background-color);
10
+
11
+ @media (hover: hover) {
12
+ &:hover {
13
+ background-color: var(--button-primary-hover-background-color);
14
+ }
15
+ }
16
+
17
+ &:active {
18
+ background-color: var(--button-primary-active-background-color);
19
+ }
20
+
21
+ & > .loader-wrwZSN {
22
+ color: var(--button-inverse-color);
23
+ }
24
+
25
+ &:not([disabled]) .hint-JAXcv5 {
26
+ color: var(--button-inverse-hint-color);
27
+ }
28
+ }
29
+
30
+ .secondary-em6CEW {
31
+ color: var(--button-default-color);
32
+ background-color: var(--button-secondary-background-color);
33
+
34
+ @media (hover: hover) {
35
+ &:hover {
36
+ background-color: var(--button-secondary-hover-background-color);
37
+ }
38
+ }
39
+
40
+ &:active {
41
+ background-color: var(--button-secondary-active-background-color);
42
+ }
43
+
44
+ & > .loader-wrwZSN {
45
+ color: var(--button-default-color);
46
+ }
47
+
48
+ &:not([disabled]) .hint-JAXcv5 {
49
+ color: var(--button-hint-color);
50
+ }
51
+ }
52
+
53
+ .accent-cr9M8g {
54
+ color: var(--button-inverse-color);
55
+ background-color: var(--button-accent-background-color);
56
+
57
+ @media (hover: hover) {
58
+ &:hover {
59
+ background-color: var(--button-accent-hover-background-color);
60
+ }
61
+ }
62
+
63
+ &:active {
64
+ background-color: var(--button-accent-active-background-color);
65
+ }
66
+
67
+ & > .loader-wrwZSN {
68
+ color: var(--button-inverse-color);
69
+ }
70
+
71
+ &:not([disabled]) .hint-JAXcv5 {
72
+ color: var(--button-inverse-hint-color);
73
+ }
74
+ }
75
+
76
+ .outlined-xzRrKO {
77
+ color: var(--button-default-color);
78
+ box-shadow: inset 0 0 0 1px var(--button-primary-background-color);
79
+ background: none;
80
+
81
+ @media (hover: hover) {
82
+ &:hover {
83
+ background-color: var(--button-subtle-hover-background-color);
84
+ box-shadow: inset 0 0 0 1px var(--button-primary-hover-background-color);
85
+ }
86
+ }
87
+
88
+ &:active {
89
+ background-color: var(--button-subtle-active-background-color);
90
+ box-shadow: inset 0 0 0 1px var(--button-primary-active-background-color);
91
+ }
92
+
93
+ & > .loader-wrwZSN {
94
+ color: var(--button-default-color);
95
+ }
96
+
97
+ &:not([disabled]) .hint-JAXcv5 {
98
+ color: var(--button-hint-color);
99
+ }
100
+ }
101
+
102
+ .transparent-osSlaN {
103
+ color: var(--button-default-color);
104
+ background-color: var(--button-transparent-background-color);
105
+
106
+ @media (hover: hover) {
107
+ &:hover {
108
+ background-color: var(--button-subtle-hover-background-color);
109
+ }
110
+ }
111
+
112
+ &:active {
113
+ background-color: var(--button-subtle-active-background-color);
114
+ }
115
+
116
+ & > .loader-wrwZSN {
117
+ color: var(--button-default-color);
118
+ }
119
+
120
+ &:not([disabled]) .hint-JAXcv5 {
121
+ color: var(--button-hint-color);
122
+ }
123
+ }
124
+
125
+ .text-fLOhKS {
126
+ background-color: var(--button-transparent-background-color);
127
+ color: var(--button-default-color);
128
+
129
+ @media (hover: hover) {
130
+ &:hover {
131
+ color: var(--button-text-hover-color);
132
+ }
133
+ }
134
+
135
+ &:active {
136
+ color: var(--button-text-active-color);
137
+ }
138
+
139
+ & > .loader-wrwZSN {
140
+ color: var(--button-default-color);
141
+ }
142
+
143
+ &:not([disabled]) .hint-JAXcv5 {
144
+ color: var(--button-hint-color);
145
+ }
146
+ }
147
+
148
+ .component-GmKJmC[disabled] {
149
+ &:not(.loading-LzTYPE) {
150
+ background-color: var(--button-disabled-background-color);
151
+ color: var(--button-disabled-color);
152
+ box-shadow: none;
153
+
154
+ &.hint-JAXcv5 {
155
+ color: var(--button-disabled-hint-color);
156
+ }
157
+ }
158
+
159
+ &.transparent-osSlaN, &.text-fLOhKS {
160
+ background-color: var(--button-transparent-background-color);
161
+ color: var(--button-disabled-color);
162
+ }
163
+ }
164
+
@@ -0,0 +1,2 @@
1
+ export { Button } from './button.js';
2
+ export type { ButtonProps } from './button.js';
@@ -0,0 +1 @@
1
+ export { Button } from "./button.js";
@@ -0,0 +1,30 @@
1
+ import "./index_module.css";
2
+ const index_module = {
3
+ component: "component-vxc0gC",
4
+ "size-32": "size-32-oD8MDp",
5
+ size32: "size-32-oD8MDp",
6
+ "size-40": "size-40-Pu2_h6",
7
+ size40: "size-40-Pu2_h6",
8
+ "size-48": "size-48-U3XIYx",
9
+ size48: "size-48-U3XIYx",
10
+ "size-56": "size-56-P_94sz",
11
+ size56: "size-56-P_94sz",
12
+ "size-64": "size-64-ZlQLt3",
13
+ size64: "size-64-ZlQLt3",
14
+ "size-72": "size-72-zkSrcs",
15
+ size72: "size-72-zkSrcs",
16
+ withRightAddon: "withRightAddon-TNcFTm",
17
+ text: "text-Ifdq0q",
18
+ withLeftAddon: "withLeftAddon-N3YDHw",
19
+ addons: "addons-NxGsiY",
20
+ block: "block-xREK1A",
21
+ rectangular: "rectangular-PFbww7",
22
+ rounded: "rounded-kQqesg",
23
+ loading: "loading-DPEeSl",
24
+ label: "label-gwmTIK",
25
+ stretchText: "stretchText-Nm2RYn",
26
+ hint: "hint-AFu_zz",
27
+ loader: "loader-BRksFP",
28
+ transparent: "transparent-w7vhL3"
29
+ };
30
+ export default index_module;
@@ -0,0 +1,242 @@
1
+ @import "./vars.css";
2
+
3
+ .component-vxc0gC {
4
+ box-sizing: border-box;
5
+ vertical-align: middle;
6
+ width: fit-content;
7
+ font-family: var(--font-ubuntu, "Ubuntu", sans-serif);
8
+ font-size: var(--size-16, 16px);
9
+ text-align: center;
10
+ margin: var(--size-0, 0px);
11
+ padding: var(--size-0, 0px) var(--size-24, 24px);
12
+ cursor: pointer;
13
+ -webkit-user-select: none;
14
+ user-select: none;
15
+ border: none;
16
+ outline: none;
17
+ flex-flow: row;
18
+ justify-content: center;
19
+ align-items: center;
20
+ text-decoration: none;
21
+ transition: background-color .1s ease-in-out, color .1s ease-in-out, box-shadow .1s ease-in-out;
22
+ display: inline-flex;
23
+ position: relative;
24
+ }
25
+
26
+ .size-32-oD8MDp, .size-40-Pu2_h6 {
27
+ font-size: 14px;
28
+ font-weight: 500;
29
+ line-height: 20px;
30
+ font-family: var(--font-family-system, system-ui, sans-serif);
31
+ }
32
+
33
+ .size-32-oD8MDp {
34
+ min-width: 80px;
35
+ min-height: 32px;
36
+ padding: var(--size-0, 0px) var(--size-16, 16px);
37
+ }
38
+
39
+ .size-40-Pu2_h6 {
40
+ min-width: 88px;
41
+ min-height: 40px;
42
+ padding: var(--size-0, 0px) var(--size-20, 20px);
43
+ }
44
+
45
+ .size-48-U3XIYx, .size-56-P_94sz {
46
+ font-size: 16px;
47
+ font-weight: 500;
48
+ line-height: 20px;
49
+ font-family: var(--font-family-system, system-ui, sans-serif);
50
+ }
51
+
52
+ .size-48-U3XIYx {
53
+ min-width: 104px;
54
+ min-height: 48px;
55
+ padding: var(--size-0, 0px) var(--size-24, 24px);
56
+ }
57
+
58
+ .size-56-P_94sz {
59
+ min-width: 128px;
60
+ min-height: 56px;
61
+ padding: var(--size-0, 0px) var(--size-28, 28px);
62
+ }
63
+
64
+ .size-64-ZlQLt3, .size-72-zkSrcs {
65
+ font-size: 18px;
66
+ font-weight: 500;
67
+ line-height: 24px;
68
+ font-family: var(--font-family-system, system-ui, sans-serif);
69
+ }
70
+
71
+ .size-64-ZlQLt3 {
72
+ min-width: 160px;
73
+ min-height: 64px;
74
+ padding: var(--size-0, 0px) var(--size-32, 32px);
75
+ }
76
+
77
+ .size-72-zkSrcs {
78
+ min-width: 168px;
79
+ min-height: 72px;
80
+ padding: var(--size-0, 0px) var(--size-36, 36px);
81
+ }
82
+
83
+ .withRightAddon-TNcFTm {
84
+ &.size-32-oD8MDp {
85
+ padding-right: var(--size-12, 12px);
86
+ }
87
+
88
+ &.size-40-Pu2_h6 {
89
+ padding-right: var(--size-16, 16px);
90
+ }
91
+
92
+ &.size-48-U3XIYx {
93
+ padding-right: var(--size-20, 20px);
94
+ }
95
+
96
+ &.size-56-P_94sz {
97
+ padding-right: var(--size-24, 24px);
98
+ }
99
+
100
+ &.size-64-ZlQLt3 {
101
+ padding-right: var(--size-28, 28px);
102
+ }
103
+
104
+ &.size-72-zkSrcs {
105
+ padding-right: var(--size-32, 32px);
106
+ }
107
+
108
+ &.text-Ifdq0q {
109
+ padding-right: var(--size-0, 0px);
110
+ }
111
+ }
112
+
113
+ .withLeftAddon-N3YDHw {
114
+ &.size-32-oD8MDp {
115
+ padding-left: var(--size-12, 12px);
116
+ }
117
+
118
+ &.size-40-Pu2_h6 {
119
+ padding-left: var(--size-16, 16px);
120
+ }
121
+
122
+ &.size-48-U3XIYx {
123
+ padding-left: var(--size-20, 20px);
124
+ }
125
+
126
+ &.size-56-P_94sz {
127
+ padding-left: var(--size-24, 24px);
128
+ }
129
+
130
+ &.size-64-ZlQLt3 {
131
+ padding-left: var(--size-28, 28px);
132
+ }
133
+
134
+ &.size-72-zkSrcs {
135
+ padding-left: var(--size-32, 32px);
136
+ }
137
+
138
+ &.text-Ifdq0q {
139
+ padding-left: var(--size-0, 0px);
140
+ }
141
+ }
142
+
143
+ .text-Ifdq0q {
144
+ min-width: 0;
145
+ min-height: 0;
146
+ padding: var(--size-0, 0px);
147
+ appearance: none;
148
+ border: none;
149
+
150
+ &.size-48-U3XIYx, &.size-56-P_94sz {
151
+ min-height: 24px;
152
+ }
153
+ }
154
+
155
+ .size-48-U3XIYx, .size-56-P_94sz {
156
+ & .addons-NxGsiY:first-child {
157
+ margin-right: var(--size-8, 8px);
158
+ }
159
+
160
+ & .addons-NxGsiY:last-child {
161
+ margin-left: var(--size-8, 8px);
162
+ }
163
+
164
+ & .addons-NxGsiY:only-child {
165
+ margin: var(--size-0, 0px);
166
+ }
167
+ }
168
+
169
+ .size-64-ZlQLt3, .size-72-zkSrcs {
170
+ & .addons-NxGsiY:first-child {
171
+ margin-right: var(--size-12, 12px);
172
+ }
173
+
174
+ & .addons-NxGsiY:last-child {
175
+ margin-left: var(--size-12, 12px);
176
+ }
177
+
178
+ & .addons-NxGsiY:only-child {
179
+ margin: var(--size-0, 0px);
180
+ }
181
+ }
182
+
183
+ .block-xREK1A {
184
+ width: 100%;
185
+ display: flex;
186
+ }
187
+
188
+ .rectangular-PFbww7 {
189
+ border-radius: var(--border-radius-8, 8px);
190
+ }
191
+
192
+ .rounded-kQqesg {
193
+ border-radius: var(--border-radius-pill, 99px);
194
+ }
195
+
196
+ .addons-NxGsiY {
197
+ margin: var(--size-0, 0px);
198
+ flex-shrink: 0;
199
+ align-items: center;
200
+ display: flex;
201
+
202
+ &:first-child {
203
+ margin-right: var(--size-6, 6px);
204
+ }
205
+
206
+ &:last-child {
207
+ margin-left: var(--size-6, 6px);
208
+ }
209
+
210
+ &:only-child {
211
+ margin: var(--size-0, 0px);
212
+ }
213
+ }
214
+
215
+ .loading-DPEeSl .label-gwmTIK, .loading-DPEeSl .addons-NxGsiY {
216
+ opacity: 0;
217
+ }
218
+
219
+ .stretchText-Nm2RYn {
220
+ flex-grow: 1;
221
+ }
222
+
223
+ .hint-AFu_zz {
224
+ font-size: 14px;
225
+ font-weight: 400;
226
+ line-height: 18px;
227
+ font-family: var(--font-primary, "Inter", sans-serif);
228
+ color: var(--button-hint-color);
229
+ display: block;
230
+ }
231
+
232
+ .loader-BRksFP {
233
+ left: var(--size-0, 0px);
234
+ right: var(--size-0, 0px);
235
+ margin: var(--size-0, 0px) auto;
236
+ position: absolute;
237
+ }
238
+
239
+ .transparent-w7vhL3 {
240
+ min-width: 0;
241
+ }
242
+
@@ -0,0 +1,24 @@
1
+ :root {
2
+ --button-default-color: #1c1a18;
3
+ --button-inverse-color: #f5f3ec;
4
+ --button-hint-color: #1c1a1862;
5
+ --button-inverse-hint-color: #f5f3ec72;
6
+ --button-primary-background-color: #ff6b0a;
7
+ --button-primary-hover-background-color: #f05f00;
8
+ --button-primary-active-background-color: #d95300;
9
+ --button-secondary-background-color: #e0dbce;
10
+ --button-secondary-hover-background-color: #d5cdbc;
11
+ --button-secondary-active-background-color: #c8beaa;
12
+ --button-accent-background-color: #7967f2;
13
+ --button-accent-hover-background-color: #6c5ae6;
14
+ --button-accent-active-background-color: #5e4cd8;
15
+ --button-transparent-background-color: transparent;
16
+ --button-subtle-hover-background-color: #f2f3ea;
17
+ --button-subtle-active-background-color: #e5e7db;
18
+ --button-text-hover-color: #4a463f;
19
+ --button-text-active-color: #6a645a;
20
+ --button-disabled-background-color: #cfd1cb;
21
+ --button-disabled-color: #858981;
22
+ --button-disabled-hint-color: #9da099;
23
+ }
24
+
@@ -0,0 +1,5 @@
1
+ import "./colors_module.css";
2
+ const colors_module = {
3
+ component: "component-xRdHbz"
4
+ };
5
+ export default colors_module;
@@ -0,0 +1,6 @@
1
+ @import "./vars.css";
2
+
3
+ .component-xRdHbz {
4
+ color: var(--spinner-default-color);
5
+ }
6
+
@@ -0,0 +1,2 @@
1
+ export { Spinner } from './spinner.js';
2
+ export type { SpinnerProps } from './spinner.js';
@@ -0,0 +1 @@
1
+ export { Spinner } from "./spinner.js";
@@ -0,0 +1,9 @@
1
+ import "./index_module.css";
2
+ const index_module = {
3
+ spinner: "spinner-KHk8mw",
4
+ spin_animation: "spin_animation-pow2iy",
5
+ spinAnimation: "spin_animation-pow2iy",
6
+ visible: "visible-wk34JZ",
7
+ gradient: "gradient-QKWB8G"
8
+ };
9
+ export default index_module;
@@ -0,0 +1,29 @@
1
+ @import "./vars.css";
2
+
3
+ .spinner-KHk8mw {
4
+ box-sizing: content-box;
5
+ animation: spin_animation-pow2iy var(--spinner-animation-duration) infinite
6
+ var(--spinner-animation-timing-function);
7
+ display: none;
8
+ }
9
+
10
+ .visible-wk34JZ {
11
+ display: var(--spinner-display-visible);
12
+ vertical-align: middle;
13
+ }
14
+
15
+ .gradient-QKWB8G {
16
+ width: 100%;
17
+ height: 100%;
18
+ }
19
+
20
+ @keyframes spin_animation-pow2iy {
21
+ from {
22
+ transform: rotate(0);
23
+ }
24
+
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
@@ -0,0 +1,7 @@
1
+ import "./preset_module.css";
2
+ const preset_module = {
3
+ preset16: "preset16-UI50gd",
4
+ preset24: "preset24-PPOWk6",
5
+ preset48: "preset48-Py1Pzz"
6
+ };
7
+ export default preset_module;
@@ -0,0 +1,12 @@
1
+ .preset16-UI50gd {
2
+ padding: 1px;
3
+ }
4
+
5
+ .preset24-PPOWk6 {
6
+ padding: var(--size-2, 2px);
7
+ }
8
+
9
+ .preset48-Py1Pzz {
10
+ padding: var(--size-4, 4px);
11
+ }
12
+
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ export type SpinnerProps = {
3
+ /**
4
+ * Управление видимостью компонента
5
+ * @default false
6
+ */
7
+ visible?: boolean;
8
+ /**
9
+ * Размер спиннера
10
+ *
11
+ * @default 24
12
+ */
13
+ size?: 16 | 24 | 48;
14
+ /**
15
+ * Дополнительный класс
16
+ */
17
+ className?: string;
18
+ /**
19
+ * Идентификатор компонента в DOM
20
+ */
21
+ id?: string;
22
+ /**
23
+ * Идентификатор для систем автоматизированного тестирования
24
+ */
25
+ dataTestId?: string;
26
+ /**
27
+ * Дополнительные инлайн стили для cпиннера
28
+ */
29
+ style?: React.CSSProperties;
30
+ };
31
+ export declare const Spinner: ({ visible, size, className, id, dataTestId, style }: SpinnerProps) => React.JSX.Element;
@@ -0,0 +1,80 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import { useId } from "react";
4
+ import colors_module from "./colors.module.js";
5
+ import index_module from "./index.module.js";
6
+ import preset_module from "./preset.module.js";
7
+ const PRESET_CONFIG = {
8
+ 16: [
9
+ 2,
10
+ 14,
11
+ 'preset16'
12
+ ],
13
+ 24: [
14
+ 2,
15
+ 20,
16
+ 'preset24'
17
+ ],
18
+ 48: [
19
+ 4,
20
+ 40,
21
+ 'preset48'
22
+ ]
23
+ };
24
+ const Spinner = ({ visible, size = 24, className, id, dataTestId, style })=>{
25
+ const [lineWidth, presetSize, styleKey] = PRESET_CONFIG[size];
26
+ const presetClassname = preset_module[styleKey];
27
+ const uniqId = useId();
28
+ const radius = presetSize / 2 - lineWidth / 2;
29
+ const rotationAngle = Math.ceil(180 * Math.asin(lineWidth / 2 / radius) / Math.PI);
30
+ const gap = 90;
31
+ const pathLength = 360;
32
+ const strokeDasharray = `${pathLength - gap - rotationAngle} ${gap + rotationAngle}`;
33
+ const gradient = `conic-gradient(from ${rotationAngle}deg, transparent ${gap - 2 * rotationAngle}deg, currentColor)`;
34
+ return /*#__PURE__*/ jsxs("svg", {
35
+ "aria-hidden": "true",
36
+ viewBox: `0 0 ${presetSize} ${presetSize}`,
37
+ style: {
38
+ ...style,
39
+ height: presetSize,
40
+ width: presetSize
41
+ },
42
+ className: classnames(index_module.spinner, colors_module.component, presetClassname, className, {
43
+ [index_module.visible]: visible
44
+ }),
45
+ "data-test-id": dataTestId,
46
+ id: id,
47
+ children: [
48
+ /*#__PURE__*/ jsx("defs", {
49
+ children: /*#__PURE__*/ jsx("mask", {
50
+ id: uniqId,
51
+ children: /*#__PURE__*/ jsx("circle", {
52
+ cx: "50%",
53
+ cy: "50%",
54
+ r: radius,
55
+ strokeWidth: lineWidth,
56
+ strokeLinecap: "round",
57
+ stroke: "#fff",
58
+ strokeDashoffset: -rotationAngle,
59
+ strokeDasharray: strokeDasharray,
60
+ pathLength: pathLength
61
+ })
62
+ })
63
+ }),
64
+ /*#__PURE__*/ jsx("foreignObject", {
65
+ x: "0",
66
+ y: "0",
67
+ width: presetSize,
68
+ height: presetSize,
69
+ mask: `url(#${uniqId})`,
70
+ children: /*#__PURE__*/ jsx("div", {
71
+ className: index_module.gradient,
72
+ style: {
73
+ backgroundImage: gradient
74
+ }
75
+ })
76
+ })
77
+ ]
78
+ });
79
+ };
80
+ export { Spinner };
@@ -0,0 +1,7 @@
1
+ :root {
2
+ --spinner-display-visible: inline-block;
3
+ --spinner-animation-duration: .8s;
4
+ --spinner-animation-timing-function: linear;
5
+ --spinner-default-color: #030306e0;
6
+ }
7
+
package/dist/index.d.ts CHANGED
@@ -1 +1,4 @@
1
- export {};
1
+ export { Button } from './components/Button/index.js';
2
+ export type { ButtonProps } from './components/Button/index.js';
3
+ export { Spinner } from './components/Spinner/index.js';
4
+ export type { SpinnerProps } from './components/Spinner/index.js';
package/dist/index.js CHANGED
@@ -0,0 +1,2 @@
1
+ export { Button } from "./components/Button/index.js";
2
+ export { Spinner } from "./components/Spinner/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koslibs/components",
3
- "version": "0.0.0",
3
+ "version": "1.0.0",
4
4
  "description": "Shared React components for koslibs projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,8 @@
49
49
  "format:check": "koslibs-lint format:check",
50
50
  "storybook": "koslibs-builder storybook:start",
51
51
  "build-storybook": "koslibs-builder storybook:build",
52
- "test": "node --test tests/*.test.mjs",
52
+ "test": "npm run test:unit && npm run test:imports",
53
+ "test:unit": "koslibs-builder lib:test",
53
54
  "test:imports": "node --test tests/component-imports.test.mjs",
54
55
  "prepack": "koslibs-builder lib:build",
55
56
  "changeset": "koslibs-release add",
@@ -76,5 +77,8 @@
76
77
  "peerDependencies": {
77
78
  "react": "^18.3.1 || ^19.0.0",
78
79
  "react-dom": "^18.3.1 || ^19.0.0"
80
+ },
81
+ "dependencies": {
82
+ "classnames": "2.5.1"
79
83
  }
80
84
  }