Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): updates for button, density and colors #1939

Merged
merged 5 commits into from
Dec 10, 2024
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/two-hotels-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@penumbra-zone/ui': minor
---

Update Button component to match the latest designs
10 changes: 5 additions & 5 deletions packages/ui/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const DensityWrapper = ({ children, showDensityControl }) => {
<Tabs
options={[
{ label: 'Sparse', value: 'sparse' },
{ label: 'Medium', value: 'medium' },
{ label: 'Compact', value: 'compact' },
{ label: 'Slim', value: 'slim' },
]}
value={density}
onChange={setDensity}
Expand All @@ -36,14 +36,14 @@ const DensityWrapper = ({ children, showDensityControl }) => {
</div>
);

if (density === 'medium') {
return <Density medium>{densityTabs}</Density>;
}

if (density === 'compact') {
return <Density compact>{densityTabs}</Density>;
}

if (density === 'slim') {
return <Density slim>{densityTabs}</Density>;
}

return <Density sparse>{densityTabs}</Density>;
};

Expand Down
46 changes: 19 additions & 27 deletions packages/ui/src/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { FC, forwardRef, MouseEventHandler, ReactNode } from 'react';
import { LucideIcon } from 'lucide-react';
import cn from 'clsx';
import { getOutlineColorByActionType, ActionType } from '../utils/action-type';
import { Priority, buttonBase, getBackground, getFocusOutline, getOverlays } from '../utils/button';
import { button } from '../utils/typography';
import {
Priority,
buttonBase,
getBackground,
getOverlays,
getFont,
getSize,
getIconSize,
} from '../utils/button';
import { useDensity } from '../utils/density';

const iconOnlyAdornment = cn('rounded-full p-1 w-max');
const sparse = (iconOnly?: boolean | 'adornment') =>
cn('rounded-sm h-12', iconOnly ? 'w-12 min-w-12 pl-0 pr-0' : 'w-full pl-4 pr-4');

const compact = (iconOnly?: boolean | 'adornment') =>
cn('rounded-full h-8 min-w-8 w-max', iconOnly ? 'pl-2 pr-2' : 'pl-4 pr-4');

