diff --git a/packages/core/stories/ghost-button/ghost-button.stories.tsx b/packages/core/stories/ghost-button/ghost-button.stories.tsx new file mode 100644 index 0000000000..0ca6754480 --- /dev/null +++ b/packages/core/stories/ghost-button/ghost-button.stories.tsx @@ -0,0 +1,204 @@ +import { Banner, Button, FlowLayout, StackLayout } from "@salt-ds/core"; +import { CloseIcon, SearchIcon } from "@salt-ds/icons"; +import { GhostButton, SystemStatus } from "@salt-ds/lab"; +import type { Meta, StoryFn } from "@storybook/react"; + +export default { + title: "Lab/GhostButton", + component: GhostButton, + argTypes: { onClick: { action: "clicked" } }, +} as Meta; + +export const TestingEverything: StoryFn = (props) => { + return ( + +
+ + Button + + +
+ + + Button + + + + + Button + + + + + Button + + + + + Button + + + + + Button + + + + + Button + + + + + Button + + + + + Button + + +
+ + Button + +
+ + + Search + + + + + Search + + + + + Search + + + + + Search + + +
+ ); +}; + +const ButtonGridTemplate: StoryFn = (props) => { + return ( + + Submit + + + + + Search + + + Submit + + + + + Search + + + + Submit + + + + + Search + + + + ); +}; + +export const Disabled: StoryFn = (props) => { + return ( + + + Submit + + + + + Search + + + Submit + + + + + Search + + + + Submit + + + + + Search + + + + + + Submit + + + + + + Search + + + + Submit + + + + + + Search + + + + + Submit + + + + + + Search + + + + + ); +}; + +export const Solid = ButtonGridTemplate.bind({}); +Solid.args = { + adaptiveAppearance: "solid", +}; + +export const NonSolid = ButtonGridTemplate.bind({}); +NonSolid.args = { + adaptiveAppearance: "non-solid", +}; diff --git a/packages/lab/src/ghost-button/GhostButton.css b/packages/lab/src/ghost-button/GhostButton.css new file mode 100644 index 0000000000..484b5362ac --- /dev/null +++ b/packages/lab/src/ghost-button/GhostButton.css @@ -0,0 +1,151 @@ +/* TODO: sort and rename*/ + +.salt-theme.salt-theme-next[data-mode="light"] { + /* added the below token, needs renaming*/ + --salt-palette-alpha-weaky: var(--salt-color-black-20a); + + --salt-palette-alpha-hover-alt: var(--salt-color-black-10a); + --salt-palette-alpha-active-alt: var(--salt-color-black-20a); +} +.salt-theme.salt-theme-next[data-mode="dark"] { + /* added the below token, needs renaming*/ + --salt-palette-alpha-weaky: var(--salt-color-white-20a); + + --salt-palette-alpha-hover-alt: var(--salt-color-black-10a); + --salt-palette-alpha-active-alt: var(--salt-color-black-20a); +} + +.salt-theme[data-mode="light"] { + /* added the below token, needs renaming*/ + --salt-palette-alpha-weaky: var(--salt-color-black-fade-background-selection); + --salt-palette-alpha-weaker: var(--salt-color-black-fade-background-hover); + /* added alpha tokens, need renaming */ + --salt-palette-alpha-hover-alt: var(--salt-color-black-fade-background-hover); + --salt-palette-alpha-active-alt: var(--salt-color-black-fade-background-selection); +} + +.salt-theme[data-mode="dark"] { + /* added the below token, needs renaming*/ + --salt-palette-alpha-weaky: var(--salt-color-white-fade-background-selection); + --salt-palette-alpha-weaker: var(--salt-color-white-fade-background-hover); + /* added alpha tokens, need renaming */ + --salt-palette-alpha-hover-alt: var(--salt-color-black-fade-background-hover); + --salt-palette-alpha-active-alt: var(--salt-color-black-fade-background-selection); +} +.salt-theme { + /*added 2 colors to current theme, not the same opacity but same would need another 2 tokens in opacity*/ + --salt-color-white-fade-background-hover: rgba(255, 255, 255, var(--salt-opacity-8)); + --salt-color-white-fade-background-selection: rgba(255, 255, 255, var(--salt-opacity-15)); +} +.salt-theme, +.salt-theme-next { + --salt-actionable-alpha-background-hover-alt: var(--salt-palette-alpha-hover-alt); + --salt-actionable-alpha-background-active-alt: var(--salt-palette-alpha-active-alt); + --salt-actionable-alpha-background-hover: var(--salt-palette-alpha-weaker); + --salt-actionable-alpha-background-active: var(--salt-palette-alpha-weaky); +} + +/* ------------------------------- */ + +.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); +} diff --git a/packages/lab/src/ghost-button/GhostButton.tsx b/packages/lab/src/ghost-button/GhostButton.tsx new file mode 100644 index 0000000000..1c6d971f2b --- /dev/null +++ b/packages/lab/src/ghost-button/GhostButton.tsx @@ -0,0 +1,83 @@ +import { useComponentCssInjection } from "@salt-ds/styles"; +import { useWindow } from "@salt-ds/window"; +import { clsx } from "clsx"; +import { + type ComponentPropsWithoutRef, + type ReactElement, + forwardRef, +} from "react"; + +import { makePrefixer, useButton } from "@salt-ds/core"; +import ghostButtonCss from "./GhostButton.css"; + +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( + function GhostButton( + { + children, + className, + disabled = false, + adaptiveAppearance = "non-solid", + focusableWhenDisabled, + onKeyUp, + onKeyDown, + onBlur, + onClick, + type = "button", + ...restProps + }, + ref, + ): ReactElement { + const { active, buttonProps } = useButton({ + disabled, + focusableWhenDisabled, + onKeyUp, + onKeyDown, + onBlur, + onClick, + }); + + const targetWindow = useWindow(); + useComponentCssInjection({ + testId: "salt-ghost-button", + css: ghostButtonCss, + window: targetWindow, + }); + + return ( + + ); + }, +); diff --git a/packages/lab/src/ghost-button/index.ts b/packages/lab/src/ghost-button/index.ts new file mode 100644 index 0000000000..c55973bee5 --- /dev/null +++ b/packages/lab/src/ghost-button/index.ts @@ -0,0 +1 @@ +export * from "./GhostButton"; diff --git a/packages/lab/src/index.ts b/packages/lab/src/index.ts index ec404c772e..1a23eb5452 100644 --- a/packages/lab/src/index.ts +++ b/packages/lab/src/index.ts @@ -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, diff --git a/packages/theme/css/foundations/opacity.css b/packages/theme/css/foundations/opacity.css index 0c5cd0e6f4..3cbd27ec13 100644 --- a/packages/theme/css/foundations/opacity.css +++ b/packages/theme/css/foundations/opacity.css @@ -1,6 +1,7 @@ .salt-theme { --salt-opacity-0: 0; - --salt-opacity-8: 0.08; + /*--salt-opacity-8: 0.08; TODO: deprecate and add .1 and .2? */ + --salt-opacity-10: 0.1; --salt-opacity-15: 0.15; --salt-opacity-25: 0.25; --salt-opacity-40: 0.4;