Introduce accent for chips (#911)

* Introduce accent for chips

* Add top bar on Mobile on Settings pages

* Various fixes

* Fix according to peer review
This commit is contained in:
Charles Bochet
2023-07-24 16:49:33 -07:00
committed by GitHub
parent b2f4108d89
commit d6afbe8e8e
23 changed files with 166 additions and 279 deletions

View File

@ -1,60 +1,27 @@
import { useTheme } from '@emotion/react';
import { import {
DropdownButton, Chip,
DropdownOptionType, ChipAccent,
} from '@/ui/button/components/DropdownButton'; ChipSize,
import { IconCheck, IconNotes } from '@/ui/icon'; ChipVariant,
import { } from '@/ui/chip/components/Chip';
ActivityType, import { IconPhone } from '@/ui/icon';
CommentThread, import { CommentThread } from '~/generated/graphql';
useUpdateCommentThreadMutation,
} from '~/generated/graphql';
type OwnProps = { type OwnProps = {
commentThread: Pick<CommentThread, 'id' | 'type'>; commentThread: Pick<CommentThread, 'type'>;
}; };
export function CommentThreadTypeDropdown({ commentThread }: OwnProps) { export function CommentThreadTypeDropdown({ commentThread }: OwnProps) {
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation(); const theme = useTheme();
const options: DropdownOptionType[] = [
{ label: 'Note', key: 'note', icon: <IconNotes /> },
{ label: 'Task', key: 'task', icon: <IconCheck /> },
];
function getSelectedOptionKey() {
if (commentThread.type === ActivityType.Note) {
return 'note';
} else if (commentThread.type === ActivityType.Task) {
return 'task';
} else {
return undefined;
}
}
const convertSelectionOptionKeyToActivityType = (key: string) => {
switch (key) {
case 'note':
return ActivityType.Note;
case 'task':
return ActivityType.Task;
default:
return undefined;
}
};
const handleSelect = (selectedOption: DropdownOptionType) => {
updateCommentThreadMutation({
variables: {
id: commentThread.id,
type: convertSelectionOptionKeyToActivityType(selectedOption.key),
},
});
};
return ( return (
<DropdownButton <Chip
options={options} label={commentThread.type}
onSelection={handleSelect} leftComponent={<IconPhone size={theme.icon.size.md} />}
selectedOptionKey={getSelectedOptionKey()} size={ChipSize.Large}
accent={ChipAccent.TextSecondary}
variant={ChipVariant.Highlighted}
/> />
); );
} }

View File

@ -47,10 +47,6 @@ export const SmallName: Story = {
}, },
}; };
export const Clickable: Story = {
args: { ...SmallName.args, clickable: true },
};
export const BigName: Story = { export const BigName: Story = {
args: { args: {
id: 'google', id: 'google',

View File

@ -1,9 +1,10 @@
import { PersonChip } from '@/people/components/PersonChip';
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell'; import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
import { Company, User } from '~/generated/graphql'; import { Company, User } from '~/generated/graphql';
import { UserChip } from '../../users/components/UserChip';
import { CompanyAccountOwnerPicker } from './CompanyAccountOwnerPicker'; import { CompanyAccountOwnerPicker } from './CompanyAccountOwnerPicker';
export type CompanyAccountOnwer = Pick<Company, 'id'> & { export type CompanyAccountOnwer = Pick<Company, 'id'> & {
@ -38,7 +39,7 @@ export function CompanyAccountOwnerCell({ company }: OwnProps) {
} }
nonEditModeContent={ nonEditModeContent={
company.accountOwner?.displayName ? ( company.accountOwner?.displayName ? (
<PersonChip <UserChip
id={company.accountOwner.id} id={company.accountOwner.id}
name={company.accountOwner?.displayName ?? ''} name={company.accountOwner?.displayName ?? ''}
pictureUrl={company.accountOwner?.avatarUrl ?? ''} pictureUrl={company.accountOwner?.avatarUrl ?? ''}

View File

@ -19,6 +19,7 @@ import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedSta
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql'; import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
import { EntityChipVariant } from '../../ui/chip/components/EntityChip';
import { PipelineProgressForBoard } from '../types/CompanyProgress'; import { PipelineProgressForBoard } from '../types/CompanyProgress';
import { CompanyChip } from './CompanyChip'; import { CompanyChip } from './CompanyChip';
@ -177,7 +178,7 @@ export function CompanyBoardCard() {
id={company.id} id={company.id}
name={company.name} name={company.name}
pictureUrl={getLogoUrlFromDomainName(company.domainName)} pictureUrl={getLogoUrlFromDomainName(company.domainName)}
clickable={false} variant={EntityChipVariant.Transparent}
/> />
<StyledCheckboxContainer className="checkbox-container"> <StyledCheckboxContainer className="checkbox-container">
<Checkbox <Checkbox

View File

@ -1,25 +1,26 @@
import { EntityChip } from '@/ui/chip/components/EntityChip'; import { EntityChip, EntityChipVariant } from '@/ui/chip/components/EntityChip';
type OwnProps = { type OwnProps = {
id: string; id: string;
name: string; name: string;
pictureUrl?: string; pictureUrl?: string;
clickable?: boolean; variant?: EntityChipVariant;
}; };
export function CompanyChip({ export function CompanyChip({
id, id,
name, name,
pictureUrl, pictureUrl,
clickable = true, variant = EntityChipVariant.Regular,
}: OwnProps) { }: OwnProps) {
return ( return (
<EntityChip <EntityChip
entityId={id} entityId={id}
linkToEntity={clickable ? `/companies/${id}` : undefined} linkToEntity={`/companies/${id}`}
name={name} name={name}
avatarType="squared" avatarType="squared"
pictureUrl={pictureUrl} pictureUrl={pictureUrl}
variant={variant}
/> />
); );
} }

View File

@ -37,7 +37,6 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
<CompanyChip <CompanyChip
id={company.id} id={company.id}
name={company.name} name={company.name}
clickable
pictureUrl={getLogoUrlFromDomainName(company.domainName)} pictureUrl={getLogoUrlFromDomainName(company.domainName)}
/> />
} }

View File

@ -1,25 +1,26 @@
import { EntityChip } from '@/ui/chip/components/EntityChip'; import { EntityChip, EntityChipVariant } from '@/ui/chip/components/EntityChip';
export type PersonChipPropsType = { export type PersonChipPropsType = {
id: string; id: string;
name: string; name: string;
pictureUrl?: string; pictureUrl?: string;
clickable?: boolean; variant?: EntityChipVariant;
}; };
export function PersonChip({ export function PersonChip({
id, id,
name, name,
pictureUrl, pictureUrl,
clickable = true, variant,
}: PersonChipPropsType) { }: PersonChipPropsType) {
return ( return (
<EntityChip <EntityChip
entityId={id} entityId={id}
linkToEntity={clickable ? `/person/${id}` : undefined} linkToEntity={`/person/${id}`}
name={name} name={name}
avatarType="rounded" avatarType="rounded"
pictureUrl={pictureUrl} pictureUrl={pictureUrl}
variant={variant}
/> />
); );
} }

View File

@ -1,144 +0,0 @@
import React, { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { IconChevronDown } from '@/ui/icon/index';
type ButtonProps = React.ComponentProps<'button'>;
export type DropdownOptionType = {
key: string;
label: string;
icon: React.ReactNode;
};
type OwnProps = {
options: DropdownOptionType[];
selectedOptionKey?: string;
onSelection: (value: DropdownOptionType) => void;
} & ButtonProps;
const StyledButton = styled.button<ButtonProps & { isOpen: boolean }>`
align-items: center;
background: ${({ theme }) => theme.background.tertiary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-bottom-left-radius: ${({ isOpen, theme }) =>
isOpen ? 0 : theme.border.radius.sm};
border-bottom-right-radius: ${({ isOpen, theme }) =>
isOpen ? 0 : theme.border.radius.sm};
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
svg {
align-items: center;
display: flex;
height: 14px;
justify-content: center;
width: 14px;
}
`;
const StyledDropdownItem = styled.button<ButtonProps>`
align-items: center;
background: ${({ theme }) => theme.background.tertiary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
svg {
align-items: center;
display: flex;
height: 14px;
justify-content: center;
width: 14px;
}
`;
const DropdownContainer = styled.div`
position: relative;
`;
const DropdownMenu = styled.div`
display: flex;
flex-direction: column;
position: absolute;
width: 100%;
`;
export function DropdownButton({
options,
selectedOptionKey,
onSelection,
...buttonProps
}: OwnProps) {
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState<
DropdownOptionType | undefined
>(undefined);
useEffect(() => {
if (selectedOptionKey) {
const option = options.find((option) => option.key === selectedOptionKey);
setSelectedOption(option);
} else {
setSelectedOption(options[0]);
}
}, [selectedOptionKey, options]);
if (!options.length) {
throw new Error('You must provide at least one option.');
}
const handleSelect =
(option: DropdownOptionType) =>
(event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
onSelection(option);
setSelectedOption(option);
setIsOpen(false);
};
return (
<>
{selectedOption && (
<DropdownContainer>
<StyledButton
onClick={() => setIsOpen(!isOpen)}
{...buttonProps}
isOpen={isOpen}
>
{selectedOption.icon}
{selectedOption.label}
{options.length > 1 && <IconChevronDown />}
</StyledButton>
{isOpen && (
<DropdownMenu>
{options
.filter((option) => option.label !== selectedOption.label)
.map((option, index) => (
<StyledDropdownItem
key={index}
onClick={handleSelect(option)}
>
{option.icon}
{option.label}
</StyledDropdownItem>
))}
</DropdownMenu>
)}
</DropdownContainer>
)}
</>
);
}

View File

@ -8,6 +8,11 @@ export enum ChipSize {
Small = 'small', Small = 'small',
} }
export enum ChipAccent {
TextPrimary = 'text-primary',
TextSecondary = 'text-secondary',
}
export enum ChipVariant { export enum ChipVariant {
Highlighted = 'highlighted', Highlighted = 'highlighted',
Regular = 'regular', Regular = 'regular',
@ -21,6 +26,7 @@ type OwnProps = {
label: string; label: string;
maxWidth?: string; maxWidth?: string;
variant?: ChipVariant; variant?: ChipVariant;
accent?: ChipAccent;
leftComponent?: React.ReactNode; leftComponent?: React.ReactNode;
rightComponent?: React.ReactNode; rightComponent?: React.ReactNode;
className?: string; className?: string;
@ -34,14 +40,18 @@ const StyledContainer = styled.div<Partial<OwnProps>>`
? theme.background.transparent.light ? theme.background.transparent.light
: 'transparent'}; : 'transparent'};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme, disabled }) => color: ${({ theme, disabled, accent }) =>
disabled ? theme.font.color.light : theme.font.color.primary}; disabled
? theme.font.color.light
: accent === ChipAccent.TextPrimary
? theme.font.color.primary
: theme.font.color.secondary};
cursor: ${({ clickable, disabled, variant }) => cursor: ${({ clickable, disabled, variant }) =>
disabled || variant === ChipVariant.Transparent disabled || variant === ChipVariant.Transparent
? 'auto' ? 'inherit'
: clickable : clickable
? 'pointer' ? 'pointer'
: 'auto'}; : 'inherit'};
display: inline-flex; display: inline-flex;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
@ -98,6 +108,7 @@ export function Chip({
variant = ChipVariant.Regular, variant = ChipVariant.Regular,
leftComponent, leftComponent,
rightComponent, rightComponent,
accent = ChipAccent.TextPrimary,
maxWidth, maxWidth,
className, className,
}: OwnProps) { }: OwnProps) {
@ -106,9 +117,11 @@ export function Chip({
data-testid="chip" data-testid="chip"
clickable={clickable} clickable={clickable}
variant={variant} variant={variant}
accent={accent}
size={size} size={size}
disabled={disabled} disabled={disabled}
className={className} className={className}
maxWidth={maxWidth}
> >
{leftComponent} {leftComponent}
<StyledLabel> <StyledLabel>

View File

@ -12,14 +12,21 @@ type OwnProps = {
name: string; name: string;
pictureUrl?: string; pictureUrl?: string;
avatarType?: AvatarType; avatarType?: AvatarType;
variant?: EntityChipVariant;
}; };
export enum EntityChipVariant {
Regular = 'regular',
Transparent = 'transparent',
}
export function EntityChip({ export function EntityChip({
linkToEntity, linkToEntity,
entityId, entityId,
name, name,
pictureUrl, pictureUrl,
avatarType = 'rounded', avatarType = 'rounded',
variant = EntityChipVariant.Regular,
}: OwnProps) { }: OwnProps) {
const navigate = useNavigate(); const navigate = useNavigate();
@ -35,7 +42,13 @@ export function EntityChip({
<div onClick={handleLinkClick}> <div onClick={handleLinkClick}>
<Chip <Chip
label={name} label={name}
variant={linkToEntity ? ChipVariant.Highlighted : ChipVariant.Regular} variant={
linkToEntity
? variant === EntityChipVariant.Regular
? ChipVariant.Highlighted
: ChipVariant.Regular
: ChipVariant.Transparent
}
leftComponent={ leftComponent={
<Avatar <Avatar
avatarUrl={pictureUrl} avatarUrl={pictureUrl}

View File

@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ExhaustiveComponentDecorator } from '~/testing/decorators/ExhaustiveComponentDecorator'; import { ExhaustiveComponentDecorator } from '~/testing/decorators/ExhaustiveComponentDecorator';
import { Chip, ChipSize, ChipVariant } from '../Chip'; import { Chip, ChipAccent, ChipSize, ChipVariant } from '../Chip';
const meta: Meta<typeof Chip> = { const meta: Meta<typeof Chip> = {
title: 'UI/Chip/Chip', title: 'UI/Chip/Chip',
@ -18,13 +18,15 @@ export const Default: Story = {
label: 'Chip test', label: 'Chip test',
size: ChipSize.Small, size: ChipSize.Small,
variant: ChipVariant.Highlighted, variant: ChipVariant.Highlighted,
accent: ChipAccent.TextPrimary,
disabled: false,
clickable: true, clickable: true,
maxWidth: '200px', maxWidth: '200px',
}, },
decorators: [ComponentDecorator], decorators: [ComponentDecorator],
}; };
export const All: Story = { export const Catalog: Story = {
args: { size: ChipSize.Large, clickable: true, label: 'Hello' }, args: { size: ChipSize.Large, clickable: true, label: 'Hello' },
argTypes: { argTypes: {
size: { control: false }, size: { control: false },
@ -42,6 +44,7 @@ export const All: Story = {
ChipVariant.Transparent, ChipVariant.Transparent,
], ],
sizes: [ChipSize.Small, ChipSize.Large], sizes: [ChipSize.Small, ChipSize.Large],
accents: [ChipAccent.TextPrimary, ChipAccent.TextSecondary],
states: ['default', 'hover', 'active', 'disabled'], states: ['default', 'hover', 'active', 'disabled'],
}, },
decorators: [ExhaustiveComponentDecorator], decorators: [ExhaustiveComponentDecorator],

View File

@ -1,20 +1,29 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useIsMobile } from '../../hooks/useIsMobile';
import { TopBar } from '../top-bar/components/TopBar';
import { RightDrawerContainer } from './RightDrawerContainer'; import { RightDrawerContainer } from './RightDrawerContainer';
type OwnProps = { type OwnProps = {
children: JSX.Element | JSX.Element[]; children: JSX.Element | JSX.Element[];
title: string;
icon: React.ReactNode;
}; };
const StyledContainer = styled.div` const StyledContainer = styled.div<{ isMobile: boolean }>`
display: flex; display: flex;
padding-top: ${({ theme }) => theme.spacing(4)}; flex-direction: column;
padding-top: ${({ theme, isMobile }) => (!isMobile ? theme.spacing(4) : 0)};
width: 100%; width: 100%;
`; `;
export function SubMenuTopBarContainer({ children }: OwnProps) { export function SubMenuTopBarContainer({ children, title, icon }: OwnProps) {
const isMobile = useIsMobile();
return ( return (
<StyledContainer> <StyledContainer isMobile={isMobile}>
{isMobile && <TopBar title={title} icon={icon} />}
<RightDrawerContainer topMargin={16}>{children}</RightDrawerContainer> <RightDrawerContainer topMargin={16}>{children}</RightDrawerContainer>
</StyledContainer> </StyledContainer>
); );

View File

@ -74,11 +74,8 @@ export function TopBar({
const navigate = useNavigate(); const navigate = useNavigate();
const navigateBack = useCallback(() => navigate(-1), [navigate]); const navigateBack = useCallback(() => navigate(-1), [navigate]);
const isMobile = useIsMobile();
const isNavbarOpened = useRecoilValue(isNavbarOpenedState); const isNavbarOpened = useRecoilValue(isNavbarOpenedState);
const showNavCollapseButton = isMobile || !isNavbarOpened;
const iconSize = useIsMobile() const iconSize = useIsMobile()
? navbarIconSize.mobile ? navbarIconSize.mobile
: navbarIconSize.desktop; : navbarIconSize.desktop;
@ -87,7 +84,7 @@ export function TopBar({
<> <>
<TopBarContainer> <TopBarContainer>
<StyledLeftContainer> <StyledLeftContainer>
{showNavCollapseButton && ( {!isNavbarOpened && (
<TopBarButtonContainer> <TopBarButtonContainer>
<NavCollapseButton direction="right" /> <NavCollapseButton direction="right" />
</TopBarButtonContainer> </TopBarButtonContainer>

View File

@ -11,6 +11,8 @@ type OwnProps = {
const StyledClickable = styled.div` const StyledClickable = styled.div`
display: flex; display: flex;
overflow: hidden;
white-space: nowrap;
a { a {
color: inherit; color: inherit;

View File

@ -15,8 +15,8 @@ type OwnProps = {
const StyledContainer = styled.div` const StyledContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: ${({ theme }) => theme.spacing(2)}; padding-top: ${({ theme }) => theme.spacing(9)};
width: ${({ theme }) => (useIsMobile() ? '100%' : leftNavbarWidth.desktop)}; width: ${() => (useIsMobile() ? '100%' : leftNavbarWidth.desktop)};
`; `;
export default function SubMenuNavbar({ children, backButtonTitle }: OwnProps) { export default function SubMenuNavbar({ children, backButtonTitle }: OwnProps) {

View File

@ -82,8 +82,6 @@ export function RightDrawer() {
: theme.rightDrawerWidth : theme.rightDrawerWidth
: '0'; : '0';
console.log(rightDrawerWidth);
if (!isDefined(rightDrawerPage)) { if (!isDefined(rightDrawerPage)) {
return <></>; return <></>;
} }

View File

@ -0,0 +1,19 @@
import { EntityChip, EntityChipVariant } from '@/ui/chip/components/EntityChip';
export type UserChipPropsType = {
id: string;
name: string;
pictureUrl?: string;
variant?: EntityChipVariant;
};
export function UserChip({ id, name, pictureUrl, variant }: UserChipPropsType) {
return (
<EntityChip
entityId={id}
name={name}
avatarType="rounded"
pictureUrl={pictureUrl}
/>
);
}

View File

@ -24,7 +24,10 @@ export function People() {
async function handleAddButtonClick() { async function handleAddButtonClick() {
await insertOnePerson({ await insertOnePerson({
variables: { variables: {
data: {}, data: {
firstName: '',
lastName: '',
},
}, },
refetchQueries: [getOperationName(GET_PEOPLE) ?? ''], refetchQueries: [getOperationName(GET_PEOPLE) ?? ''],
}); });

View File

@ -1,6 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { ColorSchemePicker } from '@/ui/color-scheme/components/ColorSchemePicker'; import { ColorSchemePicker } from '@/ui/color-scheme/components/ColorSchemePicker';
import { IconSettings } from '@/ui/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
import { useColorScheme } from '@/ui/themes/hooks/useColorScheme'; import { useColorScheme } from '@/ui/themes/hooks/useColorScheme';
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle'; import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
@ -27,7 +28,7 @@ export function SettingsExperience() {
const { colorScheme, setColorScheme } = useColorScheme(); const { colorScheme, setColorScheme } = useColorScheme();
return ( return (
<SubMenuTopBarContainer> <SubMenuTopBarContainer icon={<IconSettings size={16} />} title="Settings">
<div> <div>
<StyledContainer> <StyledContainer>
<MainSectionTitle>Experience</MainSectionTitle> <MainSectionTitle>Experience</MainSectionTitle>

View File

@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import { EmailField } from '@/settings/profile/components/EmailField'; import { EmailField } from '@/settings/profile/components/EmailField';
import { NameFields } from '@/settings/profile/components/NameFields'; import { NameFields } from '@/settings/profile/components/NameFields';
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader'; import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
import { IconSettings } from '@/ui/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle'; import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle'; import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
@ -26,8 +27,8 @@ const StyledSectionContainer = styled.div`
export function SettingsProfile() { export function SettingsProfile() {
return ( return (
<SubMenuTopBarContainer> <SubMenuTopBarContainer icon={<IconSettings size={16} />} title="Settings">
<div> <>
<StyledContainer> <StyledContainer>
<MainSectionTitle>Profile</MainSectionTitle> <MainSectionTitle>Profile</MainSectionTitle>
<StyledSectionContainer> <StyledSectionContainer>
@ -49,7 +50,7 @@ export function SettingsProfile() {
<EmailField /> <EmailField />
</StyledSectionContainer> </StyledSectionContainer>
</StyledContainer> </StyledContainer>
</div> </>
</SubMenuTopBarContainer> </SubMenuTopBarContainer>
); );
} }

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { NameField } from '@/settings/workspace/components/NameField'; import { NameField } from '@/settings/workspace/components/NameField';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { IconSettings } from '@/ui/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle'; import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle'; import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
@ -24,7 +25,7 @@ const StyledSectionContainer = styled.div`
export function SettingsWorksapce() { export function SettingsWorksapce() {
return ( return (
<SubMenuTopBarContainer> <SubMenuTopBarContainer icon={<IconSettings size={16} />} title="Settings">
<div> <div>
<StyledContainer> <StyledContainer>
<MainSectionTitle>General</MainSectionTitle> <MainSectionTitle>General</MainSectionTitle>

View File

@ -4,7 +4,7 @@ import { useRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { Button } from '@/ui/button/components/Button'; import { Button } from '@/ui/button/components/Button';
import { IconTrash } from '@/ui/icon'; import { IconSettings, IconTrash } from '@/ui/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/components/SubMenuTopBarContainer';
import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle'; import { MainSectionTitle } from '@/ui/title/components/MainSectionTitle';
import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle'; import { SubSectionTitle } from '@/ui/title/components/SubSectionTitle';
@ -75,7 +75,7 @@ export function SettingsWorkspaceMembers() {
}; };
return ( return (
<SubMenuTopBarContainer> <SubMenuTopBarContainer icon={<IconSettings size={16} />} title="Settings">
<StyledContainer> <StyledContainer>
<MainSectionTitle>Members</MainSectionTitle> <MainSectionTitle>Members</MainSectionTitle>
{workspace?.inviteHash && ( {workspace?.inviteHash && (

View File

@ -1,5 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Decorator, StrictArgs } from '@storybook/react'; import { Decorator } from '@storybook/react';
function stateProps(state: string) { function stateProps(state: string) {
switch (state) { switch (state) {
@ -22,11 +22,20 @@ const StyledSizeTitle = styled.h1`
margin: ${({ theme }) => theme.spacing(2)}; margin: ${({ theme }) => theme.spacing(2)};
`; `;
const StyledVariantTitle = styled.h1` const StyledVariantTitle = styled.h2`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: ${({ theme }) => theme.spacing(2)};
width: 100px;
`;
const StyledAccentTitle = styled.h3`
color: ${({ theme }) => theme.font.color.tertiary}; color: ${({ theme }) => theme.font.color.tertiary};
font-size: ${({ theme }) => theme.font.size.md}; font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.semiBold}; font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin: ${({ theme }) => theme.spacing(2)}; margin: ${({ theme }) => theme.spacing(2)};
width: 100px;
`; `;
const StyledStateTitle = styled.span` const StyledStateTitle = styled.span`
@ -49,62 +58,58 @@ const StyledSizeContainer = styled.div`
padding: ${({ theme }) => theme.spacing(2)}; padding: ${({ theme }) => theme.spacing(2)};
`; `;
const StyledLineContainer = styled.div` const StyledVariantContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledAccentContainer = styled.div`
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: row; flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
`; `;
const StyledComponentContainer = styled.div` const StyledStateContainer = styled.div`
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: ${({ theme }) => theme.spacing(2)}; padding: ${({ theme }) => theme.spacing(2)};
`; `;
function renderSize(
size: string,
variants: string[],
states: string[],
args: StrictArgs,
Story: React.FC<StrictArgs>,
) {
return (
<StyledSizeContainer key={size}>
<StyledSizeTitle>{size}</StyledSizeTitle>
{variants.map((variant) => (
<div key={variant}>
<StyledVariantTitle>{variant}</StyledVariantTitle>
<StyledLineContainer>
{states.map((state) => (
<StyledComponentContainer key={`${variant}-container-${state}`}>
<StyledStateTitle>{state}</StyledStateTitle>
<Story
args={{ ...args, variant: variant, ...stateProps(state) }}
/>
</StyledComponentContainer>
))}
</StyledLineContainer>
</div>
))}
</StyledSizeContainer>
);
}
export const ExhaustiveComponentDecorator: Decorator = (Story, context) => { export const ExhaustiveComponentDecorator: Decorator = (Story, context) => {
const parameters = context.parameters; const parameters = context.parameters;
return ( return (
<StyledContainer> <StyledContainer>
{parameters.sizes.map((size: string) => {parameters.sizes.map((size: string) => (
renderSize( <StyledSizeContainer key={size}>
size, <StyledSizeTitle>{size}</StyledSizeTitle>
parameters.variants, {parameters.variants.map((variant: string) => (
parameters.states, <StyledVariantContainer key={variant}>
context.args, <StyledVariantTitle>{variant}</StyledVariantTitle>
Story, {parameters.accents.map((accent: string) => (
), <StyledAccentContainer key={accent}>
)} <StyledAccentTitle>{accent}</StyledAccentTitle>
{parameters.states.map((state: string) => (
<StyledStateContainer key={state}>
<StyledStateTitle>{state}</StyledStateTitle>
<Story
args={{
...context.args,
accent: accent,
variant: variant,
...stateProps(state),
}}
/>
</StyledStateContainer>
))}
</StyledAccentContainer>
))}
</StyledVariantContainer>
))}
</StyledSizeContainer>
))}
</StyledContainer> </StyledContainer>
); );
}; };