Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/card-hover-active-states.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-ui-components": minor
---

feat(Card): add hover and active states for interactive cards. When rendered as `<a>` or `<button>`, the Card now shows an elevated border on hover (`border-theme-card-hover`) with an enhanced shadow, and an accent border on active. Adds `--color-border-high` and `--color-card-border-hover` CSS variables.
15 changes: 14 additions & 1 deletion packages/ui-components/src/components/Card/Card.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ const cardStyles = `
jn:shadow-theme-default
`

const cardInteractiveStyles = `
Comment thread
MartinS-git marked this conversation as resolved.
jn:cursor-pointer
jn:hover:border-theme-card-hover
jn:hover:shadow-theme-default-hover
jn:active:border-theme-accent
Comment thread
MartinS-git marked this conversation as resolved.
jn:focus:outline-hidden
jn:focus-visible:ring-2
jn:focus-visible:ring-theme-focus
jn:focus-visible:ring-offset-1
jn:focus-visible:ring-offset-theme-focus
`

const cardPadding = "jn:p-4"

export interface CardProps extends HTMLAttributes<HTMLElement> {
Expand Down Expand Up @@ -60,7 +72,8 @@ export interface CardProps extends HTMLAttributes<HTMLElement> {
*/
export const Card = forwardRef<HTMLElement, CardProps>(
({ children, padding = false, className = "", href, onClick, disabled = false, ...props }, ref) => {
const combinedClassName = `juno-card ${cardStyles} ${padding ? cardPadding : ""} ${className} ${disabled ? "jn:cursor-not-allowed" : ""}`
const isInteractive = (!!href || !!onClick) && !disabled
const combinedClassName = `juno-card ${isInteractive ? "juno-card-interactive" : ""} ${cardStyles} ${isInteractive ? cardInteractiveStyles : ""} ${padding ? cardPadding : ""} ${className} ${disabled ? "jn:cursor-not-allowed" : ""}`

// Handle click event when disabled
const handleClick: MouseEventHandler<HTMLElement> = (event) => {
Expand Down
36 changes: 36 additions & 0 deletions packages/ui-components/src/components/Card/Card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,40 @@ describe("Card Component", () => {
expect(handleClick).not.toHaveBeenCalled()
})
})

describe("Interactive Styles", () => {
test("applies interactive styles when onClick is provided", () => {
Comment thread
MartinS-git marked this conversation as resolved.
render(
<Card data-testid="card" onClick={() => {}}>
Button
</Card>
)
expect(screen.getByTestId("card")).toHaveClass("juno-card-interactive")
})

test("applies interactive styles when href is provided", () => {
render(
<Card data-testid="card" href="https://example.com">
Link
</Card>
)
expect(screen.getByTestId("card")).toHaveClass("juno-card-interactive")
})

test("does not apply interactive styles when neither href nor onClick are provided", () => {
render(<Card data-testid="card">Div</Card>)
expect(screen.getByTestId("card")).not.toHaveClass("juno-card-interactive")
})

test("does not apply interactive styles when disabled", () => {
render(
<Card data-testid="card" onClick={() => {}} disabled>
Disabled
</Card>
)
const element = screen.getByTestId("card")
expect(element).not.toHaveClass("juno-card-interactive")
expect(element).toHaveClass("jn:cursor-not-allowed")
})
})
})
45 changes: 31 additions & 14 deletions packages/ui-components/src/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@
--border-color-theme-box-default: var(--color-box-border);

--border-color-theme-card-default: var(--color-card-border-default);
--border-color-theme-card-hover: var(--color-card-border-hover);

Comment thread
MartinS-git marked this conversation as resolved.
--border-color-theme-codeblock-bar: var(--color-codeblock-bar-border);
--border-color-theme-codeblock-tab-active: var(--color-text-default);
Expand Down Expand Up @@ -532,6 +533,7 @@
/* LT Global */
--color-global-bg: var(--color-juno-grey-light-1);
--color-border-default: var(--color-juno-grey-light-7);
--color-border-high: var(--color-juno-grey-light-9);
--color-global-text: var(--color-juno-text-default);
--color-content-area-bg: var(--color-background-lvl-0);
/* LT FOCUS */
Expand Down Expand Up @@ -595,7 +597,7 @@
--color-button-primary-danger-active-text: var(--color-white);
--color-button-primary-danger-active-bg: var(--color-juno-primary-danger-5);

/* LT DataGrid */
/* LT DataGrid */
--color-datagridtoolbar-bg: var(--color-background-lvl-1);
--color-datagridrow-selected: var(--color-accent);

Expand Down Expand Up @@ -650,6 +652,7 @@
/* LT Card */
--color-card-background-default: var(--color-white);
--color-card-border-default: var(--color-border-default);
--color-card-border-hover: var(--color-border-high);
/* LT Checkbox */
--color-checkbox-bg: var(--color-background-lvl-5);
--color-checkbox-checked-color: var(--color-accent);
Expand Down Expand Up @@ -742,7 +745,7 @@
); /* Dark Theme DateTimePicker range background. We need to use a synthetisized solid value based on the current theme's accent color, the way that flatpickr is built here forbids using translucent colors. */
/* LT Box Shadow */
--box-shadow-default: 0 1px 2px 0 rgba(34, 54, 73, 0.3);
--box-shadow-hover: 0 0 0 1px var(--color-color-shadow, rgba(79, 79, 79, 0.3)), 0 2px 8px 0 rgba(0, 0, 0, 0.3);
--box-shadow-hover: 0 0 0 1px var(--color-color-shadow, rgba(79, 79, 79, 0.2)), 0 2px 8px 0 rgba(0, 0, 0, 0.2);
Comment thread
MartinS-git marked this conversation as resolved.
Comment thread
MartinS-git marked this conversation as resolved.
/* LT PageFooter */
--color-pagefooter-text: var(--color-text-light);
--color-pagefooter-background: var(--color-white);
Expand Down Expand Up @@ -783,6 +786,7 @@
/* DT Global */
--color-global-bg: var(--color-juno-grey-blue-10);
--color-border-default: var(--color-juno-grey-blue-3);
--color-border-high: var(--color-juno-grey-blue-1);
--color-global-text: var(--color-juno-text-default);
--color-content-area-bg: var(--color-background-lvl-0);

