Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c9e5462
refactor(CardHorizontal): 💡 Migrate from styled-components to CSS Mod…
punkbit Apr 15, 2026
c83e5cd
style(CardHorizontal): 💄 Fix CSS linting errors and property ordering
punkbit Apr 15, 2026
384d50c
style(CardHorizontal): 💄 Clean up stylelint disable comments
punkbit Apr 15, 2026
02116cd
docs(CardHorizontal): 📝 Add explanatory comment for stylelint disable
punkbit Apr 15, 2026
5ac73d5
chore: 🤖 add comment explaining why lint rule's disabled
punkbit Apr 15, 2026
64039a0
style: 💄 Disable media-feature-range-notation rule for better browser…
punkbit Apr 15, 2026
c2006a2
fix(CardHorizontal): 🐛 Scope :focus/:active styles to selectable card…
punkbit Apr 15, 2026
2390af6
style(CardHorizontal): 💄 Use :focus-visible instead of :focus per pro…
punkbit Apr 15, 2026
b7386da
fix(CardHorizontal): 🐛 Add Enter key handling for role=button accessi…
punkbit Apr 15, 2026
5237255
fix(CardHorizontal): 🐛 Remove pointer-events: none to allow not-allow…
punkbit Apr 15, 2026
8b7213f
fix(CardHorizontal): 🐛 Add hover styles for disabled selected cards t…
punkbit Apr 15, 2026
d4f965f
fix(CardHorizontal): 🐛 Ensure disabled styles override selected style…
punkbit Apr 15, 2026
d6b38b6
style: 💄 default+disabled+selected
punkbit Apr 15, 2026
7f793e6
style: 💄 not selectable must use not-allowed cursor
punkbit Apr 15, 2026
7bd2fc4
fix(CardHorizontal): 🐛 Remove orphaned tab stop and use consistent :f…
punkbit Apr 15, 2026
cd82731
style(CardHorizontal): 💄 Remove duplicate CSS selectors
punkbit Apr 15, 2026
342cd88
style(CardHorizontal): 💄 Fix remaining :focus and remove incorrect no…
punkbit Apr 15, 2026
8aee23b
style(CardHorizontal): 💄 Use explicit flex-direction and direct gap t…
punkbit Apr 15, 2026
223435c
style(CardHorizontal): 💄 Fix CSS property ordering
punkbit Apr 15, 2026
bfcc0b8
fix(CardHorizontal): 🐛 Fix CSS specificity for disabled selectable cards
punkbit Apr 15, 2026
a7844f9
style(CardHorizontal): 💄 Use modern :not() selector list notation
punkbit Apr 15, 2026
84ac44d
fix(CardHorizontal): 🐛 Fix types, use flex, add children class, and E…
punkbit Apr 15, 2026
5f6a64c
fix(CardHorizontal): 🐛 Update onButtonClick type to accept both Mouse…
punkbit Apr 15, 2026
50e7f9b
docs(CardHorizontal): 📝 Add JSDoc for isSelectable and infoText props
punkbit Apr 15, 2026
eba0b26
chore: 🤖 Add changeset for CardHorizontal CSS Modules migration
punkbit Apr 15, 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
12 changes: 12 additions & 0 deletions .changeset/migrate-cardhorizontal-css-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@clickhouse/click-ui": patch
---

Migrate CardHorizontal component from styled-components to CSS Modules

- Replace styled-components with CSS Modules using BEM naming convention
- Add CVA (class-variance-authority) for variant management
- Implement forwardRef for ref forwarding
- Improve accessibility with proper keyboard navigation (Space and Enter keys)
- Add comprehensive JSDoc documentation for props
- Fix visual bugs with disabled and selected states
299 changes: 299 additions & 0 deletions src/components/CardHorizontal/CardHorizontal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/* WARNING: Disabled `no-descending-specificity` due to complex state-based selectors (default, hover, active, disabled) with varying specificity are grouped logically by state for maintainability, not ordered by specificity. This makes the code easier to maintain at time of writing */
/* stylelint-disable no-descending-specificity */
.card-horizontal {
display: flex;
width: 100%;
max-width: 100%;
justify-content: flex-start;
align-items: center;
border: 1px solid transparent;
border-radius: var(--click-card-horizontal-radii-all);
font: var(--click-card-horizontal-typography-title-default);
}

.card-horizontal__content {
display: flex;
width: 100%;
flex-direction: row;
}

