Skip to content

Commit

Permalink
ghost first try
Browse files Browse the repository at this point in the history
  • Loading branch information
Fercas123 committed Nov 29, 2024
1 parent 5c8d47b commit 9717b1f
Show file tree
Hide file tree
Showing 7 changed files with 418 additions and 0 deletions.
196 changes: 196 additions & 0 deletions packages/core/stories/ghost-button/ghost-button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { Banner, FlowLayout, StackLayout } from "@salt-ds/core";

Check failure on line 1 in packages/core/stories/ghost-button/ghost-button.stories.tsx

View workflow job for this annotation

GitHub Actions / lint

organizeImports

Import statements differs from the output
import { GhostButton, SystemStatus } from "@salt-ds/lab";
import { CloseIcon, SearchIcon } from "@salt-ds/icons";
import type { Meta, StoryFn } from "@storybook/react";

export default {
title: "Lab/GhostButton",
component: GhostButton,
argTypes: { onClick: { action: "clicked" } },
} as Meta<typeof GhostButton>;

export const TestingEverything: StoryFn<typeof GhostButton> = (props) => {
return (
<StackLayout>
<div style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</div>
<Banner status="success" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<Banner status="error" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<Banner status="info" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<Banner status="warning" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<Banner variant="secondary" status="success" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<Banner variant="secondary" status="error" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<Banner variant="secondary" status="info" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<Banner variant="secondary" status="warning" style={{ width: 500 }}>
<GhostButton aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</Banner>
<div style={{ width: 500 }}>
<GhostButton disabled aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</div>
<SystemStatus status="success" style={{ width: 500 }}>
<GhostButton adaptiveAppearance="solid" aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</SystemStatus>
<SystemStatus status="error" style={{ width: 500 }}>
<GhostButton adaptiveAppearance="solid" aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</SystemStatus>
<SystemStatus status="info" style={{ width: 500 }}>
<GhostButton adaptiveAppearance="solid" aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</SystemStatus>
<SystemStatus status="warning" style={{ width: 500 }}>
<GhostButton adaptiveAppearance="solid" aria-label="Search" {...props}>
<CloseIcon aria-hidden /> Button
</GhostButton>
</SystemStatus>
</StackLayout>
);
};

const ButtonGridTemplate: StoryFn<typeof GhostButton> = (props) => {
return (
<FlowLayout>
<GhostButton {...props}>Submit</GhostButton>
<GhostButton aria-label="Search" {...props}>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton {...props}>
<SearchIcon aria-hidden /> Search
</GhostButton>
<Banner status="info">
<GhostButton {...props}>Submit</GhostButton>
<GhostButton aria-label="Search" {...props}>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton {...props}>
<SearchIcon aria-hidden /> Search
</GhostButton>
</Banner>
<SystemStatus status="info">
<GhostButton {...props}>Submit</GhostButton>
<GhostButton aria-label="Search" {...props}>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton {...props}>
<SearchIcon aria-hidden /> Search
</GhostButton>
</SystemStatus>
</FlowLayout>
);
};

export const Disabled: StoryFn = (props) => {
return (
<StackLayout gap={3}>
<FlowLayout>
<GhostButton disabled>Submit</GhostButton>
<GhostButton aria-label="Search" disabled>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton disabled>
<SearchIcon aria-hidden /> Search
</GhostButton>
<Banner status="info">
<GhostButton disabled>Submit</GhostButton>
<GhostButton aria-label="Search" disabled>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton disabled>
<SearchIcon aria-hidden /> Search
</GhostButton>
</Banner>
<SystemStatus status="info">
<GhostButton disabled>Submit</GhostButton>
<GhostButton aria-label="Search" disabled>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton disabled>
<SearchIcon aria-hidden /> Search
</GhostButton>
</SystemStatus>
</FlowLayout>
<FlowLayout>
<GhostButton adaptiveAppearance="solid" disabled>
Submit
</GhostButton>
<GhostButton adaptiveAppearance="solid" aria-label="Search" disabled>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton adaptiveAppearance="solid" disabled>
<SearchIcon aria-hidden /> Search
</GhostButton>
<Banner status="info">
<GhostButton adaptiveAppearance="solid" disabled>
Submit
</GhostButton>
<GhostButton adaptiveAppearance="solid" aria-label="Search" disabled>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton adaptiveAppearance="solid" disabled>
<SearchIcon aria-hidden /> Search
</GhostButton>
</Banner>
<SystemStatus status="info">
<GhostButton adaptiveAppearance="solid" disabled>
Submit
</GhostButton>
<GhostButton adaptiveAppearance="solid" aria-label="Search" disabled>
<SearchIcon aria-hidden />
</GhostButton>
<GhostButton adaptiveAppearance="solid" disabled>
<SearchIcon aria-hidden /> Search
</GhostButton>
</SystemStatus>
</FlowLayout>
</StackLayout>
);
};

