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:
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 ?? ''}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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],
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -82,8 +82,6 @@ export function RightDrawer() {
|
|||||||
: theme.rightDrawerWidth
|
: theme.rightDrawerWidth
|
||||||
: '0';
|
: '0';
|
||||||
|
|
||||||
console.log(rightDrawerWidth);
|
|
||||||
|
|
||||||
if (!isDefined(rightDrawerPage)) {
|
if (!isDefined(rightDrawerPage)) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
19
front/src/modules/users/components/UserChip.tsx
Normal file
19
front/src/modules/users/components/UserChip.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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) ?? ''],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 && (
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user