Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f473e21
Rename 'Guided tour' to 'Guide'
ghengeveld Oct 23, 2025
69cc7ba
Add support for task dependencies and update ChecklistModule to show …
ghengeveld Oct 23, 2025
ce5ebc0
Change ChecklistModule based on progress
ghengeveld Oct 23, 2025
4cd3631
Support custom action button label
ghengeveld Oct 23, 2025
bd55f1e
Allow muting the ChecklistModule, either permanently or for a particu…
ghengeveld Oct 23, 2025
c89bce5
Change selection of items in the ChecklistModule
ghengeveld Oct 27, 2025
02c9aca
Tweak spacing for visual consistency
ghengeveld Oct 27, 2025
d685d12
Make ChecklistModule collapsible and fix card shadow styling
ghengeveld Oct 27, 2025
489113d
Split Card 'All' story into separate stories
ghengeveld Oct 27, 2025
e23d3f9
Drop superfluous initialCollapsed arg for Collapsible
ghengeveld Oct 27, 2025
b7e60c5
Hide ChecklistModule in prod mode
ghengeveld Oct 27, 2025
46081e5
Support providing useCollapsible state to Collapsible
ghengeveld Oct 27, 2025
b92f735
Animate closing of last checklist task
ghengeveld Oct 27, 2025
2e65137
Fix a11y issue
ghengeveld Oct 27, 2025
6c633c4
Don't show Guide page in production mode
ghengeveld Oct 27, 2025
91a3519
Fix visual snapshot for FocusProxy
ghengeveld Oct 27, 2025
770baf7
Prevent showing empty checklist on first render when all tasks are co…
ghengeveld Oct 27, 2025
84440e2
Move ChecklistModule above search and fix Explorer stories to show th…
ghengeveld Oct 27, 2025
36e688a
Revert "Tweak spacing for visual consistency"
ghengeveld Oct 27, 2025
385066d
Fix sidebar spacing
ghengeveld Oct 27, 2025
7c51785
More flexible component API
ghengeveld Oct 27, 2025
1a917a3
Collapse spacing when ChecklistModule is hidden
ghengeveld Oct 27, 2025
36df52d
Drop useless story
ghengeveld Oct 28, 2025
69eac58
Add progress indicator, animate progress value changes and add menu t…
ghengeveld Oct 28, 2025
5d1b37e
Centralize hashchange detection and add several FocusRing utility com…
ghengeveld Oct 28, 2025
9f8bf34
Add story for GuidePage and hoist store/state up from Checklist to Gu…
ghengeveld Oct 28, 2025
7db2e15
Update sidebar menu to use Listbox components and put What's new last…
ghengeveld Oct 28, 2025
0e109db
Pull checklist logic out into a hook
ghengeveld Oct 28, 2025
4289e0e
Fix progress updates for guide in sidebar menu
ghengeveld Oct 28, 2025
000cae7
Refactor checklist state management to use accepted and done properti…
ghengeveld Oct 30, 2025
076adee
Fix import
ghengeveld Oct 30, 2025
c5cc465
Fix collapsible state
ghengeveld Oct 31, 2025
0f5f1a6
Avoid passing unsupported attribute to SVG
ghengeveld Oct 31, 2025
894f311
Don't show link to onboarding guide in prod mode
ghengeveld Oct 31, 2025
f5f0ad8
Add missing key prop
ghengeveld Oct 31, 2025
dcfe2fc
Avoid conditional hook call
ghengeveld Oct 31, 2025
49492f0
Avoid NodeJS type in client-side file
ghengeveld Oct 31, 2025
2a2eb6c
Fix type errors and story data
ghengeveld Oct 31, 2025
b82881f
Simplify TextFlip component
ghengeveld Oct 31, 2025
4c0104f
Use ref to track latest text value so onAnimationEnd always syncs wit…
ghengeveld Oct 31, 2025
8030fc3
Fix numeric sorting
ghengeveld Oct 31, 2025
230577e
Fix invalid nesting of buttons
ghengeveld Oct 31, 2025
405b934
Fix invalid DOM nesting
ghengeveld Oct 31, 2025
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
8 changes: 7 additions & 1 deletion code/core/src/cli/globalSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ const userSettingSchema = z.object({
// (we can remove keys once they are deprecated)
userSince: z.number().optional(),
init: z.object({ skipOnboarding: z.boolean().optional() }).optional(),
checklist: z.object({ completed: z.array(z.string()), skipped: z.array(z.string()) }).optional(),
checklist: z
.object({
muted: z.union([z.boolean(), z.array(z.string())]).optional(),
accepted: z.array(z.string()),
skipped: z.array(z.string()),
})
.optional(),
});