export const Solid = ButtonGridTemplate.bind({});
Solid.args = {
adaptiveAppearance: "solid",
};

export const NonSolid = ButtonGridTemplate.bind({});
NonSolid.args = {
adaptiveAppearance: "non-solid",
};
102 changes: 102 additions & 0 deletions packages/lab/src/ghost-button/GhostButton.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
.saltGhostButton {
align-items: var(--saltGhostButton-alignItems, center);
appearance: none;
background: var(--saltGhostButton-background, var(--ghostButton-background));
border-color: var(--saltGhostButton-borderColor, var(--ghostButton-borderColor, transparent));
border-style: var(--saltGhostButton-borderStyle, solid);
border-width: var(--saltGhostButton-borderWidth, var(--salt-size-border, 0));
border-radius: var(--saltGhostButton-borderRadius, var(--salt-palette-corner-weak, 0));
color: var(--saltGhostButton-text-color, var(--ghostButton-text-color));
cursor: var(--saltGhostButton-cursor, pointer);
display: inline-flex;
gap: var(--salt-spacing-50);
justify-content: var(--saltGhostButton-justifyContent, center);
font-size: var(--saltGhostButton-fontSize, var(--salt-text-fontSize));
font-family: var(--saltGhostButton-fontFamily, var(--salt-text-action-fontFamily));
line-height: var(--saltGhostButton-lineHeight, var(--salt-text-lineHeight));
letter-spacing: var(--saltGhostButton-letterSpacing, var(--salt-text-action-letterSpacing));
text-transform: var(--saltGhostButton-textTransform, var(--salt-text-action-textTransform));
padding: 0 var(--saltGhostButton-padding, calc(var(--salt-spacing-100) - var(--saltGhostButton-borderWidth, var(--salt-size-border, 0))));
margin: var(--saltGhostButton-margin, 0);
height: var(--saltGhostButton-height, var(--salt-size-base));
min-width: var(--saltGhostButton-minWidth, unset);
position: relative;
text-align: var(--saltGhostButton-textAlign, var(--salt-text-action-textAlign));
text-decoration: none;
transition: none;
width: var(--saltGhostButton-width, auto);
-webkit-appearance: none;
-webkit-tap-highlight-color: transparent;
font-weight: var(--saltGhostButton-fontWeight, var(--salt-text-action-fontWeight));
}

/* Pseudo-class applied to the root element on focus */
.saltGhostButton:focus-visible {
outline-style: var(--salt-focused-outlineStyle);
outline-width: var(--salt-focused-outlineWidth);
outline-color: var(--salt-focused-outlineColor);
outline-offset: var(--salt-focused-outlineOffset);
background: var(--saltGhostButton-background-hover, var(--ghostButton-background-hover));
color: var(--saltGhostButton-text-color-hover, var(--ghostButton-text-color-hover));
}

/* Pseudo-class applied to the root element on focus when Button is active */
.saltGhostButton.saltGhostButton-active:focus-visible,
.saltGhostButton:focus-visible:active {
background: var(--saltGhostButton-background-active-hover, var(--ghostButton-background));
color: var(--saltGhostButton-text-color-active-hover, var(--ghostButton-text-color));
}

/* Pseudo-class applied to the root element on hover when Button is not active or disabled */
.saltGhostButton:hover {
background: var(--saltGhostButton-background-hover, var(--ghostButton-background-hover));
color: var(--saltGhostButton-text-color-hover, var(--ghostButton-text-color-hover));
}

/* Pseudo-class applied to the root element when Button is active and not disabled */
.saltGhostButton:active,
.saltGhostButton.saltGhostButton-active {
background: var(--saltGhostButton-background-active, var(--ghostButton-background-active));
color: var(--saltGhostButton-text-color-active, var(--ghostButton-text-color-active));
}