.card-horizontal__icon-text-content {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
gap: var(--click-card-horizontal-space-md-gap);
}

.card-horizontal__icon {
width: var(--click-card-horizontal-icon-size-all);
height: var(--click-card-horizontal-icon-size-all);
}

.card-horizontal__header {
display: flex;
width: 100%;
max-width: 100%;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: var(--click-card-horizontal-space-md-gap);
}

.card-horizontal__description {
display: flex;
width: 100%;
flex: 1;
flex-direction: column;
align-self: start;
gap: var(--click-card-horizontal-space-md-gap);
font: var(--click-card-horizontal-typography-description-default);
}

.card-horizontal__children {
display: flex;
width: 100%;
flex: 1;
flex-direction: column;
align-self: start;
gap: var(--click-card-horizontal-space-md-gap);
font: var(--click-card-horizontal-typography-description-default);
}

.card-horizontal_md {
padding: var(--click-card-horizontal-space-md-y) var(--click-card-horizontal-space-md-x);
}

.card-horizontal_sm {
padding: var(--click-card-horizontal-space-sm-y) var(--click-card-horizontal-space-sm-x);
}

.card-horizontal_md .card-horizontal__content {
gap: var(--click-card-horizontal-space-md-gap);
}

.card-horizontal_sm .card-horizontal__content {
gap: var(--click-card-horizontal-space-sm-gap);
}

.card-horizontal_md .card-horizontal__icon-text-content {
gap: var(--click-card-horizontal-space-md-gap);
}

.card-horizontal_sm .card-horizontal__icon-text-content {
gap: var(--click-card-horizontal-space-sm-gap);
}

.card-horizontal_default {
border-color: var(--click-card-horizontal-default-color-stroke-default);
background: var(--click-card-horizontal-default-color-background-default);
color: var(--click-card-horizontal-default-color-title-default);
}

.card-horizontal_default .card-horizontal__description {
color: var(--click-card-horizontal-default-color-description-default);
}

.card-horizontal_muted {
border-color: var(--click-card-horizontal-muted-color-stroke-default);
background: var(--click-card-horizontal-muted-color-background-default);
color: var(--click-card-horizontal-muted-color-title-default);
}

.card-horizontal_muted .card-horizontal__description {
color: var(--click-card-horizontal-muted-color-description-default);
}

.card-horizontal_selectable {
cursor: pointer;
}

.card-horizontal_default.card-horizontal_selectable:not(.card-horizontal_selected, .card-horizontal_disabled) {
border-color: var(--click-card-horizontal-default-color-stroke-hover);
}

.card-horizontal_muted.card-horizontal_selectable:not(.card-horizontal_selected, .card-horizontal_disabled) {
border-color: var(--click-card-horizontal-muted-color-stroke-hover);
}

.card-horizontal_default.card-horizontal_selectable:hover:not(.card-horizontal_disabled) {
border-color: var(--click-card-horizontal-default-color-stroke-default);
background-color: var(--click-card-horizontal-default-color-background-hover);
color: var(--click-card-horizontal-default-color-title-hover);
font: var(--click-card-horizontal-typography-title-hover);
}

.card-horizontal_default.card-horizontal_selectable:hover:not(.card-horizontal_disabled) .card-horizontal__description {
color: var(--click-card-horizontal-default-color-description-hover);
font: var(--click-card-horizontal-typography-description-hover);
}

.card-horizontal_muted.card-horizontal_selectable:hover:not(.card-horizontal_disabled) {
border-color: var(--click-card-horizontal-muted-color-stroke-default);
background-color: var(--click-card-horizontal-muted-color-background-hover);
color: var(--click-card-horizontal-muted-color-title-hover);
font: var(--click-card-horizontal-typography-title-hover);
}

.card-horizontal_muted.card-horizontal_selectable:hover:not(.card-horizontal_disabled) .card-horizontal__description {
color: var(--click-card-horizontal-muted-color-description-hover);
font: var(--click-card-horizontal-typography-description-hover);
}

.card-horizontal_default.card-horizontal_selected,
.card-horizontal_default.card-horizontal_selectable.card-horizontal_selected {
border-color: var(--click-card-horizontal-default-color-stroke-active);
background-color: var(--click-card-horizontal-default-color-background-active);
color: var(--click-card-horizontal-default-color-title-active);
}

