Feat/rename and color picker (#780)
* WIP * Add menu for rename/color select * Add stories * Remove useless code * Fix color name, add icon for selected color * Remove useless comment * Unify color vocabulary * Fix rebase * Rename story * Improve hotkeys and imports
This commit is contained in:
@ -2185,11 +2185,11 @@ export type DeleteManyPipelineProgressMutation = { __typename?: 'Mutation', dele
|
|||||||
|
|
||||||
export type UpdatePipelineStageMutationVariables = Exact<{
|
export type UpdatePipelineStageMutationVariables = Exact<{
|
||||||
id?: InputMaybe<Scalars['String']>;
|
id?: InputMaybe<Scalars['String']>;
|
||||||
name?: InputMaybe<Scalars['String']>;
|
data: PipelineStageUpdateInput;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UpdatePipelineStageMutation = { __typename?: 'Mutation', updateOnePipelineStage?: { __typename?: 'PipelineStage', id: string, name: string } | null };
|
export type UpdatePipelineStageMutation = { __typename?: 'Mutation', updateOnePipelineStage?: { __typename?: 'PipelineStage', id: string, name: string, color: string } | null };
|
||||||
|
|
||||||
export type SearchPeopleQueryVariables = Exact<{
|
export type SearchPeopleQueryVariables = Exact<{
|
||||||
where?: InputMaybe<PersonWhereInput>;
|
where?: InputMaybe<PersonWhereInput>;
|
||||||
@ -3957,10 +3957,11 @@ export type DeleteManyPipelineProgressMutationHookResult = ReturnType<typeof use
|
|||||||
export type DeleteManyPipelineProgressMutationResult = Apollo.MutationResult<DeleteManyPipelineProgressMutation>;
|
export type DeleteManyPipelineProgressMutationResult = Apollo.MutationResult<DeleteManyPipelineProgressMutation>;
|
||||||
export type DeleteManyPipelineProgressMutationOptions = Apollo.BaseMutationOptions<DeleteManyPipelineProgressMutation, DeleteManyPipelineProgressMutationVariables>;
|
export type DeleteManyPipelineProgressMutationOptions = Apollo.BaseMutationOptions<DeleteManyPipelineProgressMutation, DeleteManyPipelineProgressMutationVariables>;
|
||||||
export const UpdatePipelineStageDocument = gql`
|
export const UpdatePipelineStageDocument = gql`
|
||||||
mutation UpdatePipelineStage($id: String, $name: String) {
|
mutation UpdatePipelineStage($id: String, $data: PipelineStageUpdateInput!) {
|
||||||
updateOnePipelineStage(where: {id: $id}, data: {name: $name}) {
|
updateOnePipelineStage(where: {id: $id}, data: $data) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -3980,7 +3981,7 @@ export type UpdatePipelineStageMutationFn = Apollo.MutationFunction<UpdatePipeli
|
|||||||
* const [updatePipelineStageMutation, { data, loading, error }] = useUpdatePipelineStageMutation({
|
* const [updatePipelineStageMutation, { data, loading, error }] = useUpdatePipelineStageMutation({
|
||||||
* variables: {
|
* variables: {
|
||||||
* id: // value for 'id'
|
* id: // value for 'id'
|
||||||
* name: // value for 'name'
|
* data: // value for 'data'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -71,7 +71,17 @@ export function EntityBoardColumn({
|
|||||||
updatePipelineStage({
|
updatePipelineStage({
|
||||||
variables: {
|
variables: {
|
||||||
id: pipelineStageId,
|
id: pipelineStageId,
|
||||||
name: value,
|
data: { name: value },
|
||||||
|
},
|
||||||
|
refetchQueries: [getOperationName(GET_PIPELINES) || ''],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditColumnColor(value: string) {
|
||||||
|
updatePipelineStage({
|
||||||
|
variables: {
|
||||||
|
id: pipelineStageId,
|
||||||
|
data: { color: value },
|
||||||
},
|
},
|
||||||
refetchQueries: [getOperationName(GET_PIPELINES) || ''],
|
refetchQueries: [getOperationName(GET_PIPELINES) || ''],
|
||||||
});
|
});
|
||||||
@ -81,9 +91,10 @@ export function EntityBoardColumn({
|
|||||||
<Droppable droppableId={column.pipelineStageId}>
|
<Droppable droppableId={column.pipelineStageId}>
|
||||||
{(droppableProvided) => (
|
{(droppableProvided) => (
|
||||||
<BoardColumn
|
<BoardColumn
|
||||||
|
onColumnColorEdit={handleEditColumnColor}
|
||||||
onTitleEdit={handleEditColumnTitle}
|
onTitleEdit={handleEditColumnTitle}
|
||||||
title={column.title}
|
title={column.title}
|
||||||
colorCode={column.colorCode}
|
color={column.colorCode}
|
||||||
pipelineStageId={column.pipelineStageId}
|
pipelineStageId={column.pipelineStageId}
|
||||||
totalAmount={boardColumnTotal}
|
totalAmount={boardColumnTotal}
|
||||||
isFirstColumn={column.index === 0}
|
isFirstColumn={column.index === 0}
|
||||||
|
|||||||
@ -9,10 +9,11 @@ export const DELETE_PIPELINE_PROGRESS = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const UPDATE_PIPELINE_STAGE = gql`
|
export const UPDATE_PIPELINE_STAGE = gql`
|
||||||
mutation UpdatePipelineStage($id: String, $name: String) {
|
mutation UpdatePipelineStage($id: String, $data: PipelineStageUpdateInput!) {
|
||||||
updateOnePipelineStage(where: { id: $id }, data: { name: $name }) {
|
updateOnePipelineStage(where: { id: $id }, data: $data) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import React, { ChangeEvent } from 'react';
|
import React from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { debounce } from '~/utils/debounce';
|
import { usePreviousHotkeyScope } from '@/ui/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { Tag } from '@/ui/tag/components/Tag';
|
||||||
|
|
||||||
import { EditColumnTitleInput } from './EditColumnTitleInput';
|
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||||
|
|
||||||
|
import { BoardColumnMenu } from './BoardColumnMenu';
|
||||||
|
|
||||||
export const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
export const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
@ -29,10 +34,14 @@ const StyledHeader = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyledColumnTitle = styled.h3`
|
export const StyledColumnTitle = styled.h3<{
|
||||||
|
colorHexCode?: string;
|
||||||
|
colorName?: string;
|
||||||
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
color: ${({ color }) => color};
|
color: ${({ colorHexCode, colorName, theme }) =>
|
||||||
|
colorName ? theme.tag.text[colorName] : colorHexCode};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-size: ${({ theme }) => theme.font.size.md};
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
@ -52,49 +61,67 @@ const StyledAmount = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
colorCode?: string;
|
color?: string;
|
||||||
title: string;
|
title: string;
|
||||||
pipelineStageId?: string;
|
pipelineStageId?: string;
|
||||||
onTitleEdit: (title: string) => void;
|
onTitleEdit: (title: string) => void;
|
||||||
|
onColumnColorEdit: (color: string) => void;
|
||||||
totalAmount?: number;
|
totalAmount?: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
isFirstColumn: boolean;
|
isFirstColumn: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function BoardColumn({
|
export function BoardColumn({
|
||||||
colorCode,
|
color,
|
||||||
title,
|
title,
|
||||||
onTitleEdit,
|
onTitleEdit,
|
||||||
|
onColumnColorEdit,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
children,
|
children,
|
||||||
isFirstColumn,
|
isFirstColumn,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [isEditing, setIsEditing] = React.useState(false);
|
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] =
|
||||||
const [internalValue, setInternalValue] = React.useState(title);
|
React.useState(false);
|
||||||
|
|
||||||
const debouncedOnUpdate = debounce(onTitleEdit, 200);
|
const {
|
||||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
setHotkeyScopeAndMemorizePreviousScope,
|
||||||
setInternalValue(event.target.value);
|
goBackToPreviousHotkeyScope,
|
||||||
debouncedOnUpdate(event.target.value);
|
} = usePreviousHotkeyScope();
|
||||||
};
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.Escape, Key.Enter],
|
||||||
|
handleClose,
|
||||||
|
BoardColumnHotkeyScope.BoardColumn,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleTitleClick() {
|
||||||
|
setIsBoardColumnMenuOpen(true);
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, {
|
||||||
|
goto: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
goBackToPreviousHotkeyScope();
|
||||||
|
setIsBoardColumnMenuOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||||
<StyledHeader onClick={() => setIsEditing(true)}>
|
<StyledHeader>
|
||||||
<StyledColumnTitle color={colorCode}>
|
<Tag onClick={handleTitleClick} color={color} text={title} />
|
||||||
{isEditing ? (
|
|
||||||
<EditColumnTitleInput
|
|
||||||
color={colorCode}
|
|
||||||
onFocusLeave={() => setIsEditing(false)}
|
|
||||||
value={internalValue}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div>{title}</div>
|
|
||||||
)}
|
|
||||||
</StyledColumnTitle>
|
|
||||||
{!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>}
|
{!!totalAmount && <StyledAmount>${totalAmount}</StyledAmount>}
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
|
{isBoardColumnMenuOpen && (
|
||||||
|
<BoardColumnMenu
|
||||||
|
onClose={() => setIsBoardColumnMenuOpen(false)}
|
||||||
|
onTitleEdit={onTitleEdit}
|
||||||
|
onColumnColorEdit={onColumnColorEdit}
|
||||||
|
title={title}
|
||||||
|
color={color}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
</StyledColumn>
|
</StyledColumn>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,99 @@
|
|||||||
|
import { ChangeEvent, useState } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
|
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||||
|
import { textInputStyle } from '@/ui/themes/effects';
|
||||||
|
import { debounce } from '~/utils/debounce';
|
||||||
|
|
||||||
|
export const StyledEditTitleContainer = styled.div`
|
||||||
|
--vertical-padding: ${({ theme }) => theme.spacing(1)};
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: calc(36px - 2 * var(--vertical-padding));
|
||||||
|
padding: var(--vertical-padding) 0;
|
||||||
|
|
||||||
|
width: calc(100%);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledEditModeInput = styled.input`
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
|
||||||
|
${textInputStyle}
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
onClose: () => void;
|
||||||
|
title: string;
|
||||||
|
onTitleEdit: (title: string) => void;
|
||||||
|
onColumnColorEdit: (color: string) => void;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledColorSample = styled.div<{ colorName: string }>`
|
||||||
|
background-color: ${({ theme, colorName }) =>
|
||||||
|
theme.tag.background[colorName]};
|
||||||
|
border: 1px solid ${({ theme, colorName }) => theme.color[colorName]};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const COLOR_OPTIONS = [
|
||||||
|
{ name: 'Green', id: 'green' },
|
||||||
|
{ name: 'Turquoise', id: 'turquoise' },
|
||||||
|
{ name: 'Sky', id: 'sky' },
|
||||||
|
{ name: 'Blue', id: 'blue' },
|
||||||
|
{ name: 'Purple', id: 'purple' },
|
||||||
|
{ name: 'Pink', id: 'pink' },
|
||||||
|
{ name: 'Red', id: 'red' },
|
||||||
|
{ name: 'Orange', id: 'orange' },
|
||||||
|
{ name: 'Yellow', id: 'yellow' },
|
||||||
|
{ name: 'Gray', id: 'gray' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function BoardColumnEditTitleMenu({
|
||||||
|
onClose,
|
||||||
|
onTitleEdit,
|
||||||
|
onColumnColorEdit,
|
||||||
|
title,
|
||||||
|
color,
|
||||||
|
}: OwnProps) {
|
||||||
|
const [internalValue, setInternalValue] = useState(title);
|
||||||
|
const debouncedOnUpdate = debounce(onTitleEdit, 200);
|
||||||
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setInternalValue(event.target.value);
|
||||||
|
debouncedOnUpdate(event.target.value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<StyledEditTitleContainer>
|
||||||
|
<StyledEditModeInput
|
||||||
|
value={internalValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</StyledEditTitleContainer>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
{COLOR_OPTIONS.map((colorOption) => (
|
||||||
|
<DropdownMenuSelectableItem
|
||||||
|
key={colorOption.name}
|
||||||
|
onClick={() => {
|
||||||
|
onColumnColorEdit(colorOption.id);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
selected={colorOption.id === color}
|
||||||
|
>
|
||||||
|
<StyledColorSample colorName={colorOption.id} />
|
||||||
|
{colorOption.name}
|
||||||
|
</DropdownMenuSelectableItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
68
front/src/modules/ui/board/components/BoardColumnMenu.tsx
Normal file
68
front/src/modules/ui/board/components/BoardColumnMenu.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { IconPencil } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
import { icon } from '@/ui//themes/icon';
|
||||||
|
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
|
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
||||||
|
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
||||||
|
|
||||||
|
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
||||||
|
|
||||||
|
const StyledMenuContainer = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
z-index: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
onClose: () => void;
|
||||||
|
title: string;
|
||||||
|
color?: string;
|
||||||
|
onTitleEdit: (title: string) => void;
|
||||||
|
onColumnColorEdit: (color: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BoardColumnMenu({
|
||||||
|
onClose,
|
||||||
|
onTitleEdit,
|
||||||
|
onColumnColorEdit,
|
||||||
|
title,
|
||||||
|
color,
|
||||||
|
}: OwnProps) {
|
||||||
|
const [openMenu, setOpenMenu] = useState('actions');
|
||||||
|
const boardColumnMenuRef = useRef(null);
|
||||||
|
|
||||||
|
useListenClickOutsideArrayOfRef({
|
||||||
|
refs: [boardColumnMenuRef],
|
||||||
|
callback: onClose,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||||
|
<DropdownMenu>
|
||||||
|
{openMenu === 'actions' && (
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<DropdownMenuSelectableItem onClick={() => setOpenMenu('title')}>
|
||||||
|
<DropdownButton.StyledIcon>
|
||||||
|
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
||||||
|
</DropdownButton.StyledIcon>
|
||||||
|
Rename
|
||||||
|
</DropdownMenuSelectableItem>
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
)}
|
||||||
|
{openMenu === 'title' && (
|
||||||
|
<BoardColumnEditTitleMenu
|
||||||
|
color={color}
|
||||||
|
onClose={onClose}
|
||||||
|
onTitleEdit={onTitleEdit}
|
||||||
|
onColumnColorEdit={onColumnColorEdit}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DropdownMenu>
|
||||||
|
</StyledMenuContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
|
|
||||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
|
||||||
|
|
||||||
import { ColumnHotkeyScope } from './ColumnHotkeyScope';
|
|
||||||
|
|
||||||
const StyledEditTitleInput = styled.input`
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
color: ${({ color }) => color};
|
|
||||||
font-family: ${({ theme }) => theme.font.family};
|
|
||||||
font-size: ${({ theme }) => theme.font.size.md};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
|
||||||
font-family: ${({ theme }) => theme.font.family};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
||||||
}
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function EditColumnTitleInput({
|
|
||||||
color,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onFocusLeave,
|
|
||||||
}: {
|
|
||||||
color?: string;
|
|
||||||
value: string;
|
|
||||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
onFocusLeave: () => void;
|
|
||||||
}) {
|
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
useListenClickOutsideArrayOfRef({
|
|
||||||
refs: [inputRef],
|
|
||||||
callback: () => {
|
|
||||||
onFocusLeave();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
|
||||||
setHotkeyScope(ColumnHotkeyScope.EditColumnName, { goto: false });
|
|
||||||
|
|
||||||
useScopedHotkeys('enter', onFocusLeave, ColumnHotkeyScope.EditColumnName);
|
|
||||||
useScopedHotkeys('esc', onFocusLeave, ColumnHotkeyScope.EditColumnName);
|
|
||||||
return (
|
|
||||||
<StyledEditTitleInput
|
|
||||||
ref={inputRef}
|
|
||||||
placeholder={'Enter column name'}
|
|
||||||
color={color}
|
|
||||||
autoFocus
|
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
|
import { BoardColumnEditTitleMenu } from '../BoardColumnEditTitleMenu';
|
||||||
|
|
||||||
|
const meta: Meta<typeof BoardColumnEditTitleMenu> = {
|
||||||
|
title: 'UI/Board/BoardColumnMenu',
|
||||||
|
component: BoardColumnEditTitleMenu,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof BoardColumnEditTitleMenu>;
|
||||||
|
|
||||||
|
export const AllTags: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<BoardColumnEditTitleMenu
|
||||||
|
color="green"
|
||||||
|
title={'Column title'}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
onClose={() => {}}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
onTitleEdit={() => {}}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
onColumnColorEdit={() => {}}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
};
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
export enum BoardColumnHotkeyScope {
|
||||||
|
BoardColumn = 'board-column',
|
||||||
|
}
|
||||||
@ -50,7 +50,7 @@ const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDropdownMenuContainer = styled.ul`
|
export const StyledDropdownMenuContainer = styled.ul`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
|
|||||||
41
front/src/modules/ui/tag/components/Tag.tsx
Normal file
41
front/src/modules/ui/tag/components/Tag.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
export const StyledTag = styled.h3<{
|
||||||
|
colorHexCode?: string;
|
||||||
|
colorId?: string;
|
||||||
|
}>`
|
||||||
|
align-items: center;
|
||||||
|
background: ${({ colorId, theme }) =>
|
||||||
|
colorId ? theme.tag.background[colorId] : null};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
|
color: ${({ colorHexCode, colorId, theme }) =>
|
||||||
|
colorId ? theme.tag.text[colorId] : colorHexCode};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
color?: string;
|
||||||
|
text: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Tag({ color, text, onClick }: OwnProps) {
|
||||||
|
const colorHexCode = color?.charAt(0) === '#' ? color : undefined;
|
||||||
|
const colorId = color?.charAt(0) === '#' ? undefined : color;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledTag colorHexCode={colorHexCode} colorId={colorId} onClick={onClick}>
|
||||||
|
{text}
|
||||||
|
</StyledTag>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
|
import { Tag } from '../Tag';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Tag> = {
|
||||||
|
title: 'UI/Accessories/Tag',
|
||||||
|
component: Tag,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Tag>;
|
||||||
|
|
||||||
|
const TESTED_COLORS = [
|
||||||
|
'green',
|
||||||
|
'turquoise',
|
||||||
|
'sky',
|
||||||
|
'blue',
|
||||||
|
'purple',
|
||||||
|
'pink',
|
||||||
|
'red',
|
||||||
|
'orange',
|
||||||
|
'yellow',
|
||||||
|
'gray',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AllTags: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<>
|
||||||
|
{TESTED_COLORS.map((color) => (
|
||||||
|
<Tag text="Urgent" color={color} />
|
||||||
|
))}
|
||||||
|
</>,
|
||||||
|
),
|
||||||
|
};
|
||||||
@ -23,7 +23,7 @@ export const grayScale = {
|
|||||||
gray0: '#ffffff',
|
gray0: '#ffffff',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const color = {
|
export const color: { [key: string]: string } = {
|
||||||
yellow: '#ffd338',
|
yellow: '#ffd338',
|
||||||
yellow80: '#2e2a1a',
|
yellow80: '#2e2a1a',
|
||||||
yellow70: '#453d1e',
|
yellow70: '#453d1e',
|
||||||
@ -51,7 +51,7 @@ export const color = {
|
|||||||
turquoise30: '#9af0b0',
|
turquoise30: '#9af0b0',
|
||||||
turquoise20: '#c9fbd9',
|
turquoise20: '#c9fbd9',
|
||||||
turquoise10: '#e8fde9',
|
turquoise10: '#e8fde9',
|
||||||
sskyky: '#00e0ff',
|
sky: '#00e0ff',
|
||||||
sky80: '#1a2d2e',
|
sky80: '#1a2d2e',
|
||||||
sky70: '#1e3f40',
|
sky70: '#1e3f40',
|
||||||
sky60: '#224f50',
|
sky60: '#224f50',
|
||||||
|
|||||||
55
front/src/modules/ui/themes/tag.ts
Normal file
55
front/src/modules/ui/themes/tag.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { color } from './colors';
|
||||||
|
|
||||||
|
export const tagLight: { [key: string]: { [key: string]: string } } = {
|
||||||
|
text: {
|
||||||
|
green: color.green60,
|
||||||
|
turquoise: color.turquoise60,
|
||||||
|
sky: color.sky60,
|
||||||
|
blue: color.blue60,
|
||||||
|
purple: color.purple60,
|
||||||
|
pink: color.pink60,
|
||||||
|
red: color.red60,
|
||||||
|
orange: color.orange60,
|
||||||
|
yellow: color.yellow60,
|
||||||
|
gray: color.gray60,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
green: color.green20,
|
||||||
|
turquoise: color.turquoise20,
|
||||||
|
sky: color.sky20,
|
||||||
|
blue: color.blue20,
|
||||||
|
purple: color.purple20,
|
||||||
|
pink: color.pink20,
|
||||||
|
red: color.red20,
|
||||||
|
orange: color.orange20,
|
||||||
|
yellow: color.yellow20,
|
||||||
|
gray: color.gray20,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tagDark = {
|
||||||
|
text: {
|
||||||
|
green: color.green10,
|
||||||
|
turquoise: color.turquoise10,
|
||||||
|
sky: color.sky10,
|
||||||
|
blue: color.blue10,
|
||||||
|
purple: color.purple10,
|
||||||
|
pink: color.pink10,
|
||||||
|
red: color.red10,
|
||||||
|
orange: color.orange10,
|
||||||
|
yellow: color.yellow10,
|
||||||
|
gray: color.gray10,
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
green: color.green60,
|
||||||
|
turquoise: color.turquoise60,
|
||||||
|
sky: color.sky60,
|
||||||
|
blue: color.blue60,
|
||||||
|
purple: color.purple60,
|
||||||
|
pink: color.pink60,
|
||||||
|
red: color.red60,
|
||||||
|
orange: color.orange60,
|
||||||
|
yellow: color.yellow60,
|
||||||
|
gray: color.gray60,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -7,6 +7,7 @@ import { boxShadowDark, boxShadowLight } from './boxShadow';
|
|||||||
import { color, grayScale } from './colors';
|
import { color, grayScale } from './colors';
|
||||||
import { fontDark, fontLight } from './font';
|
import { fontDark, fontLight } from './font';
|
||||||
import { icon } from './icon';
|
import { icon } from './icon';
|
||||||
|
import { tagDark, tagLight } from './tag';
|
||||||
import { text } from './text';
|
import { text } from './text';
|
||||||
|
|
||||||
const common = {
|
const common = {
|
||||||
@ -47,6 +48,7 @@ export const lightTheme = {
|
|||||||
accent: accentLight,
|
accent: accentLight,
|
||||||
background: backgroundLight,
|
background: backgroundLight,
|
||||||
border: borderLight,
|
border: borderLight,
|
||||||
|
tag: tagLight,
|
||||||
boxShadow: boxShadowLight,
|
boxShadow: boxShadowLight,
|
||||||
font: fontLight,
|
font: fontLight,
|
||||||
name: 'light',
|
name: 'light',
|
||||||
@ -60,6 +62,7 @@ export const darkTheme: ThemeType = {
|
|||||||
accent: accentDark,
|
accent: accentDark,
|
||||||
background: backgroundDark,
|
background: backgroundDark,
|
||||||
border: borderDark,
|
border: borderDark,
|
||||||
|
tag: tagDark,
|
||||||
boxShadow: boxShadowDark,
|
boxShadow: boxShadowDark,
|
||||||
font: fontDark,
|
font: fontDark,
|
||||||
name: 'dark',
|
name: 'dark',
|
||||||
|
|||||||
@ -416,9 +416,12 @@ model PipelineStage {
|
|||||||
/// @Validator.IsOptional()
|
/// @Validator.IsOptional()
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
|
/// @Validator.IsOptional()
|
||||||
name String
|
name String
|
||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
|
/// @Validator.IsOptional()
|
||||||
type String
|
type String
|
||||||
|
/// @Validator.IsOptional()
|
||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
color String
|
color String
|
||||||
/// @Validator.IsNumber()
|
/// @Validator.IsNumber()
|
||||||
|
|||||||
Reference in New Issue
Block a user