Refactor UI folder (#2016)
* Added Overview page * Revised Getting Started page * Minor revision * Edited readme, minor modifications to docs * Removed sweep.yaml, .devcontainer, .ergomake * Moved security.md to .github, added contributing.md * changes as per code review * updated contributing.md * fixed broken links & added missing links in doc, improved structure * fixed link in wsl setup * fixed server link, added https cloning in yarn-setup * removed package-lock.json * added doc card, admonitions * removed underline from nav buttons * refactoring modules/ui * refactoring modules/ui * Change folder case * Fix theme location * Fix case 2 * Fix storybook --------- Co-authored-by: Nimra Ahmed <nimra1408@gmail.com> Co-authored-by: Nimra Ahmed <50912134+nimraahmed@users.noreply.github.com>
This commit is contained in:
313
front/src/modules/ui/input/button/components/Button.tsx
Normal file
313
front/src/modules/ui/input/button/components/Button.tsx
Normal file
@ -0,0 +1,313 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
import { SoonPill } from '@/ui/display/pill/components/SoonPill';
|
||||
|
||||
export type ButtonSize = 'medium' | 'small';
|
||||
export type ButtonPosition = 'standalone' | 'left' | 'middle' | 'right';
|
||||
export type ButtonVariant = 'primary' | 'secondary' | 'tertiary';
|
||||
export type ButtonAccent = 'default' | 'blue' | 'danger';
|
||||
|
||||
export type ButtonProps = {
|
||||
className?: string;
|
||||
Icon?: IconComponent;
|
||||
title?: string;
|
||||
fullWidth?: boolean;
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
position?: ButtonPosition;
|
||||
accent?: ButtonAccent;
|
||||
soon?: boolean;
|
||||
disabled?: boolean;
|
||||
focus?: boolean;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
|
||||
const StyledButton = styled.button<
|
||||
Pick<
|
||||
ButtonProps,
|
||||
'fullWidth' | 'variant' | 'size' | 'position' | 'accent' | 'focus'
|
||||
>
|
||||
>`
|
||||
align-items: center;
|
||||
${({ theme, variant, accent, disabled, focus }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return `
|
||||
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 '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'
|
||||
? 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, 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;
|
||||
}
|
||||
}};
|
||||
border-style: solid;
|
||||
border-width: ${({ variant, position }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'secondary':
|
||||
return position === 'middle' ? '1px 0px' : '1px';
|
||||
case 'tertiary':
|
||||
return '0';
|
||||
}
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: 500;
|
||||
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;
|
||||
|
||||
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Button = ({
|
||||
className,
|
||||
Icon,
|
||||
title,
|
||||
fullWidth = false,
|
||||
variant = 'primary',
|
||||
size = 'medium',
|
||||
accent = 'default',
|
||||
position = 'standalone',
|
||||
soon = false,
|
||||
disabled = false,
|
||||
focus = false,
|
||||
onClick,
|
||||
}: ButtonProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
fullWidth={fullWidth}
|
||||
variant={variant}
|
||||
size={size}
|
||||
position={position}
|
||||
disabled={soon || disabled}
|
||||
focus={focus}
|
||||
accent={accent}
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||
{title}
|
||||
{soon && <SoonPill />}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
57
front/src/modules/ui/input/button/components/ButtonGroup.tsx
Normal file
57
front/src/modules/ui/input/button/components/ButtonGroup.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ButtonPosition, ButtonProps } from './Button';
|
||||
|
||||
const StyledButtonGroupContainer = styled.div`
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export type ButtonGroupProps = Pick<
|
||||
ButtonProps,
|
||||
'variant' | 'size' | 'accent'
|
||||
> & {
|
||||
className?: string;
|
||||
children: ReactNode[];
|
||||
};
|
||||
|
||||
export const ButtonGroup = ({
|
||||
className,
|
||||
children,
|
||||
variant,
|
||||
size,
|
||||
accent,
|
||||
}: ButtonGroupProps) => (
|
||||
<StyledButtonGroupContainer className={className}>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
if (!React.isValidElement(child)) return null;
|
||||
|
||||
let position: ButtonPosition;
|
||||
|
||||
if (index === 0) {
|
||||
position = 'left';
|
||||
} else if (index === children.length - 1) {
|
||||
position = 'right';
|
||||
} else {
|
||||
position = 'middle';
|
||||
}
|
||||
|
||||
const additionalProps: any = { position, variant, accent, size };
|
||||
|
||||
if (variant) {
|
||||
additionalProps.variant = variant;
|
||||
}
|
||||
|
||||
if (accent) {
|
||||
additionalProps.variant = variant;
|
||||
}
|
||||
|
||||
if (size) {
|
||||
additionalProps.size = size;
|
||||
}
|
||||
|
||||
return React.cloneElement(child, additionalProps);
|
||||
})}
|
||||
</StyledButtonGroupContainer>
|
||||
);
|
||||
106
front/src/modules/ui/input/button/components/FloatingButton.tsx
Normal file
106
front/src/modules/ui/input/button/components/FloatingButton.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
export type FloatingButtonSize = 'small' | 'medium';
|
||||
export type FloatingButtonPosition = 'standalone' | 'left' | 'middle' | 'right';
|
||||
|
||||
export type FloatingButtonProps = {
|
||||
className?: string;
|
||||
Icon?: IconComponent;
|
||||
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 const FloatingButton = ({
|
||||
className,
|
||||
Icon,
|
||||
title,
|
||||
size = 'small',
|
||||
applyBlur = true,
|
||||
applyShadow = true,
|
||||
disabled = false,
|
||||
focus = false,
|
||||
}: FloatingButtonProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledButton
|
||||
disabled={disabled}
|
||||
focus={focus && !disabled}
|
||||
size={size}
|
||||
applyBlur={applyBlur}
|
||||
applyShadow={applyShadow}
|
||||
className={className}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||
{title}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,48 @@
|
||||
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 const FloatingButtonGroup = ({
|
||||
children,
|
||||
size,
|
||||
}: FloatingButtonGroupProps) => (
|
||||
<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>
|
||||
);
|
||||
@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
export type FloatingIconButtonSize = 'small' | 'medium';
|
||||
export type FloatingIconButtonPosition =
|
||||
| 'standalone'
|
||||
| 'left'
|
||||
| 'middle'
|
||||
| 'right';
|
||||
|
||||
export type FloatingIconButtonProps = {
|
||||
className?: string;
|
||||
Icon?: IconComponent;
|
||||
size?: FloatingIconButtonSize;
|
||||
position?: FloatingIconButtonPosition;
|
||||
applyShadow?: boolean;
|
||||
applyBlur?: boolean;
|
||||
disabled?: boolean;
|
||||
focus?: boolean;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
const StyledButton = styled.button<
|
||||
Pick<
|
||||
FloatingIconButtonProps,
|
||||
'size' | 'position' | 'applyShadow' | 'applyBlur' | 'focus' | 'isActive'
|
||||
>
|
||||
>`
|
||||
align-items: center;
|
||||
backdrop-filter: ${({ applyBlur }) => (applyBlur ? 'blur(20px)' : 'none')};
|
||||
background: ${({ theme, isActive }) =>
|
||||
!!isActive
|
||||
? theme.background.transparent.medium
|
||||
: 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'};
|
||||
box-sizing: border-box;
|
||||
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)};
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
transition: background ${({ theme }) => theme.animation.duration.instant}s
|
||||
ease;
|
||||
white-space: nowrap;
|
||||
|
||||
${({ position, size }) => {
|
||||
const sizeInPx =
|
||||
(size === 'small' ? 24 : 32) - (position === 'standalone' ? 0 : 4);
|
||||
|
||||
return `
|
||||
height: ${sizeInPx}px;
|
||||
width: ${sizeInPx}px;
|
||||
`;
|
||||
}}
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme, isActive }) =>
|
||||
!!isActive ?? theme.background.transparent.lighter};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: ${({ theme, disabled }) =>
|
||||
!disabled ? theme.background.transparent.medium : 'transparent'};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FloatingIconButton = ({
|
||||
className,
|
||||
Icon,
|
||||
size = 'small',
|
||||
position = 'standalone',
|
||||
applyShadow = true,
|
||||
applyBlur = true,
|
||||
disabled = false,
|
||||
focus = false,
|
||||
onClick,
|
||||
isActive,
|
||||
}: FloatingIconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledButton
|
||||
disabled={disabled}
|
||||
focus={focus && !disabled}
|
||||
size={size}
|
||||
applyShadow={applyShadow}
|
||||
applyBlur={applyBlur}
|
||||
className={className}
|
||||
position={position}
|
||||
onClick={onClick}
|
||||
isActive={isActive}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,64 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
import {
|
||||
FloatingIconButton,
|
||||
FloatingIconButtonPosition,
|
||||
FloatingIconButtonProps,
|
||||
} from './FloatingIconButton';
|
||||
|
||||
const StyledFloatingIconButtonGroupContainer = styled.div`
|
||||
backdrop-filter: blur(20px);
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
box-shadow: ${({ theme }) =>
|
||||
`0px 2px 4px 0px ${theme.background.transparent.light}, 0px 0px 4px 0px ${theme.background.transparent.medium}`};
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
`;
|
||||
|
||||
export type FloatingIconButtonGroupProps = Pick<
|
||||
FloatingIconButtonProps,
|
||||
'className' | 'size'
|
||||
> & {
|
||||
iconButtons: {
|
||||
Icon: IconComponent;
|
||||
onClick?: (event: MouseEvent<any>) => void;
|
||||
isActive?: boolean;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const FloatingIconButtonGroup = ({
|
||||
iconButtons,
|
||||
size,
|
||||
className,
|
||||
}: FloatingIconButtonGroupProps) => (
|
||||
<StyledFloatingIconButtonGroupContainer className={className}>
|
||||
{iconButtons.map(({ Icon, onClick, isActive }, index) => {
|
||||
const position: FloatingIconButtonPosition =
|
||||
iconButtons.length === 1
|
||||
? 'standalone'
|
||||
: index === 0
|
||||
? 'left'
|
||||
: index === iconButtons.length - 1
|
||||
? 'right'
|
||||
: 'middle';
|
||||
|
||||
return (
|
||||
<FloatingIconButton
|
||||
key={`floating-icon-button-${index}`}
|
||||
applyBlur={false}
|
||||
applyShadow={false}
|
||||
Icon={Icon}
|
||||
onClick={onClick}
|
||||
position={position}
|
||||
size={size}
|
||||
isActive={isActive}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledFloatingIconButtonGroupContainer>
|
||||
);
|
||||
303
front/src/modules/ui/input/button/components/IconButton.tsx
Normal file
303
front/src/modules/ui/input/button/components/IconButton.tsx
Normal file
@ -0,0 +1,303 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
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 IconButtonProps = {
|
||||
className?: string;
|
||||
Icon?: IconComponent;
|
||||
variant?: IconButtonVariant;
|
||||
size?: IconButtonSize;
|
||||
position?: IconButtonPosition;
|
||||
accent?: IconButtonAccent;
|
||||
disabled?: boolean;
|
||||
focus?: boolean;
|
||||
dataTestId?: string;
|
||||
ariaLabel?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
|
||||
const StyledButton = styled.button<
|
||||
Pick<IconButtonProps, 'variant' | 'size' | 'position' | 'accent' | 'focus'>
|
||||
>`
|
||||
align-items: center;
|
||||
${({ theme, variant, accent, disabled, focus }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
switch (accent) {
|
||||
case 'default':
|
||||
return `
|
||||
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 '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-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;
|
||||
}
|
||||
}};
|
||||
border-style: solid;
|
||||
border-width: ${({ variant, position }) => {
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
case 'secondary':
|
||||
return position === 'middle' ? '1px 0px' : '1px';
|
||||
case 'tertiary':
|
||||
return '0';
|
||||
}
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: 500;
|
||||
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')};
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const IconButton = ({
|
||||
className,
|
||||
Icon,
|
||||
variant = 'primary',
|
||||
size = 'medium',
|
||||
accent = 'default',
|
||||
position = 'standalone',
|
||||
disabled = false,
|
||||
focus = false,
|
||||
dataTestId,
|
||||
ariaLabel,
|
||||
onClick,
|
||||
}: IconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledButton
|
||||
data-testid={dataTestId}
|
||||
variant={variant}
|
||||
size={size}
|
||||
position={position}
|
||||
disabled={disabled}
|
||||
focus={focus}
|
||||
accent={accent}
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
import { IconButton, IconButtonPosition, IconButtonProps } from './IconButton';
|
||||
|
||||
const StyledIconButtonGroupContainer = styled.div`
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export type IconButtonGroupProps = Pick<
|
||||
IconButtonProps,
|
||||
'accent' | 'size' | 'variant'
|
||||
> & {
|
||||
iconButtons: {
|
||||
Icon: IconComponent;
|
||||
onClick?: (event: MouseEvent<any>) => void;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const IconButtonGroup = ({
|
||||
accent,
|
||||
iconButtons,
|
||||
size,
|
||||
variant,
|
||||
}: IconButtonGroupProps) => (
|
||||
<StyledIconButtonGroupContainer>
|
||||
{iconButtons.map(({ Icon, onClick }, index) => {
|
||||
const position: IconButtonPosition =
|
||||
index === 0
|
||||
? 'left'
|
||||
: index === iconButtons.length - 1
|
||||
? 'right'
|
||||
: 'middle';
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
accent={accent}
|
||||
Icon={Icon}
|
||||
onClick={onClick}
|
||||
position={position}
|
||||
size={size}
|
||||
variant={variant}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledIconButtonGroupContainer>
|
||||
);
|
||||
110
front/src/modules/ui/input/button/components/LightButton.tsx
Normal file
110
front/src/modules/ui/input/button/components/LightButton.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import React, { MouseEvent, useMemo } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { TablerIconsProps } from '@/ui/display/icon';
|
||||
|
||||
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 const 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>
|
||||
);
|
||||
};
|
||||
110
front/src/modules/ui/input/button/components/LightIconButton.tsx
Normal file
110
front/src/modules/ui/input/button/components/LightIconButton.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { ComponentProps, MouseEvent } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
export type LightIconButtonAccent = 'secondary' | 'tertiary';
|
||||
export type LightIconButtonSize = 'small' | 'medium';
|
||||
|
||||
export type LightIconButtonProps = {
|
||||
className?: string;
|
||||
testId?: string;
|
||||
Icon?: IconComponent;
|
||||
title?: string;
|
||||
size?: LightIconButtonSize;
|
||||
accent?: LightIconButtonAccent;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
focus?: boolean;
|
||||
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
||||
} & Pick<ComponentProps<'button'>, 'aria-label'>;
|
||||
|
||||
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 const LightIconButton = ({
|
||||
'aria-label': ariaLabel,
|
||||
className,
|
||||
testId,
|
||||
Icon,
|
||||
active = false,
|
||||
size = 'small',
|
||||
accent = 'secondary',
|
||||
disabled = false,
|
||||
focus = false,
|
||||
onClick,
|
||||
}: LightIconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledButton
|
||||
data-testid={testId}
|
||||
aria-label={ariaLabel}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
focus={focus && !disabled}
|
||||
accent={accent}
|
||||
className={className}
|
||||
size={size}
|
||||
active={active}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
117
front/src/modules/ui/input/button/components/MainButton.tsx
Normal file
117
front/src/modules/ui/input/button/components/MainButton.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
type Variant = 'primary' | 'secondary';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
fullWidth?: boolean;
|
||||
variant?: Variant;
|
||||
soon?: boolean;
|
||||
} & React.ComponentProps<'button'>;
|
||||
|
||||
const StyledButton = styled.button<Pick<Props, 'fullWidth' | 'variant'>>`
|
||||
align-items: center;
|
||||
background: ${({ theme, variant, disabled }) => {
|
||||
if (disabled) {
|
||||
return theme.background.secondary;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
return theme.background.radialGradient;
|
||||
case 'secondary':
|
||||
return theme.background.primary;
|
||||
default:
|
||||
return theme.background.primary;
|
||||
}
|
||||
}};
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme, disabled, variant }) => {
|
||||
if (disabled) {
|
||||
return theme.background.transparent.lighter;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
return theme.background.transparent.light;
|
||||
case 'secondary':
|
||||
return theme.border.color.medium;
|
||||
default:
|
||||
return theme.background.primary;
|
||||
}
|
||||
}};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
${({ theme, disabled }) => {
|
||||
if (disabled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `box-shadow: ${theme.boxShadow.light};`;
|
||||
}}
|
||||
color: ${({ theme, variant, disabled }) => {
|
||||
if (disabled) {
|
||||
return theme.font.color.light;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case 'primary':
|
||||
return theme.grayScale.gray0;
|
||||
case 'secondary':
|
||||
return theme.font.color.primary;
|
||||
default:
|
||||
return theme.font.color.primary;
|
||||
}
|
||||
}};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(3)};
|
||||
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
|
||||
${({ theme, variant }) => {
|
||||
switch (variant) {
|
||||
case 'secondary':
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.tertiary};
|
||||
}
|
||||
`;
|
||||
default:
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.radialGradientHover}};
|
||||
}
|
||||
`;
|
||||
}
|
||||
}};
|
||||
`;
|
||||
|
||||
type MainButtonProps = Props & {
|
||||
Icon?: IconComponent;
|
||||
};
|
||||
|
||||
export const MainButton = ({
|
||||
Icon,
|
||||
title,
|
||||
fullWidth = false,
|
||||
variant = 'primary',
|
||||
type,
|
||||
onClick,
|
||||
disabled,
|
||||
}: MainButtonProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledButton {...{ disabled, fullWidth, onClick, type, variant }}>
|
||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||
{title}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,48 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
const StyledIconButton = styled.button`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.color.blue};
|
||||
border: none;
|
||||
|
||||
border-radius: 50%;
|
||||
color: ${({ theme }) => theme.font.color.inverted};
|
||||
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
outline: none;
|
||||
padding: 0;
|
||||
transition: color 0.1s ease-in-out, background 0.1s ease-in-out;
|
||||
|
||||
&:disabled {
|
||||
background: ${({ theme }) => theme.background.quaternary};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
cursor: default;
|
||||
}
|
||||
width: 20px;
|
||||
`;
|
||||
|
||||
type RoundedIconButtonProps = {
|
||||
Icon: IconComponent;
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
export const RoundedIconButton = ({
|
||||
Icon,
|
||||
onClick,
|
||||
disabled,
|
||||
}: RoundedIconButtonProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledIconButton {...{ disabled, onClick }}>
|
||||
{<Icon size={theme.icon.size.md} />}
|
||||
</StyledIconButton>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
{/* Button.mdx */}
|
||||
|
||||
import { Meta, Controls, Story } from '@storybook/blocks';
|
||||
import * as ButtonStories from './Button.stories';
|
||||
|
||||
<Meta of={ButtonStories} />
|
||||
|
||||
Button is a clickable interactive element that triggers a response.
|
||||
|
||||
You can place text and icons inside of a button.
|
||||
|
||||
Buttons are often used for form submissions and to toggle elements into view.
|
||||
|
||||
<Story of={ButtonStories.Default} />
|
||||
<br />
|
||||
|
||||
## Properties
|
||||
|
||||
<Controls />
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
|
||||
<Button title='Click me' />
|
||||
```
|
||||
@ -0,0 +1,279 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconSearch } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
import {
|
||||
Button,
|
||||
ButtonAccent,
|
||||
ButtonPosition,
|
||||
ButtonSize,
|
||||
ButtonVariant,
|
||||
} from '../Button';
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
title: 'UI/Button/Button',
|
||||
component: Button,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Button>;
|
||||
|
||||
export const Default: Story = {
|
||||
argTypes: {
|
||||
Icon: { control: false },
|
||||
},
|
||||
args: {
|
||||
title: 'Button',
|
||||
size: 'small',
|
||||
variant: 'primary',
|
||||
accent: 'danger',
|
||||
disabled: false,
|
||||
focus: false,
|
||||
fullWidth: false,
|
||||
soon: false,
|
||||
position: 'standalone',
|
||||
Icon: IconSearch,
|
||||
className: '',
|
||||
},
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export const Catalog: CatalogStory<Story, typeof Button> = {
|
||||
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 },
|
||||
className: { control: false },
|
||||
},
|
||||
parameters: {
|
||||
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||
catalog: {
|
||||
dimensions: [
|
||||
{
|
||||
name: 'sizes',
|
||||
values: ['small', 'medium'] satisfies ButtonSize[],
|
||||
props: (size: ButtonSize) => ({ 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 ButtonAccent[],
|
||||
props: (accent: ButtonAccent) => ({ accent }),
|
||||
},
|
||||
{
|
||||
name: 'variants',
|
||||
values: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'tertiary',
|
||||
] satisfies ButtonVariant[],
|
||||
props: (variant: ButtonVariant) => ({ variant }),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
decorators: [CatalogDecorator],
|
||||
};
|
||||
|
||||
export const SoonCatalog: CatalogStory<Story, typeof Button> = {
|
||||
args: { title: 'Filter', Icon: IconSearch, soon: true },
|
||||
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 },
|
||||
className: { control: false },
|
||||
},
|
||||
parameters: {
|
||||
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||
catalog: {
|
||||
dimensions: [
|
||||
{
|
||||
name: 'sizes',
|
||||
values: ['small', 'medium'] satisfies ButtonSize[],
|
||||
props: (size: ButtonSize) => ({ 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 ButtonAccent[],
|
||||
props: (accent: ButtonAccent) => ({ accent }),
|
||||
},
|
||||
{
|
||||
name: 'variants',
|
||||
values: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'tertiary',
|
||||
] satisfies ButtonVariant[],
|
||||
props: (variant: ButtonVariant) => ({ variant }),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
decorators: [CatalogDecorator],
|
||||
};
|
||||
|
||||
export const PositionCatalog: CatalogStory<Story, typeof Button> = {
|
||||
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: {
|
||||
pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] },
|
||||
catalog: {
|
||||
dimensions: [
|
||||
{
|
||||
name: 'positions',
|
||||
values: [
|
||||
'standalone',
|
||||
'left',
|
||||
'middle',
|
||||
'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],
|
||||
};
|
||||
|
||||
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],
|
||||
};
|
||||
@ -0,0 +1,77 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
import { Button, ButtonAccent, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { ButtonGroup } from '../ButtonGroup';
|
||||
|
||||
const meta: Meta<typeof ButtonGroup> = {
|
||||
title: 'UI/Button/ButtonGroup',
|
||||
component: ButtonGroup,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ButtonGroup>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
size: 'small',
|
||||
variant: 'primary',
|
||||
accent: 'danger',
|
||||
children: [
|
||||
<Button Icon={IconNotes} title="Note" />,
|
||||
<Button Icon={IconCheckbox} title="Task" />,
|
||||
<Button Icon={IconTimelineEvent} title="Activity" />,
|
||||
],
|
||||
},
|
||||
argTypes: {
|
||||
children: { control: false },
|
||||
},
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export const Catalog: CatalogStory<Story, typeof ButtonGroup> = {
|
||||
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,84 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconSearch } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
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: CatalogStory<Story, typeof FloatingButton> = {
|
||||
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,59 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
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: CatalogStory<Story, typeof FloatingButtonGroup> = {
|
||||
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,85 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconSearch } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
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: CatalogStory<Story, typeof FloatingIconButton> = {
|
||||
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,53 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
import { FloatingIconButtonSize } from '../FloatingIconButton';
|
||||
import { FloatingIconButtonGroup } from '../FloatingIconButtonGroup';
|
||||
|
||||
const meta: Meta<typeof FloatingIconButtonGroup> = {
|
||||
title: 'UI/Button/FloatingIconButtonGroup',
|
||||
component: FloatingIconButtonGroup,
|
||||
args: {
|
||||
iconButtons: [
|
||||
{ Icon: IconNotes },
|
||||
{ Icon: IconCheckbox },
|
||||
{ Icon: IconTimelineEvent },
|
||||
],
|
||||
},
|
||||
argTypes: {
|
||||
iconButtons: { control: false },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof FloatingIconButtonGroup>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
size: 'small',
|
||||
},
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export const Catalog: CatalogStory<Story, typeof FloatingIconButtonGroup> = {
|
||||
argTypes: {
|
||||
size: { 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],
|
||||
};
|
||||
@ -0,0 +1,180 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconSearch } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
import {
|
||||
IconButton,
|
||||
IconButtonAccent,
|
||||
IconButtonPosition,
|
||||
IconButtonSize,
|
||||
IconButtonVariant,
|
||||
} from '../IconButton';
|
||||
|
||||
const meta: Meta<typeof IconButton> = {
|
||||
title: 'UI/Button/IconButton',
|
||||
component: IconButton,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof IconButton>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
size: 'small',
|
||||
variant: 'primary',
|
||||
accent: 'danger',
|
||||
disabled: false,
|
||||
focus: false,
|
||||
position: 'standalone',
|
||||
Icon: IconSearch,
|
||||
},
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export const Catalog: CatalogStory<Story, typeof IconButton> = {
|
||||
args: { Icon: IconSearch },
|
||||
argTypes: {
|
||||
size: { control: false },
|
||||
variant: { control: false },
|
||||
focus: { control: false },
|
||||
accent: { control: false },
|
||||
disabled: { control: false },
|
||||
Icon: { control: false },
|
||||
position: { control: false },
|
||||
},
|
||||
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 PositionCatalog: CatalogStory<Story, typeof IconButton> = {
|
||||
args: { Icon: IconSearch },
|
||||
argTypes: {
|
||||
size: { control: false },
|
||||
variant: { control: false },
|
||||
focus: { control: false },
|
||||
accent: { control: false },
|
||||
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],
|
||||
};
|
||||
@ -0,0 +1,74 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
import {
|
||||
IconButtonAccent,
|
||||
IconButtonSize,
|
||||
IconButtonVariant,
|
||||
} from '../IconButton';
|
||||
import { IconButtonGroup } from '../IconButtonGroup';
|
||||
|
||||
const meta: Meta<typeof IconButtonGroup> = {
|
||||
title: 'UI/Button/IconButtonGroup',
|
||||
component: IconButtonGroup,
|
||||
args: {
|
||||
iconButtons: [
|
||||
{ Icon: IconNotes },
|
||||
{ Icon: IconCheckbox },
|
||||
{ Icon: IconTimelineEvent },
|
||||
],
|
||||
},
|
||||
argTypes: {
|
||||
iconButtons: { control: false },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof IconButtonGroup>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
size: 'small',
|
||||
variant: 'primary',
|
||||
accent: 'danger',
|
||||
},
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export const Catalog: CatalogStory<Story, typeof IconButtonGroup> = {
|
||||
argTypes: {
|
||||
size: { control: false },
|
||||
variant: { control: false },
|
||||
accent: { control: false },
|
||||
},
|
||||
parameters: {
|
||||
catalog: {
|
||||
dimensions: [
|
||||
{
|
||||
name: 'sizes',
|
||||
values: ['small', 'medium'] satisfies IconButtonSize[],
|
||||
props: (size: IconButtonSize) => ({ size }),
|
||||
},
|
||||
{
|
||||
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],
|
||||
};
|
||||
@ -0,0 +1,88 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconSearch } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
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: CatalogStory<Story, typeof LightButton> = {
|
||||
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,97 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconSearch } from '@/ui/display/icon';
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { CatalogStory } from '~/testing/types';
|
||||
|
||||
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: CatalogStory<Story, typeof LightIconButton> = {
|
||||
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],
|
||||
};
|
||||
@ -0,0 +1,60 @@
|
||||
import { expect, jest } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { IconBrandGoogle } from '@/ui/display/icon';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { MainButton } from '../MainButton';
|
||||
|
||||
const clickJestFn = jest.fn();
|
||||
|
||||
const meta: Meta<typeof MainButton> = {
|
||||
title: 'UI/Button/MainButton',
|
||||
component: MainButton,
|
||||
decorators: [ComponentDecorator],
|
||||
args: { title: 'A primary Button', onClick: clickJestFn },
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof MainButton>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(clickJestFn).toHaveBeenCalledTimes(0);
|
||||
const button = canvas.getByRole('button');
|
||||
await userEvent.click(button);
|
||||
|
||||
expect(clickJestFn).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: { Icon: IconBrandGoogle },
|
||||
};
|
||||
|
||||
export const DisabledWithIcon: Story = {
|
||||
args: { ...WithIcon.args, disabled: true },
|
||||
};
|
||||
|
||||
export const FullWidth: Story = {
|
||||
args: { fullWidth: true },
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: { title: 'A secondary Button', variant: 'secondary' },
|
||||
};
|
||||
|
||||
export const SecondaryWithIcon: Story = {
|
||||
args: { ...Secondary.args, ...WithIcon.args },
|
||||
};
|
||||
|
||||
export const SecondaryDisabledWithIcon: Story = {
|
||||
args: { ...SecondaryWithIcon.args, disabled: true },
|
||||
};
|
||||
|
||||
export const SecondaryFullWidth: Story = {
|
||||
args: { ...Secondary.args, ...FullWidth.args },
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
import { expect, jest } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { IconArrowRight } from '@/ui/display/icon';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { RoundedIconButton } from '../RoundedIconButton';
|
||||
|
||||
const clickJestFn = jest.fn();
|
||||
|
||||
const meta: Meta<typeof RoundedIconButton> = {
|
||||
title: 'UI/Button/RoundedIconButton',
|
||||
component: RoundedIconButton,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RoundedIconButton>;
|
||||
|
||||
export const Default: Story = {
|
||||
decorators: [ComponentDecorator],
|
||||
argTypes: { Icon: { control: false } },
|
||||
args: { onClick: clickJestFn, Icon: IconArrowRight },
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(clickJestFn).toHaveBeenCalledTimes(0);
|
||||
const button = canvas.getByRole('button');
|
||||
await userEvent.click(button);
|
||||
|
||||
expect(clickJestFn).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,237 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
AnimatePresence,
|
||||
AnimationControls,
|
||||
motion,
|
||||
useAnimation,
|
||||
} from 'framer-motion';
|
||||
|
||||
import { Checkmark } from '@/ui/display/checkmark/components/Checkmark';
|
||||
import DarkNoise from '@/ui/theme/assets/dark-noise.jpg';
|
||||
import LightNoise from '@/ui/theme/assets/light-noise.png';
|
||||
import { ColorScheme } from '~/generated/graphql';
|
||||
|
||||
const StyledColorSchemeBackground = styled.div<
|
||||
Pick<ColorSchemeCardProps, 'variant'>
|
||||
>`
|
||||
align-items: flex-end;
|
||||
background: ${({ variant }) => {
|
||||
switch (variant) {
|
||||
case 'dark':
|
||||
return `url(${DarkNoise.toString()});`;
|
||||
case 'light':
|
||||
default:
|
||||
return `url(${LightNoise.toString()});`;
|
||||
}
|
||||
}};
|
||||
border: ${({ variant, theme }) => {
|
||||
switch (variant) {
|
||||
case 'dark':
|
||||
return `1px solid ${theme.grayScale.gray70};`;
|
||||
case 'light':
|
||||
default:
|
||||
return `1px solid ${theme.grayScale.gray20};`;
|
||||
}
|
||||
}};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 80px;
|
||||
justify-content: flex-end;
|
||||
overflow: hidden;
|
||||
padding-left: ${({ theme }) => theme.spacing(6)};
|
||||
padding-top: ${({ theme }) => theme.spacing(6)};
|
||||
width: 120px;
|
||||
`;
|
||||
|
||||
const StyledColorSchemeContent = styled(motion.div)<
|
||||
Pick<ColorSchemeCardProps, 'variant'>
|
||||
>`
|
||||
background: ${({ theme, variant }) => {
|
||||
switch (variant) {
|
||||
case 'dark':
|
||||
return theme.grayScale.gray75;
|
||||
case 'light':
|
||||
return theme.grayScale.gray0;
|
||||
}
|
||||
}};
|
||||
|
||||
border-left: ${({ variant, theme }) => {
|
||||
switch (variant) {
|
||||
case 'dark':
|
||||
return `1px solid ${theme.grayScale.gray60};`;
|
||||
case 'light':
|
||||
default:
|
||||
return `1px solid ${theme.grayScale.gray20};`;
|
||||
}
|
||||
}};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md} 0px 0px 0px;
|
||||
border-top: ${({ variant, theme }) => {
|
||||
switch (variant) {
|
||||
case 'dark':
|
||||
return `1px solid ${theme.grayScale.gray60};`;
|
||||
case 'light':
|
||||
default:
|
||||
return `1px solid ${theme.grayScale.gray20};`;
|
||||
}
|
||||
}};
|
||||
box-sizing: border-box;
|
||||
color: ${({ variant, theme }) => {
|
||||
switch (variant) {
|
||||
case 'dark':
|
||||
return theme.grayScale.gray30;
|
||||
case 'light':
|
||||
default:
|
||||
return theme.grayScale.gray60;
|
||||
}
|
||||
}};
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-size: 20px;
|
||||
height: 56px;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export type ColorSchemeSegmentProps = {
|
||||
variant: `${Lowercase<ColorScheme.Dark | ColorScheme.Light>}`;
|
||||
controls: AnimationControls;
|
||||
} & React.ComponentPropsWithoutRef<'div'>;
|
||||
|
||||
const ColorSchemeSegment = ({
|
||||
variant,
|
||||
controls,
|
||||
style,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
}: ColorSchemeSegmentProps) => (
|
||||
<StyledColorSchemeBackground
|
||||
{...{ variant, style, onClick, onMouseEnter, onMouseLeave }}
|
||||
>
|
||||
<StyledColorSchemeContent animate={controls} variant={variant}>
|
||||
Aa
|
||||
</StyledColorSchemeContent>
|
||||
</StyledColorSchemeBackground>
|
||||
);
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledMixedColorSchemeSegment = styled.div`
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
display: flex;
|
||||
height: 80px;
|
||||
overflow: hidden;
|
||||
width: 120px;
|
||||
`;
|
||||
|
||||
const StyledCheckmarkContainer = styled(motion.div)`
|
||||
bottom: 0px;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
`;
|
||||
|
||||
export type ColorSchemeCardProps = {
|
||||
variant: `${Lowercase<ColorScheme>}`;
|
||||
selected?: boolean;
|
||||
} & React.ComponentPropsWithoutRef<'div'>;
|
||||
|
||||
const checkmarkAnimationVariants = {
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
};
|
||||
|
||||
export const ColorSchemeCard = ({
|
||||
variant,
|
||||
selected,
|
||||
onClick,
|
||||
}: ColorSchemeCardProps) => {
|
||||
const controls = useAnimation();
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
controls.start({
|
||||
height: 61,
|
||||
fontSize: '22px',
|
||||
transition: { duration: 0.1 },
|
||||
});
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
controls.start({
|
||||
height: 56,
|
||||
fontSize: '20px',
|
||||
transition: { duration: 0.1 },
|
||||
});
|
||||
};
|
||||
|
||||
if (variant === 'system') {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledMixedColorSchemeSegment
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={onClick}
|
||||
>
|
||||
<ColorSchemeSegment
|
||||
style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
|
||||
controls={controls}
|
||||
variant="light"
|
||||
/>
|
||||
<ColorSchemeSegment
|
||||
style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}
|
||||
controls={controls}
|
||||
variant="dark"
|
||||
/>
|
||||
</StyledMixedColorSchemeSegment>
|
||||
<AnimatePresence>
|
||||
{selected && (
|
||||
<StyledCheckmarkContainer
|
||||
key="system"
|
||||
variants={checkmarkAnimationVariants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Checkmark />
|
||||
</StyledCheckmarkContainer>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ColorSchemeSegment
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
controls={controls}
|
||||
variant={variant}
|
||||
onClick={onClick}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{selected && (
|
||||
<StyledCheckmarkContainer
|
||||
key={variant}
|
||||
variants={checkmarkAnimationVariants}
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Checkmark />
|
||||
</StyledCheckmarkContainer>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ColorScheme } from '~/generated/graphql';
|
||||
|
||||
import { ColorSchemeCard } from './ColorSchemeCard';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
> * + * {
|
||||
margin-left: ${({ theme }) => theme.spacing(4)};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCardContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export type ColorSchemePickerProps = {
|
||||
value: ColorScheme;
|
||||
onChange: (value: ColorScheme) => void;
|
||||
};
|
||||
|
||||
export const ColorSchemePicker = ({
|
||||
value,
|
||||
onChange,
|
||||
}: ColorSchemePickerProps) => (
|
||||
<StyledContainer>
|
||||
<StyledCardContainer>
|
||||
<ColorSchemeCard
|
||||
onClick={() => onChange(ColorScheme.Light)}
|
||||
variant="light"
|
||||
selected={value === ColorScheme.Light}
|
||||
/>
|
||||
<StyledLabel>Light</StyledLabel>
|
||||
</StyledCardContainer>
|
||||
<StyledCardContainer>
|
||||
<ColorSchemeCard
|
||||
onClick={() => onChange(ColorScheme.Dark)}
|
||||
variant="dark"
|
||||
selected={value === ColorScheme.Dark}
|
||||
/>
|
||||
<StyledLabel>Dark</StyledLabel>
|
||||
</StyledCardContainer>
|
||||
<StyledCardContainer>
|
||||
<ColorSchemeCard
|
||||
onClick={() => onChange(ColorScheme.System)}
|
||||
variant="system"
|
||||
selected={value === ColorScheme.System}
|
||||
/>
|
||||
<StyledLabel>System settings</StyledLabel>
|
||||
</StyledCardContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
@ -0,0 +1,49 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { ColorSchemeCard } from '../ColorSchemeCard';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
> * + * {
|
||||
margin-left: ${({ theme }) => theme.spacing(4)};
|
||||
}
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof ColorSchemeCard> = {
|
||||
title: 'UI/ColorScheme/ColorSchemeCard',
|
||||
component: ColorSchemeCard,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<StyledContainer>
|
||||
<Story />
|
||||
</StyledContainer>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
argTypes: {
|
||||
variant: { control: false },
|
||||
},
|
||||
args: { selected: false },
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ColorSchemeCard>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => (
|
||||
<>
|
||||
<ColorSchemeCard variant="light" selected={args.selected} />
|
||||
<ColorSchemeCard variant="dark" selected={args.selected} />
|
||||
<ColorSchemeCard variant="system" selected={args.selected} />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
export const Selected: Story = {
|
||||
...Default,
|
||||
args: { selected: true },
|
||||
};
|
||||
@ -3,9 +3,9 @@ import { HotkeysEvent } from 'react-hotkeys-hook/dist/types';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { RoundedIconButton } from '@/ui/button/components/RoundedIconButton';
|
||||
import { IconArrowRight } from '@/ui/icon/index';
|
||||
import { IconArrowRight } from '@/ui/display/icon/index';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { RoundedIconButton } from '@/ui/input/button/components/RoundedIconButton';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { InputHotkeyScope } from '../types/InputHotkeyScope';
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconCheck, IconMinus } from '@/ui/icon';
|
||||
import { IconCheck, IconMinus } from '@/ui/display/icon';
|
||||
|
||||
export enum CheckboxVariant {
|
||||
Primary = 'primary',
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { StyledInput } from '@/ui/field/meta-types/input/components/internal/TextInput';
|
||||
import { StyledInput } from '@/ui/data/field/meta-types/input/components/internal/TextInput';
|
||||
import { ComputeNodeDimensions } from '@/ui/utilities/dimensions/components/ComputeNodeDimensions';
|
||||
|
||||
export type EntityTitleDoubleTextInputProps = {
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconButton } from '@/ui/button/components/IconButton';
|
||||
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/dropdown/scopes/DropdownScope';
|
||||
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
|
||||
import { IconButton } from '../button/components/IconButton';
|
||||
import { LightIconButton } from '../button/components/LightIconButton';
|
||||
import { IconApps } from '../constants/icons';
|
||||
import { DropdownMenuSkeletonItem } from '../relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
import { IconPickerHotkeyScope } from '../types/IconPickerHotkeyScope';
|
||||
|
||||
@ -2,8 +2,13 @@ import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Button } from '@/ui/button/components/Button';
|
||||
import { IconFileUpload, IconTrash, IconUpload, IconX } from '@/ui/icon';
|
||||
import {
|
||||
IconFileUpload,
|
||||
IconTrash,
|
||||
IconUpload,
|
||||
IconX,
|
||||
} from '@/ui/display/icon';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
@ -11,8 +11,8 @@ import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { IconAlertCircle } from '@/ui/icon';
|
||||
import { IconEye, IconEyeOff } from '@/ui/icon/index';
|
||||
import { IconAlertCircle } from '@/ui/display/icon';
|
||||
import { IconEye, IconEyeOff } from '@/ui/display/icon/index';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
|
||||
@ -6,11 +6,11 @@ import { hasFlag } from 'country-flag-icons';
|
||||
import * as Flags from 'country-flag-icons/react/3x2';
|
||||
import { CountryCallingCode } from 'libphonenumber-js';
|
||||
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
import { useDropdown } from '@/ui/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/dropdown/scopes/DropdownScope';
|
||||
import { IconChevronDown } from '@/ui/icon';
|
||||
import { IconChevronDown } from '@/ui/display/icon';
|
||||
import { IconWorld } from '@/ui/input/constants/icons';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
|
||||
import { CountryPickerHotkeyScope } from '../types/CountryPickerHotkeyScope';
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
import { MenuItemSelectAvatar } from '@/ui/menu-item/components/MenuItemSelectAvatar';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar';
|
||||
|
||||
import { Country } from './CountryPickerDropdownButton';
|
||||
|
||||
|
||||
30
front/src/modules/ui/input/editor/components/BlockEditor.tsx
Normal file
30
front/src/modules/ui/input/editor/components/BlockEditor.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { BlockNoteEditor } from '@blocknote/core';
|
||||
import { BlockNoteView } from '@blocknote/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
interface BlockEditorProps {
|
||||
editor: BlockNoteEditor;
|
||||
}
|
||||
|
||||
const StyledEditor = styled.div`
|
||||
min-height: 200px;
|
||||
width: 100%;
|
||||
& .editor {
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
font-size: 13px;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
}
|
||||
& .editor [class^='_inlineContent']:before {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-style: normal !important;
|
||||
}
|
||||
.tippy-box:has(.mantine-Toolbar-root) {
|
||||
transform: translateX(-13%);
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlockEditor = ({ editor }: BlockEditorProps) => (
|
||||
<StyledEditor>
|
||||
<BlockNoteView editor={editor} />
|
||||
</StyledEditor>
|
||||
);
|
||||
@ -1,7 +1,7 @@
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
|
||||
const StyledCreateNewButton = styled(MenuItem)<{ hovered: boolean }>`
|
||||
${({ hovered, theme }) =>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { useRef } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
import { MenuItemMultiSelectAvatar } from '@/ui/menu-item/components/MenuItemMultiSelectAvatar';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { DropdownMenuSearchInput } from '@/ui/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { StyledDropdownMenu } from '@/ui/layout/dropdown/components/StyledDropdownMenu';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRef } from 'react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { IconPlus } from '@/ui/icon';
|
||||
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
import { MenuItemSelect } from '@/ui/menu-item/components/MenuItemSelect';
|
||||
import { MenuItemSelectAvatar } from '@/ui/menu-item/components/MenuItemSelectAvatar';
|
||||
import { IconPlus } from '@/ui/display/icon';
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/layout/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemSelect } from '@/ui/navigation/menu-item/components/MenuItemSelect';
|
||||
import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
|
||||
@ -2,7 +2,7 @@ import { expect } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { IconUserCircle } from '@/ui/icon';
|
||||
import { IconUserCircle } from '@/ui/display/icon';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||
|
||||
import { IconApps } from '../constants/icons';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user