Refactor buttons (#1257)
* Refactor buttons * Complete components creation * Complete refactoring * fix lint * Complete button work
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon/index';
|
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon/index';
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ export function ActivityCreateButton({
|
|||||||
}: ActivityCreateButtonProps) {
|
}: ActivityCreateButtonProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<ButtonGroup variant={ButtonVariant.Secondary}>
|
<ButtonGroup variant={'secondary'}>
|
||||||
<Button
|
<Button
|
||||||
icon={<IconNotes size={theme.icon.size.sm} />}
|
icon={<IconNotes size={theme.icon.size.sm} />}
|
||||||
title="Note"
|
title="Note"
|
||||||
|
|||||||
@ -6,11 +6,7 @@ import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateAct
|
|||||||
import { NoteList } from '@/activities/notes/components/NoteList';
|
import { NoteList } from '@/activities/notes/components/NoteList';
|
||||||
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
import { useNotes } from '@/activities/notes/hooks/useNotes';
|
||||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import {
|
import { Button } from '@/ui/button/components/Button';
|
||||||
Button,
|
|
||||||
ButtonSize,
|
|
||||||
ButtonVariant,
|
|
||||||
} from '@/ui/button/components/Button';
|
|
||||||
import { ActivityType } from '~/generated/graphql';
|
import { ActivityType } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledTaskGroupEmptyContainer = styled.div`
|
const StyledTaskGroupEmptyContainer = styled.div`
|
||||||
@ -64,7 +60,7 @@ export function Notes({ entity }: { entity: ActivityTargetableEntity }) {
|
|||||||
<Button
|
<Button
|
||||||
icon={<IconNotes size={theme.icon.size.sm} />}
|
icon={<IconNotes size={theme.icon.size.sm} />}
|
||||||
title="New note"
|
title="New note"
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
onClick={() => openCreateActivity(ActivityType.Note, [entity])}
|
onClick={() => openCreateActivity(ActivityType.Note, [entity])}
|
||||||
/>
|
/>
|
||||||
</StyledTaskGroupEmptyContainer>
|
</StyledTaskGroupEmptyContainer>
|
||||||
@ -79,8 +75,8 @@ export function Notes({ entity }: { entity: ActivityTargetableEntity }) {
|
|||||||
button={
|
button={
|
||||||
<Button
|
<Button
|
||||||
icon={<IconNotes size={theme.icon.size.md} />}
|
icon={<IconNotes size={theme.icon.size.md} />}
|
||||||
size={ButtonSize.Small}
|
size="small"
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
title="Add note"
|
title="Add note"
|
||||||
onClick={() => openCreateActivity(ActivityType.Note, [entity])}
|
onClick={() => openCreateActivity(ActivityType.Note, [entity])}
|
||||||
></Button>
|
></Button>
|
||||||
|
|||||||
@ -1,28 +1,20 @@
|
|||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { GET_ACTIVITIES } from '@/activities/graphql/queries/getActivities';
|
import { GET_ACTIVITIES } from '@/activities/graphql/queries/getActivities';
|
||||||
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/graphql/queries/getActivitiesByTarget';
|
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/graphql/queries/getActivitiesByTarget';
|
||||||
import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies';
|
import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies';
|
||||||
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
|
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
||||||
import { IconTrash } from '@/ui/icon';
|
import { IconTrash } from '@/ui/icon';
|
||||||
import { isRightDrawerOpenState } from '@/ui/right-drawer/states/isRightDrawerOpenState';
|
import { isRightDrawerOpenState } from '@/ui/right-drawer/states/isRightDrawerOpenState';
|
||||||
import { useDeleteActivityMutation } from '~/generated/graphql';
|
import { useDeleteActivityMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
activityId: string;
|
activityId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ActivityActionBar({ activityId }: OwnProps) {
|
export function ActivityActionBar({ activityId }: OwnProps) {
|
||||||
const theme = useTheme();
|
|
||||||
const [deleteActivityMutation] = useDeleteActivityMutation();
|
const [deleteActivityMutation] = useDeleteActivityMutation();
|
||||||
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||||
|
|
||||||
@ -40,13 +32,11 @@ export function ActivityActionBar({ activityId }: OwnProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<LightIconButton
|
||||||
<IconButton
|
icon={<IconTrash />}
|
||||||
icon={
|
onClick={deleteActivity}
|
||||||
<IconTrash size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
|
accent="tertiary"
|
||||||
}
|
size="medium"
|
||||||
onClick={deleteActivity}
|
/>
|
||||||
/>
|
|
||||||
</StyledContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,7 @@ import { useTheme } from '@emotion/react';
|
|||||||
|
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import {
|
import { Button } from '@/ui/button/components/Button';
|
||||||
Button,
|
|
||||||
ButtonSize,
|
|
||||||
ButtonVariant,
|
|
||||||
} from '@/ui/button/components/Button';
|
|
||||||
import { IconPlus } from '@/ui/icon';
|
import { IconPlus } from '@/ui/icon';
|
||||||
import { ActivityType } from '~/generated/graphql';
|
import { ActivityType } from '~/generated/graphql';
|
||||||
|
|
||||||
@ -25,8 +21,8 @@ export function AddTaskButton({
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
icon={<IconPlus size={theme.icon.size.md} />}
|
icon={<IconPlus size={theme.icon.size.md} />}
|
||||||
size={ButtonSize.Small}
|
size="small"
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
title="Add task"
|
title="Add task"
|
||||||
onClick={() => openCreateActivity(ActivityType.Task, [entity])}
|
onClick={() => openCreateActivity(ActivityType.Task, [entity])}
|
||||||
></Button>
|
></Button>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { IconCheckbox } from '@tabler/icons-react';
|
|||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
||||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { ActivityType } from '~/generated/graphql';
|
import { ActivityType } from '~/generated/graphql';
|
||||||
|
|
||||||
import { AddTaskButton } from './AddTaskButton';
|
import { AddTaskButton } from './AddTaskButton';
|
||||||
@ -69,7 +69,7 @@ export function TaskGroups({ entity, showAddButton }: OwnProps) {
|
|||||||
<Button
|
<Button
|
||||||
icon={<IconCheckbox size={theme.icon.size.sm} />}
|
icon={<IconCheckbox size={theme.icon.size.sm} />}
|
||||||
title="New task"
|
title="New task"
|
||||||
variant={ButtonVariant.Secondary}
|
variant={'secondary'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openCreateActivity(ActivityType.Task, entity ? [entity] : undefined)
|
openCreateActivity(ActivityType.Task, entity ? [entity] : undefined)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,7 @@ import {
|
|||||||
PersonForSelect,
|
PersonForSelect,
|
||||||
} from '@/people/components/PeoplePicker';
|
} from '@/people/components/PeoplePicker';
|
||||||
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
|
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
|
||||||
import { ButtonSize } from '@/ui/button/components/Button';
|
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import { TextInput } from '@/ui/input/text/components/TextInput';
|
import { TextInput } from '@/ui/input/text/components/TextInput';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
@ -123,11 +122,11 @@ export function AddPersonToCompany({
|
|||||||
<RecoilScope>
|
<RecoilScope>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<div ref={refs.setReference}>
|
<div ref={refs.setReference}>
|
||||||
<IconButton
|
<LightIconButton
|
||||||
icon={<IconPlus size={14} />}
|
icon={<IconPlus />}
|
||||||
onClick={handleOpenPicker}
|
onClick={handleOpenPicker}
|
||||||
size={ButtonSize.Small}
|
size="small"
|
||||||
variant={'transparent'}
|
accent="tertiary"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -16,11 +16,11 @@ export function CompanyTableMockData() {
|
|||||||
);
|
);
|
||||||
const setEntityTableData = useSetEntityTableData();
|
const setEntityTableData = useSetEntityTableData();
|
||||||
|
|
||||||
setEntityTableData(mockedCompaniesData, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setEntityTableData(mockedCompaniesData, []);
|
||||||
|
|
||||||
setColumns(companyViewFields);
|
setColumns(companyViewFields);
|
||||||
}, [setColumns]);
|
}, [setColumns, setEntityTableData]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import styled from '@emotion/styled';
|
|||||||
import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react';
|
import { autoUpdate, flip, offset, useFloating } from '@floating-ui/react';
|
||||||
import { IconDotsVertical, IconLinkOff, IconTrash } from '@tabler/icons-react';
|
import { IconDotsVertical, IconLinkOff, IconTrash } from '@tabler/icons-react';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
@ -165,11 +165,10 @@ export function PeopleCard({
|
|||||||
</StyledCardInfo>
|
</StyledCardInfo>
|
||||||
{isHovered && (
|
{isHovered && (
|
||||||
<div ref={refs.setReference}>
|
<div ref={refs.setReference}>
|
||||||
<IconButton
|
<FloatingIconButton
|
||||||
onClick={handleToggleOptions}
|
onClick={handleToggleOptions}
|
||||||
variant="shadow"
|
|
||||||
size="small"
|
size="small"
|
||||||
icon={<IconDotsVertical size={theme.icon.size.md} />}
|
icon={<IconDotsVertical />}
|
||||||
/>
|
/>
|
||||||
{isOptionsOpen && (
|
{isOptionsOpen && (
|
||||||
<StyledDropdownMenu ref={refs.setFloating} style={floatingStyles}>
|
<StyledDropdownMenu ref={refs.setFloating} style={floatingStyles}>
|
||||||
@ -177,15 +176,11 @@ export function PeopleCard({
|
|||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<DropdownMenuSelectableItem onClick={handleDetachPerson}>
|
<DropdownMenuSelectableItem onClick={handleDetachPerson}>
|
||||||
<IconButton icon={<IconLinkOff size={14} />} size="small" />
|
<IconLinkOff size={14} />
|
||||||
Detach relation
|
Detach relation
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
<DropdownMenuSelectableItem onClick={handleDeletePerson}>
|
<DropdownMenuSelectableItem onClick={handleDeletePerson}>
|
||||||
<IconButton
|
<IconTrash size={14} color={theme.font.color.danger} />
|
||||||
icon={<IconTrash size={14} />}
|
|
||||||
size="small"
|
|
||||||
textColor="danger"
|
|
||||||
/>
|
|
||||||
<StyledRemoveOption>Delete person</StyledRemoveOption>
|
<StyledRemoveOption>Delete person</StyledRemoveOption>
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
</StyledDropdownMenuItemsContainer>
|
</StyledDropdownMenuItemsContainer>
|
||||||
|
|||||||
@ -57,10 +57,10 @@ export function PipelineAddButton() {
|
|||||||
buttonComponents={
|
buttonComponents={
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconPlus size={16} />}
|
icon={<IconPlus size={16} />}
|
||||||
size="large"
|
size="medium"
|
||||||
data-testid="add-company-progress-button"
|
dataTestId="add-company-progress-button"
|
||||||
textColor={'secondary'}
|
accent="default"
|
||||||
variant="border"
|
variant="secondary"
|
||||||
onClick={toggleDropdownButton}
|
onClick={toggleDropdownButton}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,8 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { useAuth } from '@/auth/hooks/useAuth';
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import {
|
import { ConfirmationModal } from '@/ui/modal/components/ConfirmationModal';
|
||||||
ConfirmationModal,
|
|
||||||
StyledConfirmationButton,
|
|
||||||
} from '@/ui/modal/components/ConfirmationModal';
|
|
||||||
import { H2Title } from '@/ui/typography/components/H2Title';
|
import { H2Title } from '@/ui/typography/components/H2Title';
|
||||||
import { useDeleteUserAccountMutation } from '~/generated/graphql';
|
import { useDeleteUserAccountMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
@ -40,9 +37,11 @@ export function DeleteAccount() {
|
|||||||
description="Delete account and all the associated data"
|
description="Delete account and all the associated data"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StyledConfirmationButton
|
<Button
|
||||||
|
accent="danger"
|
||||||
|
size="small"
|
||||||
onClick={() => setIsDeleteAccountModalOpen(true)}
|
onClick={() => setIsDeleteAccountModalOpen(true)}
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
title="Delete account"
|
title="Delete account"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { useRecoilValue } from 'recoil';
|
|||||||
import { useAuth } from '@/auth/hooks/useAuth';
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ButtonVariant } from '@/ui/button/components/Button';
|
|
||||||
import {
|
import {
|
||||||
ConfirmationModal,
|
ConfirmationModal,
|
||||||
StyledConfirmationButton,
|
StyledConfirmationButton,
|
||||||
@ -38,7 +37,7 @@ export function DeleteWorkspace() {
|
|||||||
<H2Title title="Danger zone" description="Delete your whole workspace" />
|
<H2Title title="Danger zone" description="Delete your whole workspace" />
|
||||||
<StyledConfirmationButton
|
<StyledConfirmationButton
|
||||||
onClick={() => setIsDeleteWorkSpaceModalOpen(true)}
|
onClick={() => setIsDeleteWorkSpaceModalOpen(true)}
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
title="Delete workspace"
|
title="Delete workspace"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
|
import { useSpreadsheetImportInitialStep } from '@/spreadsheet-import/hooks/useSpreadsheetImportInitialStep';
|
||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
import { ButtonVariant } from '@/ui/button/components/Button';
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { IconButton } from '@/ui/button/components/IconButton';
|
||||||
import { useDialog } from '@/ui/dialog/hooks/useDialog';
|
import { useDialog } from '@/ui/dialog/hooks/useDialog';
|
||||||
import { IconX } from '@/ui/icon/index';
|
import { IconX } from '@/ui/icon/index';
|
||||||
@ -25,8 +23,6 @@ type ModalCloseButtonProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
|
export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const { initialStepState } = useSpreadsheetImportInternal();
|
const { initialStepState } = useSpreadsheetImportInternal();
|
||||||
|
|
||||||
const { initialStep } = useSpreadsheetImportInitialStep(
|
const { initialStep } = useSpreadsheetImportInitialStep(
|
||||||
@ -49,19 +45,14 @@ export const ModalCloseButton = ({ onClose }: ModalCloseButtonProps) => {
|
|||||||
message: 'Are you sure? Your current information will not be saved.',
|
message: 'Are you sure? Your current information will not be saved.',
|
||||||
buttons: [
|
buttons: [
|
||||||
{ title: 'Cancel' },
|
{ title: 'Cancel' },
|
||||||
{ title: 'Exit', onClick: onClose, variant: ButtonVariant.Danger },
|
{ title: 'Exit', onClick: onClose, accent: 'danger' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StyledCloseButtonContainer>
|
||||||
<StyledCloseButtonContainer>
|
<IconButton icon={<IconX />} onClick={handleClose} />
|
||||||
<IconButton
|
</StyledCloseButtonContainer>
|
||||||
icon={<IconX size={16} color={theme.font.color.tertiary} />}
|
|
||||||
onClick={handleClose}
|
|
||||||
/>
|
|
||||||
</StyledCloseButtonContainer>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableDat
|
|||||||
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
|
import { setColumn } from '@/spreadsheet-import/utils/setColumn';
|
||||||
import { setIgnoreColumn } from '@/spreadsheet-import/utils/setIgnoreColumn';
|
import { setIgnoreColumn } from '@/spreadsheet-import/utils/setIgnoreColumn';
|
||||||
import { setSubColumn } from '@/spreadsheet-import/utils/setSubColumn';
|
import { setSubColumn } from '@/spreadsheet-import/utils/setSubColumn';
|
||||||
import { ButtonVariant } from '@/ui/button/components/Button';
|
|
||||||
import { useDialog } from '@/ui/dialog/hooks/useDialog';
|
import { useDialog } from '@/ui/dialog/hooks/useDialog';
|
||||||
import { Modal } from '@/ui/modal/components/Modal';
|
import { Modal } from '@/ui/modal/components/Modal';
|
||||||
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
||||||
@ -224,7 +223,7 @@ export const MatchColumnsStep = <T extends string>({
|
|||||||
{
|
{
|
||||||
title: 'Continue',
|
title: 'Continue',
|
||||||
onClick: handleAlertOnContinue,
|
onClick: handleAlertOnContinue,
|
||||||
variant: ButtonVariant.Primary,
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Table } from '@/spreadsheet-import/components/Table';
|
|||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
import type { Data } from '@/spreadsheet-import/types';
|
import type { Data } from '@/spreadsheet-import/types';
|
||||||
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
|
import { addErrorsAndRunHooks } from '@/spreadsheet-import/utils/dataMutations';
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { useDialog } from '@/ui/dialog/hooks/useDialog';
|
import { useDialog } from '@/ui/dialog/hooks/useDialog';
|
||||||
import { IconTrash } from '@/ui/icon';
|
import { IconTrash } from '@/ui/icon';
|
||||||
import { Toggle } from '@/ui/input/toggle/components/Toggle';
|
import { Toggle } from '@/ui/input/toggle/components/Toggle';
|
||||||
@ -173,7 +173,7 @@ export const ValidationStep = <T extends string>({
|
|||||||
{ title: 'Cancel' },
|
{ title: 'Cancel' },
|
||||||
{
|
{
|
||||||
title: 'Submit',
|
title: 'Submit',
|
||||||
variant: ButtonVariant.Primary,
|
variant: 'primary',
|
||||||
onClick: submitData,
|
onClick: submitData,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -201,7 +201,7 @@ export const ValidationStep = <T extends string>({
|
|||||||
<Button
|
<Button
|
||||||
icon={<IconTrash />}
|
icon={<IconTrash />}
|
||||||
title="Remove"
|
title="Remove"
|
||||||
variant={ButtonVariant.Danger}
|
accent="danger"
|
||||||
onClick={deleteSelectedRows}
|
onClick={deleteSelectedRows}
|
||||||
disabled={selectedRows.size === 0}
|
disabled={selectedRows.size === 0}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -3,79 +3,245 @@ import styled from '@emotion/styled';
|
|||||||
import { TablerIconsProps } from '@tabler/icons-react';
|
import { TablerIconsProps } from '@tabler/icons-react';
|
||||||
|
|
||||||
import { SoonPill } from '@/ui/pill/components/SoonPill';
|
import { SoonPill } from '@/ui/pill/components/SoonPill';
|
||||||
import { rgba } from '@/ui/theme/constants/colors';
|
|
||||||
|
|
||||||
export enum ButtonSize {
|
export type ButtonSize = 'medium' | 'small';
|
||||||
Medium = 'medium',
|
export type ButtonPosition = 'standalone' | 'left' | 'middle' | 'right';
|
||||||
Small = 'small',
|
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary';
|
||||||
}
|
export type ButtonAccent = 'default' | 'blue' | 'danger';
|
||||||
|
|
||||||
export enum ButtonPosition {
|
|
||||||
Left = 'left',
|
|
||||||
Middle = 'middle',
|
|
||||||
Right = 'right',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ButtonVariant {
|
|
||||||
Primary = 'primary',
|
|
||||||
Secondary = 'secondary',
|
|
||||||
Tertiary = 'tertiary',
|
|
||||||
TertiaryBold = 'tertiaryBold',
|
|
||||||
TertiaryLight = 'tertiaryLight',
|
|
||||||
Danger = 'danger',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ButtonProps = {
|
export type ButtonProps = {
|
||||||
|
className?: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
variant?: ButtonVariant;
|
variant?: ButtonVariant;
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
position?: ButtonPosition;
|
position?: ButtonPosition;
|
||||||
|
accent?: ButtonAccent;
|
||||||
soon?: boolean;
|
soon?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
} & React.ComponentProps<'button'>;
|
focus?: boolean;
|
||||||
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
const StyledButton = styled.button<
|
const StyledButton = styled.button<
|
||||||
Pick<ButtonProps, 'fullWidth' | 'variant' | 'size' | 'position' | 'title'>
|
Pick<
|
||||||
|
ButtonProps,
|
||||||
|
'fullWidth' | 'variant' | 'size' | 'position' | 'accent' | 'focus'
|
||||||
|
>
|
||||||
>`
|
>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ theme, variant, disabled }) => {
|
${({ theme, variant, accent, disabled, focus }) => {
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
case 'primary':
|
case 'primary':
|
||||||
if (disabled) {
|
switch (accent) {
|
||||||
return rgba(theme.color.blue, 0.4);
|
case 'default':
|
||||||
} else {
|
return `
|
||||||
return theme.color.blue;
|
background: ${theme.background.secondary};
|
||||||
|
border-color: ${
|
||||||
|
!disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
color: ${
|
||||||
|
!disabled
|
||||||
|
? theme.font.color.secondary
|
||||||
|
: theme.font.color.extraLight
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled
|
||||||
|
? theme.background.tertiary
|
||||||
|
: theme.background.secondary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled
|
||||||
|
? theme.background.quaternary
|
||||||
|
: theme.background.secondary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'blue':
|
||||||
|
return `
|
||||||
|
background: ${!disabled ? theme.color.blue : theme.color.blue20};
|
||||||
|
border-color: ${
|
||||||
|
!disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
color: ${theme.grayScale.gray0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.blue50 : theme.color.blue20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.blue60 : theme.color.blue20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'danger':
|
||||||
|
return `
|
||||||
|
background: ${!disabled ? theme.color.red : theme.color.red20};
|
||||||
|
border-color: ${
|
||||||
|
!disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.red
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus ? `0 0 0 3px ${theme.color.red10}` : 'none'
|
||||||
|
};
|
||||||
|
color: ${theme.grayScale.gray0};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.red50 : theme.color.red20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.red50 : theme.color.red20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
case 'secondary':
|
case 'secondary':
|
||||||
return theme.background.primary;
|
|
||||||
default:
|
|
||||||
return 'transparent';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
border-color: ${({ theme, variant }) => {
|
|
||||||
switch (variant) {
|
|
||||||
case 'primary':
|
|
||||||
case 'secondary':
|
|
||||||
return `${theme.background.transparent.medium}`;
|
|
||||||
case 'danger':
|
|
||||||
return `${theme.border.color.danger}`;
|
|
||||||
case 'tertiary':
|
case 'tertiary':
|
||||||
default:
|
switch (accent) {
|
||||||
return 'none';
|
case 'default':
|
||||||
|
return `
|
||||||
|
background: ${
|
||||||
|
focus ? theme.background.transparent.primary : 'transparent'
|
||||||
|
};
|
||||||
|
border-color: ${
|
||||||
|
variant === 'secondary'
|
||||||
|
? !disabled && focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: focus
|
||||||
|
? theme.color.blue
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
color: ${
|
||||||
|
!disabled
|
||||||
|
? theme.font.color.secondary
|
||||||
|
: theme.font.color.extraLight
|
||||||
|
};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.transparent.light : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.transparent.light : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'blue':
|
||||||
|
return `
|
||||||
|
background: ${
|
||||||
|
focus ? theme.background.transparent.primary : 'transparent'
|
||||||
|
};
|
||||||
|
border-color: ${
|
||||||
|
variant === 'secondary'
|
||||||
|
? focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.color.blue20
|
||||||
|
: focus
|
||||||
|
? theme.color.blue
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
color: ${!disabled ? theme.color.blue : theme.accent.accent4060};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.accent.tertiary : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.accent.secondary : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'danger':
|
||||||
|
return `
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.transparent.primary : 'transparent'
|
||||||
|
};
|
||||||
|
border-color: ${
|
||||||
|
variant === 'secondary'
|
||||||
|
? focus
|
||||||
|
? theme.color.red
|
||||||
|
: theme.color.red20
|
||||||
|
: focus
|
||||||
|
? theme.color.red
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus ? `0 0 0 3px ${theme.color.red10}` : 'none'
|
||||||
|
};
|
||||||
|
color: ${!disabled ? theme.font.color.danger : theme.color.red20};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.danger : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.danger : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}};
|
}}
|
||||||
border-radius: ${({ position }) => {
|
|
||||||
|
border-radius: ${({ position, theme }) => {
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 'left':
|
case 'left':
|
||||||
return '4px 0px 0px 4px';
|
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||||
case 'right':
|
case 'right':
|
||||||
return '0px 4px 4px 0px';
|
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||||
case 'middle':
|
case 'middle':
|
||||||
return '0px';
|
return '0px';
|
||||||
default:
|
case 'standalone':
|
||||||
return '4px';
|
return theme.border.radius.sm;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
@ -83,68 +249,20 @@ const StyledButton = styled.button<
|
|||||||
switch (variant) {
|
switch (variant) {
|
||||||
case 'primary':
|
case 'primary':
|
||||||
case 'secondary':
|
case 'secondary':
|
||||||
case 'danger':
|
return position === 'middle' ? '1px 0px' : '1px';
|
||||||
return position === 'middle' ? `1px 0 1px 0` : `1px`;
|
|
||||||
case 'tertiary':
|
case 'tertiary':
|
||||||
default:
|
|
||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
box-shadow: ${({ theme, variant }) => {
|
|
||||||
switch (variant) {
|
|
||||||
case 'primary':
|
|
||||||
case 'secondary':
|
|
||||||
return theme.boxShadow.extraLight;
|
|
||||||
default:
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
color: ${({ theme, variant, disabled }) => {
|
|
||||||
if (disabled) {
|
|
||||||
switch (variant) {
|
|
||||||
case 'primary':
|
|
||||||
return theme.grayScale.gray0;
|
|
||||||
case 'danger':
|
|
||||||
return theme.border.color.danger;
|
|
||||||
default:
|
|
||||||
return theme.font.color.extraLight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (variant) {
|
|
||||||
case 'primary':
|
|
||||||
return theme.grayScale.gray0;
|
|
||||||
case 'tertiaryLight':
|
|
||||||
return theme.font.color.tertiary;
|
|
||||||
case 'danger':
|
|
||||||
return theme.color.red;
|
|
||||||
default:
|
|
||||||
return theme.font.color.secondary;
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-family: ${({ theme }) => theme.font.family};
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
font-weight: ${({ theme, variant }) => {
|
font-weight: 500;
|
||||||
switch (variant) {
|
|
||||||
case 'tertiary':
|
|
||||||
case 'tertiaryLight':
|
|
||||||
return theme.font.weight.regular;
|
|
||||||
default:
|
|
||||||
return theme.font.weight.medium;
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
justify-content: flex-start;
|
padding: ${({ theme }) => {
|
||||||
padding: ${({ theme, title }) => {
|
return `0 ${theme.spacing(2)}`;
|
||||||
if (!title) {
|
|
||||||
return `${theme.spacing(1)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${theme.spacing(2)} ${theme.spacing(3)}`;
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
transition: background 0.1s ease;
|
transition: background 0.1s ease;
|
||||||
@ -153,49 +271,24 @@ const StyledButton = styled.button<
|
|||||||
|
|
||||||
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
|
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:active {
|
|
||||||
${({ theme, variant, disabled }) => {
|
|
||||||
if (disabled) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (variant) {
|
|
||||||
case 'primary':
|
|
||||||
return `background: linear-gradient(0deg, ${theme.background.transparent.medium} 0%, ${theme.background.transparent.medium} 100%), ${theme.color.blue}`;
|
|
||||||
case 'danger':
|
|
||||||
return `background: ${theme.background.transparent.danger}`;
|
|
||||||
default:
|
|
||||||
return `background: ${theme.background.tertiary}`;
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
${({ theme, variant }) => {
|
|
||||||
switch (variant) {
|
|
||||||
case 'tertiaryLight':
|
|
||||||
case 'tertiaryBold':
|
|
||||||
case 'tertiary':
|
|
||||||
return `color: ${theme.color.blue};`;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function Button({
|
export function Button({
|
||||||
|
className,
|
||||||
icon: initialIcon,
|
icon: initialIcon,
|
||||||
title,
|
title,
|
||||||
fullWidth = false,
|
fullWidth = false,
|
||||||
variant = ButtonVariant.Primary,
|
variant = 'primary',
|
||||||
size = ButtonSize.Medium,
|
size = 'medium',
|
||||||
position,
|
accent = 'default',
|
||||||
|
position = 'standalone',
|
||||||
soon = false,
|
soon = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
...props
|
focus = false,
|
||||||
|
onClick,
|
||||||
}: ButtonProps) {
|
}: ButtonProps) {
|
||||||
const icon = useMemo(() => {
|
const icon = useMemo(() => {
|
||||||
if (!initialIcon || !React.isValidElement(initialIcon)) {
|
if (!initialIcon || !React.isValidElement(initialIcon)) {
|
||||||
@ -214,8 +307,10 @@ export function Button({
|
|||||||
size={size}
|
size={size}
|
||||||
position={position}
|
position={position}
|
||||||
disabled={soon || disabled}
|
disabled={soon || disabled}
|
||||||
title={title}
|
focus={focus}
|
||||||
{...props}
|
accent={accent}
|
||||||
|
className={className}
|
||||||
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
{title}
|
{title}
|
||||||
|
|||||||
@ -8,11 +8,19 @@ const StyledButtonGroupContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type ButtonGroupProps = Pick<ButtonProps, 'variant' | 'size'> & {
|
export type ButtonGroupProps = Pick<
|
||||||
|
ButtonProps,
|
||||||
|
'variant' | 'size' | 'accent'
|
||||||
|
> & {
|
||||||
children: ReactNode[];
|
children: ReactNode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ButtonGroup({ children, variant, size }: ButtonGroupProps) {
|
export function ButtonGroup({
|
||||||
|
children,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
accent,
|
||||||
|
}: ButtonGroupProps) {
|
||||||
return (
|
return (
|
||||||
<StyledButtonGroupContainer>
|
<StyledButtonGroupContainer>
|
||||||
{React.Children.map(children, (child, index) => {
|
{React.Children.map(children, (child, index) => {
|
||||||
@ -21,19 +29,23 @@ export function ButtonGroup({ children, variant, size }: ButtonGroupProps) {
|
|||||||
let position: ButtonPosition;
|
let position: ButtonPosition;
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
position = ButtonPosition.Left;
|
position = 'left';
|
||||||
} else if (index === children.length - 1) {
|
} else if (index === children.length - 1) {
|
||||||
position = ButtonPosition.Right;
|
position = 'right';
|
||||||
} else {
|
} else {
|
||||||
position = ButtonPosition.Middle;
|
position = 'middle';
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalProps: any = { position };
|
const additionalProps: any = { position, variant, accent, size };
|
||||||
|
|
||||||
if (variant) {
|
if (variant) {
|
||||||
additionalProps.variant = variant;
|
additionalProps.variant = variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (accent) {
|
||||||
|
additionalProps.variant = variant;
|
||||||
|
}
|
||||||
|
|
||||||
if (size) {
|
if (size) {
|
||||||
additionalProps.size = size;
|
additionalProps.size = size;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 StyledDropdownContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledDropdownMenu = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function DropdownButton_Deprecated({
|
|
||||||
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 && (
|
|
||||||
<StyledDropdownContainer>
|
|
||||||
<StyledButton
|
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
|
||||||
{...buttonProps}
|
|
||||||
isOpen={isOpen}
|
|
||||||
>
|
|
||||||
{selectedOption.icon}
|
|
||||||
{selectedOption.label}
|
|
||||||
{options.length > 1 && <IconChevronDown />}
|
|
||||||
</StyledButton>
|
|
||||||
{isOpen && (
|
|
||||||
<StyledDropdownMenu>
|
|
||||||
{options
|
|
||||||
.filter((option) => option.label !== selectedOption.label)
|
|
||||||
.map((option, index) => (
|
|
||||||
<StyledDropdownItem
|
|
||||||
key={index}
|
|
||||||
onClick={handleSelect(option)}
|
|
||||||
>
|
|
||||||
{option.icon}
|
|
||||||
{option.label}
|
|
||||||
</StyledDropdownItem>
|
|
||||||
))}
|
|
||||||
</StyledDropdownMenu>
|
|
||||||
)}
|
|
||||||
</StyledDropdownContainer>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
113
front/src/modules/ui/button/components/FloatingButton.tsx
Normal file
113
front/src/modules/ui/button/components/FloatingButton.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { TablerIconsProps } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
export type FloatingButtonSize = 'small' | 'medium';
|
||||||
|
export type FloatingButtonPosition = 'standalone' | 'left' | 'middle' | 'right';
|
||||||
|
|
||||||
|
export type FloatingButtonProps = {
|
||||||
|
className?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
title?: string;
|
||||||
|
size?: FloatingButtonSize;
|
||||||
|
position?: FloatingButtonPosition;
|
||||||
|
applyShadow?: boolean;
|
||||||
|
applyBlur?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
focus?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledButton = styled.button<
|
||||||
|
Pick<
|
||||||
|
FloatingButtonProps,
|
||||||
|
'size' | 'focus' | 'position' | 'applyBlur' | 'applyShadow'
|
||||||
|
>
|
||||||
|
>`
|
||||||
|
align-items: center;
|
||||||
|
backdrop-filter: ${({ applyBlur }) => (applyBlur ? 'blur(20px)' : 'none')};
|
||||||
|
background: ${({ theme }) => theme.background.primary};
|
||||||
|
|
||||||
|
border: ${({ focus, theme }) =>
|
||||||
|
focus ? `1px solid ${theme.color.blue}` : 'none'};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
box-shadow: ${({ theme, applyShadow, focus }) =>
|
||||||
|
applyShadow
|
||||||
|
? `0px 2px 4px 0px ${
|
||||||
|
theme.background.transparent.light
|
||||||
|
}, 0px 0px 4px 0px ${theme.background.transparent.medium}${
|
||||||
|
focus ? `,0 0 0 3px ${theme.color.blue10}` : ''
|
||||||
|
}`
|
||||||
|
: focus
|
||||||
|
? `0 0 0 3px ${theme.color.blue10}`
|
||||||
|
: 'none'};
|
||||||
|
color: ${({ theme, disabled, focus }) => {
|
||||||
|
return !disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.font.color.secondary
|
||||||
|
: theme.font.color.extraLight;
|
||||||
|
}};
|
||||||
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: row;
|
||||||
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
|
padding: ${({ theme }) => {
|
||||||
|
return `0 ${theme.spacing(2)}`;
|
||||||
|
}};
|
||||||
|
transition: background 0.1s ease;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.lighter : 'transparent'};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function FloatingButton({
|
||||||
|
className,
|
||||||
|
icon: initialIcon,
|
||||||
|
title,
|
||||||
|
size = 'small',
|
||||||
|
applyBlur = true,
|
||||||
|
applyShadow = true,
|
||||||
|
disabled = false,
|
||||||
|
focus = false,
|
||||||
|
}: FloatingButtonProps) {
|
||||||
|
const icon = useMemo(() => {
|
||||||
|
if (!initialIcon || !React.isValidElement(initialIcon)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement<TablerIconsProps>(initialIcon as any, {
|
||||||
|
size: 14,
|
||||||
|
});
|
||||||
|
}, [initialIcon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
disabled={disabled}
|
||||||
|
focus={focus && !disabled}
|
||||||
|
size={size}
|
||||||
|
applyBlur={applyBlur}
|
||||||
|
applyShadow={applyShadow}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{title}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { FloatingButtonPosition, FloatingButtonProps } from './FloatingButton';
|
||||||
|
|
||||||
|
const StyledFloatingButtonGroupContainer = styled.div`
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
|
box-shadow: ${({ theme }) =>
|
||||||
|
`0px 2px 4px 0px ${theme.background.transparent.light}, 0px 0px 4px 0px ${theme.background.transparent.medium}`};
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type FloatingButtonGroupProps = Pick<FloatingButtonProps, 'size'> & {
|
||||||
|
children: React.ReactElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FloatingButtonGroup({
|
||||||
|
children,
|
||||||
|
size,
|
||||||
|
}: FloatingButtonGroupProps) {
|
||||||
|
return (
|
||||||
|
<StyledFloatingButtonGroupContainer>
|
||||||
|
{React.Children.map(children, (child, index) => {
|
||||||
|
let position: FloatingButtonPosition;
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
position = 'left';
|
||||||
|
} else if (index === children.length - 1) {
|
||||||
|
position = 'right';
|
||||||
|
} else {
|
||||||
|
position = 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalProps: any = {
|
||||||
|
position,
|
||||||
|
size,
|
||||||
|
applyShadow: false,
|
||||||
|
applyBlur: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
additionalProps.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement(child, additionalProps);
|
||||||
|
})}
|
||||||
|
</StyledFloatingButtonGroupContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
127
front/src/modules/ui/button/components/FloatingIconButton.tsx
Normal file
127
front/src/modules/ui/button/components/FloatingIconButton.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { TablerIconsProps } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
export type FloatingIconButtonSize = 'small' | 'medium';
|
||||||
|
export type FloatingIconButtonPosition =
|
||||||
|
| 'standalone'
|
||||||
|
| 'left'
|
||||||
|
| 'middle'
|
||||||
|
| 'right';
|
||||||
|
|
||||||
|
export type FloatingIconButtonProps = {
|
||||||
|
className?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
size?: FloatingIconButtonSize;
|
||||||
|
position?: FloatingIconButtonPosition;
|
||||||
|
applyShadow?: boolean;
|
||||||
|
applyBlur?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
focus?: boolean;
|
||||||
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledButton = styled.button<
|
||||||
|
Pick<
|
||||||
|
FloatingIconButtonProps,
|
||||||
|
'size' | 'position' | 'applyShadow' | 'applyBlur' | 'focus'
|
||||||
|
>
|
||||||
|
>`
|
||||||
|
align-items: center;
|
||||||
|
backdrop-filter: ${({ applyBlur }) => (applyBlur ? 'blur(20px)' : 'none')};
|
||||||
|
background: ${({ theme }) => theme.background.primary};
|
||||||
|
border: ${({ focus, theme }) =>
|
||||||
|
focus ? `1px solid ${theme.color.blue}` : 'transparent'};
|
||||||
|
border-radius: ${({ position, theme }) => {
|
||||||
|
switch (position) {
|
||||||
|
case 'left':
|
||||||
|
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||||
|
case 'right':
|
||||||
|
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||||
|
case 'middle':
|
||||||
|
return '0px';
|
||||||
|
case 'standalone':
|
||||||
|
return theme.border.radius.sm;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
box-shadow: ${({ theme, applyShadow, focus }) =>
|
||||||
|
applyShadow
|
||||||
|
? `0px 2px 4px ${theme.background.transparent.light}, 0px 0px 4px ${
|
||||||
|
theme.background.transparent.medium
|
||||||
|
}${focus ? `,0 0 0 3px ${theme.color.blue10}` : ''}`
|
||||||
|
: focus
|
||||||
|
? `0 0 0 3px ${theme.color.blue10}`
|
||||||
|
: 'none'};
|
||||||
|
color: ${({ theme, disabled, focus }) => {
|
||||||
|
return !disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.font.color.tertiary
|
||||||
|
: theme.font.color.extraLight;
|
||||||
|
}};
|
||||||
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: row;
|
||||||
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
transition: background 0.1s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.lighter : 'transparent'};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function FloatingIconButton({
|
||||||
|
className,
|
||||||
|
icon: initialIcon,
|
||||||
|
size = 'small',
|
||||||
|
position = 'standalone',
|
||||||
|
applyShadow = true,
|
||||||
|
applyBlur = true,
|
||||||
|
disabled = false,
|
||||||
|
focus = false,
|
||||||
|
onClick,
|
||||||
|
}: FloatingIconButtonProps) {
|
||||||
|
const icon = useMemo(() => {
|
||||||
|
if (!initialIcon || !React.isValidElement(initialIcon)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement<TablerIconsProps>(initialIcon as any, {
|
||||||
|
size: 16,
|
||||||
|
});
|
||||||
|
}, [initialIcon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
disabled={disabled}
|
||||||
|
focus={focus && !disabled}
|
||||||
|
size={size}
|
||||||
|
applyShadow={applyShadow}
|
||||||
|
applyBlur={applyBlur}
|
||||||
|
className={className}
|
||||||
|
position={position}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FloatingIconButtonPosition,
|
||||||
|
FloatingIconButtonProps,
|
||||||
|
} from './FloatingIconButton';
|
||||||
|
|
||||||
|
const StyledFloatingIconButtonGroupContainer = styled.div`
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
|
box-shadow: ${({ theme }) =>
|
||||||
|
`0px 2px 4px 0px ${theme.background.transparent.light}, 0px 0px 4px 0px ${theme.background.transparent.medium}`};
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type FloatingIconButtonGroupProps = Pick<
|
||||||
|
FloatingIconButtonProps,
|
||||||
|
'size'
|
||||||
|
> & {
|
||||||
|
children: React.ReactElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FloatingIconButtonGroup({
|
||||||
|
children,
|
||||||
|
size,
|
||||||
|
}: FloatingIconButtonGroupProps) {
|
||||||
|
return (
|
||||||
|
<StyledFloatingIconButtonGroupContainer>
|
||||||
|
{React.Children.map(children, (child, index) => {
|
||||||
|
let position: FloatingIconButtonPosition;
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
position = 'left';
|
||||||
|
} else if (index === children.length - 1) {
|
||||||
|
position = 'right';
|
||||||
|
} else {
|
||||||
|
position = 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalProps: any = {
|
||||||
|
position,
|
||||||
|
size,
|
||||||
|
applyShadow: false,
|
||||||
|
applyBlur: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
additionalProps.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement(child, additionalProps);
|
||||||
|
})}
|
||||||
|
</StyledFloatingIconButtonGroupContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,143 +1,307 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { TablerIconsProps } from '@tabler/icons-react';
|
||||||
|
|
||||||
export type IconButtonVariant = 'transparent' | 'border' | 'shadow' | 'white';
|
export type IconButtonSize = 'medium' | 'small';
|
||||||
|
export type IconButtonPosition = 'standalone' | 'left' | 'middle' | 'right';
|
||||||
|
export type IconButtonVariant = 'primary' | 'secondary' | 'tertiary';
|
||||||
|
export type IconButtonAccent = 'default' | 'blue' | 'danger';
|
||||||
|
|
||||||
export type IconButtonSize = 'large' | 'medium' | 'small';
|
export type IconButtonProps = {
|
||||||
|
className?: string;
|
||||||
export type IconButtonFontColor =
|
|
||||||
| 'primary'
|
|
||||||
| 'secondary'
|
|
||||||
| 'tertiary'
|
|
||||||
| 'danger';
|
|
||||||
|
|
||||||
export type ButtonProps = {
|
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
variant?: IconButtonVariant;
|
variant?: IconButtonVariant;
|
||||||
size?: IconButtonSize;
|
size?: IconButtonSize;
|
||||||
textColor?: IconButtonFontColor;
|
position?: IconButtonPosition;
|
||||||
} & React.ComponentProps<'button'>;
|
accent?: IconButtonAccent;
|
||||||
|
disabled?: boolean;
|
||||||
|
focus?: boolean;
|
||||||
|
dataTestId?: string;
|
||||||
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
const StyledIconButton = styled.button<
|
const StyledButton = styled.button<
|
||||||
Pick<ButtonProps, 'variant' | 'size' | 'textColor'>
|
Pick<IconButtonProps, 'variant' | 'size' | 'position' | 'accent' | 'focus'>
|
||||||
>`
|
>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ theme, variant }) => {
|
${({ theme, variant, accent, disabled, focus }) => {
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
case 'shadow':
|
case 'primary':
|
||||||
case 'white':
|
switch (accent) {
|
||||||
return theme.background.transparent.primary;
|
case 'default':
|
||||||
case 'transparent':
|
return `
|
||||||
case 'border':
|
background: ${theme.background.secondary};
|
||||||
default:
|
border-color: ${
|
||||||
return 'transparent';
|
!disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
color: ${
|
||||||
|
!disabled
|
||||||
|
? theme.font.color.secondary
|
||||||
|
: theme.font.color.extraLight
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled
|
||||||
|
? theme.background.tertiary
|
||||||
|
: theme.background.secondary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled
|
||||||
|
? theme.background.quaternary
|
||||||
|
: theme.background.secondary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'blue':
|
||||||
|
return `
|
||||||
|
background: ${!disabled ? theme.color.blue : theme.color.blue20};
|
||||||
|
border-color: ${
|
||||||
|
!disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
color: ${theme.grayScale.gray0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.blue50 : theme.color.blue20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.blue60 : theme.color.blue20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'danger':
|
||||||
|
return `
|
||||||
|
background: ${!disabled ? theme.color.red : theme.color.red20};
|
||||||
|
border-color: ${
|
||||||
|
!disabled
|
||||||
|
? focus
|
||||||
|
? theme.color.red
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus ? `0 0 0 3px ${theme.color.red10}` : 'none'
|
||||||
|
};
|
||||||
|
color: ${theme.grayScale.gray0};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.red50 : theme.color.red20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.color.red50 : theme.color.red20
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'secondary':
|
||||||
|
case 'tertiary':
|
||||||
|
switch (accent) {
|
||||||
|
case 'default':
|
||||||
|
return `
|
||||||
|
background: ${
|
||||||
|
focus ? theme.background.transparent.primary : 'transparent'
|
||||||
|
};
|
||||||
|
border-color: ${
|
||||||
|
variant === 'secondary'
|
||||||
|
? !disabled && focus
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.background.transparent.light
|
||||||
|
: focus
|
||||||
|
? theme.color.blue
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
color: ${
|
||||||
|
!disabled
|
||||||
|
? theme.font.color.secondary
|
||||||
|
: theme.font.color.extraLight
|
||||||
|
};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.transparent.light : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.transparent.light : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'blue':
|
||||||
|
return `
|
||||||
|
background: ${
|
||||||
|
focus ? theme.background.transparent.primary : 'transparent'
|
||||||
|
};
|
||||||
|
border-color: ${
|
||||||
|
variant === 'secondary'
|
||||||
|
? !disabled
|
||||||
|
? theme.color.blue
|
||||||
|
: theme.color.blue20
|
||||||
|
: focus
|
||||||
|
? theme.color.blue
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus
|
||||||
|
? `0 0 0 3px ${theme.accent.tertiary}`
|
||||||
|
: 'none'
|
||||||
|
};
|
||||||
|
color: ${!disabled ? theme.color.blue : theme.accent.accent4060};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.accent.tertiary : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.accent.secondary : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
case 'danger':
|
||||||
|
return `
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.transparent.primary : 'transparent'
|
||||||
|
};
|
||||||
|
border-color: ${
|
||||||
|
variant === 'secondary'
|
||||||
|
? !disabled
|
||||||
|
? theme.color.red
|
||||||
|
: theme.color.red20
|
||||||
|
: focus
|
||||||
|
? theme.color.red
|
||||||
|
: 'transparent'
|
||||||
|
};
|
||||||
|
border-width: ${!disabled && focus ? '1px 1px !important' : 0};
|
||||||
|
box-shadow: ${
|
||||||
|
!disabled && focus ? `0 0 0 3px ${theme.color.red10}` : 'none'
|
||||||
|
};
|
||||||
|
color: ${!disabled ? theme.font.color.danger : theme.color.red20};
|
||||||
|
&:hover {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.danger : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${
|
||||||
|
!disabled ? theme.background.danger : 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}};
|
}}
|
||||||
border-color: ${({ theme, variant }) => {
|
|
||||||
switch (variant) {
|
border-radius: ${({ position, theme }) => {
|
||||||
case 'border':
|
switch (position) {
|
||||||
return theme.border.color.medium;
|
case 'left':
|
||||||
case 'shadow':
|
return `${theme.border.radius.sm} 0px 0px ${theme.border.radius.sm}`;
|
||||||
case 'white':
|
case 'right':
|
||||||
case 'transparent':
|
return `0px ${theme.border.radius.sm} ${theme.border.radius.sm} 0px`;
|
||||||
default:
|
case 'middle':
|
||||||
return 'none';
|
return '0px';
|
||||||
|
case 'standalone':
|
||||||
|
return theme.border.radius.sm;
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
border-radius: ${({ theme }) => {
|
|
||||||
return theme.border.radius.sm;
|
|
||||||
}};
|
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: ${({ variant }) => {
|
border-width: ${({ variant, position }) => {
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
case 'border':
|
case 'primary':
|
||||||
return '1px';
|
case 'secondary':
|
||||||
case 'shadow':
|
return position === 'middle' ? '1px 0px' : '1px';
|
||||||
case 'white':
|
case 'tertiary':
|
||||||
case 'transparent':
|
return '0';
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
box-shadow: ${({ theme, variant }) => {
|
|
||||||
switch (variant) {
|
|
||||||
case 'shadow':
|
|
||||||
return theme.boxShadow.light;
|
|
||||||
case 'border':
|
|
||||||
case 'white':
|
|
||||||
case 'transparent':
|
|
||||||
default:
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
color: ${({ theme, disabled, textColor }) => {
|
|
||||||
if (disabled) {
|
|
||||||
return theme.font.color.extraLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return textColor === 'danger'
|
|
||||||
? theme.color.red
|
|
||||||
: theme.font.color[textColor ?? 'secondary'];
|
|
||||||
}};
|
|
||||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-direction: row;
|
||||||
height: ${({ size }) => {
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
switch (size) {
|
font-weight: 500;
|
||||||
case 'large':
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
return '32px';
|
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
case 'medium':
|
|
||||||
return '24px';
|
|
||||||
case 'small':
|
|
||||||
default:
|
|
||||||
return '20px';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transition: background 0.1s ease;
|
transition: background 0.1s ease;
|
||||||
&:hover {
|
|
||||||
background: ${({ theme, disabled }) => {
|
white-space: nowrap;
|
||||||
return disabled ? 'auto' : theme.background.transparent.light;
|
|
||||||
}};
|
width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
user-select: none;
|
|
||||||
&:active {
|
|
||||||
background: ${({ theme, disabled }) => {
|
|
||||||
return disabled ? 'auto' : theme.background.transparent.medium;
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
width: ${({ size }) => {
|
|
||||||
switch (size) {
|
|
||||||
case 'large':
|
|
||||||
return '32px';
|
|
||||||
case 'medium':
|
|
||||||
return '24px';
|
|
||||||
case 'small':
|
|
||||||
default:
|
|
||||||
return '20px';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function IconButton({
|
export function IconButton({
|
||||||
icon,
|
className,
|
||||||
variant = 'transparent',
|
icon: initialIcon,
|
||||||
|
variant = 'primary',
|
||||||
size = 'medium',
|
size = 'medium',
|
||||||
textColor = 'tertiary',
|
accent = 'default',
|
||||||
|
position = 'standalone',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
...props
|
focus = false,
|
||||||
}: ButtonProps) {
|
dataTestId,
|
||||||
|
onClick,
|
||||||
|
}: IconButtonProps) {
|
||||||
|
const icon = useMemo(() => {
|
||||||
|
if (!initialIcon || !React.isValidElement(initialIcon)) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement<TablerIconsProps>(initialIcon as any, {
|
||||||
|
size: 16,
|
||||||
|
});
|
||||||
|
}, [initialIcon]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledIconButton
|
<StyledButton
|
||||||
|
data-testid={dataTestId}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
|
position={position}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
textColor={textColor}
|
focus={focus}
|
||||||
{...props}
|
accent={accent}
|
||||||
|
className={className}
|
||||||
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</StyledIconButton>
|
</StyledButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +1,55 @@
|
|||||||
import React, { type ComponentProps } from 'react';
|
import React from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import type { IconButtonSize, IconButtonVariant } from './IconButton';
|
import { IconButtonPosition, IconButtonProps } from './IconButton';
|
||||||
|
|
||||||
const StyledIconButtonGroupContainer = styled.div`
|
const StyledIconButtonGroupContainer = styled.div`
|
||||||
align-items: flex-start;
|
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||||
background: ${({ theme }) => theme.background.transparent.primary};
|
|
||||||
border-radius: ${({ theme }) => theme.spacing(1)};
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(0.5)};
|
|
||||||
padding: ${({ theme }) => theme.spacing(0.5)};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type IconButtonGroupProps = Omit<ComponentProps<'div'>, 'children'> & {
|
export type IconButtonGroupProps = Pick<
|
||||||
variant: IconButtonVariant;
|
IconButtonProps,
|
||||||
size: IconButtonSize;
|
'variant' | 'size' | 'accent'
|
||||||
children: React.ReactElement | React.ReactElement[];
|
> & {
|
||||||
|
children: React.ReactElement[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IconButtonGroup({
|
export function IconButtonGroup({
|
||||||
children,
|
children,
|
||||||
variant,
|
variant,
|
||||||
size,
|
size,
|
||||||
...props
|
accent,
|
||||||
}: IconButtonGroupProps) {
|
}: IconButtonGroupProps) {
|
||||||
return (
|
return (
|
||||||
<StyledIconButtonGroupContainer {...props}>
|
<StyledIconButtonGroupContainer>
|
||||||
{React.Children.map(
|
{React.Children.map(children, (child, index) => {
|
||||||
Array.isArray(children) ? children : [children],
|
let position: IconButtonPosition;
|
||||||
(child) =>
|
|
||||||
React.cloneElement(child, {
|
if (index === 0) {
|
||||||
...(variant ? { variant } : {}),
|
position = 'left';
|
||||||
...(size ? { size } : {}),
|
} else if (index === children.length - 1) {
|
||||||
}),
|
position = 'right';
|
||||||
)}
|
} else {
|
||||||
|
position = 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalProps: any = { position };
|
||||||
|
|
||||||
|
if (variant) {
|
||||||
|
additionalProps.variant = variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accent) {
|
||||||
|
additionalProps.accent = accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size) {
|
||||||
|
additionalProps.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement(child, additionalProps);
|
||||||
|
})}
|
||||||
</StyledIconButtonGroupContainer>
|
</StyledIconButtonGroupContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
109
front/src/modules/ui/button/components/LightButton.tsx
Normal file
109
front/src/modules/ui/button/components/LightButton.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, { MouseEvent, useMemo } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { TablerIconsProps } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
export type LightButtonAccent = 'secondary' | 'tertiary';
|
||||||
|
|
||||||
|
export type LightButtonProps = {
|
||||||
|
className?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
title?: string;
|
||||||
|
accent?: LightButtonAccent;
|
||||||
|
active?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
focus?: boolean;
|
||||||
|
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledButton = styled.button<
|
||||||
|
Pick<LightButtonProps, 'accent' | 'active' | 'focus'>
|
||||||
|
>`
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: ${({ theme, focus }) =>
|
||||||
|
focus ? `1px solid ${theme.color.blue}` : 'none'};
|
||||||
|
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
box-shadow: ${({ theme, focus }) =>
|
||||||
|
focus ? `0 0 0 3px ${theme.color.blue10}` : 'none'};
|
||||||
|
color: ${({ theme, accent, active, disabled, focus }) => {
|
||||||
|
switch (accent) {
|
||||||
|
case 'secondary':
|
||||||
|
return active || focus
|
||||||
|
? theme.color.blue
|
||||||
|
: !disabled
|
||||||
|
? theme.font.color.secondary
|
||||||
|
: theme.font.color.extraLight;
|
||||||
|
case 'tertiary':
|
||||||
|
return active || focus
|
||||||
|
? theme.color.blue
|
||||||
|
: !disabled
|
||||||
|
? theme.font.color.tertiary
|
||||||
|
: theme.font.color.extraLight;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
height: 24px;
|
||||||
|
padding: ${({ theme }) => {
|
||||||
|
return `0 ${theme.spacing(2)}`;
|
||||||
|
}};
|
||||||
|
|
||||||
|
transition: background 0.1s ease;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.light : 'transparent'};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function LightButton({
|
||||||
|
className,
|
||||||
|
icon: initialIcon,
|
||||||
|
title,
|
||||||
|
active = false,
|
||||||
|
accent = 'secondary',
|
||||||
|
disabled = false,
|
||||||
|
focus = false,
|
||||||
|
onClick,
|
||||||
|
}: LightButtonProps) {
|
||||||
|
const icon = useMemo(() => {
|
||||||
|
if (!initialIcon || !React.isValidElement(initialIcon)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement<TablerIconsProps>(initialIcon as any, {
|
||||||
|
size: 14,
|
||||||
|
});
|
||||||
|
}, [initialIcon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
focus={focus && !disabled}
|
||||||
|
accent={accent}
|
||||||
|
className={className}
|
||||||
|
active={active}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
{title}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
112
front/src/modules/ui/button/components/LightIconButton.tsx
Normal file
112
front/src/modules/ui/button/components/LightIconButton.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import React, { MouseEvent, useMemo } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { TablerIconsProps } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
export type LightIconButtonAccent = 'secondary' | 'tertiary';
|
||||||
|
export type LightIconButtonSize = 'small' | 'medium';
|
||||||
|
|
||||||
|
export type LightIconButtonProps = {
|
||||||
|
className?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
title?: string;
|
||||||
|
size?: LightIconButtonSize;
|
||||||
|
accent?: LightIconButtonAccent;
|
||||||
|
active?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
focus?: boolean;
|
||||||
|
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledButton = styled.button<
|
||||||
|
Pick<LightIconButtonProps, 'accent' | 'active' | 'size' | 'focus'>
|
||||||
|
>`
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
border: ${({ disabled, theme, focus }) =>
|
||||||
|
!disabled && focus ? `1px solid ${theme.color.blue}` : 'none'};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
box-shadow: ${({ disabled, theme, focus }) =>
|
||||||
|
!disabled && focus ? `0 0 0 3px ${theme.color.blue10}` : 'none'};
|
||||||
|
color: ${({ theme, accent, active, disabled, focus }) => {
|
||||||
|
switch (accent) {
|
||||||
|
case 'secondary':
|
||||||
|
return active || focus
|
||||||
|
? theme.color.blue
|
||||||
|
: !disabled
|
||||||
|
? theme.font.color.secondary
|
||||||
|
: theme.font.color.extraLight;
|
||||||
|
case 'tertiary':
|
||||||
|
return active || focus
|
||||||
|
? theme.color.blue
|
||||||
|
: !disabled
|
||||||
|
? theme.font.color.tertiary
|
||||||
|
: theme.font.color.extraLight;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
font-family: ${({ theme }) => theme.font.family};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
height: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
transition: background 0.1s ease;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.light : 'transparent'};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: ${({ theme, disabled }) =>
|
||||||
|
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function LightIconButton({
|
||||||
|
className,
|
||||||
|
icon: initialIcon,
|
||||||
|
active = false,
|
||||||
|
size = 'small',
|
||||||
|
accent = 'secondary',
|
||||||
|
disabled = false,
|
||||||
|
focus = false,
|
||||||
|
onClick,
|
||||||
|
}: LightIconButtonProps) {
|
||||||
|
const icon = useMemo(() => {
|
||||||
|
if (!initialIcon || !React.isValidElement(initialIcon)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement<TablerIconsProps>(initialIcon as any, {
|
||||||
|
size: 16,
|
||||||
|
});
|
||||||
|
}, [initialIcon]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
focus={focus && !disabled}
|
||||||
|
accent={accent}
|
||||||
|
className={className}
|
||||||
|
size={size}
|
||||||
|
active={active}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,89 +1,106 @@
|
|||||||
import { expect, jest } from '@storybook/jest';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { userEvent, within } from '@storybook/testing-library';
|
|
||||||
|
|
||||||
import { IconSearch } from '@/ui/icon';
|
import { IconSearch } from '@/ui/icon';
|
||||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
import { Button, ButtonPosition, ButtonSize, ButtonVariant } from '../Button';
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonAccent,
|
||||||
|
ButtonPosition,
|
||||||
|
ButtonSize,
|
||||||
|
ButtonVariant,
|
||||||
|
} from '../Button';
|
||||||
|
|
||||||
const meta: Meta<typeof Button> = {
|
const meta: Meta<typeof Button> = {
|
||||||
title: 'UI/Button/Button',
|
title: 'UI/Button/Button',
|
||||||
component: Button,
|
component: Button,
|
||||||
argTypes: {
|
|
||||||
icon: {
|
|
||||||
type: 'boolean',
|
|
||||||
mapping: {
|
|
||||||
true: <IconSearch size={14} />,
|
|
||||||
false: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
control: 'radio',
|
|
||||||
options: [undefined, ...Object.values(ButtonPosition)],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
args: { title: 'Lorem ipsum' },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof Button>;
|
type Story = StoryObj<typeof Button>;
|
||||||
|
|
||||||
const clickJestFn = jest.fn();
|
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: { onClick: clickJestFn },
|
args: {
|
||||||
decorators: [ComponentDecorator],
|
title: 'Button',
|
||||||
play: async ({ canvasElement }) => {
|
size: 'small',
|
||||||
const canvas = within(canvasElement);
|
variant: 'primary',
|
||||||
const button = canvas.getByRole('button');
|
accent: 'danger',
|
||||||
|
disabled: false,
|
||||||
const numberOfClicks = clickJestFn.mock.calls.length;
|
focus: false,
|
||||||
await userEvent.click(button);
|
fullWidth: false,
|
||||||
expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
|
soon: false,
|
||||||
|
position: 'standalone',
|
||||||
|
icon: <IconSearch />,
|
||||||
|
className: '',
|
||||||
},
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Sizes: Story = {
|
export const Catalog: Story = {
|
||||||
|
args: { title: 'Filter', icon: <IconSearch /> },
|
||||||
argTypes: {
|
argTypes: {
|
||||||
size: { control: false },
|
size: { control: false },
|
||||||
|
variant: { control: false },
|
||||||
|
accent: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
fullWidth: { control: false },
|
||||||
|
soon: { control: false },
|
||||||
|
position: { control: false },
|
||||||
|
className: { control: false },
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||||
catalog: {
|
catalog: {
|
||||||
dimensions: [
|
dimensions: [
|
||||||
{
|
{
|
||||||
name: 'sizes',
|
name: 'sizes',
|
||||||
values: Object.values(ButtonSize),
|
values: ['small', 'medium'] satisfies ButtonSize[],
|
||||||
props: (size: ButtonSize) => ({ size }),
|
props: (size: ButtonSize) => ({ size }),
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
decorators: [CatalogDecorator],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Variants: Story = {
|
|
||||||
argTypes: {
|
|
||||||
disabled: { control: false },
|
|
||||||
variant: { control: false },
|
|
||||||
},
|
|
||||||
parameters: {
|
|
||||||
pseudo: { hover: ['.hover'], active: ['.active'], focus: ['.focus'] },
|
|
||||||
catalog: {
|
|
||||||
dimensions: [
|
|
||||||
{
|
{
|
||||||
name: 'state',
|
name: 'states',
|
||||||
values: ['default', 'disabled', 'hover', 'active', 'focus'],
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
],
|
||||||
props: (state: string) => {
|
props: (state: string) => {
|
||||||
if (state === 'disabled') return { disabled: true };
|
switch (state) {
|
||||||
if (state === 'default') return {};
|
case 'default':
|
||||||
return { className: state };
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'active':
|
||||||
|
return { active: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { focus: true, disabled: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'accents',
|
||||||
|
values: ['default', 'blue', 'danger'] satisfies ButtonAccent[],
|
||||||
|
props: (accent: ButtonAccent) => ({ accent }),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'variants',
|
name: 'variants',
|
||||||
values: Object.values(ButtonVariant),
|
values: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'tertiary',
|
||||||
|
] satisfies ButtonVariant[],
|
||||||
props: (variant: ButtonVariant) => ({ variant }),
|
props: (variant: ButtonVariant) => ({ variant }),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -92,18 +109,71 @@ export const Variants: Story = {
|
|||||||
decorators: [CatalogDecorator],
|
decorators: [CatalogDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Positions: Story = {
|
export const SoonCatalog: Story = {
|
||||||
|
args: { title: 'Filter', icon: <IconSearch />, soon: true },
|
||||||
argTypes: {
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
variant: { control: false },
|
||||||
|
accent: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
fullWidth: { control: false },
|
||||||
|
soon: { control: false },
|
||||||
position: { control: false },
|
position: { control: false },
|
||||||
|
className: { control: false },
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||||
catalog: {
|
catalog: {
|
||||||
dimensions: [
|
dimensions: [
|
||||||
{
|
{
|
||||||
name: 'positions',
|
name: 'sizes',
|
||||||
values: ['none', ...Object.values(ButtonPosition)],
|
values: ['small', 'medium'] satisfies ButtonSize[],
|
||||||
props: (position: ButtonPosition | 'none') =>
|
props: (size: ButtonSize) => ({ size }),
|
||||||
position === 'none' ? {} : { position },
|
},
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'active':
|
||||||
|
return { active: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { focus: true, disabled: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accents',
|
||||||
|
values: ['default', 'blue', 'danger'] satisfies ButtonAccent[],
|
||||||
|
props: (accent: ButtonAccent) => ({ accent }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'variants',
|
||||||
|
values: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'tertiary',
|
||||||
|
] satisfies ButtonVariant[],
|
||||||
|
props: (variant: ButtonVariant) => ({ variant }),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -111,20 +181,95 @@ export const Positions: Story = {
|
|||||||
decorators: [CatalogDecorator],
|
decorators: [CatalogDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithAdornments: Story = {
|
export const PositionCatalog: Story = {
|
||||||
|
args: { title: 'Filter', icon: <IconSearch /> },
|
||||||
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
variant: { control: false },
|
||||||
|
accent: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
fullWidth: { control: false },
|
||||||
|
soon: { control: false },
|
||||||
|
position: { control: false },
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||||
catalog: {
|
catalog: {
|
||||||
dimensions: [
|
dimensions: [
|
||||||
{
|
{
|
||||||
name: 'adornments',
|
name: 'positions',
|
||||||
values: ['with icon', 'with soon pill'],
|
values: [
|
||||||
props: (value: string) =>
|
'standalone',
|
||||||
value === 'with icon'
|
'left',
|
||||||
? { icon: <IconSearch size={14} /> }
|
'middle',
|
||||||
: { soon: true },
|
'right',
|
||||||
|
] satisfies ButtonPosition[],
|
||||||
|
props: (position: ButtonPosition) => ({ position }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'active':
|
||||||
|
return { active: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { focus: true, disabled: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies ButtonSize[],
|
||||||
|
props: (size: ButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'variants',
|
||||||
|
values: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'tertiary',
|
||||||
|
] satisfies ButtonVariant[],
|
||||||
|
props: (variant: ButtonVariant) => ({ variant }),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
decorators: [CatalogDecorator],
|
decorators: [CatalogDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FullWidth: Story = {
|
||||||
|
args: { title: 'Filter', icon: <IconSearch />, fullWidth: true },
|
||||||
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
variant: { control: false },
|
||||||
|
accent: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
fullWidth: { control: false },
|
||||||
|
soon: { control: false },
|
||||||
|
position: { control: false },
|
||||||
|
className: { control: false },
|
||||||
|
icon: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|||||||
@ -1,36 +1,76 @@
|
|||||||
import { expect, jest } from '@storybook/jest';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { userEvent, within } from '@storybook/testing-library';
|
|
||||||
|
|
||||||
|
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
import { Button, ButtonPosition } from '../Button';
|
import { Button, ButtonAccent, ButtonSize, ButtonVariant } from '../Button';
|
||||||
import { ButtonGroup } from '../ButtonGroup';
|
import { ButtonGroup } from '../ButtonGroup';
|
||||||
|
|
||||||
const clickJestFn = jest.fn();
|
|
||||||
|
|
||||||
const meta: Meta<typeof ButtonGroup> = {
|
const meta: Meta<typeof ButtonGroup> = {
|
||||||
title: 'UI/Button/ButtonGroup',
|
title: 'UI/Button/ButtonGroup',
|
||||||
component: ButtonGroup,
|
component: ButtonGroup,
|
||||||
decorators: [ComponentDecorator],
|
|
||||||
argTypes: { children: { control: false } },
|
|
||||||
args: {
|
|
||||||
children: Object.values(ButtonPosition).map((position) => (
|
|
||||||
<Button title={position} onClick={clickJestFn} />
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof ButtonGroup>;
|
type Story = StoryObj<typeof ButtonGroup>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
play: async ({ canvasElement }) => {
|
args: {
|
||||||
const canvas = within(canvasElement);
|
size: 'small',
|
||||||
const leftButton = canvas.getByRole('button', { name: 'left' });
|
variant: 'primary',
|
||||||
|
accent: 'danger',
|
||||||
const numberOfClicks = clickJestFn.mock.calls.length;
|
children: [
|
||||||
await userEvent.click(leftButton);
|
<Button icon={<IconNotes />} title="Note" />,
|
||||||
expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
|
<Button icon={<IconCheckbox />} title="Task" />,
|
||||||
|
<Button icon={<IconTimelineEvent />} title="Activity" />,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
argTypes: {
|
||||||
|
children: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: {
|
||||||
|
children: [
|
||||||
|
<Button icon={<IconNotes />} title="Note" />,
|
||||||
|
<Button icon={<IconCheckbox />} title="Task" />,
|
||||||
|
<Button icon={<IconTimelineEvent />} title="Activity" />,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
variant: { control: false },
|
||||||
|
accent: { control: false },
|
||||||
|
children: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies ButtonSize[],
|
||||||
|
props: (size: ButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accents',
|
||||||
|
values: ['default', 'blue', 'danger'] satisfies ButtonAccent[],
|
||||||
|
props: (accent: ButtonAccent) => ({ accent }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'variants',
|
||||||
|
values: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'tertiary',
|
||||||
|
] satisfies ButtonVariant[],
|
||||||
|
props: (variant: ButtonVariant) => ({ variant }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,83 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { IconSearch } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import { FloatingButton, FloatingButtonSize } from '../FloatingButton';
|
||||||
|
|
||||||
|
const meta: Meta<typeof FloatingButton> = {
|
||||||
|
title: 'UI/Button/FloatingButton',
|
||||||
|
component: FloatingButton,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof FloatingButton>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
title: 'Filter',
|
||||||
|
size: 'small',
|
||||||
|
disabled: false,
|
||||||
|
focus: false,
|
||||||
|
applyBlur: true,
|
||||||
|
applyShadow: true,
|
||||||
|
position: 'standalone',
|
||||||
|
icon: <IconSearch />,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
icon: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: { title: 'Filter', icon: <IconSearch /> },
|
||||||
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
position: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies FloatingButtonSize[],
|
||||||
|
props: (size: FloatingButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { disabled: true, focus: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
|
};
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import { FloatingButton, FloatingButtonSize } from '../FloatingButton';
|
||||||
|
import { FloatingButtonGroup } from '../FloatingButtonGroup';
|
||||||
|
|
||||||
|
const meta: Meta<typeof FloatingButtonGroup> = {
|
||||||
|
title: 'UI/Button/FloatingButtonGroup',
|
||||||
|
component: FloatingButtonGroup,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof FloatingButtonGroup>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'small',
|
||||||
|
children: [
|
||||||
|
<FloatingButton icon={<IconNotes />} />,
|
||||||
|
<FloatingButton icon={<IconCheckbox />} />,
|
||||||
|
<FloatingButton icon={<IconTimelineEvent />} />,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
children: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: {
|
||||||
|
children: [
|
||||||
|
<FloatingButton icon={<IconNotes />} />,
|
||||||
|
<FloatingButton icon={<IconCheckbox />} />,
|
||||||
|
<FloatingButton icon={<IconTimelineEvent />} />,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
children: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies FloatingButtonSize[],
|
||||||
|
props: (size: FloatingButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
|
};
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { IconSearch } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FloatingIconButton,
|
||||||
|
FloatingIconButtonSize,
|
||||||
|
} from '../FloatingIconButton';
|
||||||
|
|
||||||
|
const meta: Meta<typeof FloatingIconButton> = {
|
||||||
|
title: 'UI/Button/FloatingIconButton',
|
||||||
|
component: FloatingIconButton,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof FloatingIconButton>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'small',
|
||||||
|
disabled: false,
|
||||||
|
focus: false,
|
||||||
|
applyBlur: true,
|
||||||
|
applyShadow: true,
|
||||||
|
position: 'standalone',
|
||||||
|
icon: <IconSearch />,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
icon: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: { icon: <IconSearch /> },
|
||||||
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies FloatingIconButtonSize[],
|
||||||
|
props: (size: FloatingIconButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { disabled: true, focus: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
|
};
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FloatingIconButton,
|
||||||
|
FloatingIconButtonSize,
|
||||||
|
} from '../FloatingIconButton';
|
||||||
|
import { FloatingIconButtonGroup } from '../FloatingIconButtonGroup';
|
||||||
|
|
||||||
|
const meta: Meta<typeof FloatingIconButtonGroup> = {
|
||||||
|
title: 'UI/Button/FloatingIconButtonGroup',
|
||||||
|
component: FloatingIconButtonGroup,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof FloatingIconButtonGroup>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'small',
|
||||||
|
children: [
|
||||||
|
<FloatingIconButton icon={<IconNotes />} />,
|
||||||
|
<FloatingIconButton icon={<IconCheckbox />} />,
|
||||||
|
<FloatingIconButton icon={<IconTimelineEvent />} />,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
children: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: {
|
||||||
|
children: [
|
||||||
|
<FloatingIconButton icon={<IconNotes />} />,
|
||||||
|
<FloatingIconButton icon={<IconCheckbox />} />,
|
||||||
|
<FloatingIconButton icon={<IconTimelineEvent />} />,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
size: { control: false },
|
||||||
|
children: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies FloatingIconButtonSize[],
|
||||||
|
props: (size: FloatingIconButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
|
};
|
||||||
@ -1,156 +1,179 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { expect, jest } from '@storybook/jest';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { userEvent, within } from '@storybook/testing-library';
|
|
||||||
|
|
||||||
import { IconUser } from '@/ui/icon';
|
import { IconSearch } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
import { IconButton } from '../IconButton';
|
import {
|
||||||
|
IconButton,
|
||||||
type IconButtonProps = React.ComponentProps<typeof IconButton>;
|
IconButtonAccent,
|
||||||
|
IconButtonPosition,
|
||||||
const StyledContainer = styled.div`
|
IconButtonSize,
|
||||||
display: flex;
|
IconButtonVariant,
|
||||||
flex: 1;
|
} from '../IconButton';
|
||||||
flex-direction: column;
|
|
||||||
padding: 20px;
|
|
||||||
width: 800px;
|
|
||||||
> * + * {
|
|
||||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTitle = styled.h1`
|
|
||||||
font-size: ${({ theme }) => theme.font.size.lg};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
|
||||||
margin-top: ${({ theme }) => theme.spacing(3)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledDescription = styled.span`
|
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
|
||||||
font-size: ${({ theme }) => theme.font.size.xs};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledLine = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: row;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledIconButtonContainer = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
|
||||||
width: 50px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const meta: Meta<typeof IconButton> = {
|
const meta: Meta<typeof IconButton> = {
|
||||||
title: 'UI/Button/IconButton',
|
title: 'UI/Button/IconButton',
|
||||||
component: IconButton,
|
component: IconButton,
|
||||||
decorators: [
|
|
||||||
(Story) => (
|
|
||||||
<StyledContainer>
|
|
||||||
<Story />
|
|
||||||
</StyledContainer>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
argTypes: { icon: { control: false }, variant: { control: false } },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof IconButton>;
|
type Story = StoryObj<typeof IconButton>;
|
||||||
|
|
||||||
const variants: IconButtonProps['variant'][] = [
|
export const Default: Story = {
|
||||||
'transparent',
|
args: {
|
||||||
'border',
|
size: 'small',
|
||||||
'shadow',
|
variant: 'primary',
|
||||||
'white',
|
accent: 'danger',
|
||||||
];
|
disabled: false,
|
||||||
|
focus: false,
|
||||||
const clickJestFn = jest.fn();
|
position: 'standalone',
|
||||||
|
icon: <IconSearch />,
|
||||||
const states = {
|
|
||||||
default: {
|
|
||||||
description: 'Default',
|
|
||||||
extraProps: (variant: string) => ({
|
|
||||||
'data-testid': `${variant}-button-default`,
|
|
||||||
onClick: clickJestFn,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
description: 'Hover',
|
|
||||||
extraProps: (variant: string) => ({
|
|
||||||
id: `${variant}-button-hover`,
|
|
||||||
'data-testid': `${variant}-button-hover`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
pressed: {
|
|
||||||
description: 'Pressed',
|
|
||||||
extraProps: (variant: string) => ({
|
|
||||||
id: `${variant}-button-pressed`,
|
|
||||||
'data-testid': `${variant}-button-pressed`,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
description: 'Disabled',
|
|
||||||
extraProps: (variant: string) => ({
|
|
||||||
'data-testid': `${variant}-button-disabled`,
|
|
||||||
disabled: true,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LargeSize: Story = {
|
export const Catalog: Story = {
|
||||||
args: { size: 'large' },
|
args: { icon: <IconSearch /> },
|
||||||
render: (args) => (
|
argTypes: {
|
||||||
<>
|
size: { control: false },
|
||||||
{variants.map((variant) => (
|
variant: { control: false },
|
||||||
<div key={variant}>
|
focus: { control: false },
|
||||||
<StyledTitle>{variant}</StyledTitle>
|
accent: { control: false },
|
||||||
<StyledLine>
|
disabled: { control: false },
|
||||||
{Object.entries(states).map(
|
icon: { control: false },
|
||||||
([state, { description, extraProps }]) => (
|
position: { control: false },
|
||||||
<StyledIconButtonContainer
|
|
||||||
key={`${variant}-container-${state}`}
|
|
||||||
>
|
|
||||||
<StyledDescription>{description}</StyledDescription>
|
|
||||||
<IconButton
|
|
||||||
{...args}
|
|
||||||
{...extraProps(variant ?? '')}
|
|
||||||
variant={variant}
|
|
||||||
icon={<IconUser size={args.size === 'small' ? 14 : 16} />}
|
|
||||||
/>
|
|
||||||
</StyledIconButtonContainer>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</StyledLine>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
const button = canvas.getByTestId('transparent-button-default');
|
|
||||||
|
|
||||||
const numberOfClicks = clickJestFn.mock.calls.length;
|
|
||||||
await userEvent.click(button);
|
|
||||||
expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
|
|
||||||
},
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies IconButtonSize[],
|
||||||
|
props: (size: IconButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'active':
|
||||||
|
return { active: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { focus: true, disabled: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accents',
|
||||||
|
values: ['default', 'blue', 'danger'] satisfies IconButtonAccent[],
|
||||||
|
props: (accent: IconButtonAccent) => ({ accent }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'variants',
|
||||||
|
values: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'tertiary',
|
||||||
|
] satisfies IconButtonVariant[],
|
||||||
|
props: (variant: IconButtonVariant) => ({ variant }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MediumSize: Story = {
|
export const PositionCatalog: Story = {
|
||||||
...LargeSize,
|
args: { icon: <IconSearch /> },
|
||||||
args: { size: 'medium' },
|
argTypes: {
|
||||||
};
|
size: { control: false },
|
||||||
|
variant: { control: false },
|
||||||
export const SmallSize: Story = {
|
focus: { control: false },
|
||||||
...LargeSize,
|
accent: { control: false },
|
||||||
args: { size: 'small' },
|
disabled: { control: false },
|
||||||
|
position: { control: false },
|
||||||
|
icon: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'positions',
|
||||||
|
values: [
|
||||||
|
'standalone',
|
||||||
|
'left',
|
||||||
|
'middle',
|
||||||
|
'right',
|
||||||
|
] satisfies IconButtonPosition[],
|
||||||
|
props: (position: IconButtonPosition) => ({ position }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'active':
|
||||||
|
return { active: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { focus: true, disabled: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies IconButtonSize[],
|
||||||
|
props: (size: IconButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'variants',
|
||||||
|
values: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'tertiary',
|
||||||
|
] satisfies IconButtonVariant[],
|
||||||
|
props: (variant: IconButtonVariant) => ({ variant }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { IconBell } from '@tabler/icons-react';
|
|
||||||
|
|
||||||
|
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon';
|
||||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
import { IconButton } from '../IconButton';
|
import { Button } from '../Button';
|
||||||
|
import {
|
||||||
|
IconButtonAccent,
|
||||||
|
IconButtonSize,
|
||||||
|
IconButtonVariant,
|
||||||
|
} from '../IconButton';
|
||||||
import { IconButtonGroup } from '../IconButtonGroup';
|
import { IconButtonGroup } from '../IconButtonGroup';
|
||||||
|
|
||||||
const meta: Meta<typeof IconButtonGroup> = {
|
const meta: Meta<typeof IconButtonGroup> = {
|
||||||
@ -15,40 +20,58 @@ const meta: Meta<typeof IconButtonGroup> = {
|
|||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof IconButtonGroup>;
|
type Story = StoryObj<typeof IconButtonGroup>;
|
||||||
|
|
||||||
const args = {
|
|
||||||
children: [
|
|
||||||
<IconButton icon={<IconBell />} />,
|
|
||||||
<IconButton icon={<IconBell />} />,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args,
|
args: {
|
||||||
|
size: 'small',
|
||||||
|
variant: 'primary',
|
||||||
|
accent: 'danger',
|
||||||
|
children: [
|
||||||
|
<Button icon={<IconNotes />} />,
|
||||||
|
<Button icon={<IconCheckbox />} />,
|
||||||
|
<Button icon={<IconTimelineEvent />} />,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
children: { control: false },
|
||||||
|
},
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Catalog: Story = {
|
export const Catalog: Story = {
|
||||||
args,
|
args: {
|
||||||
|
children: [
|
||||||
|
<Button icon={<IconNotes />} />,
|
||||||
|
<Button icon={<IconCheckbox />} />,
|
||||||
|
<Button icon={<IconTimelineEvent />} />,
|
||||||
|
],
|
||||||
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
size: { control: false },
|
size: { control: false },
|
||||||
variant: { control: false },
|
variant: { control: false },
|
||||||
|
accent: { control: false },
|
||||||
|
children: { control: false },
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
catalog: {
|
catalog: {
|
||||||
dimensions: [
|
dimensions: [
|
||||||
{
|
{
|
||||||
name: 'variants',
|
name: 'sizes',
|
||||||
values: ['transparent', 'border', 'shadow', 'white'],
|
values: ['small', 'medium'] satisfies IconButtonSize[],
|
||||||
props: (variant: string) => ({
|
props: (size: IconButtonSize) => ({ size }),
|
||||||
variant,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sizes',
|
name: 'accents',
|
||||||
values: ['large', 'medium', 'small'],
|
values: ['default', 'blue', 'danger'] satisfies IconButtonAccent[],
|
||||||
props: (size: string) => ({
|
props: (accent: IconButtonAccent) => ({ accent }),
|
||||||
size,
|
},
|
||||||
}),
|
{
|
||||||
|
name: 'variants',
|
||||||
|
values: [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'tertiary',
|
||||||
|
] satisfies IconButtonVariant[],
|
||||||
|
props: (variant: IconButtonVariant) => ({ variant }),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,87 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { IconSearch } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import { LightButton, LightButtonAccent } from '../LightButton';
|
||||||
|
|
||||||
|
const meta: Meta<typeof LightButton> = {
|
||||||
|
title: 'UI/Button/LightButton',
|
||||||
|
component: LightButton,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof LightButton>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
title: 'Filter',
|
||||||
|
accent: 'secondary',
|
||||||
|
disabled: false,
|
||||||
|
active: false,
|
||||||
|
focus: false,
|
||||||
|
icon: <IconSearch />,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
icon: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: { title: 'Filter', icon: <IconSearch /> },
|
||||||
|
argTypes: {
|
||||||
|
accent: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
active: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'accents',
|
||||||
|
values: ['secondary', 'tertiary'] satisfies LightButtonAccent[],
|
||||||
|
props: (accent: LightButtonAccent) => ({ accent }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'active',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
'disabled+active',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'active':
|
||||||
|
return { active: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { disabled: true, focus: true };
|
||||||
|
case 'disabled+active':
|
||||||
|
return { disabled: true, active: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
|
};
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { IconSearch } from '@/ui/icon';
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LightIconButton,
|
||||||
|
LightIconButtonAccent,
|
||||||
|
LightIconButtonSize,
|
||||||
|
} from '../LightIconButton';
|
||||||
|
|
||||||
|
const meta: Meta<typeof LightIconButton> = {
|
||||||
|
title: 'UI/Button/LightIconButton',
|
||||||
|
component: LightIconButton,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof LightIconButton>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
title: 'Filter',
|
||||||
|
accent: 'secondary',
|
||||||
|
disabled: false,
|
||||||
|
active: false,
|
||||||
|
focus: false,
|
||||||
|
icon: <IconSearch />,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
icon: { control: false },
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: { title: 'Filter', icon: <IconSearch /> },
|
||||||
|
argTypes: {
|
||||||
|
accent: { control: false },
|
||||||
|
disabled: { control: false },
|
||||||
|
active: { control: false },
|
||||||
|
focus: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'], active: ['.pressed'] },
|
||||||
|
catalog: {
|
||||||
|
dimensions: [
|
||||||
|
{
|
||||||
|
name: 'states',
|
||||||
|
values: [
|
||||||
|
'default',
|
||||||
|
'hover',
|
||||||
|
'pressed',
|
||||||
|
'disabled',
|
||||||
|
'active',
|
||||||
|
'focus',
|
||||||
|
'disabled+focus',
|
||||||
|
'disabled+active',
|
||||||
|
],
|
||||||
|
props: (state: string) => {
|
||||||
|
switch (state) {
|
||||||
|
case 'default':
|
||||||
|
return {};
|
||||||
|
case 'hover':
|
||||||
|
case 'pressed':
|
||||||
|
return { className: state };
|
||||||
|
case 'focus':
|
||||||
|
return { focus: true };
|
||||||
|
case 'disabled':
|
||||||
|
return { disabled: true };
|
||||||
|
case 'active':
|
||||||
|
return { active: true };
|
||||||
|
case 'disabled+focus':
|
||||||
|
return { disabled: true, focus: true };
|
||||||
|
case 'disabled+active':
|
||||||
|
return { disabled: true, active: true };
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accents',
|
||||||
|
values: ['secondary', 'tertiary'] satisfies LightIconButtonAccent[],
|
||||||
|
props: (accent: LightIconButtonAccent) => ({ accent }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sizes',
|
||||||
|
values: ['small', 'medium'] satisfies LightIconButtonSize[],
|
||||||
|
props: (size: LightIconButtonSize) => ({ size }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
|
};
|
||||||
@ -12,15 +12,15 @@ const clickJestFn = jest.fn();
|
|||||||
const meta: Meta<typeof RoundedIconButton> = {
|
const meta: Meta<typeof RoundedIconButton> = {
|
||||||
title: 'UI/Button/RoundedIconButton',
|
title: 'UI/Button/RoundedIconButton',
|
||||||
component: RoundedIconButton,
|
component: RoundedIconButton,
|
||||||
decorators: [ComponentDecorator],
|
|
||||||
argTypes: { icon: { control: false } },
|
|
||||||
args: { onClick: clickJestFn, icon: <IconArrowRight size={15} /> },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof RoundedIconButton>;
|
type Story = StoryObj<typeof RoundedIconButton>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
argTypes: { icon: { control: false } },
|
||||||
|
args: { onClick: clickJestFn, icon: <IconArrowRight size={15} /> },
|
||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useCallback } from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
|
|
||||||
const StyledDialogOverlay = styled(motion.div)`
|
const StyledDialogOverlay = styled(motion.div)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -110,12 +110,12 @@ export function Dialog({
|
|||||||
{buttons.map((button) => (
|
{buttons.map((button) => (
|
||||||
<StyledDialogButton
|
<StyledDialogButton
|
||||||
key={button.title}
|
key={button.title}
|
||||||
onClick={(e) => {
|
onClick={(event) => {
|
||||||
button?.onClick?.(e);
|
button?.onClick?.(event);
|
||||||
closeSnackbar();
|
closeSnackbar();
|
||||||
}}
|
}}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
variant={button.variant ?? ButtonVariant.Secondary}
|
variant={button.variant ?? 'secondary'}
|
||||||
{...button}
|
{...button}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export function DropdownButton({
|
|||||||
dropdownHotkeyScope,
|
dropdownHotkeyScope,
|
||||||
dropdownButtonCustomHotkeyScope,
|
dropdownButtonCustomHotkeyScope,
|
||||||
]);
|
]);
|
||||||
console.log(dropdownComponents, isDropdownButtonOpen);
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer ref={containerRef}>
|
<StyledContainer ref={containerRef}>
|
||||||
{hotkey && (
|
{hotkey && (
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
import { ComponentProps } from 'react';
|
import { ComponentProps } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import {
|
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||||
IconButtonGroup,
|
|
||||||
type IconButtonGroupProps,
|
|
||||||
} from '@/ui/button/components/IconButtonGroup';
|
|
||||||
import { hoverBackground } from '@/ui/theme/constants/effects';
|
import { hoverBackground } from '@/ui/theme/constants/effects';
|
||||||
|
|
||||||
const styledIconButtonGroupClassName = 'dropdown-menu-item-actions';
|
|
||||||
|
|
||||||
export type DropdownMenuItemAccent = 'regular' | 'danger';
|
export type DropdownMenuItemAccent = 'regular' | 'danger';
|
||||||
|
|
||||||
const StyledItem = styled.li<{ accent: DropdownMenuItemAccent }>`
|
const StyledItem = styled.li<{ accent: DropdownMenuItemAccent }>`
|
||||||
@ -39,22 +34,16 @@ const StyledItem = styled.li<{ accent: DropdownMenuItemAccent }>`
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
width: calc(100% - 2 * var(--horizontal-padding));
|
width: calc(100% - 2 * var(--horizontal-padding));
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.${styledIconButtonGroupClassName} {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledActions = styled(IconButtonGroup)`
|
const StyledActions = styled(ButtonGroup)`
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: ${({ theme }) => theme.spacing(1)};
|
right: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type DropdownMenuItemProps = ComponentProps<'li'> & {
|
export type DropdownMenuItemProps = ComponentProps<'li'> & {
|
||||||
actions?: IconButtonGroupProps['children'];
|
actions?: React.ReactNode[];
|
||||||
accent?: DropdownMenuItemAccent;
|
accent?: DropdownMenuItemAccent;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,11 +57,7 @@ export function DropdownMenuItem({
|
|||||||
<StyledItem {...props} accent={accent}>
|
<StyledItem {...props} accent={accent}>
|
||||||
{children}
|
{children}
|
||||||
{actions && (
|
{actions && (
|
||||||
<StyledActions
|
<StyledActions variant="tertiary" size="small">
|
||||||
className={styledIconButtonGroupClassName}
|
|
||||||
variant="transparent"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{actions}
|
{actions}
|
||||||
</StyledActions>
|
</StyledActions>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
||||||
import { IconPencil } from '@/ui/icon';
|
import { IconPencil } from '@/ui/icon';
|
||||||
|
|
||||||
import { useEditableField } from '../hooks/useEditableField';
|
import { useEditableField } from '../hooks/useEditableField';
|
||||||
@ -11,11 +11,10 @@ export function EditableFieldEditButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<FloatingIconButton
|
||||||
variant="shadow"
|
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
icon={<IconPencil size={14} />}
|
icon={<IconPencil />}
|
||||||
data-testid="editable-field-edit-mode-container"
|
data-testid="editable-field-edit-mode-container"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { IconFileUpload, IconTrash, IconUpload } from '@/ui/icon';
|
import { IconFileUpload, IconTrash, IconUpload } from '@/ui/icon';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@ -123,7 +123,7 @@ export function ImageInput({
|
|||||||
<Button
|
<Button
|
||||||
icon={<IconUpload size={theme.icon.size.sm} />}
|
icon={<IconUpload size={theme.icon.size.sm} />}
|
||||||
onClick={onUploadButtonClick}
|
onClick={onUploadButtonClick}
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
title="Upload"
|
title="Upload"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -131,7 +131,7 @@ export function ImageInput({
|
|||||||
<Button
|
<Button
|
||||||
icon={<IconTrash size={theme.icon.size.sm} />}
|
icon={<IconTrash size={theme.icon.size.sm} />}
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
title="Remove"
|
title="Remove"
|
||||||
disabled={!picture || disabled}
|
disabled={!picture || disabled}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@ -4,13 +4,12 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { IconButton } from '@/ui/button/components/IconButton';
|
||||||
|
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
||||||
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||||
import { IconChevronLeft, IconHeart, IconPlus } from '@/ui/icon/index';
|
import { IconChevronLeft, IconHeart, IconPlus } from '@/ui/icon/index';
|
||||||
import NavCollapseButton from '@/ui/navbar/components/NavCollapseButton';
|
import NavCollapseButton from '@/ui/navbar/components/NavCollapseButton';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
|
||||||
|
|
||||||
import { navbarIconSize } from '../../../navbar/constants';
|
|
||||||
import { OverflowingTextWithTooltip } from '../../../tooltip/OverflowingTextWithTooltip';
|
import { OverflowingTextWithTooltip } from '../../../tooltip/OverflowingTextWithTooltip';
|
||||||
import { isNavbarOpenedState } from '../../states/isNavbarOpenedState';
|
import { isNavbarOpenedState } from '../../states/isNavbarOpenedState';
|
||||||
|
|
||||||
@ -48,10 +47,6 @@ const StyledTopBarButtonContainer = styled.div`
|
|||||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledBackIconButton = styled(IconButton)`
|
|
||||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTopBarIconTitleContainer = styled.div`
|
const StyledTopBarIconTitleContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -89,10 +84,6 @@ export function PageBar({
|
|||||||
|
|
||||||
const isNavbarOpened = useRecoilValue(isNavbarOpenedState);
|
const isNavbarOpened = useRecoilValue(isNavbarOpenedState);
|
||||||
|
|
||||||
const iconSize = useIsMobile()
|
|
||||||
? navbarIconSize.mobile
|
|
||||||
: navbarIconSize.desktop;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledTopBarContainer>
|
<StyledTopBarContainer>
|
||||||
@ -104,8 +95,10 @@ export function PageBar({
|
|||||||
)}
|
)}
|
||||||
{hasBackButton && (
|
{hasBackButton && (
|
||||||
<StyledTopBarButtonContainer>
|
<StyledTopBarButtonContainer>
|
||||||
<StyledBackIconButton
|
<LightIconButton
|
||||||
icon={<IconChevronLeft size={iconSize} />}
|
size="medium"
|
||||||
|
accent="tertiary"
|
||||||
|
icon={<IconChevronLeft />}
|
||||||
onClick={navigateBack}
|
onClick={navigateBack}
|
||||||
/>
|
/>
|
||||||
</StyledTopBarButtonContainer>
|
</StyledTopBarButtonContainer>
|
||||||
@ -117,26 +110,27 @@ export function PageBar({
|
|||||||
</StyledTitleContainer>
|
</StyledTitleContainer>
|
||||||
</StyledTopBarIconTitleContainer>
|
</StyledTopBarIconTitleContainer>
|
||||||
</StyledLeftContainer>
|
</StyledLeftContainer>
|
||||||
|
|
||||||
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||||
<StyledActionButtonsContainer>
|
<StyledActionButtonsContainer>
|
||||||
{onFavoriteButtonClick && (
|
{onFavoriteButtonClick && (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconHeart size={16} />}
|
icon={<IconHeart size={16} />}
|
||||||
size="large"
|
size="medium"
|
||||||
|
variant="secondary"
|
||||||
data-testid="add-button"
|
data-testid="add-button"
|
||||||
textColor={isFavorite ? 'danger' : 'secondary'}
|
accent={isFavorite ? 'danger' : 'default'}
|
||||||
onClick={onFavoriteButtonClick}
|
onClick={onFavoriteButtonClick}
|
||||||
variant="border"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{onAddButtonClick && (
|
{onAddButtonClick && (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconPlus size={16} />}
|
icon={<IconPlus size={16} />}
|
||||||
size="large"
|
size="medium"
|
||||||
|
variant="secondary"
|
||||||
data-testid="add-button"
|
data-testid="add-button"
|
||||||
textColor="secondary"
|
accent="default"
|
||||||
onClick={onAddButtonClick}
|
onClick={onAddButtonClick}
|
||||||
variant="border"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{extraButtons}
|
{extraButtons}
|
||||||
|
|||||||
@ -38,10 +38,10 @@ export function ShowPageAddButton({
|
|||||||
buttonComponents={
|
buttonComponents={
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconPlus size={16} />}
|
icon={<IconPlus size={16} />}
|
||||||
size="large"
|
size="medium"
|
||||||
data-testid="add-showpage-button"
|
dataTestId="add-showpage-button"
|
||||||
textColor={'secondary'}
|
accent="default"
|
||||||
variant="border"
|
variant="secondary"
|
||||||
onClick={toggleDropdownButton}
|
onClick={toggleDropdownButton}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
import { AnimatePresence, LayoutGroup } from 'framer-motion';
|
import { AnimatePresence, LayoutGroup } from 'framer-motion';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { TextInput } from '@/ui/input/text/components/TextInput';
|
import { TextInput } from '@/ui/input/text/components/TextInput';
|
||||||
import { Modal } from '@/ui/modal/components/Modal';
|
import { Modal } from '@/ui/modal/components/Modal';
|
||||||
import {
|
import {
|
||||||
@ -32,6 +32,7 @@ const StyledConfirmationModal = styled(Modal)`
|
|||||||
|
|
||||||
const StyledCenteredButton = styled(Button)`
|
const StyledCenteredButton = styled(Button)`
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCenteredTitle = styled.div`
|
const StyledCenteredTitle = styled.div`
|
||||||
@ -107,21 +108,19 @@ export function ConfirmationModal({
|
|||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
<StyledConfirmationButton
|
<StyledCenteredButton
|
||||||
onClick={onConfirmClick}
|
onClick={onConfirmClick}
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
|
accent="danger"
|
||||||
title={deleteButtonText}
|
title={deleteButtonText}
|
||||||
disabled={!isValidValue}
|
disabled={!isValidValue}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<StyledCenteredButton
|
<StyledCenteredButton
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
variant={ButtonVariant.Secondary}
|
variant="secondary"
|
||||||
title="Cancel"
|
title="Cancel"
|
||||||
fullWidth
|
fullWidth
|
||||||
style={{
|
|
||||||
marginTop: 10,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</StyledConfirmationModal>
|
</StyledConfirmationModal>
|
||||||
</LayoutGroup>
|
</LayoutGroup>
|
||||||
|
|||||||
@ -5,11 +5,7 @@ import { useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||||
import {
|
import { Button } from '@/ui/button/components/Button';
|
||||||
Button,
|
|
||||||
ButtonSize,
|
|
||||||
ButtonVariant,
|
|
||||||
} from '@/ui/button/components/Button';
|
|
||||||
import { IconHelpCircle } from '@/ui/icon';
|
import { IconHelpCircle } from '@/ui/icon';
|
||||||
|
|
||||||
const StyledButtonContainer = styled.div`
|
const StyledButtonContainer = styled.div`
|
||||||
@ -91,8 +87,8 @@ export default function SupportChat() {
|
|||||||
return isFrontChatLoaded ? (
|
return isFrontChatLoaded ? (
|
||||||
<StyledButtonContainer>
|
<StyledButtonContainer>
|
||||||
<Button
|
<Button
|
||||||
variant={ButtonVariant.Tertiary}
|
variant={'tertiary'}
|
||||||
size={ButtonSize.Small}
|
size={'small'}
|
||||||
title="Support"
|
title="Support"
|
||||||
icon={<IconHelpCircle size={theme.icon.size.md} />}
|
icon={<IconHelpCircle size={theme.icon.size.md} />}
|
||||||
onClick={handleSupportClick}
|
onClick={handleSupportClick}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ const StyledSoonPill = styled.span`
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => `0 ${theme.spacing(2)}`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function SoonPill() {
|
export function SoonPill() {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
||||||
import { IconChevronsRight } from '@/ui/icon/index';
|
import { IconChevronsRight } from '@/ui/icon/index';
|
||||||
|
|
||||||
import { IconButton } from '../../button/components/IconButton';
|
|
||||||
import { useRightDrawer } from '../hooks/useRightDrawer';
|
import { useRightDrawer } from '../hooks/useRightDrawer';
|
||||||
|
|
||||||
export function RightDrawerTopBarCloseButton() {
|
export function RightDrawerTopBarCloseButton() {
|
||||||
@ -11,9 +11,11 @@ export function RightDrawerTopBarCloseButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<LightIconButton
|
||||||
icon={<IconChevronsRight size={16} />}
|
icon={<IconChevronsRight />}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
|
size="medium"
|
||||||
|
accent="tertiary"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import {
|
|||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { IconButton } from '../../button/components/IconButton';
|
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
||||||
|
|
||||||
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
import { isRightDrawerExpandedState } from '../states/isRightDrawerExpandedState';
|
||||||
|
|
||||||
export function RightDrawerTopBarExpandButton() {
|
export function RightDrawerTopBarExpandButton() {
|
||||||
@ -17,12 +18,14 @@ export function RightDrawerTopBarExpandButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<LightIconButton
|
||||||
|
size="medium"
|
||||||
|
accent="tertiary"
|
||||||
icon={
|
icon={
|
||||||
isRightDrawerExpanded ? (
|
isRightDrawerExpanded ? (
|
||||||
<IconLayoutSidebarRightCollapse size={16} />
|
<IconLayoutSidebarRightCollapse />
|
||||||
) : (
|
) : (
|
||||||
<IconLayoutSidebarRightExpand size={16} />
|
<IconLayoutSidebarRightExpand />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
|
|||||||
@ -46,12 +46,12 @@ export const EntityTableColumnMenu = ({
|
|||||||
{hiddenColumns.map((column) => (
|
{hiddenColumns.map((column) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={column.id}
|
key={column.id}
|
||||||
actions={
|
actions={[
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconPlus size={theme.icon.size.sm} />}
|
icon={<IconPlus size={theme.icon.size.sm} />}
|
||||||
onClick={() => onAddColumn(column.id)}
|
onClick={() => onAddColumn(column.id)}
|
||||||
/>
|
/>,
|
||||||
}
|
]}
|
||||||
>
|
>
|
||||||
{column.columnIcon &&
|
{column.columnIcon &&
|
||||||
cloneElement(column.columnIcon, {
|
cloneElement(column.columnIcon, {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||||
|
|
||||||
@ -64,10 +63,6 @@ const StyledAddIconButtonWrapper = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledAddIconButton = styled(IconButton)`
|
|
||||||
border-radius: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
|
const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -76,8 +71,6 @@ const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export function EntityTableHeader() {
|
export function EntityTableHeader() {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const [offset, setOffset] = useRecoilState(resizeFieldOffsetState);
|
const [offset, setOffset] = useRecoilState(resizeFieldOffsetState);
|
||||||
const [columns, setColumns] = useRecoilScopedState(
|
const [columns, setColumns] = useRecoilScopedState(
|
||||||
tableColumnsScopedState,
|
tableColumnsScopedState,
|
||||||
@ -207,9 +200,9 @@ export function EntityTableHeader() {
|
|||||||
<th>
|
<th>
|
||||||
{hiddenColumns.length > 0 && (
|
{hiddenColumns.length > 0 && (
|
||||||
<StyledAddIconButtonWrapper>
|
<StyledAddIconButtonWrapper>
|
||||||
<StyledAddIconButton
|
<IconButton
|
||||||
size="large"
|
size="medium"
|
||||||
icon={<IconPlus size={theme.icon.size.md} />}
|
icon={<IconPlus />}
|
||||||
onClick={toggleColumnMenu}
|
onClick={toggleColumnMenu}
|
||||||
/>
|
/>
|
||||||
{isColumnMenuOpen && (
|
{isColumnMenuOpen && (
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
|||||||
import { IconPencil } from '@tabler/icons-react';
|
import { IconPencil } from '@tabler/icons-react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
|
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
|
||||||
@ -114,8 +114,7 @@ export function EditableCell({
|
|||||||
transition={{ duration: 0.1 }}
|
transition={{ duration: 0.1 }}
|
||||||
whileHover={{ scale: 1.04 }}
|
whileHover={{ scale: 1.04 }}
|
||||||
>
|
>
|
||||||
<IconButton
|
<FloatingIconButton
|
||||||
variant="shadow"
|
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handlePenClick}
|
onClick={handlePenClick}
|
||||||
icon={<IconPencil size={14} />}
|
icon={<IconPencil size={14} />}
|
||||||
|
|||||||
@ -109,20 +109,22 @@ export function TableOptionsDropdownContent({
|
|||||||
const renderFieldActions = useCallback(
|
const renderFieldActions = useCallback(
|
||||||
(column: ViewFieldDefinition<ViewFieldMetadata>) =>
|
(column: ViewFieldDefinition<ViewFieldMetadata>) =>
|
||||||
// Do not allow hiding last visible column
|
// Do not allow hiding last visible column
|
||||||
!column.isVisible || visibleColumns.length > 1 ? (
|
!column.isVisible || visibleColumns.length > 1
|
||||||
<IconButton
|
? [
|
||||||
icon={
|
<IconButton
|
||||||
column.isVisible ? (
|
icon={
|
||||||
<IconMinus size={theme.icon.size.sm} />
|
column.isVisible ? (
|
||||||
) : (
|
<IconMinus size={theme.icon.size.sm} />
|
||||||
<IconPlus size={theme.icon.size.sm} />
|
) : (
|
||||||
)
|
<IconPlus size={theme.icon.size.sm} />
|
||||||
}
|
)
|
||||||
onClick={() =>
|
}
|
||||||
handleColumnVisibilityChange(column.id, !column.isVisible)
|
onClick={() =>
|
||||||
}
|
handleColumnVisibilityChange(column.id, !column.isVisible)
|
||||||
/>
|
}
|
||||||
) : undefined,
|
/>,
|
||||||
|
]
|
||||||
|
: undefined,
|
||||||
[handleColumnVisibilityChange, theme.icon.size.sm, visibleColumns.length],
|
[handleColumnVisibilityChange, theme.icon.size.sm, visibleColumns.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { Button, ButtonSize } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||||
@ -136,7 +136,7 @@ export const TableUpdateViewButtonGroup = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<ButtonGroup size={ButtonSize.Small}>
|
<ButtonGroup size="small">
|
||||||
<Button
|
<Button
|
||||||
title="Update view"
|
title="Update view"
|
||||||
disabled={
|
disabled={
|
||||||
@ -146,7 +146,7 @@ export const TableUpdateViewButtonGroup = ({
|
|||||||
onClick={handleViewSubmit}
|
onClick={handleViewSubmit}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
size={ButtonSize.Small}
|
size="small"
|
||||||
icon={<IconChevronDown />}
|
icon={<IconChevronDown />}
|
||||||
onClick={handleArrowDownButtonClick}
|
onClick={handleArrowDownButtonClick}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,6 +5,8 @@ export const accentLight = {
|
|||||||
secondary: color.blueAccent20,
|
secondary: color.blueAccent20,
|
||||||
tertiary: color.blueAccent15,
|
tertiary: color.blueAccent15,
|
||||||
quaternary: color.blueAccent10,
|
quaternary: color.blueAccent10,
|
||||||
|
accent3570: color.blueAccent35,
|
||||||
|
accent4060: color.blueAccent40,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accentDark = {
|
export const accentDark = {
|
||||||
@ -12,4 +14,6 @@ export const accentDark = {
|
|||||||
secondary: color.blueAccent80,
|
secondary: color.blueAccent80,
|
||||||
tertiary: color.blueAccent85,
|
tertiary: color.blueAccent85,
|
||||||
quaternary: color.blueAccent90,
|
quaternary: color.blueAccent90,
|
||||||
|
accent3570: color.blueAccent70,
|
||||||
|
accent4060: color.blueAccent60,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -114,9 +114,13 @@ export const color = {
|
|||||||
gray20: grayScale.gray15,
|
gray20: grayScale.gray15,
|
||||||
gray10: grayScale.gray10,
|
gray10: grayScale.gray10,
|
||||||
blueAccent90: '#141a25',
|
blueAccent90: '#141a25',
|
||||||
blueAccent85: '#151D2E',
|
blueAccent85: '#151d2e',
|
||||||
blueAccent80: '#152037',
|
blueAccent80: '#152037',
|
||||||
blueAccent75: '#16233F',
|
blueAccent75: '#16233f',
|
||||||
|
blueAccent70: '#17294a',
|
||||||
|
blueAccent60: '#18356d',
|
||||||
|
blueAccent40: '#a3c0f8',
|
||||||
|
blueAccent35: '#c8d9fb',
|
||||||
blueAccent25: '#dae6fc',
|
blueAccent25: '#dae6fc',
|
||||||
blueAccent20: '#e2ecfd',
|
blueAccent20: '#e2ecfd',
|
||||||
blueAccent15: '#edf2fe',
|
blueAccent15: '#edf2fe',
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button } from '@/ui/button/components/Button';
|
||||||
import { IconCopy, IconLink } from '@/ui/icon';
|
import { IconCopy, IconLink } from '@/ui/icon';
|
||||||
import { TextInput } from '@/ui/input/text/components/TextInput';
|
import { TextInput } from '@/ui/input/text/components/TextInput';
|
||||||
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/snack-bar/hooks/useSnackBar';
|
||||||
@ -33,7 +33,7 @@ export function WorkspaceInviteLink({ inviteLink }: OwnProps) {
|
|||||||
</StyledLinkContainer>
|
</StyledLinkContainer>
|
||||||
<Button
|
<Button
|
||||||
icon={<IconLink size={theme.icon.size.md} />}
|
icon={<IconLink size={theme.icon.size.md} />}
|
||||||
variant={ButtonVariant.Primary}
|
variant={'primary'}
|
||||||
title="Copy link"
|
title="Copy link"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
enqueueSnackBar('Link copied to clipboard', {
|
enqueueSnackBar('Link copied to clipboard', {
|
||||||
|
|||||||
@ -4,11 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import {
|
import { Button } from '@/ui/button/components/Button';
|
||||||
Button,
|
|
||||||
ButtonSize,
|
|
||||||
ButtonVariant,
|
|
||||||
} from '@/ui/button/components/Button';
|
|
||||||
import { IconSettings, 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 { ConfirmationModal } from '@/ui/modal/components/ConfirmationModal';
|
import { ConfirmationModal } from '@/ui/modal/components/ConfirmationModal';
|
||||||
@ -115,8 +111,8 @@ export function SettingsWorkspaceMembers() {
|
|||||||
setIsConfirmationModalOpen(true);
|
setIsConfirmationModalOpen(true);
|
||||||
setUserToDelete(member.user.id);
|
setUserToDelete(member.user.id);
|
||||||
}}
|
}}
|
||||||
variant={ButtonVariant.Tertiary}
|
variant={'tertiary'}
|
||||||
size={ButtonSize.Small}
|
size={'small'}
|
||||||
icon={<IconTrash size={theme.icon.size.md} />}
|
icon={<IconTrash size={theme.icon.size.md} />}
|
||||||
/>
|
/>
|
||||||
</StyledButtonContainer>
|
</StyledButtonContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user