[React] Tree navigation (#5800)

* Adds placehold "PartCategoryTree"

- Global navigation (within the "Part" context) for part categories
- Replaces multiple tree views from legacy UI

* FIx for ApiImage component

- Do not continuously request if the user is unauthorized

* Add StockLocation tree

* Add initial data to nav tree panels

* Display data

* Render tree view

* Fix unused variables
This commit is contained in:
Oliver 2023-10-28 00:35:52 +11:00 committed by GitHub
parent 70578500ca
commit f29a675824
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 392 additions and 30 deletions

View File

@ -26,6 +26,7 @@
"@mantine/hooks": "<7",
"@mantine/modals": "<7",
"@mantine/notifications": "<7",
"@naisutech/react-tree": "^3.1.0",
"@sentry/react": "^7.74.1",
"@tabler/icons-react": "^2.39.0",
"@tanstack/react-query": "^5.0.0",
@ -41,6 +42,7 @@
"react-router-dom": "^6.17.0",
"react-select": "^5.7.7",
"react-simplemde-editor": "^5.2.0",
"styled-components": "^6.1.0",
"zustand": "^4.4.3"
},
"devDependencies": {

View File

@ -23,11 +23,17 @@ import { api } from '../../App';
export function ApiImage(props: ImageProps) {
const [image, setImage] = useState<string>('');
const [authorized, setAuthorized] = useState<boolean>(true);
const queryKey = useId();
const imgQuery = useQuery({
queryKey: ['image', queryKey, props.src],
enabled: props.src != undefined && props.src != null && props.src != '',
enabled:
authorized &&
props.src != undefined &&
props.src != null &&
props.src != '',
queryFn: async () => {
if (!props.src) {
return null;
@ -37,11 +43,21 @@ export function ApiImage(props: ImageProps) {
responseType: 'blob'
})
.then((response) => {
let img = new Blob([response.data], {
type: response.headers['content-type']
});
let url = URL.createObjectURL(img);
setImage(url);
switch (response.status) {
case 200:
let img = new Blob([response.data], {
type: response.headers['content-type']
});
let url = URL.createObjectURL(img);
setImage(url);
break;
default:
// User is not authorized to view this image
setAuthorized(false);
console.error(`Error fetching image ${props.src}:`, response);
break;
}
return response;
})
.catch((error) => {

View File

@ -1,4 +1,12 @@
import { Anchor, Breadcrumbs, Paper, Text } from '@mantine/core';
import {
ActionIcon,
Anchor,
Breadcrumbs,
Group,
Paper,
Text
} from '@mantine/core';
import { IconMenu2 } from '@tabler/icons-react';
import { useNavigate } from 'react-router-dom';
export type Breadcrumb = {
@ -9,23 +17,36 @@ export type Breadcrumb = {
/**
* Construct a breadcrumb list, with integrated navigation.
*/
export function BreadcrumbList({ breadcrumbs }: { breadcrumbs: Breadcrumb[] }) {
export function BreadcrumbList({
breadcrumbs,
navCallback
}: {
breadcrumbs: Breadcrumb[];
navCallback?: () => void;
}) {
const navigate = useNavigate();
return (
<Paper p="3" radius="xs">
<Breadcrumbs separator=">">
{breadcrumbs.map((breadcrumb, index) => {
return (
<Anchor
key={`breadcrumb-${index}`}
onClick={() => breadcrumb.url && navigate(breadcrumb.url)}
>
<Text size="sm">{breadcrumb.name}</Text>
</Anchor>
);
})}
</Breadcrumbs>
<Group spacing="xs">
{navCallback && (
<ActionIcon key="nav-action" onClick={navCallback}>
<IconMenu2 />
</ActionIcon>
)}
<Breadcrumbs key="breadcrumbs" separator=">">
{breadcrumbs.map((breadcrumb, index) => {
return (
<Anchor
key={`breadcrumb-${index}`}
onClick={() => breadcrumb.url && navigate(breadcrumb.url)}
>
<Text size="sm">{breadcrumb.name}</Text>
</Anchor>
);
})}
</Breadcrumbs>
</Group>
</Paper>
);
}

View File

@ -15,6 +15,7 @@ import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiPaths, apiUrl } from '../../states/ApiState';
import { StylishText } from '../items/StylishText';
/**
* Construct a notification drawer.
@ -65,7 +66,7 @@ export function NotificationDrawer({
}}
title={
<Group position="apart" noWrap={true}>
<Text size="lg">{t`Notifications`}</Text>
<StylishText size="lg">{t`Notifications`}</StylishText>
<ActionIcon
onClick={() => {
onClose();

View File

@ -17,6 +17,7 @@ export function PageDetail({
detail,
imageUrl,
breadcrumbs,
breadcrumbAction,
actions
}: {
title?: string;
@ -24,13 +25,17 @@ export function PageDetail({
imageUrl?: string;
detail?: ReactNode;
breadcrumbs?: Breadcrumb[];
breadcrumbAction?: () => void;
actions?: ReactNode[];
}) {
return (
<Stack spacing="xs">
{breadcrumbs && breadcrumbs.length > 0 && (
<Paper p="xs" radius="xs" shadow="xs">
<BreadcrumbList breadcrumbs={breadcrumbs} />
<BreadcrumbList
navCallback={breadcrumbAction}
breadcrumbs={breadcrumbs}
/>
</Paper>
)}
<Paper p="xs" radius="xs" shadow="xs">

View File

@ -0,0 +1,94 @@
import { t } from '@lingui/macro';
import { Drawer, Group, LoadingOverlay, Stack, Text } from '@mantine/core';
import { ReactTree } from '@naisutech/react-tree';
import { IconSitemap } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiPaths, apiUrl } from '../../states/ApiState';
import { StylishText } from '../items/StylishText';
export function PartCategoryTree({
opened,
onClose,
selectedCategory
}: {
opened: boolean;
onClose: () => void;
selectedCategory?: number | null;
}) {
const navigate = useNavigate();
const treeQuery = useQuery({
enabled: opened,
queryKey: ['part_category_tree', opened],
queryFn: async () =>
api
.get(apiUrl(ApiPaths.category_tree), {})
.then((response) =>
response.data.map((category: any) => {
return {
id: category.pk,
label: category.name,
parentId: category.parent
};
})
)
.catch((error) => {
console.error('Error fetching part categpry tree:', error);
return error;
}),
refetchOnMount: true
});
function renderNode({ node }: { node: any }) {
return (
<Group
position="apart"
key={node.id}
noWrap={true}
onClick={() => {
onClose();
navigate(`/part/category/${node.id}`);
}}
>
<Text>{node.label}</Text>
</Group>
);
}
return (
<Drawer
opened={opened}
size="md"
position="left"
onClose={onClose}
withCloseButton={true}
styles={{
header: {
width: '100%'
},
title: {
width: '100%'
}
}}
title={
<Group position="left" p="ms" spacing="md" noWrap={true}>
<IconSitemap />
<StylishText size="lg">{t`Part Categories`}</StylishText>
</Group>
}
>
<Stack spacing="xs">
<LoadingOverlay visible={treeQuery.isFetching} />
<ReactTree
nodes={treeQuery.data ?? []}
RenderNode={renderNode}
defaultSelectedNodes={selectedCategory ? [selectedCategory] : []}
showEmptyItems={false}
/>
</Stack>
</Drawer>
);
}

View File

@ -0,0 +1,94 @@
import { t } from '@lingui/macro';
import { Drawer, Group, LoadingOverlay, Stack, Text } from '@mantine/core';
import { ReactTree } from '@naisutech/react-tree';
import { IconSitemap } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiPaths, apiUrl } from '../../states/ApiState';
import { StylishText } from '../items/StylishText';
export function StockLocationTree({
opened,
onClose,
selectedLocation
}: {
opened: boolean;
onClose: () => void;
selectedLocation?: number | null;
}) {
const navigate = useNavigate();
const treeQuery = useQuery({
enabled: opened,
queryKey: ['stock_location_tree', opened],
queryFn: async () =>
api
.get(apiUrl(ApiPaths.stock_location_tree), {})
.then((response) =>
response.data.map((location: any) => {
return {
id: location.pk,
label: location.name,
parentId: location.parent
};
})
)
.catch((error) => {
console.error('Error fetching stock location tree:', error);
return error;
}),
refetchOnMount: true
});
function renderNode({ node }: { node: any }) {
return (
<Group
position="apart"
key={node.id}
noWrap={true}
onClick={() => {
onClose();
navigate(`/stock/location/${node.id}`);
}}
>
<Text>{node.label}</Text>
</Group>
);
}
return (
<Drawer
opened={opened}
size="md"
position="left"
onClose={onClose}
withCloseButton={true}
styles={{
header: {
width: '100%'
},
title: {
width: '100%'
}
}}
title={
<Group position="left" noWrap={true} spacing="md" p="md">
<IconSitemap />
<StylishText size="lg">{t`Stock Locations`}</StylishText>
</Group>
}
>
<Stack spacing="xs">
<LoadingOverlay visible={treeQuery.isFetching} />
<ReactTree
nodes={treeQuery.data ?? []}
showEmptyItems={false}
RenderNode={renderNode}
defaultSelectedNodes={selectedLocation ? [selectedLocation] : []}
/>
</Stack>
</Drawer>
);
}

View File

@ -5,12 +5,13 @@ import {
IconListDetails,
IconSitemap
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PlaceholderPanel } from '../../components/items/Placeholder';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { PartCategoryTree } from '../../components/nav/PartCategoryTree';
import { PartCategoryTable } from '../../components/tables/part/PartCategoryTable';
import { PartListTable } from '../../components/tables/part/PartTable';
import { useInstance } from '../../hooks/UseInstance';
@ -24,6 +25,8 @@ import { ApiPaths } from '../../states/ApiState';
export default function CategoryDetail({}: {}) {
const { id } = useParams();
const [treeOpen, setTreeOpen] = useState(false);
const {
instance: category,
refreshInstance,
@ -88,10 +91,20 @@ export default function CategoryDetail({}: {}) {
return (
<Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PartCategoryTree
opened={treeOpen}
onClose={() => {
setTreeOpen(false);
}}
selectedCategory={category?.pk}
/>
<PageDetail
title={t`Part Category`}
detail={<Text>{category.name ?? 'Top level'}</Text>}
breadcrumbs={breadcrumbs}
breadcrumbAction={() => {
setTreeOpen(true);
}}
/>
<PanelGroup pageKey="partcategory" panels={categoryPanels} />
</Stack>

View File

@ -27,12 +27,13 @@ import {
IconUnlink,
IconVersions
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { ActionDropdown } from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { PartCategoryTree } from '../../components/nav/PartCategoryTree';
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
import { PartParameterTable } from '../../components/tables/part/PartParameterTable';
import { PartVariantTable } from '../../components/tables/part/PartVariantTable';
@ -52,6 +53,8 @@ export default function PartDetail() {
const user = useUserState();
const [treeOpen, setTreeOpen] = useState(false);
const {
instance: part,
refreshInstance,
@ -289,12 +292,22 @@ export default function PartDetail() {
<>
<Stack spacing="xs">
<LoadingOverlay visible={instanceQuery.isFetching} />
<PartCategoryTree
opened={treeOpen}
onClose={() => {
setTreeOpen(false);
}}
selectedCategory={part?.category}
/>
<PageDetail
title={t`Part` + ': ' + part.full_name}
subtitle={part.description}
imageUrl={part.image}
detail={partDetail}
breadcrumbs={breadcrumbs}
breadcrumbAction={() => {
setTreeOpen(true);
}}
actions={partActions}
/>
<PanelGroup pageKey="part" panels={partPanels} />

View File

@ -1,11 +1,12 @@
import { t } from '@lingui/macro';
import { LoadingOverlay, Stack, Text } from '@mantine/core';
import { IconPackages, IconSitemap } from '@tabler/icons-react';
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
import { StockLocationTable } from '../../components/tables/stock/StockLocationTable';
import { useInstance } from '../../hooks/UseInstance';
@ -14,6 +15,8 @@ import { ApiPaths } from '../../states/ApiState';
export default function Stock() {
const { id } = useParams();
const [treeOpen, setTreeOpen] = useState(false);
const {
instance: location,
refreshInstance,
@ -70,10 +73,18 @@ export default function Stock() {
<>
<Stack>
<LoadingOverlay visible={instanceQuery.isFetching} />
<StockLocationTree
opened={treeOpen}
onClose={() => setTreeOpen(false)}
selectedLocation={location?.pk}
/>
<PageDetail
title={t`Stock Items`}
detail={<Text>{location.name ?? 'Top level'}</Text>}
breadcrumbs={breadcrumbs}
breadcrumbAction={() => {
setTreeOpen(true);
}}
/>
<PanelGroup pageKey="stocklocation" panels={locationPanels} />
</Stack>

View File

@ -9,12 +9,13 @@ import {
IconPaperclip,
IconSitemap
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { PlaceholderPanel } from '../../components/items/Placeholder';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { useInstance } from '../../hooks/UseInstance';
@ -23,6 +24,8 @@ import { ApiPaths, apiUrl } from '../../states/ApiState';
export default function StockDetail() {
const { id } = useParams();
const [treeOpen, setTreeOpen] = useState(false);
const {
instance: stockitem,
refreshInstance,
@ -110,6 +113,11 @@ export default function StockDetail() {
return (
<Stack>
<LoadingOverlay visible={instanceQuery.isFetching} />
<StockLocationTree
opened={treeOpen}
onClose={() => setTreeOpen(false)}
selectedLocation={stockitem?.location}
/>
<PageDetail
title={t`Stock Items`}
subtitle={stockitem.part_detail?.full_name ?? 'name goes here'}
@ -119,6 +127,9 @@ export default function StockDetail() {
</Alert>
}
breadcrumbs={breadcrumbs}
breadcrumbAction={() => {
setTreeOpen(true);
}}
/>
<PanelGroup pageKey="stockitem" panels={stockPanels} />
</Stack>

View File

@ -76,6 +76,7 @@ export enum ApiPaths {
// Part URLs
part_list = 'api-part-list',
category_list = 'api-category-list',
category_tree = 'api-category-tree',
related_part_list = 'api-related-part-list',
part_attachment_list = 'api-part-attachment-list',
part_parameter_list = 'api-part-parameter-list',
@ -89,6 +90,7 @@ export enum ApiPaths {
// Stock Item URLs
stock_item_list = 'api-stock-item-list',
stock_location_list = 'api-stock-location-list',
stock_location_tree = 'api-stock-location-tree',
stock_attachment_list = 'api-stock-attachment-list',
// Purchase Order URLs
@ -165,6 +167,8 @@ export function apiEndpoint(path: ApiPaths): string {
return 'part/parameter/template/';
case ApiPaths.category_list:
return 'part/category/';
case ApiPaths.category_tree:
return 'part/category/tree/';
case ApiPaths.related_part_list:
return 'part/related/';
case ApiPaths.part_attachment_list:
@ -179,6 +183,8 @@ export function apiEndpoint(path: ApiPaths): string {
return 'stock/';
case ApiPaths.stock_location_list:
return 'stock/location/';
case ApiPaths.stock_location_tree:
return 'stock/location/tree/';
case ApiPaths.stock_attachment_list:
return 'stock/attachment/';
case ApiPaths.purchase_order_list:

View File

@ -389,6 +389,13 @@
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==
"@emotion/is-prop-valid@^1.2.0", "@emotion/is-prop-valid@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc"
integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==
dependencies:
"@emotion/memoize" "^0.8.1"
"@emotion/memoize@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
@ -424,7 +431,7 @@
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec"
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
"@emotion/unitless@^0.8.1":
"@emotion/unitless@^0.8.0", "@emotion/unitless@^0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3"
integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
@ -963,6 +970,15 @@
dependencies:
moo "^0.5.1"
"@naisutech/react-tree@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@naisutech/react-tree/-/react-tree-3.1.0.tgz#a83820425b53a1ec7a39804ff8bd9024f0a953f4"
integrity sha512-6p1l3ZIaTmbgiAf/mpFELvqwl51LDhr+09f7L+C27DBLWjtleezCMoUuiSLhrJgpixCPNL13PuI3q2yn+0AGvA==
dependencies:
"@emotion/is-prop-valid" "^1.2.0"
nanoid "^4.0.0"
react-draggable "^4.4.5"
"@playwright/test@^1.39.0":
version "1.39.0"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.39.0.tgz#d10ba8e38e44104499e25001945f07faa9fa91cd"
@ -1302,6 +1318,11 @@
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.4.tgz#fedc3e5b15c26dc18faae96bf1317487cb3658cf"
integrity sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==
"@types/stylis@^4.0.2":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.2.tgz#baabb6b3aa6787e90a6bd6cd75cd8fb9a4f256a3"
integrity sha512-Rm17MsTpQQP5Jq4BF7CdrxJsDufoiL/q5IbJZYZmOZAJALyijgF7BzLgobXUqraNcQdqFYLYGeglDp6QzaxPpg==
"@types/tern@*":
version "0.23.5"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.5.tgz#8d369a06749ea83956885cb734788ec208a0e900"
@ -1478,6 +1499,11 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
camelize@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3"
integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
caniuse-lite@^1.0.30001541:
version "1.0.30001549"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz#7d1a3dce7ea78c06ed72c32c2743ea364b3615aa"
@ -1653,12 +1679,26 @@ cosmiconfig@^8.0.0:
parse-json "^5.2.0"
path-type "^4.0.0"
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==
css-to-react-native@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32"
integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^4.0.2"
csstype@3.0.9:
version "3.0.9"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
csstype@^3.0.2:
csstype@^3.0.2, csstype@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
@ -2246,6 +2286,11 @@ nanoid@^3.3.6:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
nanoid@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
node-releases@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
@ -2392,7 +2437,12 @@ pofile@^1.1.4:
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.1.4.tgz#eab7e29f5017589b2a61b2259dff608c0cad76a2"
integrity sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==
postcss@^8.4.27:
postcss-value-parser@^4.0.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.27, postcss@^8.4.31:
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
@ -2691,6 +2741,11 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
shallowequal@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
signal-exit@^3.0.2:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
@ -2736,11 +2791,31 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
dependencies:
ansi-regex "^5.0.1"
styled-components@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.0.tgz#228e3ab9c1ee1daa4b0a06aae30df0ed14fda274"
integrity sha512-VWNfYYBuXzuLS/QYEeoPgMErP26WL+dX9//rEh80B2mmlS1yRxRxuL5eax4m6ybYEUoHWlTy2XOU32767mlMkg==
dependencies:
"@emotion/is-prop-valid" "^1.2.1"
"@emotion/unitless" "^0.8.0"
"@types/stylis" "^4.0.2"
css-to-react-native "^3.2.0"
csstype "^3.1.2"
postcss "^8.4.31"
shallowequal "^1.1.0"
stylis "^4.3.0"
tslib "^2.5.0"
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
stylis@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c"
integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -2801,7 +2876,7 @@ tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0, "tslib@^2.4.1 || ^1.9.3":
tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0, "tslib@^2.4.1 || ^1.9.3", tslib@^2.5.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==