/* Styles applied when the button triggers a dialog or menu */
.saltGhostButton[aria-expanded="true"][aria-haspopup="menu"],
.saltGhostButton[aria-expanded="true"][aria-haspopup="dialog"] {
background: var(--saltGhostButton-background-active, var(--ghostButton-background-active));
color: var(--saltGhostButton-text-color-active, var(--ghostButton-text-color-active));
border-color: var(--saltGhostButton-borderColor-active, var(--ghostButton-borderColor-active));
}

/* Pseudo-class applied to the root element if disabled={true} */
.saltGhostButton:disabled,
.saltGhostButton-disabled,
.saltGhostButton-disabled:active,
.saltGhostButton-disabled:focus-visible,
.saltGhostButton-disabled:focus-visible:active,
.saltGhostButton-disabled:hover {
background: var(--saltGhostButton-background-disabled, var(--ghostButton-background-disabled));
color: var(--saltGhostButton-text-color-disabled, var(--ghostButton-text-color-disabled));
cursor: var(--saltGhostButton-cursor-disabled, var(--salt-actionable-cursor-disabled));
}

/* TODO: update tokens*/
.saltGhostButton {
--ghostButton-text-color: var(--salt-content-primary-foreground);
--ghostButton-text-color-hover: var(--salt-content-primary-foreground);
--ghostButton-text-color-active: var(--salt-content-primary-foreground);
--ghostButton-text-color-disabled: var(--salt-content-primary-foreground-disabled);

--ghostButton-background-active: var(--salt-actionable-alpha-background-active);
--ghostButton-background-hover: var(--salt-actionable-alpha-background-hover);
}

.saltGhostButton.saltGhostButton-solid {
--ghostButton-text-color: var(--salt-content-bold-foreground);
--ghostButton-text-color-hover: var(--salt-content-bold-foreground);
--ghostButton-text-color-active: var(--salt-content-bold-foreground);
--ghostButton-text-color-disabled: var(--salt-content-bold-foreground-disabled);

--ghostButton-background-active: var(--salt-actionable-alpha-background-active-alt);
--ghostButton-background-hover: var(--salt-actionable-alpha-background-hover-alt);
}
83 changes: 83 additions & 0 deletions packages/lab/src/ghost-button/GhostButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useComponentCssInjection } from "@salt-ds/styles";

Check failure on line 1 in packages/lab/src/ghost-button/GhostButton.tsx

View workflow job for this annotation

GitHub Actions / lint

organizeImports

Import statements differs from the output
import { useWindow } from "@salt-ds/window";
import { clsx } from "clsx";
import {
type ComponentPropsWithoutRef,
forwardRef,
type ReactElement,
} from "react";

import ghostButtonCss from "./GhostButton.css";
import { makePrefixer, useButton } from "@salt-ds/core";

const withBaseName = makePrefixer("saltGhostButton");

export interface GhostButtonProps extends ComponentPropsWithoutRef<"button"> {
/**
* If `true`, the button will be disabled.
*/
disabled?: boolean;
/**
* If `true`, the button will be focusable when disabled.
*/
focusableWhenDisabled?: boolean;
/**
* The appearance of the button.
*/
adaptiveAppearance?: "solid" | "non-solid";
}

export const GhostButton = forwardRef<HTMLButtonElement, GhostButtonProps>(
function GhostButton(
{
children,
className,
disabled = false,
adaptiveAppearance = "non-solid",
focusableWhenDisabled,
onKeyUp,
onKeyDown,
onBlur,
onClick,
type = "button",
...restProps
},
ref,
): ReactElement<GhostButtonProps> {
const { active, buttonProps } = useButton({
disabled,
focusableWhenDisabled,
onKeyUp,
onKeyDown,
onBlur,
onClick,
});

const targetWindow = useWindow();
useComponentCssInjection({
testId: "salt-ghost-button",
css: ghostButtonCss,
window: targetWindow,
});

return (
<button
{...buttonProps}
className={clsx(
withBaseName(),
{
[withBaseName("disabled")]: disabled,
[withBaseName("active")]: active,
[withBaseName(adaptiveAppearance)]: adaptiveAppearance === "solid",
},
className,
)}
{...restProps}
ref={ref}
type={type}
>
{children}
</button>
);
},
);
1 change: 1 addition & 0 deletions packages/lab/src/ghost-button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./GhostButton";
1 change: 1 addition & 0 deletions packages/lab/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export {
export * from "./form-field-context-legacy";
export * from "./form-group";
export * from "./formatted-input";
export * from "./ghost-button";
export {
InputLegacy as Input,
type InputLegacyProps as InputProps,
Expand Down
Loading

0 comments on commit 9717b1f

Please sign in to comment.