Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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/mighty-towns-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-ui-components": minor
---

Add extra-small size to Button component. Add configurable marginRight prop to Spinner component.
77 changes: 59 additions & 18 deletions packages/ui-components/src/components/Button/Button.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const btnSmallBase = `
jn:leading-5
`

const btnExtraSmallBase = `
jn:text-xs
Comment thread
MartinS-git marked this conversation as resolved.
`

// default size typography
const btnDefaultBase = `
jn:text-base
Expand All @@ -55,6 +59,11 @@ const btnSmallSubduedPadding = `
jn:px-1.75
`

const btnExtraSmallDefaultPadding = `
jn:py-1
jn:px-1.5
`

// default size padding
const btnDefaultPadding = `
jn:py-1.75
Expand All @@ -81,7 +90,7 @@ const primaryButtonColors = `
jn:hover:bg-theme-button-primary-hover
jn:hover:text-theme-button-primary-hover
jn:active:bg-theme-button-primary-active
jn_active:text-theme-button-primary-active
jn:active:text-theme-button-primary-active
`

const primaryDangerButtonColors = `
Expand All @@ -101,16 +110,15 @@ const subduedButtonColors = `
jn:active:bg-theme-button-subdued-active
jn:active:text-theme-button-subdued-active
`
// jn:border
// jn:border-theme-button-subdued
// jn:hover:border-theme-button-subdued-hover
// jn:active:border-theme-button-subdued-active

const getButtonPadding = (size: ButtonSize, variant: ButtonVariant | undefined) => {
if (size === "small") {
return variant === "subdued" ? `${btnSmallSubduedPadding}` : `${btnSmallDefaultPadding}`
} else {
return variant === "subdued" ? `${btnDefaultSubduedPadding}` : `${btnDefaultPadding}`
switch (size) {
case "small":
return variant === "subdued" ? `${btnSmallSubduedPadding}` : `${btnSmallDefaultPadding}`
case "extra-small":
return `${btnExtraSmallDefaultPadding}`
default:
return variant === "subdued" ? `${btnDefaultSubduedPadding}` : `${btnDefaultPadding}`
}
Comment thread
MartinS-git marked this conversation as resolved.
}

Expand All @@ -133,15 +141,44 @@ const btnIconSmall = `
jn:mr-2
`

const btnIconExtraSmall = `
jn:mr-1
`

const btnIconDefault = `
jn:mr-2
`

const getButtonBase = (size: ButtonSize) => {
switch (size) {
case "small":
return btnSmallBase
case "extra-small":
return btnExtraSmallBase
default:
return btnDefaultBase
}
}

const iconClasses = (size: ButtonSize) => {
if (size === "small") {
return `${btnIconSmall}`
} else {
return `${btnIconDefault}`
switch (size) {
case "small":
return `${btnIconSmall}`
case "extra-small":
return `${btnIconExtraSmall}`
default:
return `${btnIconDefault}`
}
}

const iconSize = (size: ButtonSize) => {
switch (size) {
case "small":
return "1.125rem"
case "extra-small":
return "1rem"
default:
return "1.5rem"
}
}

Expand Down Expand Up @@ -192,13 +229,17 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
const titleValue = title || label || ""

const buttonIcon = progress ? (
<Spinner size={size === "small" ? "1.125rem" : "1.5rem"} color={spinnerColorClass(variant)} />
<Spinner
size={`${iconSize(size)}`}
color={spinnerColorClass(variant)}
marginRight={`${label || children ? iconClasses(size) : ""}`}
/>
Comment thread
MartinS-git marked this conversation as resolved.
) : icon ? (
<Icon
icon={icon}
title={titleValue}
className={`juno-button-icon ${label || children ? iconClasses(size) : ""} `}
size={size === "small" ? "1.125rem" : "1.5rem"}
size={`${iconSize(size)}`}
/>
) : null

Expand All @@ -215,7 +256,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
juno-button-${variant}
juno-button-${size}-size
${btnBase}
${size === "small" ? btnSmallBase : btnDefaultBase}
${getButtonBase(size)}
${getButtonPadding(size, variant)}
${getButtonColors(variant)}
${progressClass(progress)}
Expand All @@ -241,7 +282,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
juno-button-${variant}
juno-button-${size}-size
${btnBase}
${size === "small" ? btnSmallBase : btnDefaultBase}
${getButtonBase(size)}
${getButtonPadding(size, variant)}
${getButtonColors(variant)}
${progressClass(progress)}
Expand All @@ -265,7 +306,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
Button.displayName = "Button"

export type ButtonVariant = "primary" | "primary-danger" | "default" | "subdued"
type ButtonSize = "small" | "default"
type ButtonSize = "small" | "extra-small" | "default"

Comment thread
MartinS-git marked this conversation as resolved.
export interface ButtonProps extends Omit<HTMLProps<HTMLAnchorElement> | HTMLProps<HTMLButtonElement>, "size"> {
/**
Expand Down
42 changes: 40 additions & 2 deletions packages/ui-components/src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ export const Small: Story = {
},
}

export const ExtraSmall: Story = {
args: {
size: "extra-small",
label: "Extra Small",
},
}

export const Disabled: Story = {
parameters: {
docs: {
Expand Down Expand Up @@ -215,6 +222,21 @@ export const SmallWithIcon: Story = {
},
}

export const ExtraSmallWithIcon: Story = {
parameters: {
docs: {
description: {
story: "Extra Small Button with Icon",
},
},
},

args: {
...ExtraSmall.args,
icon: "warning",
},
}

export const IconOnlyButton: Story = {
parameters: {
docs: {
Expand Down Expand Up @@ -312,7 +334,7 @@ export const DefaultButtonInProgressWithProgressLabel: Story = {
parameters: {
docs: {
description: {
story: "Default Button with an action in oprogress and an alternate label while in progress",
story: "Default Button with an action in progress and an alternate label while in progress",
},
},
},
Expand Down Expand Up @@ -426,7 +448,7 @@ export const PrimaryDisabledInProgress: Story = {
parameters: {
docs: {
description: {
story: "Disabled Primnary Button with action in progress",
story: "Disabled Primary Button with action in progress",
},
},
},
Expand Down Expand Up @@ -469,3 +491,19 @@ export const SmallInProgress: Story = {
progressLabel: "Small in Progress…",
},
}

export const ExtraSmallInProgress: Story = {
parameters: {
docs: {
description: {
story: "Extra Small Button in Progress",
},
},
},

args: {
...ExtraSmall.args,
progress: true,
progressLabel: "Extra Small in Progress…",
},
}
17 changes: 17 additions & 0 deletions packages/ui-components/src/components/Button/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,21 @@ describe("Button", () => {
expect(screen.getByRole("img")).toBeInTheDocument()
expect(screen.getByRole("img")).toContainHTML("<title>customTitle</title>")
})

test("renders an extra-small button", () => {
render(<Button size="extra-small">Click me</Button>)
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByRole("button")).toHaveClass("juno-button-extra-small-size")
})

test("renders an extra-small in progress button as passed", () => {
render(
<Button size="extra-small" progress={true}>
Click me
</Button>
)
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByRole("button")).toHaveClass("in-progress")
expect(screen.getByRole("progressbar")).toHaveClass("juno-spinner")
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,23 @@ export const ComplexCustomLayout: Story = {
<DataGridToolbar {...args}>
<Stack direction="horizontal" distribution="between">
<Stack direction="vertical" gap="4">
<Stack alignment="center" gap="4">
<InputGroup>
<NativeSelect name="Filter" value="category" wrapperClassName="jn:w-full">
<NativeSelectOption value="category" label="Category" />
<NativeSelectOption value="status" label="Status" />
<NativeSelectOption value="priority" label="Priority" />
</NativeSelect>
<ComboBox>
<ComboBoxOption value="Electronics" />
<ComboBoxOption value="Clothing" />
<ComboBoxOption value="Furniture" />
</ComboBox>
</InputGroup>
<Button label="Clear all" variant="subdued" />
</Stack>
<InputGroup>
<NativeSelect name="Filter" value="category" wrapperClassName="jn:w-full">
<NativeSelectOption value="category" label="Category" />
<NativeSelectOption value="status" label="Status" />
<NativeSelectOption value="priority" label="Priority" />
</NativeSelect>
<ComboBox>
<ComboBoxOption value="Electronics" />
<ComboBoxOption value="Clothing" />
<ComboBoxOption value="Furniture" />
</ComboBox>
</InputGroup>
<Stack gap="2" wrap>
<Pill pillKey="category" pillValue="electronics" closeable />
<Pill pillKey="status" pillValue="active" closeable />
<Pill pillKey="priority" pillValue="high" closeable />
<Button label="Clear Filters" variant="subdued" size="extra-small" />
</Stack>
Comment thread
MartinS-git marked this conversation as resolved.
</Stack>
<SearchInput placeholder="Search items..." />
Expand All @@ -85,7 +83,7 @@ export const ComplexCustomLayout: Story = {
docs: {
description: {
story:
"Demonstrates a complex toolbar layout with custom styling - children aligned left and search aligned right.",
"Demonstrates a complex toolbar layout with custom styling - children aligned left and search aligned right. The extra-small 'Clear Filters' button sits inline with the filter pills.",
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import React, { HTMLProps, ReactNode } from "react"

const spinnerBaseStyles = `
jn:animate-spin
jn:mr-3
jn:h-5
jn:w-5
`
Expand Down Expand Up @@ -43,6 +42,7 @@ export const Spinner = ({
size,
className = "",
color = "",
marginRight = "",
...props
}: SpinnerProps): ReactNode => {
const mode = () => {
Expand Down Expand Up @@ -75,7 +75,7 @@ export const Spinner = ({

return (
<svg
className={`juno-spinner ${spinnerBaseStyles} ${color ? color : mode()} ${className}`}
className={`juno-spinner ${spinnerBaseStyles} ${color ? color : mode()} ${className} ${marginRight ? marginRight : "jn:mr-3"}`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
Expand Down Expand Up @@ -104,4 +104,6 @@ export interface SpinnerProps extends Omit<HTMLProps<SVGSVGElement>, "size"> {
className?: string
/** Pass a text-color class in order to apply any color to a spinner (These classes typically begin with "text-".). If passed, `color` will overwrite the semantic color as defined by `variant`. */
color?: string
/** Pass a custom right margin class (e.g. "mr-2") to control spacing to the right of the spinner. */
marginRight?: string
}
16 changes: 16 additions & 0 deletions packages/ui-components/src/components/Spinner/Spinner.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ describe("Spinner", () => {
expect(spinnerstyles.width).toBe("8rem")
})

test("renders with default right margin when no marginRight is passed", () => {
render(<Spinner />)
expect(screen.getByRole("progressbar")).toHaveClass("jn:mr-3")
})

test("renders with custom right margin when marginRight is passed", () => {
render(<Spinner marginRight="jn:mr-0" />)
expect(screen.getByRole("progressbar")).toHaveClass("jn:mr-0")
expect(screen.getByRole("progressbar")).not.toHaveClass("jn:mr-3")
})

test("renders with custom right margin class", () => {
render(<Spinner marginRight="jn:mr-6" />)
expect(screen.getByRole("progressbar")).toHaveClass("jn:mr-6")
})

test("renders a custom className", () => {
render(<Spinner className="my-custom-class" />)
expect(screen.getByRole("progressbar")).toBeInTheDocument()
Expand Down
Loading