@ -1,4 +1,4 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
import { LightIconButton } from '@/ui/button/components/LightIconButton';
|
||||||
@ -8,9 +8,10 @@ import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/Style
|
|||||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||||
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
import { IconComponent } from '@/ui/icon/types/IconComponent';
|
||||||
|
|
||||||
|
import { DropdownMenuSkeletonItem } from '../relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||||
|
|
||||||
type IconPickerProps = {
|
type IconPickerProps = {
|
||||||
icons: Record<string, IconComponent>;
|
onChange: (params: { iconKey: string; Icon: IconComponent }) => void;
|
||||||
onChange: (iconName: string) => void;
|
|
||||||
selectedIconKey?: string;
|
selectedIconKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -29,24 +30,29 @@ const StyledLightIconButton = styled(LightIconButton)<{ isSelected?: boolean }>`
|
|||||||
isSelected ? theme.background.transparent.light : 'transparent'};
|
isSelected ? theme.background.transparent.light : 'transparent'};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const convertIconKeyToLabel = (iconName: string) =>
|
const convertIconKeyToLabel = (iconKey: string) =>
|
||||||
iconName.replace(/[A-Z]/g, (letter) => ` ${letter}`).trim();
|
iconKey.replace(/[A-Z]/g, (letter) => ` ${letter}`).trim();
|
||||||
|
|
||||||
export const IconPicker = ({
|
export const IconPicker = ({ onChange, selectedIconKey }: IconPickerProps) => {
|
||||||
icons,
|
|
||||||
onChange,
|
|
||||||
selectedIconKey,
|
|
||||||
}: IconPickerProps) => {
|
|
||||||
const [searchString, setSearchString] = useState('');
|
const [searchString, setSearchString] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [icons, setIcons] = useState<Record<string, IconComponent>>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
import('../constants/icons').then((lazyLoadedIcons) => {
|
||||||
|
setIcons(lazyLoadedIcons);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const iconKeys = useMemo(() => {
|
const iconKeys = useMemo(() => {
|
||||||
const filteredIconKeys = Object.keys(icons).filter(
|
const filteredIconKeys = Object.keys(icons).filter(
|
||||||
(iconKey) =>
|
(iconKey) =>
|
||||||
iconKey !== selectedIconKey &&
|
iconKey !== selectedIconKey &&
|
||||||
(!searchString ||
|
(!searchString ||
|
||||||
convertIconKeyToLabel(iconKey)
|
[iconKey, convertIconKeyToLabel(iconKey)].some((label) =>
|
||||||
.toLowerCase()
|
label.toLowerCase().includes(searchString.toLowerCase()),
|
||||||
.includes(searchString.toLowerCase())),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -65,15 +71,19 @@ export const IconPicker = ({
|
|||||||
/>
|
/>
|
||||||
<StyledDropdownMenuSeparator />
|
<StyledDropdownMenuSeparator />
|
||||||
<StyledMenuIconItemsContainer>
|
<StyledMenuIconItemsContainer>
|
||||||
{iconKeys.map((iconKey) => (
|
{isLoading ? (
|
||||||
<StyledLightIconButton
|
<DropdownMenuSkeletonItem />
|
||||||
aria-label={convertIconKeyToLabel(iconKey)}
|
) : (
|
||||||
isSelected={selectedIconKey === iconKey}
|
iconKeys.map((iconKey) => (
|
||||||
size="medium"
|
<StyledLightIconButton
|
||||||
Icon={icons[iconKey]}
|
aria-label={convertIconKeyToLabel(iconKey)}
|
||||||
onClick={() => onChange(iconKey)}
|
isSelected={selectedIconKey === iconKey}
|
||||||
/>
|
size="medium"
|
||||||
))}
|
Icon={icons[iconKey]}
|
||||||
|
onClick={() => onChange({ iconKey, Icon: icons[iconKey] })}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</StyledMenuIconItemsContainer>
|
</StyledMenuIconItemsContainer>
|
||||||
</StyledIconPickerDropdownMenu>
|
</StyledIconPickerDropdownMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { expect } from '@storybook/jest';
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { userEvent, within } from '@storybook/testing-library';
|
import { userEvent, within } from '@storybook/testing-library';
|
||||||
|
|
||||||
import * as icons from '@/ui/icon';
|
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { sleep } from '~/testing/sleep';
|
||||||
|
|
||||||
import { IconPicker } from '../IconPicker';
|
import { IconPicker } from '../IconPicker';
|
||||||
|
|
||||||
@ -11,14 +11,6 @@ const meta: Meta<typeof IconPicker> = {
|
|||||||
title: 'UI/Input/IconPicker',
|
title: 'UI/Input/IconPicker',
|
||||||
component: IconPicker,
|
component: IconPicker,
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
args: { icons },
|
|
||||||
argTypes: {
|
|
||||||
icons: { control: false },
|
|
||||||
selectedIconKey: {
|
|
||||||
options: Object.keys(icons),
|
|
||||||
control: { type: 'select' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
@ -38,6 +30,8 @@ export const WithSearch: Story = {
|
|||||||
|
|
||||||
await userEvent.type(searchInput, 'Building skyscraper');
|
await userEvent.type(searchInput, 'Building skyscraper');
|
||||||
|
|
||||||
|
await sleep(1000);
|
||||||
|
|
||||||
const searchedIcon = canvas.getByRole('button', {
|
const searchedIcon = canvas.getByRole('button', {
|
||||||
name: 'Icon Building Skyscraper',
|
name: 'Icon Building Skyscraper',
|
||||||
});
|
});
|
||||||
|
|||||||
4200
front/src/modules/ui/input/constants/icons.ts
Normal file
4200
front/src/modules/ui/input/constants/icons.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user