.card-horizontal_default.card-horizontal_selected .card-horizontal__description,
.card-horizontal_default.card-horizontal_selectable.card-horizontal_selected .card-horizontal__description {
color: var(--click-card-horizontal-default-color-description-active);
font: var(--click-card-horizontal-typography-description-active);
}

.card-horizontal_muted.card-horizontal_selected,
.card-horizontal_muted.card-horizontal_selectable.card-horizontal_selected {
border-color: var(--click-card-horizontal-muted-color-stroke-active);
background-color: var(--click-card-horizontal-muted-color-background-active);
color: var(--click-card-horizontal-muted-color-title-active);
}

.card-horizontal_muted.card-horizontal_selected .card-horizontal__description,
.card-horizontal_muted.card-horizontal_selectable.card-horizontal_selected .card-horizontal__description {
color: var(--click-card-horizontal-muted-color-description-active);
font: var(--click-card-horizontal-typography-description-active);
}

.card-horizontal_default.card-horizontal_selectable:active:not(.card-horizontal_disabled),
.card-horizontal_default.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled),
.card-horizontal_default.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) {
border-color: var(--click-card-horizontal-default-color-stroke-active);
background-color: var(--click-card-horizontal-default-color-background-active);
color: var(--click-card-horizontal-default-color-title-active);
}

.card-horizontal_default.card-horizontal_selectable:active:not(.card-horizontal_disabled) .card-horizontal__description,
.card-horizontal_default.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled) .card-horizontal__description,
.card-horizontal_default.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) .card-horizontal__description {
color: var(--click-card-horizontal-default-color-description-active);
font: var(--click-card-horizontal-typography-description-active);
}

.card-horizontal_muted.card-horizontal_selectable:active:not(.card-horizontal_disabled),
.card-horizontal_muted.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled),
.card-horizontal_muted.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) {
border-color: var(--click-card-horizontal-muted-color-stroke-active);
background-color: var(--click-card-horizontal-muted-color-background-active);
color: var(--click-card-horizontal-muted-color-title-active);
}

.card-horizontal_muted.card-horizontal_selectable:active:not(.card-horizontal_disabled) .card-horizontal__description,
.card-horizontal_muted.card-horizontal_selectable:focus-visible:not(.card-horizontal_disabled) .card-horizontal__description,
.card-horizontal_muted.card-horizontal_selectable:focus-within:not(.card-horizontal_disabled) .card-horizontal__description {
color: var(--click-card-horizontal-muted-color-description-active);
font: var(--click-card-horizontal-typography-description-active);
}

.card-horizontal_default.card-horizontal_selected:hover:not(.card-horizontal_disabled),
.card-horizontal_default.card-horizontal_selectable.card-horizontal_selected:hover:not(.card-horizontal_disabled) {
border-color: var(--click-card-horizontal-default-color-stroke-active);
}

.card-horizontal_muted.card-horizontal_selected:hover:not(.card-horizontal_disabled),
.card-horizontal_muted.card-horizontal_selectable.card-horizontal_selected:hover:not(.card-horizontal_disabled) {
border-color: var(--click-card-horizontal-muted-color-stroke-active);
}

.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:hover {
border-color: var(--click-card-horizontal-default-color-stroke-active);
}

.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:hover {
border-color: var(--click-card-horizontal-muted-color-stroke-active);
}

.card-horizontal_disabled {
cursor: not-allowed;
}

.card-horizontal_default.card-horizontal_disabled,
.card-horizontal_default.card-horizontal_disabled:hover,
.card-horizontal_default.card-horizontal_disabled:active,
.card-horizontal_default.card-horizontal_disabled:focus-visible,
.card-horizontal_default.card-horizontal_disabled:focus-within {
border-color: var(--click-card-horizontal-default-color-stroke-disabled);
background-color: var(--click-card-horizontal-default-color-background-disabled);
color: var(--click-card-horizontal-default-color-title-disabled);
}

.card-horizontal_default.card-horizontal_disabled .card-horizontal__description,
.card-horizontal_default.card-horizontal_disabled:hover .card-horizontal__description,
.card-horizontal_default.card-horizontal_disabled:active .card-horizontal__description,
.card-horizontal_default.card-horizontal_disabled:focus-visible .card-horizontal__description,
.card-horizontal_default.card-horizontal_disabled:focus-within .card-horizontal__description {
color: var(--click-card-horizontal-default-color-description-disabled);
font: var(--click-card-horizontal-typography-description-disabled);
}