Expand Down Expand Up @@ -901,6 +905,7 @@
/* DT Card */
--color-card-background-default: var(--color-juno-grey-blue-10);
--color-card-border-default: var(--color-border-default);
--color-card-border-hover: var(--color-border-high);
/* DT Checkbox */
--color-checkbox-bg: var(--color-background-lvl-5);
--color-checkbox-checked-color: var(--color-accent);
Expand Down Expand Up @@ -1052,41 +1057,53 @@
@apply jn:text-theme-link;
}

h1, .juno-h1,
h2, .juno-h2,
h3, .juno-h3,
h4, .juno-h4,
h5, .juno-h5,
h6, .juno-h6 {
h1,
.juno-h1,
h2,
.juno-h2,
h3,
Comment thread
MartinS-git marked this conversation as resolved.
.juno-h3,
h4,
.juno-h4,
h5,
.juno-h5,
h6,
.juno-h6 {
@apply jn:font-sans jn:font-bold;
}

h1, .juno-h1 {
h1,
.juno-h1 {
font-size: 1.69rem;
line-height: 1.375;
}

h2, .juno-h2 {
h2,
.juno-h2 {
font-size: 1.56rem;
line-height: 1.375;
}

h3, .juno-h3 {
h3,
.juno-h3 {
font-size: 1.44rem;
line-height: 1.375;
}

h4, .juno-h4 {
h4,
.juno-h4 {
font-size: 1.28rem;
line-height: 1.625;
}

h5, .juno-h5 {
h5,
.juno-h5 {
font-size: 1.125rem;
line-height: 1.625;
}

h6, .juno-h6 {
h6,
.juno-h6 {
font-size: 1rem;
line-height: 1.5;
}
Expand Down
7 changes: 6 additions & 1 deletion packages/ui-components/src/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@
--border-color-theme-box-default: var(--color-box-border);

--border-color-theme-card-default: var(--color-card-border-default);
--border-color-theme-card-hover: var(--color-card-border-hover);

--border-color-theme-codeblock-bar: var(--color-codeblock-bar-border);
--border-color-theme-codeblock-tab-active: var(--color-text-default);
Expand Down Expand Up @@ -521,6 +522,7 @@
/* LT Global */
--color-global-bg: var(--color-juno-grey-light-1);
--color-border-default: var(--color-juno-grey-light-7);
--color-border-high: var(--color-juno-grey-light-9);
--color-global-text: var(--color-juno-text-default);
--color-content-area-bg: var(--color-background-lvl-0);
/* LT FOCUS */
Expand Down Expand Up @@ -635,6 +637,7 @@
/* LT Card */
--color-card-background-default: var(--color-white);
--color-card-border-default: var(--color-border-default);
--color-card-border-hover: var(--color-border-high);
/* LT Checkbox */
--color-checkbox-bg: var(--color-background-lvl-5);
--color-checkbox-checked-color: var(--color-accent);
Expand Down Expand Up @@ -730,7 +733,7 @@
); /* Light Theme DateTimePicker range background. We need to use a synthetisized solid value based on the current theme's accent color, the way that flatpickr is built here forbids using translucent colors. */
/* LT Box Shadow */
--box-shadow-default: 0 1px 2px 0 rgba(34, 54, 73, 0.3);
--box-shadow-hover: 0 0 0 1px var(--color-color-shadow, rgba(79, 79, 79, 0.3)), 0 2px 8px 0 rgba(0, 0, 0, 0.3);
--box-shadow-hover: 0 0 0 1px var(--color-color-shadow, rgba(79, 79, 79, 0.2)), 0 2px 8px 0 rgba(0, 0, 0, 0.2);
/* LT PageFooter */
--color-pagefooter-text: var(--color-text-light);
--color-pagefooter-background: var(--color-white);
Expand Down Expand Up @@ -772,6 +775,7 @@
/* DT Global */
--color-global-bg: var(--color-juno-grey-blue-10);
--color-border-default: var(--color-juno-grey-blue-3);
--color-border-high: var(--color-juno-grey-blue-1);
--color-global-text: var(--color-juno-text-default);
--color-content-area-bg: var(--color-background-lvl-0);

Expand Down Expand Up @@ -888,6 +892,7 @@
/* DT Card */
--color-card-background-default: var(--color-juno-grey-blue-10);
--color-card-border-default: var(--color-border-default);
--color-card-border-hover: var(--color-border-high);
/* DT Checkbox */
--color-checkbox-bg: var(--color-background-lvl-5);
--color-checkbox-checked-color: var(--color-accent);
Expand Down
Loading