-
Notifications
You must be signed in to change notification settings - Fork 60
UIK 5640/radio cards #3101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
slizhevskyv-semrush
wants to merge
7
commits into
UIK-5351/add-more-variables-to-theme
Choose a base branch
from
UIK-5640/radio-cards
base: UIK-5351/add-more-variables-to-theme
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
UIK 5640/radio cards #3101
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
b3586c8
[UIK-5640][radio-cards] new component/stories/unit tests
slizhevskyv-semrush 6bf93e1
[UIK-5640][radio-cards] add doc page/props description/changelog & pa…
slizhevskyv-semrush 2ccfdca
[UIK-5640][radio-cards] add default color for addons
slizhevskyv-semrush 09b0876
[UIK-5640][radio-cards] add default color for addons
slizhevskyv-semrush 5988718
[UIK-5640][radio-cards] update styles/package.json
slizhevskyv-semrush 6ecf7c4
[UIK-5640][radio-cards] update styles/package.json
slizhevskyv-semrush 247b944
[UIK-5640][radio-cards] update styles/markup for advanced mode
slizhevskyv-semrush File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| node_modules | ||
| __tests__ | ||
| src | ||
| tsconfig.json | ||
| tsconfig.tsbuildinfo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Changelog | ||
|
|
||
| CHANGELOG.md standards are inspired by [keepachangelog.com](https://keepachangelog.com/en/1.0.0/). | ||
|
|
||
| ## [18.0.0] - 2026-08-04 | ||
|
|
||
| ### Added | ||
|
|
||
| - `RadioCards` component. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # @semcore/radio-cards | ||
|
|
||
| [](https://www.npmjs.com/@semcore/radio-cards) | ||
| [](https://www.npmjs.com/package/@semcore/radio-cards) | ||
| [](https://github.com/semrush/intergalactic/blob/HEAD/LICENSE) | ||
|
|
||
| > This component is part of the Intergalactic Design System | ||
|
|
||
| ### 📖 [Component documentation](https://developer.semrush.com/intergalactic/components/radio-cards/) | ||
|
|
||
| ### 🏠 [Design system](https://developer.semrush.com/intergalactic/) | ||
|
|
||
| ## Install | ||
|
|
||
| ```sh | ||
| npm install @semcore/radio-cards | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ## 👤 Author | ||
|
|
||
| [UI-kit team](https://github.com/semrush/intergalactic/blob/HEAD/MAINTAINERS) and [others ❤️](https://github.com/semrush/intergalactic/graphs/contributors) | ||
|
|
||
| ## 🤝 Contributing | ||
|
|
||
| Contributions, issues and feature requests are welcome! | ||
|
|
||
| Feel free to check [issues page](https://github.com/semrush/intergalactic/issues). You can also take a look at the [contributing guide](https://github.com/semrush/intergalactic/blob/HEAD/CONTRIBUTING.md). | ||
|
|
||
| ## Show your support | ||
|
|
||
| Give a ⭐️ if this project helped you! | ||
|
|
||
| ## 📝 License | ||
|
|
||
| This project is [MIT](https://github.com/semrush/intergalactic/blob/HEAD/LICENSE) licensed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { runDependencyCheckTests } from '@semcore/testing-utils/shared-tests'; | ||
| import { render, cleanup, userEvent } from '@semcore/testing-utils/testing-library'; | ||
| import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; | ||
| import React from 'react'; | ||
|
|
||
| import RadioCards from '../src'; | ||
|
|
||
| describe('pills Dependency imports', () => { | ||
| runDependencyCheckTests('radio-cards'); | ||
| }); | ||
|
|
||
| describe('RadioCards', () => { | ||
| beforeEach(cleanup); | ||
|
|
||
| test('calls onChange with selected value', async () => { | ||
| const onChange = vi.fn(); | ||
|
|
||
| const { getByRole } = render( | ||
| <RadioCards aria-label='Radio cards' value='' onChange={onChange}> | ||
| <RadioCards.Item value='1'>1</RadioCards.Item> | ||
| <RadioCards.Item value='2'>2</RadioCards.Item> | ||
| <RadioCards.Item value='4'>4</RadioCards.Item> | ||
| </RadioCards>, | ||
| ); | ||
|
|
||
| await userEvent.click(getByRole('radio', { name: '4' })); | ||
|
|
||
| expect(onChange).toHaveBeenCalledWith( | ||
| '4', | ||
| expect.any(Object), | ||
| ); | ||
| }); | ||
|
|
||
| test('clicks aren\'t propagated on disabled tab', async () => { | ||
| const spy = vi.fn(); | ||
|
|
||
| const { getByRole } = render( | ||
| <RadioCards aria-label='Radio cards' value='' onChange={spy}> | ||
| <RadioCards.Item value='1'>1</RadioCards.Item> | ||
| <RadioCards.Item value='2'>2</RadioCards.Item> | ||
| <RadioCards.Item value='3'>3</RadioCards.Item> | ||
| <RadioCards.Item value='4' disabled>4</RadioCards.Item> | ||
| </RadioCards>, | ||
| ); | ||
|
|
||
| await expect(userEvent.click(getByRole('radio', { name: '4' }))).rejects.toThrow('pointer-events: none'); | ||
| expect(spy).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| test('does not call onChange when group is disabled', async () => { | ||
| const spy = vi.fn(); | ||
|
|
||
| const { getByRole } = render( | ||
| <RadioCards | ||
| aria-label='Radio cards' | ||
| value='' | ||
| disabled | ||
| onChange={spy} | ||
| > | ||
| <RadioCards.Item value='1'>1</RadioCards.Item> | ||
| <RadioCards.Item value='2'>2</RadioCards.Item> | ||
| </RadioCards>, | ||
| ); | ||
|
|
||
| await expect(userEvent.click(getByRole('radio', { name: '2' }))).rejects.toThrow('pointer-events: none'); | ||
| expect(spy).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| test('supports keyboard navigation with ArrowRight', async () => { | ||
| const spy = vi.fn(); | ||
|
|
||
| const { getByRole } = render( | ||
| <RadioCards aria-label='Radio cards' value='1' onChange={spy}> | ||
| <RadioCards.Item value='1'>1</RadioCards.Item> | ||
| <RadioCards.Item value='2'>2</RadioCards.Item> | ||
| <RadioCards.Item value='3'>3</RadioCards.Item> | ||
| </RadioCards>, | ||
| ); | ||
|
|
||
| const firstRadio = getByRole('radio', { name: '1' }); | ||
|
|
||
| firstRadio.focus(); | ||
| expect(firstRadio).toHaveFocus(); | ||
|
|
||
| await userEvent.keyboard('{ArrowRight}'); | ||
|
|
||
| expect(spy).toHaveBeenCalledWith( | ||
| '2', | ||
| expect.any(Object), | ||
| ); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| { | ||
| "name": "@semcore/radio-cards", | ||
| "description": "Semrush RadioCards Component", | ||
| "version": "18.0.0", | ||
| "main": "lib/cjs/index.js", | ||
| "module": "lib/es6/index.js", | ||
| "typings": "lib/types/index.d.ts", | ||
| "sideEffects": false, | ||
| "author": "UI-kit team <ui-kit-team@semrush.com>", | ||
| "license": "MIT", | ||
| "scripts": { | ||
| "build": "pnpm semcore-builder && pnpm vite build" | ||
| }, | ||
| "exports": { | ||
| "types": "./lib/types/index.d.ts", | ||
| "import": "./lib/esm/index.mjs", | ||
| "require": "./lib/cjs/index.js" | ||
| }, | ||
| "dependencies": { | ||
| "@semcore/typography": "^17.2.1" | ||
| }, | ||
| "peerDependencies": { | ||
| "@semcore/base-components": "^17.2.1" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/semrush/intergalactic.git", | ||
| "directory": "semcore/radio-cards" | ||
| }, | ||
| "devDependencies": { | ||
| "@semcore/core": "workspace:*", | ||
| "@semcore/base-components": "workspace:*", | ||
| "@semcore/testing-utils": "workspace:*" | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| import { Flex, NeighborLocation } from '@semcore/base-components'; | ||
| import type { Intergalactic } from '@semcore/core'; | ||
| import { Root, Component, createComponent, sstyled } from '@semcore/core'; | ||
| import a11yEnhance from '@semcore/core/lib/utils/enhances/a11yEnhance'; | ||
| import { Text as SemcoreText } from '@semcore/typography'; | ||
| import React from 'react'; | ||
|
|
||
| import type { NSRadioCards } from './RadioCards.type'; | ||
| import style from './styles/radio-cards.shadow.css'; | ||
|
|
||
| class RadioCardsRoot extends Component< | ||
| Intergalactic.InternalTypings.InferComponentProps<NSRadioCards.Component>, | ||
| typeof RadioCardsRoot.enhance, | ||
| NSRadioCards.Handlers, | ||
| {}, | ||
| {}, | ||
| NSRadioCards.DefaultProps | ||
| > { | ||
| static displayName = 'RadioCards'; | ||
| static style = style; | ||
|
|
||
| static enhance = [a11yEnhance({ | ||
| onNeighborChange: (neighborElement) => { | ||
| if (neighborElement) { | ||
| neighborElement.focus(); | ||
| neighborElement.click(); | ||
| } | ||
| }, | ||
| childSelector: () => { | ||
| const selector: [string, string] = ['role', 'radio']; | ||
|
|
||
| return selector; | ||
| }, | ||
| })] as const; | ||
|
|
||
| static defaultProps = { | ||
| defaultValue: '', | ||
| } as const; | ||
|
|
||
| uncontrolledProps() { | ||
| return { | ||
| value: '', | ||
| }; | ||
| } | ||
|
|
||
| handleClick = (value: NSRadioCards.Value) => (e: React.MouseEvent) => { | ||
| this.handlers.value(value, e); | ||
| }; | ||
|
|
||
| handleKeyDown = (value: NSRadioCards.Value) => (e: React.KeyboardEvent) => { | ||
| if (e.key === 'Enter' || e.key === ' ') { | ||
| e.preventDefault(); | ||
|
|
||
| this.handlers.value(value, e); | ||
| } | ||
| }; | ||
|
|
||
| getItemProps({ value: itemValue }: NSRadioCards.Item.Props) { | ||
| const { value, disabled } = this.asProps; | ||
| const isChecked = value === itemValue; | ||
|
|
||
| return { | ||
| tabIndex: isChecked ? 0 : -1, | ||
| checked: isChecked, | ||
| disabled, | ||
| onClick: this.handleClick(itemValue), | ||
| onKeyDown: this.handleClick(itemValue), | ||
| }; | ||
| } | ||
|
|
||
| render() { | ||
| const SRadioCards = Root; | ||
| const { Children, styles, value, disabled } = this.asProps; | ||
|
|
||
| return sstyled(styles)( | ||
| <SRadioCards render={Flex} role='radiogroup' use:tabIndex={value !== '' ? -1 : 0} aria-disabled={disabled}> | ||
| <NeighborLocation> | ||
| <Children /> | ||
| </NeighborLocation> | ||
| </SRadioCards>, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| function Item( | ||
| props: Intergalactic.InternalTypings.InferChildComponentProps< | ||
| NSRadioCards.Item.Component, | ||
| typeof RadioCardsRoot, | ||
| 'Item' | ||
| >, | ||
| ) { | ||
| const SRadioItem = Root; | ||
| const SRadioItemHeader = Flex; | ||
| const SRadioItemHeaderLeftAddon = Flex; | ||
| const SRadioItemHeaderRightAddon = SemcoreText; | ||
| const { Children, styles, iconAddon, text, textAddon, description, disabled, checked, children } = props; | ||
|
|
||
| const isAdvancedMode = children !== undefined; | ||
|
|
||
| return sstyled(styles)( | ||
| <SRadioItem | ||
| render={Flex} | ||
| tag='button' | ||
| role='radio' | ||
| aria-disabled={disabled} | ||
| aria-checked={checked} | ||
| > | ||
| {isAdvancedMode | ||
| ? ( | ||
| <Children /> | ||
| ) | ||
| : ( | ||
| <> | ||
| <SRadioItemHeader> | ||
| {iconAddon && <SRadioItemHeaderLeftAddon>{iconAddon}</SRadioItemHeaderLeftAddon>} | ||
| {text && <SemcoreText size={300} use='primary'>{text}</SemcoreText>} | ||
| {textAddon && ( | ||
| <SRadioItemHeaderRightAddon size={300}> | ||
| {textAddon} | ||
| </SRadioItemHeaderRightAddon> | ||
| )} | ||
| </SRadioItemHeader> | ||
| {description && <SemcoreText size={200} use='secondary'>{description}</SemcoreText>} | ||
| </> | ||
| )} | ||
| </SRadioItem>, | ||
| ); | ||
| } | ||
|
|
||
| const RadioCards = createComponent<NSRadioCards.Component, typeof RadioCardsRoot>(RadioCardsRoot, { | ||
| Item, | ||
| }); | ||
|
|
||
| export default RadioCards; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import type { NSFlex } from '@semcore/base-components'; | ||
| import type { Intergalactic } from '@semcore/core'; | ||
| import type { NSText } from '@semcore/typography'; | ||
|
|
||
| declare namespace NSRadioCards { | ||
| type Value = string; | ||
| type Props = NSFlex.Props & Intergalactic.RequireAtLeastOne<{ | ||
| /** Accessible name for the radio group. */ | ||
| 'aria-label'?: string; | ||
| /** References an element that labels the radio group. */ | ||
| 'aria-labelledby'?: string; | ||
| }> & { | ||
| /** Currently selected radio card value. */ | ||
| value?: NSRadioCards.Value; | ||
| /** Called when the selected radio card changes. */ | ||
| onChange?: (value: NSRadioCards.Value, event?: React.SyntheticEvent<HTMLButtonElement>) => void; | ||
| /** Disables the entire radio group and all radio cards. */ | ||
| disabled?: boolean; | ||
| }; | ||
| type DefaultProps = { | ||
| defaultValue: ''; | ||
| }; | ||
| type Handlers = { | ||
| value: NSRadioCards.Value; | ||
| }; | ||
|
|
||
| namespace Item { | ||
| type Props = NSFlex.Props & { | ||
| /** Disables radio card. */ | ||
| disabled?: boolean; | ||
| /** Optional icon displayed before the content. */ | ||
| iconAddon?: React.ReactNode; | ||
| /** Optional text displayed in the header. */ | ||
| text?: string; | ||
| /** Unique value associated with the radio card. */ | ||
| value: NSRadioCards.Value; | ||
| /** Additional text displayed in the header. */ | ||
| textAddon?: string; | ||
| /** Description displayed below the header. */ | ||
| description?: string; | ||
| }; | ||
|
|
||
| type Component = Intergalactic.Component<'button', Props>; | ||
| } | ||
|
|
||
| type Component = Intergalactic.Component<'div', Props> & { | ||
| Item: Item.Component; | ||
| }; | ||
| } | ||
|
|
||
| export type { NSRadioCards }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { default } from './RadioCards'; | ||
| export * from './RadioCards.type'; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add peerDeps. Core and base-components, I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks!