Changes
9 changed files (+275/-16)
-
-
@@ -0,0 +1,5 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only export * from "./CopyrightNotice/FirstParty.tsx"; export * from "./CopyrightNotice/ThirdParty.tsx";
-
-
-
@@ -0,0 +1,12 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import type { Meta, StoryObj } from "@storybook/react"; import { FirstParty } from "./FirstParty.tsx"; export default { component: FirstParty, } satisfies Meta<typeof FirstParty>; export const Defaults: StoryObj<typeof FirstParty> = {};
-
-
-
@@ -0,0 +1,8 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { type FC } from "react"; export const FirstParty: FC = () => { return "© 2024 Shota FUJI"; };
-
-
-
@@ -0,0 +1,88 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { Button, Text } from "@radix-ui/themes"; import type { Meta, StoryObj } from "@storybook/react"; import { ThirdParty, ThirdPartyNoticeProvider } from "./ThirdParty.tsx"; export default { component: ThirdParty, } satisfies Meta<typeof ThirdParty>; type Story = StoryObj<typeof ThirdParty>; export const WithoutProvider: Story = {}; export const TextValue: Story = { decorators: [ (Story) => ( <ThirdPartyNoticeProvider text={"Foo\nBar"}> <Story /> </ThirdPartyNoticeProvider> ), ], }; export const MassiveText: Story = { decorators: [ (Story) => ( <ThirdPartyNoticeProvider text={Array.from({ length: 1_000 }, () => "Text").join("\n")} > <Story /> </ThirdPartyNoticeProvider> ), ], }; export const ReactNode: Story = { decorators: [ (Story) => ( <ThirdPartyNoticeProvider text={ <> <Text as="p">Text</Text> <Button variant="outline" type="button"> Button </Button> </> } > <Story /> </ThirdPartyNoticeProvider> ), ], }; export const SlowLoad: Story = { decorators: [ (Story) => ( <ThirdPartyNoticeProvider text={async () => { await new Promise<void>((resolve) => { setTimeout(() => void resolve(), 5_000); }); return "Foo\nBar"; }} > <Story /> </ThirdPartyNoticeProvider> ), ], }; export const LoadFailure: Story = { decorators: [ (Story) => ( <ThirdPartyNoticeProvider text={() => { throw new Error("Test Error"); }} > <Story /> </ThirdPartyNoticeProvider> ), ], };
-
-
-
@@ -0,0 +1,102 @@// SPDX-FileCopyrightText: 2024 Shota FUJI <pockawoooh@gmail.com> // SPDX-License-Identifier: AGPL-3.0-only import { Box, Button, Dialog, Flex, Spinner, Text } from "@radix-ui/themes"; import { useQuery } from "@tanstack/react-query"; import { createContext, type FC, type ReactNode, use, useMemo } from "react"; import { ErrorCallout } from "../ErrorCallout.ts"; interface ThirdPartyNoticeContextValue { text(): string | ReactNode | Promise<string | ReactNode>; } const Context = createContext<ThirdPartyNoticeContextValue | null>(null); export const ThirdParty: FC = () => { const ctx = use(Context); if (!ctx) { return null; } return <Provided value={ctx} />; }; export interface ThirdPartyNoticeProviderProps { text: string | ReactNode | ThirdPartyNoticeContextValue["text"]; children: ReactNode; } export const ThirdPartyNoticeProvider: FC<ThirdPartyNoticeProviderProps> = ({ children, text, }) => { const value = useMemo((): ThirdPartyNoticeContextValue => { return { text: typeof text === "function" ? text : () => text, }; }, [text]); return <Context.Provider value={value}>{children}</Context.Provider>; }; interface ProvidedProps { value: ThirdPartyNoticeContextValue; } const Provided: FC<ProvidedProps> = ({ value }: ProvidedProps) => { const query = useQuery({ queryFn() { return value.text(); }, queryKey: ["CopyrightNotice.ThirdParty.text"], }); return ( <Dialog.Root> <Dialog.Trigger> <Button size="1" variant="ghost"> ライセンス </Button> </Dialog.Trigger> <Dialog.Content maxHeight="calc(100dvh - var(--space-6) * 2)"> <Dialog.Title>ライセンス</Dialog.Title> <Dialog.Description mb="3"> このアプリケーションでは以下のソフトウェアを利用しています。 </Dialog.Description> <Box style={{ whiteSpace: "pre-wrap" }}> {query.isError ? ( <ErrorCallout title="読み込みに失敗しました" severity="danger" actions={ <Button size="1" onClick={() => void query.refetch()}> 再試行 </Button> } > {query.error instanceof Error ? query.error.message : String(query.error)} </ErrorCallout> ) : query.isLoading ? ( <Flex gap="2" align="center"> <Spinner /> <Text color="gray" size="2"> 読込中 </Text> </Flex> ) : ( query.data )} </Box> <Flex position="sticky" bottom="0" justify="end" mt="5"> <Dialog.Close> <Button>閉じる</Button> </Dialog.Close> </Flex> </Dialog.Content> </Dialog.Root> ); };
-
-
-
@@ -7,6 +7,8 @@ import { Button, IconButton } from "@radix-ui/themes";import type { Meta, StoryObj } from "@storybook/react"; import { WorkspaceSchema } from "@yamori/proto/yamori/workspace/v1/workspace_pb.js"; import { ThirdPartyNoticeProvider } from "../../components/CopyrightNotice.ts"; import { Layout } from "./Layout.tsx"; export default {
-
@@ -24,6 +26,13 @@ },parameters: { layout: "fullscreen", }, decorators: [ (Story) => ( <ThirdPartyNoticeProvider text="Foo"> <Story /> </ThirdPartyNoticeProvider> ), ], } satisfies Meta<typeof Layout>; type Story = StoryObj<typeof Layout>;
-
-
-
@@ -10,10 +10,13 @@ Flex,Grid, Heading, IconButton, ScrollArea, Text, } from "@radix-ui/themes"; import { type Workspace } from "@yamori/proto/yamori/workspace/v1/workspace_pb.js"; import { type FC, type ReactNode, use, useCallback, useState } from "react"; import * as CopyrightNotice from "../../components/CopyrightNotice.ts"; import * as NavigationMenu from "../../components/NavigationMenu.ts"; import { URLContext } from "../../contexts/Router.tsx"; import { useViewTransition } from "../../hooks/useViewTransition.ts";
-
@@ -86,26 +89,38 @@ )}</Flex> </Container> </Flex> <Box <Flex className={css.menu} p="2" gridRowStart="2" gridColumnStart="1" display={{ initial: isMenuOpened ? "block" : "none", md: "block" }} display={{ initial: isMenuOpened ? "flex" : "none", md: "flex" }} direction="column" gap="2" > <NavigationMenu.Root> <NavigationMenu.Group title="労働者管理"> <NavigationMenu.Item current={workers.pattern.test(url)}> <a href={`/${workspace.id?.value}/workers`}>労働者一覧</a> </NavigationMenu.Item> {workspace.workerAddKey && ( <NavigationMenu.Item current={workersNew.pattern.test(url)}> <a href={`/${workspace.id?.value}/workers/new`}>労働者登録</a> </NavigationMenu.Item> )} </NavigationMenu.Group> </NavigationMenu.Root> </Box> <Box asChild flexGrow="1" flexShrink="1"> <ScrollArea> <NavigationMenu.Root> <NavigationMenu.Group title="労働者管理"> <NavigationMenu.Item current={workers.pattern.test(url)}> <a href={`/${workspace.id?.value}/workers`}>労働者一覧</a> </NavigationMenu.Item> {workspace.workerAddKey && ( <NavigationMenu.Item current={workersNew.pattern.test(url)}> <a href={`/${workspace.id?.value}/workers/new`}>労働者登録</a> </NavigationMenu.Item> )} </NavigationMenu.Group> </NavigationMenu.Root> </ScrollArea> </Box> <Flex asChild align="center" justify="end" gap="4"> <Text size="1" color="gray"> <CopyrightNotice.ThirdParty /> <CopyrightNotice.FirstParty /> </Text> </Flex> </Flex> <Container size={{ initial: "2", lg: "3" }} p="2"
-
-
-
@@ -12,9 +12,12 @@ Heading,IconButton, Inset, ScrollArea, Text, Theme, } from "@radix-ui/themes"; import { type FC, type ReactNode } from "react"; import * as CopyrightNotice from "../../components/CopyrightNotice.ts"; import css from "./Layout.module.css";
-
@@ -37,8 +40,11 @@ className={`${css.bgGrid} ${className || ""}`}inset="0" height="100%" p="4" pb="2" px={{ initial: "2", md: "4" }} columns={{ initial: "1", md: "minmax(0, 1fr) 30rem" }} rows="minmax(0, 1fr) max-content" gap="2" {...rest} > <Box asChild gridColumn="-2 / -1">
-
@@ -72,6 +78,12 @@ </Flex></Theme> </Card> </Box> <Flex gridColumn="-2 / -1" asChild align="center" justify="end" gap="4"> <Text size="1" color="gray"> <CopyrightNotice.ThirdParty /> <CopyrightNotice.FirstParty /> </Text> </Flex> </Grid> ); };
-
-
-
@@ -5,6 +5,7 @@ import type { Meta, StoryObj } from "@storybook/react";import { withMockedBackend } from "../../../.storybook/decorators/withMockedBackend.tsx"; import { withInmemoryRouter } from "../../../.storybook/decorators/withInmemoryRouter.tsx"; import { ThirdPartyNoticeProvider } from "../../components/CopyrightNotice.ts"; import { Create, List } from "../../mocks/yamori/workspace/v1/workspace_service.ts"; import { Page } from "./page.tsx";
-
@@ -14,7 +15,14 @@ component: Page,parameters: { layout: "fullscreen", }, decorators: [withInmemoryRouter({ initialURL: "/" })], decorators: [ withInmemoryRouter({ initialURL: "/" }), (Story) => ( <ThirdPartyNoticeProvider text="Foo"> <Story /> </ThirdPartyNoticeProvider> ), ], } satisfies Meta<typeof Page>; type Story = StoryObj<typeof Page>;
-