.card-horizontal_muted.card-horizontal_disabled,
.card-horizontal_muted.card-horizontal_disabled:hover,
.card-horizontal_muted.card-horizontal_disabled:active,
.card-horizontal_muted.card-horizontal_disabled:focus-visible,
.card-horizontal_muted.card-horizontal_disabled:focus-within {
border-color: var(--click-card-horizontal-muted-color-stroke-disabled);
background-color: var(--click-card-horizontal-muted-color-background-disabled);
color: var(--click-card-horizontal-muted-color-title-disabled);
}

.card-horizontal_muted.card-horizontal_disabled .card-horizontal__description,
.card-horizontal_muted.card-horizontal_disabled:hover .card-horizontal__description,
.card-horizontal_muted.card-horizontal_disabled:active .card-horizontal__description,
.card-horizontal_muted.card-horizontal_disabled:focus-visible .card-horizontal__description,
.card-horizontal_muted.card-horizontal_disabled:focus-within .card-horizontal__description {
color: var(--click-card-horizontal-muted-color-description-disabled);
font: var(--click-card-horizontal-typography-description-disabled);
}

.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled,
.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:active,
.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-visible,
.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-within {
border-color: var(--click-card-horizontal-default-color-stroke-active);
background-color: var(--click-card-horizontal-default-color-background-disabled);
color: var(--click-card-horizontal-default-color-title-disabled);
}

.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled .card-horizontal__description,
.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:hover .card-horizontal__description,
.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:active .card-horizontal__description,
.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-visible .card-horizontal__description,
.card-horizontal_default.card-horizontal_selected.card-horizontal_disabled:focus-within .card-horizontal__description {
color: var(--click-card-horizontal-default-color-description-disabled);
font: var(--click-card-horizontal-typography-description-disabled);
}

.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled,
.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:active,
.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-visible,
.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-within {
border-color: var(--click-card-horizontal-muted-color-stroke-active);
background-color: var(--click-card-horizontal-muted-color-background-disabled);
color: var(--click-card-horizontal-muted-color-title-disabled);
}

.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled .card-horizontal__description,
.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:hover .card-horizontal__description,
.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:active .card-horizontal__description,
.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-visible .card-horizontal__description,
.card-horizontal_muted.card-horizontal_selected.card-horizontal_disabled:focus-within .card-horizontal__description {
color: var(--click-card-horizontal-muted-color-description-disabled);
font: var(--click-card-horizontal-typography-description-disabled);
}

@media (max-width: 768px) {
.card-horizontal__content {
flex-direction: column;
}
}
15 changes: 4 additions & 11 deletions src/components/CardHorizontal/CardHorizontal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { Meta, StoryObj } from '@storybook/react-vite';
import { styled } from 'styled-components';

import { ICON_NAMES } from '../Icon/IconCommon';

import { CardHorizontal } from '@/components/CardHorizontal';

const GridCenter = styled.div`
display: grid;
width: 60%;
max-width: 480px;
`;

const meta: Meta<typeof CardHorizontal> = {
component: CardHorizontal,
title: 'Cards/Horizontal Card',
Expand All @@ -29,9 +22,9 @@ const meta: Meta<typeof CardHorizontal> = {
size: { type: { name: 'enum', value: ['sm', 'md'] } },
},
decorators: Story => (
<GridCenter>
<div style={{ display: 'grid', width: '60%', maxWidth: '480px' }}>
<Story />
</GridCenter>
</div>
),
};

Expand Down Expand Up @@ -199,8 +192,8 @@ export const NonSelectable: Story = {
args: {
icon: 'building',
title: 'Non-Selectable Card',
description: 'This card is not selectable (has infoText).',
infoText: 'Click me',
description: 'This card is not selectable.',
isSelectable: false,
},
};

Expand Down
14 changes: 14 additions & 0 deletions src/components/CardHorizontal/CardHorizontal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,20 @@ describe('CardHorizontal Component', () => {
expect(onClickMock).not.toHaveBeenCalled();
});

it('should call onClick on Enter key when selectable', () => {
const onClickMock = vitest.fn();
const { container } = renderCard({
title: 'Test Card',
isSelectable: true,
onButtonClick: onClickMock,
});

const wrapper = container.firstChild as HTMLElement;
fireEvent.keyDown(wrapper, { key: 'Enter' });

expect(onClickMock).toHaveBeenCalled();
});

it('should not have onKeyDown when not selectable', () => {
const { container } = renderCard({
title: 'Test Card',
Expand Down
Loading
Loading