@snack-uikit/button-combo 0.0.1-preview-a2f98ef7.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 (40) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +201 -0
  3. package/README.md +31 -0
  4. package/dist/cjs/components/ButtonCombo.d.ts +11 -0
  5. package/dist/cjs/components/ButtonCombo.js +99 -0
  6. package/dist/cjs/components/index.d.ts +1 -0
  7. package/dist/cjs/components/index.js +25 -0
  8. package/dist/cjs/components/styles.module.css +221 -0
  9. package/dist/cjs/components/utils.d.ts +3 -0
  10. package/dist/cjs/components/utils.js +17 -0
  11. package/dist/cjs/constants.d.ts +3 -0
  12. package/dist/cjs/constants.js +13 -0
  13. package/dist/cjs/index.d.ts +1 -0
  14. package/dist/cjs/index.js +25 -0
  15. package/dist/cjs/styles.module.css +0 -0
  16. package/dist/cjs/types.d.ts +16 -0
  17. package/dist/cjs/types.js +5 -0
  18. package/dist/esm/components/ButtonCombo.d.ts +11 -0
  19. package/dist/esm/components/ButtonCombo.js +44 -0
  20. package/dist/esm/components/index.d.ts +1 -0
  21. package/dist/esm/components/index.js +1 -0
  22. package/dist/esm/components/styles.module.css +221 -0
  23. package/dist/esm/components/utils.d.ts +3 -0
  24. package/dist/esm/components/utils.js +10 -0
  25. package/dist/esm/constants.d.ts +3 -0
  26. package/dist/esm/constants.js +7 -0
  27. package/dist/esm/index.d.ts +1 -0
  28. package/dist/esm/index.js +1 -0
  29. package/dist/esm/styles.module.css +0 -0
  30. package/dist/esm/types.d.ts +16 -0
  31. package/dist/esm/types.js +1 -0
  32. package/package.json +46 -0
  33. package/src/components/ButtonCombo.tsx +105 -0
  34. package/src/components/index.ts +1 -0
  35. package/src/components/styles.module.scss +114 -0
  36. package/src/components/utils.ts +25 -0
  37. package/src/constants.ts +9 -0
  38. package/src/index.ts +1 -0
  39. package/src/styles.module.scss +79 -0
  40. package/src/types.ts +21 -0
File without changes
@@ -0,0 +1,16 @@
1
+ import { BaseButtonProps } from '@snack-uikit/button-private';
2
+ import { BaseItemProps } from '@snack-uikit/list';
3
+ export type Item = BaseItemProps & {
4
+ /** Название */
5
+ label?: string;
6
+ };
7
+ export type BaseButtonComboProps = Pick<BaseButtonProps, 'className' | 'disabled' | 'loading' | 'size' | 'type' | 'tabIndex' | 'fullWidth'> & {
8
+ /** CSS-класс Dropdown */
9
+ dropdownClassName?: string;
10
+ /** Начальное состояние */
11
+ defaultValue?: string;
12
+ /** Начальное состояние лейбла */
13
+ defaultLabel?: string;
14
+ /** Основные элементы списка */
15
+ items: Item[];
16
+ };
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -0,0 +1,11 @@
1
+ import { WithSupportProps } from '@snack-uikit/utils';
2
+ import { BaseButtonComboProps, Item } from '../types';
3
+ export type ButtonComboProps = WithSupportProps<BaseButtonComboProps>;
4
+ export declare const ButtonCombo: import("react").ForwardRefExoticComponent<{
5
+ 'data-test-id'?: string;
6
+ } & import("react").AriaAttributes & Pick<import("@snack-uikit/button-private").BaseButtonProps, "className" | "tabIndex" | "loading" | "size" | "disabled" | "type" | "fullWidth"> & {
7
+ dropdownClassName?: string;
8
+ defaultValue?: string;
9
+ defaultLabel?: string;
10
+ items: Item[];
11
+ } & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,44 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import cn from 'classnames';
14
+ import { forwardRef, useCallback, useMemo, useState } from 'react';
15
+ import { ButtonPrivate, extractCommonButtonProps, HTML_TYPE, SIZE } from '@snack-uikit/button-private';
16
+ import { ChevronDownSVG, ChevronUpSVG } from '@snack-uikit/icons';
17
+ import { Droplist } from '@snack-uikit/list';
18
+ import { extractSupportProps, useLayoutEffect } from '@snack-uikit/utils';
19
+ import { DROPLIST_SIZE_MAP } from '../constants';
20
+ import styles from './styles.module.css';
21
+ import { extractButtonActionsProps, extractDroplistItemProps } from './utils';
22
+ export const ButtonCombo = forwardRef((_a, ref) => {
23
+ var _b;
24
+ var { size = SIZE.S, type = HTML_TYPE.Button, defaultLabel = '', className, dropdownClassName, defaultValue, items, tabIndex, fullWidth } = _a, rest = __rest(_a, ["size", "type", "defaultLabel", "className", "dropdownClassName", "defaultValue", "items", "tabIndex", "fullWidth"]);
25
+ const [open, setOpen] = useState(false);
26
+ const [activeOption, setActiveOption] = useState({});
27
+ const droplistItems = useMemo(() => items.map(extractDroplistItemProps).map(item => (Object.assign(Object.assign({}, item), { id: item === null || item === void 0 ? void 0 : item.id, content: item === null || item === void 0 ? void 0 : item.label }))), [items]);
28
+ const handleSelectionChange = useCallback((value) => {
29
+ if (value) {
30
+ const option = items.find(item => (item === null || item === void 0 ? void 0 : item.id) === value);
31
+ option && setActiveOption(option);
32
+ }
33
+ }, [items]);
34
+ useLayoutEffect(() => {
35
+ if (defaultValue) {
36
+ handleSelectionChange(defaultValue);
37
+ }
38
+ }, [defaultValue, handleSelectionChange]);
39
+ return (_jsxs("div", Object.assign({}, extractSupportProps(rest), { ref: ref, className: cn(styles.buttonComboWrapper, className), tabIndex: tabIndex, "data-full-width": fullWidth || undefined, children: [_jsx(ButtonPrivate, Object.assign({}, extractCommonButtonProps(rest), extractButtonActionsProps(activeOption), { "data-type": 'option', size: size, label: (_b = activeOption === null || activeOption === void 0 ? void 0 : activeOption.label) !== null && _b !== void 0 ? _b : defaultLabel, type: type, fullWidth: fullWidth, iconClassName: styles.icon, labelClassName: styles.label, "data-test-id": 'button-combo__option' })), _jsx(Droplist, { items: droplistItems, open: open, onOpenChange: setOpen, selection: {
40
+ defaultValue,
41
+ mode: 'single',
42
+ onChange: handleSelectionChange,
43
+ }, className: dropdownClassName, size: DROPLIST_SIZE_MAP[size], placement: 'bottom-end', closeDroplistOnItemClick: true, "data-test-id": 'button-combo__dropdown', children: _jsx(ButtonPrivate, Object.assign({}, extractCommonButtonProps(rest), { "data-type": 'dropdown', size: size, iconClassName: styles.icon, labelClassName: styles.label, icon: open ? _jsx(ChevronUpSVG, {}) : _jsx(ChevronDownSVG, {}), "data-test-id": 'button-combo__dropdown-button' })) })] })));
44
+ });
@@ -0,0 +1 @@
1
+ export * from './ButtonCombo';
@@ -0,0 +1 @@
1
+ export * from './ButtonCombo';
@@ -0,0 +1,221 @@
1
+ .buttonComboWrapper{
2
+ display:inline-flex;
3
+ align-items:center;
4
+ }
5
+ .buttonComboWrapper[data-full-width]{
6
+ flex-grow:1;
7
+ flex-shrink:1;
8
+ width:100%;
9
+ }
10
+ .buttonComboWrapper button[data-type=option]{
11
+ border-right:var(--border-width-button-combo-divider, 1px) solid var(--sys-neutral-decor-default, #dde0ea);
12
+ }
13
+ .buttonComboWrapper button[data-type=option][data-size=xs]{
14
+ height:var(--size-button-xs, 24px);
15
+ border-top-left-radius:var(--radius-button-xs, 8px);
16
+ border-bottom-left-radius:var(--radius-button-xs, 8px);
17
+ padding-right:var(--space-button-xs-from-label, 8px);
18
+ padding-left:var(--space-button-xs-from-label, 8px);
19
+ border-right-width:var(--border-width-button-combo-divider, 1px);
20
+ }
21
+ .buttonComboWrapper button[data-type=option][data-size=s]{
22
+ height:var(--size-button-s, 32px);
23
+ border-top-left-radius:var(--radius-button-s, 12px);
24
+ border-bottom-left-radius:var(--radius-button-s, 12px);
25
+ padding-right:var(--space-button-s-from-label, 16px);
26
+ padding-left:var(--space-button-s-from-label, 16px);
27
+ border-right-width:var(--border-width-button-combo-divider, 1px);
28
+ }
29
+ .buttonComboWrapper button[data-type=option][data-size=m]{
30
+ height:var(--size-button-m, 40px);
31
+ border-top-left-radius:var(--radius-button-m, 14px);
32
+ border-bottom-left-radius:var(--radius-button-m, 14px);
33
+ padding-right:var(--space-button-m-from-label, 24px);
34
+ padding-left:var(--space-button-m-from-label, 24px);
35
+ border-right-width:var(--border-width-button-combo-divider, 1px);
36
+ }
37
+ .buttonComboWrapper button[data-type=option][data-size=l]{
38
+ height:var(--size-button-l, 48px);
39
+ border-top-left-radius:var(--radius-button-l, 16px);
40
+ border-bottom-left-radius:var(--radius-button-l, 16px);
41
+ padding-right:var(--space-button-l-from-label, 32px);
42
+ padding-left:var(--space-button-l-from-label, 32px);
43
+ border-right-width:var(--border-width-button-combo-divider, 1px);
44
+ }
45
+ .buttonComboWrapper button[data-type=dropdown][data-size=xs]{
46
+ border-top-right-radius:var(--radius-button-xs, 8px);
47
+ border-bottom-right-radius:var(--radius-button-xs, 8px);
48
+ width:var(--size-button-xs, 24px);
49
+ height:var(--size-button-xs, 24px);
50
+ }
51
+ .buttonComboWrapper button[data-type=dropdown][data-size=s]{
52
+ border-top-right-radius:var(--radius-button-s, 12px);
53
+ border-bottom-right-radius:var(--radius-button-s, 12px);
54
+ width:var(--size-button-s, 32px);
55
+ height:var(--size-button-s, 32px);
56
+ }
57
+ .buttonComboWrapper button[data-type=dropdown][data-size=m]{
58
+ border-top-right-radius:var(--radius-button-m, 14px);
59
+ border-bottom-right-radius:var(--radius-button-m, 14px);
60
+ width:var(--size-button-m, 40px);
61
+ height:var(--size-button-m, 40px);
62
+ }
63
+ .buttonComboWrapper button[data-type=dropdown][data-size=l]{
64
+ border-top-right-radius:var(--radius-button-l, 16px);
65
+ border-bottom-right-radius:var(--radius-button-l, 16px);
66
+ width:var(--size-button-l, 48px);
67
+ height:var(--size-button-l, 48px);
68
+ }
69
+ .buttonComboWrapper button[data-size=xs] .label{
70
+ font-family:var(--sans-label-s-font-family, SB Sans Interface);
71
+ font-weight:var(--sans-label-s-font-weight, Semibold);
72
+ line-height:var(--sans-label-s-line-height, 14px);
73
+ font-size:var(--sans-label-s-font-size, 11px);
74
+ letter-spacing:var(--sans-label-s-letter-spacing, 0px);
75
+ paragraph-spacing:var(--sans-label-s-paragraph-spacing, 6.05px);
76
+ }
77
+ .buttonComboWrapper button[data-size=xs] .icon{
78
+ display:inline-flex;
79
+ align-items:center;
80
+ justify-content:center;
81
+ width:var(--size-icon-container-s, 24px);
82
+ height:var(--size-icon-container-s, 24px);
83
+ }
84
+ .buttonComboWrapper button[data-size=xs] .icon svg{
85
+ max-width:var(--size-icon-container-s, 24px);
86
+ max-height:var(--size-icon-container-s, 24px);
87
+ }
88
+ .buttonComboWrapper button[data-size=s] .label{
89
+ font-family:var(--sans-label-m-font-family, SB Sans Interface);
90
+ font-weight:var(--sans-label-m-font-weight, Semibold);
91
+ line-height:var(--sans-label-m-line-height, 16px);
92
+ font-size:var(--sans-label-m-font-size, 12px);
93
+ letter-spacing:var(--sans-label-m-letter-spacing, 0px);
94
+ paragraph-spacing:var(--sans-label-m-paragraph-spacing, 6.6px);
95
+ }
96
+ .buttonComboWrapper button[data-size=s] .icon{
97
+ display:inline-flex;
98
+ align-items:center;
99
+ justify-content:center;
100
+ width:var(--size-icon-container-s, 24px);
101
+ height:var(--size-icon-container-s, 24px);
102
+ }
103
+ .buttonComboWrapper button[data-size=s] .icon svg{
104
+ max-width:var(--size-icon-container-s, 24px);
105
+ max-height:var(--size-icon-container-s, 24px);
106
+ }
107
+ .buttonComboWrapper button[data-size=m] .label{
108
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
109
+ font-weight:var(--sans-label-l-font-weight, Semibold);
110
+ line-height:var(--sans-label-l-line-height, 20px);
111
+ font-size:var(--sans-label-l-font-size, 14px);
112
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
113
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
114
+ }
115
+ .buttonComboWrapper button[data-size=m] .icon{
116
+ display:inline-flex;
117
+ align-items:center;
118
+ justify-content:center;
119
+ width:var(--size-icon-container-s, 24px);
120
+ height:var(--size-icon-container-s, 24px);
121
+ }
122
+ .buttonComboWrapper button[data-size=m] .icon svg{
123
+ max-width:var(--size-icon-container-s, 24px);
124
+ max-height:var(--size-icon-container-s, 24px);
125
+ }
126
+ .buttonComboWrapper button[data-size=l] .label{
127
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
128
+ font-weight:var(--sans-label-l-font-weight, Semibold);
129
+ line-height:var(--sans-label-l-line-height, 20px);
130
+ font-size:var(--sans-label-l-font-size, 14px);
131
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
132
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
133
+ }
134
+ .buttonComboWrapper button[data-size=l] .icon{
135
+ display:inline-flex;
136
+ align-items:center;
137
+ justify-content:center;
138
+ width:var(--size-icon-container-s, 24px);
139
+ height:var(--size-icon-container-s, 24px);
140
+ }
141
+ .buttonComboWrapper button[data-size=l] .icon svg{
142
+ max-width:var(--size-icon-container-s, 24px);
143
+ max-height:var(--size-icon-container-s, 24px);
144
+ }
145
+ .buttonComboWrapper button[data-loading][data-variant=label-only] .icon{
146
+ position:absolute;
147
+ top:50%;
148
+ left:50%;
149
+ transform:translate(-50%, -50%);
150
+ }
151
+ .buttonComboWrapper button[data-loading][data-variant=label-only] .label{
152
+ opacity:var(--opacity-a0, 0);
153
+ }
154
+ .buttonComboWrapper button[data-appearance=primary]{
155
+ background-color:var(--sys-primary-accent-default, #794ed3);
156
+ }
157
+ .buttonComboWrapper button[data-appearance=primary] .label{
158
+ color:var(--sys-primary-on-accent, #fdfbff);
159
+ }
160
+ .buttonComboWrapper button[data-appearance=primary] .icon{
161
+ color:var(--sys-primary-on-accent, #fdfbff);
162
+ }
163
+ @media (hover: hover){
164
+ .buttonComboWrapper button[data-appearance=primary]:hover{
165
+ background-color:var(--sys-primary-accent-hovered, #6851a6);
166
+ }
167
+ .buttonComboWrapper button[data-appearance=primary]:hover .label{
168
+ color:var(--sys-primary-on-accent, #fdfbff);
169
+ }
170
+ .buttonComboWrapper button[data-appearance=primary]:hover .icon{
171
+ color:var(--sys-primary-on-accent, #fdfbff);
172
+ }
173
+ }
174
+ @media (hover: none) or (hover: hover){
175
+ .buttonComboWrapper button[data-appearance=primary]:focus-visible{
176
+ background-color:var(--sys-primary-accent-hovered, #6851a6);
177
+ }
178
+ .buttonComboWrapper button[data-appearance=primary]:focus-visible .label{
179
+ color:var(--sys-primary-on-accent, #fdfbff);
180
+ }
181
+ .buttonComboWrapper button[data-appearance=primary]:focus-visible .icon{
182
+ color:var(--sys-primary-on-accent, #fdfbff);
183
+ }
184
+ .buttonComboWrapper button[data-appearance=primary]:active{
185
+ background-color:var(--sys-primary-accent-pressed, #5b4796);
186
+ }
187
+ .buttonComboWrapper button[data-appearance=primary]:active .label{
188
+ color:var(--sys-primary-on-accent, #fdfbff);
189
+ }
190
+ .buttonComboWrapper button[data-appearance=primary]:active .icon{
191
+ color:var(--sys-primary-on-accent, #fdfbff);
192
+ }
193
+ .buttonComboWrapper button[data-appearance=primary][data-loading]{
194
+ background-color:var(--sys-primary-accent-pressed, #5b4796);
195
+ }
196
+ .buttonComboWrapper button[data-appearance=primary][data-loading] .label{
197
+ color:var(--sys-primary-on-accent, #fdfbff);
198
+ }
199
+ .buttonComboWrapper button[data-appearance=primary][data-loading] .icon{
200
+ color:var(--sys-primary-on-accent, #fdfbff);
201
+ }
202
+ .buttonComboWrapper button[data-appearance=primary]:disabled, .buttonComboWrapper button[data-appearance=primary][data-disabled]{
203
+ background-color:var(--sys-neutral-decor-disabled, #e6e8ef);
204
+ }
205
+ .buttonComboWrapper button[data-appearance=primary]:disabled .label, .buttonComboWrapper button[data-appearance=primary][data-disabled] .label{
206
+ color:var(--sys-neutral-text-light, #8b8e9b);
207
+ }
208
+ .buttonComboWrapper button[data-appearance=primary]:disabled .icon, .buttonComboWrapper button[data-appearance=primary][data-disabled] .icon{
209
+ color:var(--sys-neutral-text-light, #8b8e9b);
210
+ }
211
+ }
212
+ .buttonComboWrapper button[data-appearance=primary]:focus-visible{
213
+ z-index:1;
214
+ outline-color:var(--sys-available-complementary, #1c1c24);
215
+ }
216
+ .buttonComboWrapper button:focus-visible{
217
+ outline-width:var(--border-state-focus-s-border-width, 2px);
218
+ outline-style:var(--border-state-focus-s-border-style, solid);
219
+ outline-color:var(--border-state-focus-s-border-color, );
220
+ outline-offset:var(--spacing-state-focus-offset, 2px);
221
+ }
@@ -0,0 +1,3 @@
1
+ import { Item } from '../types';
2
+ export declare function extractDroplistItemProps(props: Item): Item;
3
+ export declare function extractButtonActionsProps(props: Item): Item;
@@ -0,0 +1,10 @@
1
+ export function extractDroplistItemProps(props) {
2
+ return Object.keys(props)
3
+ .filter(item => !item.startsWith('on'))
4
+ .reduce((nextProps, prop) => (Object.assign(Object.assign({}, nextProps), { [prop]: props[prop] })), {});
5
+ }
6
+ export function extractButtonActionsProps(props) {
7
+ return Object.keys(props)
8
+ .filter(item => item.startsWith('on'))
9
+ .reduce((nextProps, prop) => (Object.assign(Object.assign({}, nextProps), { [prop]: props[prop] })), {});
10
+ }
@@ -0,0 +1,3 @@
1
+ import { Size } from '@snack-uikit/button-private';
2
+ import { DroplistProps } from '@snack-uikit/list';
3
+ export declare const DROPLIST_SIZE_MAP: Record<Size, DroplistProps['size']>;
@@ -0,0 +1,7 @@
1
+ import { SIZE } from '@snack-uikit/button-private';
2
+ export const DROPLIST_SIZE_MAP = {
3
+ [SIZE.Xs]: 's',
4
+ [SIZE.S]: 's',
5
+ [SIZE.M]: 'm',
6
+ [SIZE.L]: 'l',
7
+ };
@@ -0,0 +1 @@
1
+ export * from './components';
@@ -0,0 +1 @@
1
+ export * from './components';
File without changes
@@ -0,0 +1,16 @@
1
+ import { BaseButtonProps } from '@snack-uikit/button-private';
2
+ import { BaseItemProps } from '@snack-uikit/list';
3
+ export type Item = BaseItemProps & {
4
+ /** Название */
5
+ label?: string;
6
+ };
7
+ export type BaseButtonComboProps = Pick<BaseButtonProps, 'className' | 'disabled' | 'loading' | 'size' | 'type' | 'tabIndex' | 'fullWidth'> & {
8
+ /** CSS-класс Dropdown */
9
+ dropdownClassName?: string;
10
+ /** Начальное состояние */
11
+ defaultValue?: string;
12
+ /** Начальное состояние лейбла */
13
+ defaultLabel?: string;
14
+ /** Основные элементы списка */
15
+ items: Item[];
16
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@snack-uikit/button-combo",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "title": "Button Combo",
7
+ "version": "0.0.1-preview-a2f98ef7.0",
8
+ "sideEffects": [
9
+ "*.css",
10
+ "*.woff",
11
+ "*.woff2"
12
+ ],
13
+ "description": "Кнопка с выбором действий",
14
+ "types": "./dist/esm/index.d.ts",
15
+ "exports": {
16
+ "import": "./dist/esm/index.js",
17
+ "require": "./dist/cjs/index.js"
18
+ },
19
+ "homepage": "https://github.com/cloud-ru-tech/snack-uikit/tree/master/packages/button-combo",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/cloud-ru-tech/snack-uikit.git",
23
+ "directory": "packages/button-combo"
24
+ },
25
+ "author": "Артемий Зимин <aazimin@cloud.ru>",
26
+ "contributors": [
27
+ "Артемий Зимин <aazimin@cloud.ru>"
28
+ ],
29
+ "files": [
30
+ "dist/cjs",
31
+ "dist/esm",
32
+ "src",
33
+ "./CHANGELOG.md",
34
+ "./LICENSE"
35
+ ],
36
+ "license": "Apache-2.0",
37
+ "scripts": {},
38
+ "dependencies": {
39
+ "@snack-uikit/button-private": "0.0.1-preview-a2f98ef7.0",
40
+ "@snack-uikit/icons": "0.24.1",
41
+ "@snack-uikit/list": "0.22.3-preview-a2f98ef7.0",
42
+ "@snack-uikit/utils": "3.6.0",
43
+ "classnames": "2.5.1"
44
+ },
45
+ "gitHead": "16999df080b0b870c1a90ba74aa6c296cd4d480b"
46
+ }
@@ -0,0 +1,105 @@
1
+ import cn from 'classnames';
2
+ import { forwardRef, useCallback, useMemo, useState } from 'react';
3
+
4
+ import { ButtonPrivate, extractCommonButtonProps, HTML_TYPE, SIZE } from '@snack-uikit/button-private';
5
+ import { ChevronDownSVG, ChevronUpSVG } from '@snack-uikit/icons';
6
+ import { Droplist, ItemId } from '@snack-uikit/list';
7
+ import { extractSupportProps, useLayoutEffect, WithSupportProps } from '@snack-uikit/utils';
8
+
9
+ import { DROPLIST_SIZE_MAP } from '../constants';
10
+ import { BaseButtonComboProps, Item } from '../types';
11
+ import styles from './styles.module.scss';
12
+ import { extractButtonActionsProps, extractDroplistItemProps } from './utils';
13
+
14
+ export type ButtonComboProps = WithSupportProps<BaseButtonComboProps>;
15
+
16
+ export const ButtonCombo = forwardRef<HTMLDivElement, ButtonComboProps>(
17
+ (
18
+ {
19
+ size = SIZE.S,
20
+ type = HTML_TYPE.Button,
21
+ defaultLabel = '',
22
+ className,
23
+ dropdownClassName,
24
+ defaultValue,
25
+ items,
26
+ tabIndex,
27
+ fullWidth,
28
+ ...rest
29
+ },
30
+ ref,
31
+ ) => {
32
+ const [open, setOpen] = useState<boolean>(false);
33
+ const [activeOption, setActiveOption] = useState<Item>({});
34
+
35
+ const droplistItems = useMemo(
36
+ () => items.map(extractDroplistItemProps).map(item => ({ ...item, id: item?.id, content: item?.label })),
37
+ [items],
38
+ );
39
+
40
+ const handleSelectionChange = useCallback(
41
+ (value: ItemId) => {
42
+ if (value) {
43
+ const option = items.find(item => item?.id === value);
44
+
45
+ option && setActiveOption(option);
46
+ }
47
+ },
48
+ [items],
49
+ );
50
+
51
+ useLayoutEffect(() => {
52
+ if (defaultValue) {
53
+ handleSelectionChange(defaultValue);
54
+ }
55
+ }, [defaultValue, handleSelectionChange]);
56
+
57
+ return (
58
+ <div
59
+ {...extractSupportProps(rest)}
60
+ ref={ref}
61
+ className={cn(styles.buttonComboWrapper, className)}
62
+ tabIndex={tabIndex}
63
+ data-full-width={fullWidth || undefined}
64
+ >
65
+ <ButtonPrivate
66
+ {...extractCommonButtonProps(rest)}
67
+ {...extractButtonActionsProps(activeOption)}
68
+ data-type='option'
69
+ size={size}
70
+ label={activeOption?.label ?? defaultLabel}
71
+ type={type}
72
+ fullWidth={fullWidth}
73
+ iconClassName={styles.icon}
74
+ labelClassName={styles.label}
75
+ data-test-id='button-combo__option'
76
+ />
77
+ <Droplist
78
+ items={droplistItems}
79
+ open={open}
80
+ onOpenChange={setOpen}
81
+ selection={{
82
+ defaultValue,
83
+ mode: 'single',
84
+ onChange: handleSelectionChange,
85
+ }}
86
+ className={dropdownClassName}
87
+ size={DROPLIST_SIZE_MAP[size]}
88
+ placement='bottom-end'
89
+ closeDroplistOnItemClick
90
+ data-test-id='button-combo__dropdown'
91
+ >
92
+ <ButtonPrivate
93
+ {...extractCommonButtonProps(rest)}
94
+ data-type='dropdown'
95
+ size={size}
96
+ iconClassName={styles.icon}
97
+ labelClassName={styles.label}
98
+ icon={open ? <ChevronUpSVG /> : <ChevronDownSVG />}
99
+ data-test-id='button-combo__dropdown-button'
100
+ />
101
+ </Droplist>
102
+ </div>
103
+ );
104
+ },
105
+ );
@@ -0,0 +1 @@
1
+ export * from './ButtonCombo';
@@ -0,0 +1,114 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-button-buttonFilled';
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-button-buttonCombo';
3
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
4
+ @use '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
5
+ @use '../styles.module';
6
+
7
+ $appearances: 'primary';
8
+ $sizes: 'xs', 's', 'm', 'l';
9
+ $typography: (
10
+ 'xs': styles-tokens-button-buttonFilled.$sans-label-s,
11
+ 's': styles-tokens-button-buttonFilled.$sans-label-m,
12
+ 'm': styles-tokens-button-buttonFilled.$sans-label-l,
13
+ 'l': styles-tokens-button-buttonFilled.$sans-label-l,
14
+ );
15
+
16
+ @mixin button-colors-styles {
17
+ @each $appearance in $appearances {
18
+ &[data-appearance='#{$appearance}'] {
19
+ @include styles.label-color(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
20
+ @include styles.icon-fill(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
21
+
22
+ background-color: styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'accent-default');
23
+
24
+ @media (hover: hover) {
25
+ &:hover {
26
+ @include styles.label-color(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
27
+ @include styles.icon-fill(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
28
+
29
+ background-color: styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'accent-hovered');
30
+ }
31
+ }
32
+
33
+ @media (hover: none) or (hover: hover) {
34
+ &:focus-visible {
35
+ @include styles.label-color(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
36
+ @include styles.icon-fill(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
37
+
38
+ background-color: styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'accent-hovered');
39
+ }
40
+
41
+ &:active {
42
+ @include styles.label-color(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
43
+ @include styles.icon-fill(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
44
+
45
+ background-color: styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'accent-pressed');
46
+ }
47
+
48
+ &[data-loading] {
49
+ @include styles.label-color(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
50
+ @include styles.icon-fill(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'on-accent'));
51
+
52
+ background-color: styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', $appearance, 'accent-pressed');
53
+ }
54
+
55
+ &:disabled,
56
+ &[data-disabled] {
57
+ @include styles.label-color(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', 'neutral', 'text-light'));
58
+ @include styles.icon-fill(styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', 'neutral', 'text-light'));
59
+
60
+ background-color: styles-tokens-button-buttonFilled.simple-var(styles-tokens-button-buttonFilled.$theme-variables, 'sys', 'neutral', 'decor-disabled');
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ @mixin button-outline-colors-styles {
68
+ @each $appearance in $appearances {
69
+ &[data-appearance='#{$appearance}'] {
70
+ &:focus-visible {
71
+ /* stylelint-disable-next-line declaration-property-value-allowed-list */
72
+ z-index: 1;
73
+ outline-color: styles-tokens-button-buttonFilled.$sys-available-complementary;
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ .buttonComboWrapper {
80
+ display: inline-flex;
81
+ align-items: center;
82
+
83
+ &[data-full-width] {
84
+ flex-grow: 1;
85
+ flex-shrink: 1;
86
+ width: 100%;
87
+ }
88
+
89
+ button {
90
+ &[data-type='option'] {
91
+ @each $size in $sizes {
92
+ &[data-size='#{$size}'] {
93
+ @include styles-tokens-element.composite-var(styles-tokens-button-buttonCombo.$button-combo, 'container', $size, 'option');
94
+ }
95
+ }
96
+
97
+ border-right: styles-theme-variables.$border-width-button-combo-divider solid
98
+ styles-theme-variables.$sys-neutral-decor-default;
99
+ }
100
+ &[data-type='dropdown'] {
101
+ @each $size in $sizes {
102
+ &[data-size='#{$size}'] {
103
+ @include styles-tokens-element.composite-var(styles-tokens-button-buttonCombo.$button-combo, 'container', $size, 'dropdown');
104
+ }
105
+ }
106
+ }
107
+
108
+ @include styles.button-anatomy-styles(styles-tokens-button-buttonFilled.$button-filled, $sizes, [], $typography);
109
+ @include styles.loading-label-only(styles-tokens-button-buttonFilled.$button-filled);
110
+ @include button-colors-styles;
111
+ @include button-outline-colors-styles;
112
+ @include styles.button-common-state;
113
+ }
114
+ }
@@ -0,0 +1,25 @@
1
+ import { Item } from '../types';
2
+
3
+ export function extractDroplistItemProps(props: Item): Item {
4
+ return Object.keys(props)
5
+ .filter(item => !item.startsWith('on'))
6
+ .reduce(
7
+ (nextProps, prop) => ({
8
+ ...nextProps,
9
+ [prop]: props[prop as unknown as keyof Item],
10
+ }),
11
+ {},
12
+ );
13
+ }
14
+
15
+ export function extractButtonActionsProps(props: Item): Item {
16
+ return Object.keys(props)
17
+ .filter(item => item.startsWith('on'))
18
+ .reduce(
19
+ (nextProps, prop) => ({
20
+ ...nextProps,
21
+ [prop]: props[prop as unknown as keyof Item],
22
+ }),
23
+ {},
24
+ );
25
+ }