interface BaseButtonProps {
type?: HTMLButtonElement['type'];
/**
Expand Down Expand Up @@ -109,6 +109,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
ref,
) => {
const density = useDensity();
const styleAttrs = { actionType, iconOnly, density, priority };

return (
<button
Expand All @@ -121,31 +122,22 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
{...attrs}
className={cn(
buttonBase,
button,
getBackground(actionType, priority, iconOnly),
getFocusOutline({ density, iconOnly, actionType }),
getOverlays({ actionType, iconOnly, density }),
getFont(styleAttrs),
getSize(styleAttrs),
getBackground(styleAttrs),
getOverlays(styleAttrs),

'-outline-offset-1',
priority === 'secondary' && 'outline-1',
'-outline-offset-1 focus:outline-none',
priority === 'secondary' && 'outline outline-1',
priority === 'secondary' && getOutlineColorByActionType(actionType),
'after:-outline-offset-2',

'relative',
'flex gap-2 items-center justify-center',
'text-neutral-contrast',

// eslint-disable-next-line no-nested-ternary -- allow
iconOnly === 'adornment'
? iconOnlyAdornment
: density === 'sparse'
? sparse(iconOnly)
: compact(iconOnly),
'flex items-center justify-center',
density === 'sparse' ? 'gap-2' : 'gap-1',
)}
>
{IconComponent && (
<IconComponent size={density === 'sparse' && iconOnly === true ? 24 : 16} />
)}
{IconComponent && <IconComponent size={getIconSize(density)} />}

{!iconOnly && children}
</button>
Expand Down
10 changes: 5 additions & 5 deletions packages/ui/src/Density/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ReactNode } from 'react';
import { Density as TDensity, DensityContext } from '../utils/density';

export type DensityPropType =
| { sparse: true; medium?: never; compact?: never }
| { medium: true; sparse?: never; compact?: never }
| { compact: true; sparse?: never; medium?: never };
| { sparse: true; slim?: never; compact?: never }
| { slim: true; sparse?: never; compact?: never }
| { compact: true; sparse?: never; slim?: never };

export type DensityProps = DensityPropType & {
children?: ReactNode;
Expand Down Expand Up @@ -71,9 +71,9 @@ export type DensityProps = DensityPropType & {
* />
* ```
*/
export const Density = ({ children, sparse, medium, compact }: DensityProps) => {
export const Density = ({ children, sparse, slim, compact }: DensityProps) => {
const density: TDensity =
(sparse && 'sparse') ?? (medium && 'medium') ?? (compact && 'compact') ?? 'sparse';
(sparse && 'sparse') ?? (compact && 'compact') ?? (slim && 'slim') ?? 'sparse';

return <DensityContext.Provider value={density}>{children}</DensityContext.Provider>;
};
14 changes: 7 additions & 7 deletions packages/ui/src/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ type LimitedActionType = Exclude<ActionType, 'destructive'>;

const getIndicatorColor = (actionType: LimitedActionType): string => {
if (actionType === 'accent') {
return cn('bg-tabAccent');
return cn('bg-accentRadialGradient');
}
if (actionType === 'unshield') {
return cn('bg-tabUnshield');
return cn('bg-unshieldRadialGradient');
}
return cn('bg-tabNeutral');
return cn('bg-neutralRadialGradient');
};

const getBorderColor = (actionType: LimitedActionType): string => {
Expand All @@ -27,20 +27,20 @@ const getBorderColor = (actionType: LimitedActionType): string => {
};

const getDensityClasses = (density: Density): string => {
if (density === 'compact') {
if (density === 'slim') {
return cn('h-7 gap-4');
}
if (density === 'medium') {
if (density === 'compact') {
return cn('h-[44px] gap-2');
}
return cn('h-[44px] gap-4');
};

const getDensityItemClasses = (density: Density): string => {
if (density === 'medium') {
if (density === 'compact') {
return cn(tabMedium, 'p-2');
}
if (density === 'compact') {
if (density === 'slim') {
return cn(tabSmall, 'py-1 px-2');
}
return cn(tab, 'grow shrink basis-0 p-2');
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/ValueView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const getGap = (density: Density) => {
if (density === 'sparse') {
return 'gap-2';
}
if (density === 'medium') {
if (density === 'compact') {
return 'gap-1.5';
}
return 'gap-1';
Expand All @@ -142,7 +142,7 @@ const getIconSize = (density: Density) => {
if (density === 'sparse') {
return 'lg';
}
if (density === 'medium') {
if (density === 'compact') {
return 'md';
}
return 'sm';
Expand Down
29 changes: 21 additions & 8 deletions packages/ui/src/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,11 @@ const PALETTE = {
},
base: {
black: '#000000',
blackAlt: '#0D0D0D',
white: '#ffffff',
transparent: 'transparent',
},
};
} as const;

/**
* Call `theme.spacing(x)`, where `x` is the number of spacing units (in the
Expand Down Expand Up @@ -192,6 +193,7 @@ export const theme = {
},
base: {
black: PALETTE.base.black,
blackAlt: PALETTE.base.blackAlt,
white: PALETTE.base.white,
transparent: PALETTE.base.transparent,
},
Expand All @@ -210,24 +212,35 @@ export const theme = {
unshieldFocusOutline: PALETTE.purple['400'],
neutralFocusOutline: PALETTE.neutral['400'],
destructiveFocusOutline: PALETTE.red['400'],
successFocusOutline: PALETTE.green['400'],
},
other: {
tonalStroke: PALETTE.neutral['50'] + hexOpacity(0.15),
tonalFill5: PALETTE.neutral['50'] + hexOpacity(0.05),
tonalFill10: PALETTE.neutral['50'] + hexOpacity(0.1),
solidStroke: PALETTE.neutral['700'],
solidStroke: PALETTE.neutral['900'],
dialogBackground: PALETTE.teal['700'] + hexOpacity(0.1),
overlay: PALETTE.base.black + hexOpacity(0.5),
},
},
gradient: {
card: 'linear-gradient(136deg, rgba(250, 250, 250, 0.1) 6.32%, rgba(250, 250, 250, 0.01) 75.55%)',
tabNeutral: 'radial-gradient(at 50% 100%, rgba(163, 163, 163, 0.35) 0%, transparent 50%)',
tabAccent: 'radial-gradient(at 50% 100%, rgba(244, 156, 67, 0.35) 0%, transparent 50%)',
tabUnshield: 'radial-gradient(at 50% 100%, rgba(193, 166, 204, 0.35) 0%, transparent 50%)',
dialogSuccess: `radial-gradient(100% 100% at 0% 0%, rgba(83, 174, 168, 0.20) 0%, rgba(83, 174, 168, 0.02) 100%)`,
dialogCaution: `radial-gradient(100% 100% at 0% 0%, rgba(153, 97, 15, 0.20) 0%, rgba(153, 97, 15, 0.02) 100%)`,
dialogError: `radial-gradient(100% 100% at 0% 0%, rgba(175, 38, 38, 0.20) 0%, rgba(175, 38, 38, 0.02) 100%)`,
neutralRadialGradient:
'radial-gradient(50% 100% at 50% 100%, rgba(163, 163, 163, 0.35) 0%, rgba(163, 163, 163, 0.00) 95%)',
accentRadialGradient:
'radial-gradient(50% 100% at 50% 100%, rgba(186, 77, 20, 0.35) 0%, rgba(186, 77, 20, 0.00) 95%)',
unshieldRadialGradient:
'radial-gradient(50% 100% at 50% 100%, rgba(112, 82, 121, 0.35) 0%, rgba(112, 82, 121, 0.00) 95%)',
accentRadialBackground:
'radial-gradient(100% 100% at 0% 0%, rgba(244, 156, 67, 0.25) 0%, rgba(244, 156, 67, 0.03) 100%)',
unshieldRadialBackground:
'radial-gradient(100% 100% at 0% 0%, rgba(193, 166, 204, 0.25) 0%, rgba(193, 166, 204, 0.03) 100%)',
secondaryRadialBackground:
'radial-gradient(100% 100% at 0% 0%, rgba(83, 174, 168, 0.25) 0%, rgba(83, 174, 168, 0.03) 100%)',
cautionRadialBackground:
'radial-gradient(100% 100% at 0% 0%, rgba(153, 97, 15, 0.25) 0%, rgba(153, 97, 15, 0.03) 100%)',
destructiveRadialBackground:
'radial-gradient(100% 100% at 0% 0%, rgba(175, 38, 38, 0.25) 0%, rgba(175, 38, 38, 0.03) 100%)',
buttonHover:
'linear-gradient(0deg, rgba(83, 174, 168, 0.15) 0%, rgba(83, 174, 168, 0.15) 100%)',
buttonDisabled: 'linear-gradient(0deg, rgba(10, 10, 10, 0.8) 0%, rgba(10, 10, 10, 0.8) 100%)',
Expand Down
22 changes: 9 additions & 13 deletions packages/ui/src/utils/action-type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cn from 'clsx';

export type ActionType = 'default' | 'accent' | 'unshield' | 'destructive';
export type ActionType = 'default' | 'accent' | 'unshield' | 'destructive' | 'success';

export const getColorByActionType = (actionType: ActionType): string => {
if (actionType === 'destructive') {
Expand All @@ -9,64 +9,60 @@ export const getColorByActionType = (actionType: ActionType): string => {
return cn('text-text-primary');
};

const AFTER_OUTLINE_COLOR_MAP: Record<ActionType, string> = {
default: cn('focus-within:after:outline-action-neutralFocusOutline'),
accent: cn('focus-within:after:outline-action-primaryFocusOutline'),
unshield: cn('focus-within:after:outline-action-unshieldFocusOutline'),
destructive: cn('focus-within:after:outline-action-destructiveFocusOutline'),
};

const OUTLINE_COLOR_MAP: Record<ActionType, string> = {
default: cn('outline-neutral-main'),
default: cn('outline-other-tonalStroke'),
accent: cn('outline-primary-main'),
unshield: cn('outline-unshield-main'),
destructive: cn('outline-destructive-main'),
success: cn('outline-success-main'),
};

const BEFORE_OUTLINE_COLOR_MAP: Record<ActionType, string> = {
default: cn('focus:before:outline-action-neutralFocusOutline'),
accent: cn('focus:before:outline-action-primaryFocusOutline'),
unshield: cn('focus:before:outline-action-unshieldFocusOutline'),
destructive: cn('focus:before:outline-action-destructiveFocusOutline'),
success: cn('focus:before:outline-action-successFocusOutline'),
};

const FOCUS_OUTLINE_COLOR_MAP: Record<ActionType, string> = {
default: cn('focus:outline-action-neutralFocusOutline'),
accent: cn('focus:outline-action-primaryFocusOutline'),
unshield: cn('focus:outline-action-unshieldFocusOutline'),
destructive: cn('focus:outline-action-destructiveFocusOutline'),
success: cn('focus:outline-action-successFocusOutline'),
};

const FOCUS_WITHIN_OUTLINE_COLOR_MAP: Record<ActionType, string> = {
default: cn('focus-within:outline-action-neutralFocusOutline'),
accent: cn('focus-within:outline-action-primaryFocusOutline'),
unshield: cn('focus-within:outline-action-unshieldFocusOutline'),
destructive: cn('focus-within:outline-action-destructiveFocusOutline'),
success: cn('focus-within:outline-action-successFocusOutline'),
};

const ARIA_CHECKED_OUTLINE_COLOR_MAP: Record<ActionType, string> = {
default: cn('aria-checked:outline-action-neutralFocusOutline'),
accent: cn('aria-checked:outline-action-primaryFocusOutline'),
unshield: cn('aria-checked:outline-action-unshieldFocusOutline'),
destructive: cn('aria-checked:outline-action-destructiveFocusOutline'),
success: cn('aria-checked:outline-action-successFocusOutline'),
};

const BORDER_COLOR_MAP: Record<ActionType, string> = {
default: cn('border-neutral-main'),
accent: cn('border-primary-main'),
unshield: cn('border-unshield-main'),
destructive: cn('border-destructive-main'),
success: cn('border-success-main'),
};

const BACKGROUND_COLOR_MAP: Record<ActionType, string> = {
default: cn('bg-neutral-main'),
accent: cn('bg-primary-main'),
unshield: cn('bg-unshield-main'),
destructive: cn('bg-destructive-main'),
};

export const getAfterOutlineColorByActionType = (actionType: ActionType): string => {
return AFTER_OUTLINE_COLOR_MAP[actionType];
success: cn('bg-success-main'),
};

export const getBeforeOutlineColorByActionType = (actionType: ActionType): string => {
Expand Down
Loading
Loading