let settings: Settings | undefined;
Expand Down
308 changes: 163 additions & 145 deletions code/core/src/components/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { ButtonHTMLAttributes, SyntheticEvent } from 'react';
import type { ButtonHTMLAttributes, ComponentProps } from 'react';
import React, { forwardRef, useEffect, useState } from 'react';

import { Slot } from '@radix-ui/react-slot';
import { darken, lighten, rgba, transparentize } from 'polished';
import { isPropValid, styled } from 'storybook/theming';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
as?: 'button' | 'a' | 'label' | typeof Slot;
as?: 'a' | 'button' | 'div' | 'label' | typeof Slot;
asChild?: boolean;
size?: 'small' | 'medium';
padding?: 'small' | 'medium' | 'none';
variant?: 'outline' | 'solid' | 'ghost';
onClick?: (event: SyntheticEvent) => void;
disabled?: boolean;
readOnly?: boolean;
active?: boolean;
animation?: 'none' | 'rotate360' | 'glow' | 'jiggle';
}
Expand All @@ -27,6 +27,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
variant = 'outline',
padding = 'medium',
disabled = false,
readOnly = false,
active = false,
onClick,
...props
Expand All @@ -37,7 +38,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(

const [isAnimating, setIsAnimating] = useState(false);

const handleClick = (event: SyntheticEvent) => {
const handleClick: ButtonProps['onClick'] = (event) => {
if (onClick) {
onClick(event);
}
Expand Down Expand Up @@ -65,6 +66,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
size={size}
padding={padding}
disabled={disabled}
readOnly={readOnly}
active={active}
animating={isAnimating}
animation={animation}
Expand All @@ -84,165 +86,181 @@ const StyledButton = styled('button', {
animating: boolean;
animation: ButtonProps['animation'];
}
>(({ theme, variant, size, disabled, active, animating, animation = 'none', padding }) => ({
border: 0,
cursor: disabled ? 'not-allowed' : 'pointer',
display: 'inline-flex',
gap: '6px',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
padding: (() => {
if (padding === 'none') {
return 0;
}
if (padding === 'small' && size === 'small') {
return '0 7px';
}
if (padding === 'small' && size === 'medium') {
return '0 9px';
}
if (size === 'small') {
return '0 10px';
}
if (size === 'medium') {
return '0 12px';
}
return 0;
})(),
height: size === 'small' ? '28px' : '32px',
position: 'relative',
textAlign: 'center',
textDecoration: 'none',
transitionProperty: 'background, box-shadow',
transitionDuration: '150ms',
transitionTimingFunction: 'ease-out',
verticalAlign: 'top',
whiteSpace: 'nowrap',
userSelect: 'none',
opacity: disabled ? 0.5 : 1,
margin: 0,
fontSize: `${theme.typography.size.s1}px`,
fontWeight: theme.typography.weight.bold,
lineHeight: '1',
background: (() => {
if (variant === 'solid') {
return theme.color.secondary;
}

if (variant === 'outline') {
return theme.button.background;
}

if (variant === 'ghost' && active) {
return theme.background.hoverable;
}
return 'transparent';
})(),
...(variant === 'ghost'
? {
// This is a hack to apply bar styles to the button as soon as it is part of a bar
// It is a temporary solution until we have implemented Theming 2.0.
'.sb-bar &': {
background: (() => {
if (active) {
return transparentize(0.9, theme.barTextColor);
}
return 'transparent';
})(),
color: (() => {
if (active) {
return theme.barSelectedColor;
}
return theme.barTextColor;
})(),
'&:hover': {
color: theme.barHoverColor,
background: transparentize(0.86, theme.barHoverColor),
},

'&:active': {
color: theme.barSelectedColor,
background: transparentize(0.9, theme.barSelectedColor),
},

'&:focus': {
boxShadow: `${rgba(theme.barHoverColor, 1)} 0 0 0 1px inset`,
outline: 'none',
},
},
>(
({
theme,
variant,
size,
disabled,
readOnly,
active,
animating,
animation = 'none',
padding,
}) => ({
border: 0,
cursor: readOnly ? 'inherit' : disabled ? 'not-allowed' : 'pointer',
display: 'inline-flex',
gap: '6px',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
padding: (() => {
if (padding === 'none') {
return 0;
}
: {}),
color: (() => {
if (variant === 'solid') {
return theme.color.lightest;
}

if (variant === 'outline') {
return theme.input.color;
}

if (variant === 'ghost' && active) {
return theme.color.secondary;
}

if (variant === 'ghost') {
return theme.color.mediumdark;
}
return theme.input.color;
})(),
boxShadow: variant === 'outline' ? `${theme.button.border} 0 0 0 1px inset` : 'none',
borderRadius: theme.input.borderRadius,
// Making sure that the button never shrinks below its minimum size
flexShrink: 0,

'&:hover': {
color: variant === 'ghost' ? theme.color.secondary : undefined,
if (padding === 'small' && size === 'small') {
return '0 7px';
}
if (padding === 'small' && size === 'medium') {
return '0 9px';
}
if (size === 'small') {
return '0 10px';
}
if (size === 'medium') {
return '0 12px';
}
return 0;
})(),
height: size === 'small' ? '28px' : '32px',
position: 'relative',
textAlign: 'center',
textDecoration: 'none',
transitionProperty: 'background, box-shadow',
transitionDuration: '150ms',
transitionTimingFunction: 'ease-out',
verticalAlign: 'top',
whiteSpace: 'nowrap',
userSelect: 'none',
opacity: disabled && !readOnly ? 0.5 : 1,
margin: 0,
fontSize: `${theme.typography.size.s1}px`,
fontWeight: theme.typography.weight.bold,
lineHeight: '1',
background: (() => {
let bgColor = theme.color.secondary;

if (variant === 'solid') {
bgColor = theme.color.secondary;
return theme.color.secondary;
}

if (variant === 'outline') {
bgColor = theme.button.background;
return theme.button.background;
}

if (variant === 'ghost') {
return transparentize(0.86, theme.color.secondary);
if (variant === 'ghost' && active) {
return theme.background.hoverable;
}
return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
return 'transparent';
})(),
},
...(variant === 'ghost'
? {
// This is a hack to apply bar styles to the button as soon as it is part of a bar
// It is a temporary solution until we have implemented Theming 2.0.
'.sb-bar &': {
background: (() => {
if (active) {
return transparentize(0.9, theme.barTextColor);
}
return 'transparent';
})(),
color: (() => {
if (active) {
return theme.barSelectedColor;
}
return theme.barTextColor;
})(),
...(!readOnly && {
'&:hover': {
color: theme.barHoverColor,
background: transparentize(0.86, theme.barHoverColor),
},

'&:active': {
color: variant === 'ghost' ? theme.color.secondary : undefined,
background: (() => {
let bgColor = theme.color.secondary;
'&:active': {
color: theme.barSelectedColor,
background: transparentize(0.9, theme.barSelectedColor),
},

'&:focus': {
boxShadow: `${rgba(theme.barHoverColor, 1)} 0 0 0 1px inset`,
outline: 'none',
},
}),
},
}
: {}),
color: (() => {
if (variant === 'solid') {
bgColor = theme.color.secondary;
return theme.color.lightest;
}

if (variant === 'outline') {
bgColor = theme.button.background;
return theme.input.color;
}

if (variant === 'ghost' && active) {
return theme.color.secondary;
}

if (variant === 'ghost') {
return theme.background.hoverable;
return theme.color.mediumdark;
}
return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
return theme.input.color;
})(),
},

'&:focus': {
boxShadow: `${rgba(theme.color.secondary, 1)} 0 0 0 1px inset`,
outline: 'none',
},

'> svg': {
animation:
animating && animation !== 'none' ? `${theme.animation[animation]} 1000ms ease-out` : '',
},
}));
boxShadow: variant === 'outline' ? `${theme.button.border} 0 0 0 1px inset` : 'none',
borderRadius: theme.input.borderRadius,
// Making sure that the button never shrinks below its minimum size
flexShrink: 0,

...(!readOnly && {
'&:hover': {
color: variant === 'ghost' ? theme.color.secondary : undefined,
background: (() => {
let bgColor = theme.color.secondary;

if (variant === 'solid') {
bgColor = theme.color.secondary;
}

if (variant === 'outline') {
bgColor = theme.button.background;
}

if (variant === 'ghost') {
return transparentize(0.86, theme.color.secondary);
}
return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
})(),
},

'&:active': {
color: variant === 'ghost' ? theme.color.secondary : undefined,
background: (() => {
let bgColor = theme.color.secondary;

if (variant === 'solid') {
bgColor = theme.color.secondary;
}

if (variant === 'outline') {
bgColor = theme.button.background;
}

if (variant === 'ghost') {
return theme.background.hoverable;
}
return theme.base === 'light' ? darken(0.02, bgColor) : lighten(0.03, bgColor);
})(),
},

'&:focus': {
boxShadow: `${rgba(theme.color.secondary, 1)} 0 0 0 1px inset`,
outline: 'none',
},
}),

'> svg': {
animation:
animating && animation !== 'none' ? `${theme.animation[animation]} 1000ms ease-out` : '',
},
})
);
Loading