Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4b7a0fc
[button] update styles and remove hardcoded values for w and h in oth…
j-mnizhek Jul 14, 2026
ec8009e
[theme] update value after merge
j-mnizhek Jul 14, 2026
8c93553
[theme] updates after theme processing
j-mnizhek Jul 14, 2026
02094fe
[d3-chart] remove changes for white in charts
j-mnizhek Jul 14, 2026
9494cbb
[link] update styles
j-mnizhek Jul 14, 2026
fa9a974
[button] remove legacy secondary info theme
j-mnizhek Jul 14, 2026
09bb6d5
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5564/reb…
j-mnizhek Jul 15, 2026
5125a9f
[theme] add lightness to secondary invert Button
j-mnizhek Jul 15, 2026
e3853a5
[theme] add spacings for controls
j-mnizhek Jul 16, 2026
dbccce3
Merge remote-tracking branch 'origin/UIK-5351/add-more-variables-to-t…
slizhevskyv-semrush Jul 20, 2026
936daf9
[UIK-5551][pills] pills rebranding
slizhevskyv-semrush Jul 21, 2026
7eed3fe
[UIK-5551][pills] pills rebranding
slizhevskyv-semrush Jul 21, 2026
c7c6770
[UIK-5551][pills] pills rebranding
slizhevskyv-semrush Jul 21, 2026
626a972
[UIK-5551][pills] pills rebranding
slizhevskyv-semrush Jul 22, 2026
32e2b65
[chore] Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-…
sheila-semrush Jul 24, 2026
7f62028
[pills] small style adjustment
sheila-semrush Jul 24, 2026
feb347c
[pills] updated separators height
sheila-semrush Jul 24, 2026
8c6faf2
[chore] Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-…
sheila-semrush Jul 28, 2026
f956a23
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5551/pil…
Valeria-Zimnitskaya Jul 30, 2026
f269fc6
Merge remote-tracking branch 'origin/UIK-5351/add-more-variables-to-t…
slizhevskyv-semrush Jul 31, 2026
fee89ae
[UIK-5551][pills] pills rebranding
slizhevskyv-semrush Jul 31, 2026
c1b94ab
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5551/pil…
Valeria-Zimnitskaya Aug 3, 2026
94952b5
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5551/pil…
Valeria-Zimnitskaya Aug 3, 2026
1990dd5
[chore] update test and snapshoy
Valeria-Zimnitskaya Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 2 additions & 19 deletions semcore/pills/__tests__/pills.browser-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,13 @@ test.describe(`${TAG.VISUAL}`, () => {

await test.step('Verify pills sizes styles', async () => {
if ((await locators.sizeM(page).count()) > 0) {
await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Addon"]').first()).toHaveCSS('margin-left', '8px');
await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Addon"]').nth(1)).toHaveCSS('margin-right', '8px');

await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Text"]')).toHaveCSS('margin-right', '8px');
await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Text"]')).toHaveCSS('margin-left', '8px');

await expect(locators.pillsItemText(page).nth(1)).toHaveCSS('margin-left', '8px');
await expect(locators.pillsItemText(page).nth(1)).toHaveCSS('margin-right', '8px');

const count = await locators.pillsItem(page).count();
for (let i = 0; i < count; i++)
await expect(locators.pillsItem(page).nth(i)).toHaveCSS('height', '32px');
await expect(locators.pillsItem(page).nth(i)).toHaveCSS('height', '28px');
} else if ((await locators.sizeL(page).count()) > 0) {
await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Addon"]').first()).toHaveCSS('margin-left', '12px');
await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Addon"]').nth(1)).toHaveCSS('margin-right', '12px');

await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Text"]')).toHaveCSS('margin-right', '8px');
await expect(locators.pillsItem(page).nth(0).locator('[data-ui-name="Pills.Item.Text"]')).toHaveCSS('margin-left', '8px');

await expect(locators.pillsItemText(page).nth(1)).toHaveCSS('margin-left', '12px');
await expect(locators.pillsItemText(page).nth(1)).toHaveCSS('margin-right', '12px');
const count = await locators.pillsItem(page).count();
for (let i = 0; i < count; i++)
await expect(locators.pillsItem(page).nth(i)).toHaveCSS('height', '44px');
await expect(locators.pillsItem(page).nth(i)).toHaveCSS('height', '40px');
}
});
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions semcore/pills/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"import": "./lib/esm/index.mjs",
"require": "./lib/cjs/index.js"
},
"dependencies": {
"@semcore/typography": "^17.2.1"
Comment thread
ilyabrower marked this conversation as resolved.
},
"peerDependencies": {
"@semcore/base-components": "^17.2.1"
},
Expand Down
131 changes: 108 additions & 23 deletions semcore/pills/src/Pills.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { NeighborLocation, Box, useNeighborLocationDetect } from '@semcore/base-components';
import { Box } from '@semcore/base-components';
import type { Intergalactic } from '@semcore/core';
import { createComponent, Component, sstyled, Root } from '@semcore/core';
import addonTextChildren from '@semcore/core/lib/utils/addonTextChildren';
import canUseDOM from '@semcore/core/lib/utils/canUseDOM';
import a11yEnhance from '@semcore/core/lib/utils/enhances/a11yEnhance';
import trottle from '@semcore/core/lib/utils/rafTrottle';
import { Text as SemcoreText } from '@semcore/typography';
import React from 'react';

import type { NSPills } from './Pills.type';
import style from './style/pills.shadow.css';

const TRANSITION_SLOW_THRESHOLD = 1;

type RootProps = Intergalactic.InternalTypings.InferComponentProps<NSPills.Component>;
class RootPills extends Component<
Intergalactic.InternalTypings.InferComponentProps<NSPills.Component>,
RootProps,
typeof RootPills.enhance,
NSPills.Handlers,
{},
Expand All @@ -18,14 +24,23 @@ class RootPills extends Component<
> {
static displayName = 'Pills';
static style = style;
static defaultProps = ({ behavior }: Intergalactic.InternalTypings.InferComponentProps<NSPills.Component>) => ({
static defaultProps = ({ behavior }: RootProps) => ({
size: 'm',
defaultValue: null,
behavior: behavior ?? 'auto',
} as const);

itemValues: Array<NSPills.Pill.Props['value']> = [];

lastSelectedIndex: number | null = null;
currentSelectedIndex: number | null = null;

rootRef: React.MutableRefObject<HTMLDivElement | null> = React.createRef();
pills: Array<HTMLButtonElement> = [];
segmentIndicatorRef: React.MutableRefObject<HTMLSpanElement | null> = React.createRef();

ro: ResizeObserver | null = null;

static enhance = [a11yEnhance({
onNeighborChange: (neighborElement, props) => {
if (neighborElement) {
Expand All @@ -42,12 +57,60 @@ class RootPills extends Component<
},
})] as const;

constructor(props: RootProps) {
super(props);
if (canUseDOM()) {
this.ro = new ResizeObserver(trottle(() => this.updateSegmentIndicator()));
}
}

uncontrolledProps() {
return {
value: null,
};
}

componentDidUpdate() {
this.updateSegmentIndicator();
}

componentDidMount() {
this.updateSegmentIndicator();

if (!this.ro) return;

for (const pill of this.pills) {
this.ro.observe(pill);
}
}

updateSegmentIndicator() {
if (this.currentSelectedIndex === null) return;

const root = this.rootRef?.current;
const pill = this.pills[this.currentSelectedIndex];
const indicator = this.segmentIndicatorRef?.current;

if (!pill || !root || !indicator) return;

const pillRect = pill.getBoundingClientRect();
const rootRect = root.getBoundingClientRect();

const speed = this.lastSelectedIndex === null
? '--transition-slow'
: Math.abs(this.currentSelectedIndex - this.lastSelectedIndex) > TRANSITION_SLOW_THRESHOLD
? '--transition-slow'
: '--transition-fast';
const left = pillRect.left - rootRect.left;
const width = pillRect.width;
const height = pillRect.height;

indicator.style.setProperty('--global-speed', `var(${speed})`);
indicator.style.setProperty('--global-left', `${left}px`);
indicator.style.setProperty('--global-width', `${width}px`);
indicator.style.setProperty('--global-height', `${height}px`);
}

bindHandlerClick = (value: NSPills.Pill.Props['value']) => (e: React.MouseEvent) => {
this.handlers.value(value, e);
};
Expand All @@ -65,6 +128,11 @@ class RootPills extends Component<

this.itemValues[index] = props.value;

if (isSelected) {
this.lastSelectedIndex = this.currentSelectedIndex;
this.currentSelectedIndex = index;
}

return {
index: index,
size,
Expand All @@ -74,32 +142,43 @@ class RootPills extends Component<
tabIndex: isSelected ? 0 : -1,
onClick: this.bindHandlerClick(props.value),
onKeyDown: this.bindHandleKeyDown(props.value),
ref: (node: HTMLButtonElement | null) => {
if (node === null) return;

this.pills[index] = node;
Comment thread
ilyabrower marked this conversation as resolved.
},
};
}

render() {
const SPills = Root;
const { Children, styles, controlsLength, disabled, behavior, value } = this.asProps;
const SSegmentIndicator = Box;
const { Children, styles, disabled, behavior, value } = this.asProps;

return sstyled(styles)(
<SPills
render={Box}
role={behavior === 'auto' ? 'radiogroup' : 'tablist'}
aria-disabled={disabled}
use:tabIndex={value !== null ? -1 : 0}
ref={this.rootRef}
>
<NeighborLocation controlsLength={controlsLength}>
<Children />
</NeighborLocation>
<SSegmentIndicator
tag='span'
ref={this.segmentIndicatorRef}
aria-hidden
/>
Comment thread
ilyabrower marked this conversation as resolved.
<Children />
</SPills>,
);
}
}

function Pill(props: Intergalactic.InternalTypings.InferChildComponentProps<NSPills.Pill.Component, typeof RootPills, 'Item'>) {
const SPill = Root;
const { Children, styles, addonLeft, addonRight, selected, disabled, index, behavior } = props;
const neighborLocation = useNeighborLocationDetect(index);
const SPillContainer = Box;
const SPillSeparator = Box;
const { Children, styles, addonLeft, addonRight, selected, disabled, behavior, size } = props;

const roleAreaProps = {
'role': behavior === 'auto' ? 'radio' : 'tab',
Expand All @@ -108,25 +187,31 @@ function Pill(props: Intergalactic.InternalTypings.InferChildComponentProps<NSPi
};

return sstyled(styles)(
<SPill
render={Box}
tag='button'
type='button'
tabIndex={0}
neighborLocation={neighborLocation}
aria-disabled={disabled}
{...roleAreaProps}
>
{addonLeft ? <Pills.Item.Addon tag={addonLeft} /> : null}
{addonTextChildren(Children, Pills.Item.Text, Pills.Item.Addon)}
{addonRight ? <Pills.Item.Addon tag={addonRight} /> : null}
</SPill>,
<SPillContainer>
<SPill
render={Box}
tag='button'
type='button'
tabIndex={0}
aria-disabled={disabled}
{...roleAreaProps}
>
{addonLeft ? <Pills.Item.Addon tag={addonLeft} /> : null}
{addonTextChildren(Children, Pills.Item.Text, Pills.Item.Addon)}
{addonRight ? <Pills.Item.Addon tag={addonRight} /> : null}
</SPill>
<SPillSeparator
// @ts-ignore
size={size}
aria-hidden
/>
</SPillContainer>,
);
}

function Text(props: Intergalactic.InternalTypings.InferComponentProps<NSPills.Pill.Text.Component>) {
const SText = Root;
return sstyled(props.styles)(<SText render={Box} tag='span' />);
return sstyled(props.styles)(<SText render={SemcoreText} size={200} />);
}

function Addon(props: Intergalactic.InternalTypings.InferComponentProps<NSPills.Pill.Addon.Component>) {
Expand Down
5 changes: 3 additions & 2 deletions semcore/pills/src/Pills.type.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { NSBox, NSNeighborLocation } from '@semcore/base-components';
import type { PropGetterFn, Intergalactic } from '@semcore/core';
import type { NSText } from '@semcore/typography';
import type React from 'react';

declare namespace NSPills {
// TODO: It looks like the value isn't accurate. Revise and align it with the component's logic.
type Value = string | number | boolean | null;
type Props<T extends NSPills.Value = NSPills.Value> = NSNeighborLocation.Props & NSBox.Props & {
type Props<T extends NSPills.Value = NSPills.Value> = NSBox.Props & {
/** Pills size */
size?: 'l' | 'm';
/** Disabled state */
Expand Down Expand Up @@ -55,7 +56,7 @@ declare namespace NSPills {
addonRight?: React.ElementType;
};
namespace Text {
type Component = NSBox.Component;
type Component = NSText.Component;
}
namespace Addon {
type Component = NSBox.Component;
Expand Down
Loading
Loading