Skip to content

feat(filter-tag, tag): новые view и новые механики компонентов [DS-17046]#2256

Open
dHIM24 wants to merge 4 commits into
masterfrom
DS-17046-feat
Open

feat(filter-tag, tag): новые view и новые механики компонентов [DS-17046]#2256
dHIM24 wants to merge 4 commits into
masterfrom
DS-17046-feat

Conversation

@dHIM24

@dHIM24 dHIM24 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Tag

  • Добавлены props showClear и onClear для сброса выбранного состояния
  • Изменены отступы в компоненте
  • Добавлен вариант view='muted'
  • В IndicatorTag добавлен размер 48, prop view (filled | muted) и обновлены стили SVG-формы

FilterTag

  • Добавлен prop showArrow для управления отображением шеврона в выбранном состоянии
  • Изменены отступы в компоненте
  • Шеврон отображается в невыбранном состоянии; крестик и шеврон можно показывать независимо друг от друга
  • Добавлен вариант view='muted'

Чек лист

  • Задача сформулирована и описана в JIRA
  • В названии ветки есть айдишник задачи в JIRA (fix/DS-1234), ссылку прикреплять не надо
  • У реквеста осмысленное название feat(...) или fix(...) по conventional commits (https://www.conventionalcommits.org)
  • Код покрыт тестами и протестирован в различных браузерах
  • Добавленные пропсы добавлены в демки и описаны в документации
  • К реквесту добавлен changeset

Если есть визуальные изменения

  • Прикреплено изображение было/стало

Tag

example for size-32

Снимок экрана 2026-06-19 в 12 16 05

FilterTag

example for size-32

Снимок экрана 2026-06-19 в 12 17 07

Код для песочницы:

Tag

#2256 (comment)

FilterTag

#2256 (comment)

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c692fc3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@alfalab/core-components-tag Minor
@alfalab/core-components-filter-tag Minor
@alfalab/core-components-pagination Patch
@alfalab/core-components Patch
@alfalab/core-components-select-with-tags Patch
@alfalab/core-components-tabs Patch
@alfalab/core-components-table Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Bundle size report

Entry point Size (minified)
@alfalab/core-components-pagination/index.js 9 (+1.80 KB ❌)
@alfalab/core-components-tabs/index.js 40.8 (+1.80 KB ❌)
@alfalab/core-components-tabs/desktop/index.js 39.4 (+1.80 KB ❌)
@alfalab/core-components-tabs/mobile/index.js 39.4 (+1.80 KB ❌)
@alfalab/core-components-tag/index.js 14 (+3.90 KB ❌)
@alfalab/core-components-tag/desktop/index.js 6.1 (+2.30 KB ❌)
@alfalab/core-components-tag/mobile/index.js 6.1 (+2.30 KB ❌)

@github-actions

Copy link
Copy Markdown
Contributor

Demo build (default)

https://core-ds.github.io/core-components/2256

@github-actions

Copy link
Copy Markdown
Contributor

Demo build (alfasans)

https://core-ds.github.io/core-components/2256-alfasans

@dHIM24

dHIM24 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Tag

render(() => {
    const ADDON = <DiamondsMIcon height={20} width={20} />;
    const INDICATOR_TAG_SIZES = [32, 40, 48];

    const TAG_VARIANTS = [
        {
            number: 1,
            description: '2 addons + label',
            props: {
                leftAddons: ADDON,
                rightAddons: ADDON,
                children: 'Label',
            },
        },
        {
            number: 2,
            description: 'IndicatorTag without Indicator',
            sizes: INDICATOR_TAG_SIZES,
            props: {
                Component: IndicatorTag,
                leftAddons: ADDON,
            },
        },
        {
            number: 3,
            description: 'IndicatorTag with dot Indicator',
            sizes: INDICATOR_TAG_SIZES,
            props: {
                Component: IndicatorTag,
                leftAddons: ADDON,
                indicatorProps: { mode: 'dot' },
            },
        },
        {
            number: 4,
            description: 'IndicatorTag with count Indicator',
            sizes: INDICATOR_TAG_SIZES,
            props: {
                Component: IndicatorTag,
                leftAddons: ADDON,
                indicatorProps: { mode: 'count', value: 7 },
            },
        },
        {
            number: 5,
            description: 'Tag with clear button',
            props: {
                leftAddons: ADDON,
                children: 'Label',
                showClear: true,
            },
            checkedProps: {
                leftAddons: ADDON,
                rightAddons: ADDON,
                children: 'Label',
                showClear: true,
            },
        },
        {
            number: 6,
            description: 'clear replaces rightAddons',
            props: {
                rightAddons: ADDON,
                children: 'Label',
                showClear: true,
            },
        },
        {
            number: 7,
            description: 'only Label',
            props: {
                children: 'Label',
            },
        },
        {
            number: 8,
            description: 'Only addons (Left + Right)',
            props: {
                leftAddons: ADDON,
                rightAddons: ADDON,
            },
        },
        {
            number: 9,
            description: 'only LeftAddon, без Label',
            props: {
                leftAddons: ADDON,
            },
        },
        {
            number: 10,
            description: 'IndicatorTag rounded, without Indicator',
            sizes: INDICATOR_TAG_SIZES,
            props: {
                Component: IndicatorTag,
                shape: 'rounded',
                leftAddons: ADDON,
            },
        },
        {
            number: 11,
            description: 'IndicatorTag rounded with dot Indicator',
            sizes: INDICATOR_TAG_SIZES,
            props: {
                Component: IndicatorTag,
                shape: 'rounded',
                leftAddons: ADDON,
                indicatorProps: { mode: 'dot' },
            },
        },
        {
            number: 12,
            description: 'IndicatorTag rounded with count Indicator',
            sizes: INDICATOR_TAG_SIZES,
            props: {
                Component: IndicatorTag,
                shape: 'rounded',
                leftAddons: ADDON,
                indicatorProps: { mode: 'count', value: 7 },
            },
        },
    ];

    const getVariantsForSize = (size) =>
        TAG_VARIANTS.filter(({ sizes }) => !sizes || sizes.indexOf(size) !== -1);

    const isIndicatorTagVariant = (variantProps) => {
        const columnProps = variantProps.defaultProps || variantProps.props;

        return columnProps.Component === IndicatorTag;
    };

    const VARIANT_NUMBER_STYLE = {
        minWidth: 72,
        fontSize: 14,
        lineHeight: '20px',
        color: 'var(--color-light-text-secondary)',
    };

    const DESCRIPTION_STYLE = {
        minWidth: 200,
        fontSize: 14,
        lineHeight: '20px',
        color: 'var(--color-light-text-secondary)',
    };

    const COLUMN_HEADER_STYLE = {
        minWidth: 100,
        fontSize: 12,
        fontWeight: 600,
        lineHeight: '16px',
        color: 'var(--color-light-text-secondary)',
        textAlign: 'center',
    };

    const ROW_STYLE = {
        display: 'flex',
        alignItems: 'center',
        gap: 12,
        marginBottom: 16,
    };

    const CONTAINER_STYLE = {
        display: 'flex',
        flexDirection: 'column',
    };

    const TAG_CELL_STYLE = {
        minWidth: 100,
        display: 'flex',
        justifyContent: 'center',
    };

    const SECTION_TITLE_STYLE = {
        margin: '0 0 16px',
        fontSize: 16,
        fontWeight: 600,
        lineHeight: '24px',
    };

    const SHOWCASE_BG_STYLE = {
        padding: 24,
        borderRadius: 16,
        background: 'var(--color-light-base-bg-secondary)',
    };

    const renderShowcase = (size) => (
        <div style={CONTAINER_STYLE}>
            <div style={ROW_STYLE}>
                <span style={VARIANT_NUMBER_STYLE} />
                <span style={COLUMN_HEADER_STYLE}>Default</span>
                <span style={COLUMN_HEADER_STYLE}>Checked</span>
                <span style={COLUMN_HEADER_STYLE}>Disabled</span>
                <span style={DESCRIPTION_STYLE} />
            </div>

            {getVariantsForSize(size).map(({ number, description, props, defaultProps, checkedProps, disabledProps }) => (
                <div style={ROW_STYLE} key={number}>
                    <span style={VARIANT_NUMBER_STYLE}>Variant {number}</span>
                    <div style={TAG_CELL_STYLE}>
                        <TagDesktop
                            size={size}
                            view='muted'
                            shape='rectangular'
                            {...(defaultProps || props)}
                        />
                    </div>
                    <div style={TAG_CELL_STYLE}>
                        <TagDesktop
                            size={size}
                            view='muted'
                            shape='rectangular'
                            {...(checkedProps || props)}
                            checked={true}
                        />
                    </div>
                    <div style={TAG_CELL_STYLE}>
                        {isIndicatorTagVariant({ props, defaultProps }) ? null : (
                            <TagDesktop
                                size={size}
                                view='muted'
                                shape='rectangular'
                                {...(disabledProps || props)}
                                disabled={true}
                            />
                        )}
                    </div>
                    <span style={DESCRIPTION_STYLE}>{description}</span>
                </div>
            ))}
        </div>
    );

    return (
        <div style={SHOWCASE_BG_STYLE}>
            <Container>
                {[32, 40, 48, 56].map((size) => (
                    <React.Fragment key={size}>
                        <h4 style={SECTION_TITLE_STYLE}>Size {size}</h4>
                        {renderShowcase(size)}
                        <Gap size={32} />
                    </React.Fragment>
                ))}
            </Container>
        </div>
    );
});

@dHIM24

dHIM24 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

FilterTag

render(() => {
    const [colors, setColors] = React.useState('default');
    const [view, setView] = React.useState('muted');
    const isInverted = colors === 'inverted';
    const textColor = isInverted
        ? 'var(--color-light-text-secondary-inverted)'
        : 'var(--color-light-text-secondary)';

    const ADDON = <DiamondsMIcon height={20} width={20} />;

    const AMOUNT_ITEMS = 3;

    const FILTER_TAG_VARIANTS = [
        {
            number: 1,
            description: 'leftAddons, label + chevron',
            showArrow: true,
            showClear: false,
            multipleValue: false,
            leftAddon: true,
        },
        {
            number: 2,
            description: 'leftAddons, label + clear',
            showArrow: false,
            showClear: true,
            multipleValue: false,
            leftAddon: true,
        },
        {
            number: 3,
            description: 'leftAddons, label + chevron and clear',
            showArrow: true,
            showClear: true,
            multipleValue: false,
            leftAddon: true,
        },
        {
            number: 4,
            description: 'leftAddons, label + chevron, multiple value',
            showArrow: true,
            showClear: false,
            multipleValue: true,
            leftAddon: true,
        },
        {
            number: 5,
            description: 'leftAddons, label + clear, multiple value',
            showArrow: false,
            showClear: true,
            multipleValue: true,
            leftAddon: true,
        },
        {
            number: 6,
            description: 'leftAddons, label + chevron and clear, multiple value',
            showArrow: true,
            showClear: true,
            multipleValue: true,
            leftAddon: true,
        },
        {
            number: 7,
            description: 'leftAddons and label, chevron',
            showArrow: true,
            showClear: false,
            multipleValue: false,
            leftAddon: true,
        },
        {
            number: 8,
            description: 'label and chevron',
            showArrow: true,
            showClear: false,
            multipleValue: false,
            leftAddon: false,
        },
    ];

    const getContent = ({ checked, multipleValue }) => {
        if (!checked) {
            return <span>Label</span>;
        }

        if (multipleValue) {
            return <span>Label: Выбрано {AMOUNT_ITEMS}</span>;
        }

        return <span>Label: Value</span>;
    };

    const VARIANT_NUMBER_STYLE = {
        minWidth: 72,
        fontSize: 14,
        lineHeight: '20px',
        color: textColor,
    };

    const DESCRIPTION_STYLE = {
        fontSize: 14,
        lineHeight: '20px',
        color: textColor,
    };

    const COLUMN_HEADER_STYLE = {
        fontSize: 12,
        fontWeight: 600,
        lineHeight: '16px',
        color: textColor,
        textAlign: 'center',
    };

    const getTagColumnWidth = (tagSize) => {
        if (tagSize === 32) return 155;
        if (tagSize === 40) return 170;

        return 190;
    };

    const getGridRowStyle = (tagSize) => ({
        display: 'grid',
        gridTemplateColumns: '72px repeat(3, ' + getTagColumnWidth(tagSize) + 'px)',
        columnGap: 12,
        alignItems: 'center',
    });

    const VARIANT_BLOCK_STYLE = {
        display: 'flex',
        flexDirection: 'column',
        gap: 8,
    };

    const CONTAINER_STYLE = {
        display: 'flex',
        flexDirection: 'column',
        gap: 8,
    };

    const TAG_CELL_STYLE = {
        display: 'flex',
        justifyContent: 'center',
    };

    const SECTION_TITLE_STYLE = {
        margin: '0 0 16px',
        fontSize: 16,
        fontWeight: 600,
        lineHeight: '24px',
        color: isInverted
            ? 'var(--color-light-text-primary-inverted)'
            : 'var(--color-light-text-primary)',
    };

    const SHOWCASE_BG_STYLE = {
        padding: 24,
        borderRadius: 16,
        background: isInverted
            ? 'var(--color-light-base-bg-primary-inverted)'
            : 'var(--color-light-base-bg-secondary)',
    };

    const CONTROLS_STYLE = isInverted
        ? {
              '--radio-group-label-color': 'var(--color-light-text-primary-inverted)',
              '--radio-group-mobile-label-color': 'var(--color-light-text-primary-inverted)',
          }
        : undefined;

    const getTagProps = (variant, size, { checked, disabled }) => ({
        client: 'desktop',
        size,
        view,
        shape: 'rectangular',
        colors,
        showArrow: variant.showArrow,
        showClear: variant.showClear,
        leftAddons: variant.leftAddon ? ADDON : undefined,
        checked,
        disabled,
        onClear: variant.showClear ? () => {} : undefined,
        children: getContent({ checked, multipleValue: variant.multipleValue }),
    });

    const renderShowcase = (size) => {
        const gridRowStyle = getGridRowStyle(size);

        return (
            <div style={CONTAINER_STYLE}>
                <div style={gridRowStyle}>
                    <span style={VARIANT_NUMBER_STYLE} />
                    <span style={COLUMN_HEADER_STYLE}>Default</span>
                    <span style={COLUMN_HEADER_STYLE}>Checked</span>
                    <span style={COLUMN_HEADER_STYLE}>Disabled</span>
                </div>

                {FILTER_TAG_VARIANTS.map((variant, index) => (
                    <div style={VARIANT_BLOCK_STYLE} key={variant.number}>
                        <div style={gridRowStyle}>
                            <span style={VARIANT_NUMBER_STYLE}>Variant {variant.number}</span>
                            <div style={TAG_CELL_STYLE}>
                                <FilterTag
                                    {...getTagProps(variant, size, {
                                        checked: false,
                                        disabled: false,
                                    })}
                                />
                            </div>
                            <div style={TAG_CELL_STYLE}>
                                <FilterTag
                                    {...getTagProps(variant, size, {
                                        checked: true,
                                        disabled: false,
                                    })}
                                />
                            </div>
                            <div style={TAG_CELL_STYLE}>
                                <FilterTag
                                    {...getTagProps(variant, size, {
                                        checked: true,
                                        disabled: true,
                                    })}
                                />
                            </div>
                        </div>

                        <div style={gridRowStyle}>
                            <span style={VARIANT_NUMBER_STYLE} />
                            <span style={{ ...DESCRIPTION_STYLE, gridColumn: '2 / -1' }}>
                                {variant.description}
                            </span>
                        </div>

                        {index < FILTER_TAG_VARIANTS.length - 1 && (
                            <div style={{ width: '100%', alignSelf: 'stretch' }}>
                                <Divider />
                            </div>
                        )}
                    </div>
                ))}
            </div>
        );
    };

    return (
        <div style={SHOWCASE_BG_STYLE}>
            <Container>
                <div style={CONTROLS_STYLE}>
                    <Switch
                        label='Инвертированная тема'
                        checked={isInverted}
                        colors={colors}
                        onChange={() => setColors(isInverted ? 'default' : 'inverted')}
                    />
                    <Gap size={16} />
                    <RadioGroup
                        direction='horizontal'
                        label='View'
                        name='filter-tag-example-view-desktop'
                        onChange={(_, payload) => setView(payload.value)}
                        value={view}
                    >
                        <Radio colors={colors} label='Outlined' value='outlined' />
                        <Radio colors={colors} label='Filled' value='filled' />
                        <Radio colors={colors} label='Muted' value='muted' />
                    </RadioGroup>
                </div>
                <Gap size={24} />
                {[32, 40, 48].map((size) => (
                    <React.Fragment key={size}>
                        <h4 style={SECTION_TITLE_STYLE}>Size {size}</h4>
                        {renderShowcase(size)}
                        <Gap size={32} />
                    </React.Fragment>
                ))}
            </Container>
        </div>
    );
});

@coveralls

coveralls commented Jun 19, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 28229806974

Coverage increased (+0.06%) to 82.202%

Details

  • Coverage increased (+0.06%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (25 of 26 lines covered, 96.15%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
packages/tag/src/components/native-tag/Component.tsx 15 14 93.33%
Total (5 files) 26 25 96.15%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 11782
Covered Lines: 9759
Line Coverage: 82.83%
Relevant Branches: 2242
Covered Branches: 1769
Branch Coverage: 78.9%
Branches in Coverage %: Yes
Coverage Strength: 237.29 hits per line

💛 - Coveralls

@dHIM24 dHIM24 marked this pull request as ready for review June 19, 2026 10:07
@Oladii

Oladii commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

насыпал коментов в личку

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants