From 6e65f826b7ce9f1cf20b78237d0ef3d0d8118259 Mon Sep 17 00:00:00 2001 From: Joo Chanhwi <56245920+te6-in@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:32:44 +0900 Subject: [PATCH] refactor: refactor demo-activity --- .../{demo/index.tsx => demo-activity.tsx} | 129 +++++++++++++++++- docs/components/example/demo/data.tsx | 112 --------------- docs/components/example/demo/types.ts | 14 -- docs/components/example/index.json | 5 +- docs/content/docs/react/index.mdx | 2 +- docs/public/__registry__/ui/app-screen.json | 8 +- docs/public/__registry__/ui/index.json | 4 +- 7 files changed, 140 insertions(+), 134 deletions(-) rename docs/components/example/{demo/index.tsx => demo-activity.tsx} (73%) delete mode 100644 docs/components/example/demo/data.tsx delete mode 100644 docs/components/example/demo/types.ts diff --git a/docs/components/example/demo/index.tsx b/docs/components/example/demo-activity.tsx similarity index 73% rename from docs/components/example/demo/index.tsx rename to docs/components/example/demo-activity.tsx index 0f0974bbd..96421ec2e 100644 --- a/docs/components/example/demo/index.tsx +++ b/docs/components/example/demo-activity.tsx @@ -25,15 +25,27 @@ import { ActionButton } from "seed-design/ui/action-button"; import { Snackbar, SnackbarProvider, useSnackbarAdapter } from "seed-design/ui/snackbar"; import { ExtendedFab } from "seed-design/ui/extended-fab"; -import { ARTICLES, CATEGORIES } from "./data"; -import { type Article } from "./types"; - declare module "@stackflow/config" { interface Register { Demo: unknown; } } +export type Article = { + id: number; + title: string; + content: string; + author: string; + categoryId: string; + createdAt: string; + isPopular?: boolean; +}; + +export type Category = { + id: string; + name: string; +}; + const TABS = [ { label: "추천", value: "recommendations" }, { label: "구독", value: "subscriptions" }, @@ -332,4 +344,115 @@ export function ArticleListItem({ ); } +export const CATEGORIES: Category[] = [ + { id: "travel", name: "여행" }, + { id: "food", name: "음식" }, + { id: "lifestyle", name: "라이프스타일" }, +]; + +export const ARTICLES: Article[] = [ + { + id: 1, + title: "산토리니의 일몰", + content: + "에게해의 푸른 바다와 하얀 건물들 사이로 지는 석양이 만드는 황홍빛 풍경. 산토리니에서 가장 로맨틱한 순간을 만날 수 있다.", + author: "mollit", + categoryId: "travel", + createdAt: "2023-02-15T09:30:00Z", + }, + + { + id: 2, + title: "교토의 대나무 숲", + content: + "아라시야마의 대나무 숲길을 걸으며 느끼는 고요함. 바람에 흔들리는 대나무 소리가 마음을 평온하게 만든다.", + author: "sunt", + categoryId: "travel", + createdAt: "2023-05-22T14:15:00Z", + isPopular: true, + }, + + { + id: 3, + title: "알프스 하이킹 가이드", + content: + "스위스 알프스의 초보자용 트레킹 코스. 그린델발트에서 시작하여 아이거 북벽을 감상하며 2시간 코스로 즐길 수 있다.", + author: "ullamco", + categoryId: "travel", + createdAt: "2023-10-15T12:10:00Z", + }, + { + id: 4, + title: "홈메이드 파스타의 비밀", + content: + "신선한 면과 소스만 있다면 레스토랑 급 파스타를 만들 수 있다. 올리브오일과 마늘의 양이 맛을 결정한다.", + author: "dolore", + categoryId: "food", + createdAt: "2023-03-30T16:20:00Z", + }, + + { + id: 5, + title: "제철 딸기 고르는 법", + content: + "봄철 딸기는 꼭지가 선명한 초록색이어야 한다. 윤기 나는 빨간색과 은은한 향기가 좋은 딸기의 조건.", + author: "aliqua", + categoryId: "food", + createdAt: "2023-06-18T10:05:00Z", + }, + + { + id: 6, + title: "커피 브루잉 팁", + content: + "물 온도 92도, 분쇄도는 중간, 추출 시간 3분이 기본. 원두 양은 물 200ml당 15g이 황금비율이다.", + author: "consectetur", + categoryId: "food", + createdAt: "2023-09-25T13:40:00Z", + }, + + { + id: 7, + title: "미니멀 홈 데코", + content: + "불필요한 소품은 과감히 정리하고, 화이트 톤의 기본 가구만으로 깔끔한 공간을 연출할 수 있다.", + author: "adipisicing", + categoryId: "lifestyle", + createdAt: "2023-04-12T15:55:00Z", + isPopular: true, + }, + + { + id: 8, + title: "아침 루틴의 힘", + content: + "하루를 5분 일찍 시작하는 것부터. 따뜻한 물 한잔과 스트레칭으로 활기찬 아침을 맞이하자.", + author: "elit", + categoryId: "lifestyle", + createdAt: "2023-07-28T08:25:00Z", + isPopular: true, + }, + + { + id: 9, + title: "실내 공기 정화 식물", + content: + "스투키, 스파티필름, 산세베리아는 관리가 쉽고 공기정화 능력이 뛰어난 대표적인 실내식물이다.", + author: "exercitation", + categoryId: "lifestyle", + createdAt: "2023-08-07T11:45:00Z", + }, + + { + id: 10, + title: "독서 습관 만들기", + content: + "하루 10페이지부터 시작하자. 취침 전 20분 독서는 수면의 질도 높여주는 일석이조 습관이다.", + author: "magna", + categoryId: "lifestyle", + createdAt: "2023-12-03T17:35:00Z", + isPopular: true, + }, +]; + export default DemoActivity; diff --git a/docs/components/example/demo/data.tsx b/docs/components/example/demo/data.tsx deleted file mode 100644 index a2c15870e..000000000 --- a/docs/components/example/demo/data.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import type { Article, Category } from "./types"; - -export const CATEGORIES: Category[] = [ - { id: "travel", name: "여행" }, - { id: "food", name: "음식" }, - { id: "lifestyle", name: "라이프스타일" }, -]; - -export const ARTICLES: Article[] = [ - { - id: 1, - title: "산토리니의 일몰", - content: - "에게해의 푸른 바다와 하얀 건물들 사이로 지는 석양이 만드는 황홍빛 풍경. 산토리니에서 가장 로맨틱한 순간을 만날 수 있다.", - author: "mollit", - categoryId: "travel", - createdAt: "2023-02-15T09:30:00Z", - }, - - { - id: 2, - title: "교토의 대나무 숲", - content: - "아라시야마의 대나무 숲길을 걸으며 느끼는 고요함. 바람에 흔들리는 대나무 소리가 마음을 평온하게 만든다.", - author: "sunt", - categoryId: "travel", - createdAt: "2023-05-22T14:15:00Z", - isPopular: true, - }, - - { - id: 3, - title: "알프스 하이킹 가이드", - content: - "스위스 알프스의 초보자용 트레킹 코스. 그린델발트에서 시작하여 아이거 북벽을 감상하며 2시간 코스로 즐길 수 있다.", - author: "ullamco", - categoryId: "travel", - createdAt: "2023-10-15T12:10:00Z", - }, - { - id: 4, - title: "홈메이드 파스타의 비밀", - content: - "신선한 면과 소스만 있다면 레스토랑 급 파스타를 만들 수 있다. 올리브오일과 마늘의 양이 맛을 결정한다.", - author: "dolore", - categoryId: "food", - createdAt: "2023-03-30T16:20:00Z", - }, - - { - id: 5, - title: "제철 딸기 고르는 법", - content: - "봄철 딸기는 꼭지가 선명한 초록색이어야 한다. 윤기 나는 빨간색과 은은한 향기가 좋은 딸기의 조건.", - author: "aliqua", - categoryId: "food", - createdAt: "2023-06-18T10:05:00Z", - }, - - { - id: 6, - title: "커피 브루잉 팁", - content: - "물 온도 92도, 분쇄도는 중간, 추출 시간 3분이 기본. 원두 양은 물 200ml당 15g이 황금비율이다.", - author: "consectetur", - categoryId: "food", - createdAt: "2023-09-25T13:40:00Z", - }, - - { - id: 7, - title: "미니멀 홈 데코", - content: - "불필요한 소품은 과감히 정리하고, 화이트 톤의 기본 가구만으로 깔끔한 공간을 연출할 수 있다.", - author: "adipisicing", - categoryId: "lifestyle", - createdAt: "2023-04-12T15:55:00Z", - isPopular: true, - }, - - { - id: 8, - title: "아침 루틴의 힘", - content: - "하루를 5분 일찍 시작하는 것부터. 따뜻한 물 한잔과 스트레칭으로 활기찬 아침을 맞이하자.", - author: "elit", - categoryId: "lifestyle", - createdAt: "2023-07-28T08:25:00Z", - isPopular: true, - }, - - { - id: 9, - title: "실내 공기 정화 식물", - content: - "스투키, 스파티필름, 산세베리아는 관리가 쉽고 공기정화 능력이 뛰어난 대표적인 실내식물이다.", - author: "exercitation", - categoryId: "lifestyle", - createdAt: "2023-08-07T11:45:00Z", - }, - - { - id: 10, - title: "독서 습관 만들기", - content: - "하루 10페이지부터 시작하자. 취침 전 20분 독서는 수면의 질도 높여주는 일석이조 습관이다.", - author: "magna", - categoryId: "lifestyle", - createdAt: "2023-12-03T17:35:00Z", - isPopular: true, - }, -]; diff --git a/docs/components/example/demo/types.ts b/docs/components/example/demo/types.ts deleted file mode 100644 index 647863034..000000000 --- a/docs/components/example/demo/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type Article = { - id: number; - title: string; - content: string; - author: string; - categoryId: string; - createdAt: string; - isPopular?: boolean; -}; - -export type Category = { - id: string; - name: string; -}; diff --git a/docs/components/example/index.json b/docs/components/example/index.json index 9ba9d6eca..aaabd0fce 100644 --- a/docs/components/example/index.json +++ b/docs/components/example/index.json @@ -23,8 +23,8 @@ "alert-dialog-preview": "import { Column, Columns } from \"@seed-design/react\";\nimport { ActionButton } from \"seed-design/ui/action-button\";\nimport {\n AlertDialogAction,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogRoot,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"seed-design/ui/alert-dialog\";\n\nconst AlertDialogSingle = () => {\n return (\n // You can set z-index dialog with \"--layer-index\" custom property. useful for stackflow integration.\n \n \n 열기\n \n \n \n 주의\n 이 작업은 되돌릴 수 없습니다.\n \n \n \n \n \n 취소\n \n \n \n \n 확인\n \n \n \n \n \n \n );\n};\n\nexport default AlertDialogSingle;", "alert-dialog-single": "import { ActionButton } from \"seed-design/ui/action-button\";\nimport {\n AlertDialogAction,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogRoot,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"seed-design/ui/alert-dialog\";\n\nconst AlertDialogSingle = () => {\n // You can set z-index dialog with \"--layer-index\" custom property. useful for stackflow integration.\n return (\n \n \n 열기\n \n \n \n 제목\n 단일 선택지를 제공합니다.\n \n \n \n 확인\n \n \n \n \n );\n};\n\nexport default AlertDialogSingle;", "alert-dialog-stackflow": "import { useActivity } from \"@stackflow/react\";\nimport { useFlow } from \"@stackflow/react/future\";\nimport { ActionButton } from \"seed-design/ui/action-button\";\nimport {\n AlertDialogAction,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogRoot,\n AlertDialogTitle,\n} from \"seed-design/ui/alert-dialog\";\n\nconst AlertDialogStackflow = () => {\n const activity = useActivity();\n const { pop } = useFlow();\n\n return (\n !open && pop()}>\n \n \n 제목\n Stackflow\n \n \n \n 확인\n \n \n \n \n );\n};\n\nexport default AlertDialogStackflow;", - "app-screen-preview": "import { Flex } from \"seed-design/ui/layout\";\nimport { IconBellFill } from \"@daangn/react-monochrome-icon\";\nimport type { ActivityComponentType } from \"@stackflow/react/future\";\nimport {\n AppBar,\n AppScreen,\n CloseButton,\n IconButton,\n Left,\n Right,\n Title,\n} from \"seed-design/ui/app-screen\";\n\ndeclare module \"@stackflow/config\" {\n interface Register {\n AppScreenPreview: unknown;\n }\n}\n\nconst AppScreenPreviewActivity: ActivityComponentType<\"AppScreenPreview\"> = () => {\n return (\n \n \n \n \n Preview\n \n \n \n \n \n \n }\n >\n \n Preview\n \n \n );\n};\n\nexport default AppScreenPreviewActivity;", - "app-screen-transparent-bar": "import { Flex } from \"seed-design/ui/layout\";\nimport { IconBellFill } from \"@daangn/react-monochrome-icon\";\nimport type { ActivityComponentType } from \"@stackflow/react/future\";\nimport {\n AppBar,\n AppScreen,\n CloseButton,\n IconButton,\n Left,\n Right,\n Title,\n} from \"seed-design/ui/app-screen\";\n\ndeclare module \"@stackflow/config\" {\n interface Register {\n AppScreenTransparentBar: unknown;\n }\n}\n\nconst AppScreenTransparentBarActivity: ActivityComponentType<\"AppScreenTransparentBar\"> = () => {\n return (\n \n \n \n \n Transparent Bar\n \n \n \n \n \n \n }\n >\n \n \"Penguin\"\n \n \n );\n};\n\nexport default AppScreenTransparentBarActivity;", + "app-screen-preview": "import { IconBellFill } from \"@daangn/react-monochrome-icon\";\nimport type { ActivityComponentType } from \"@stackflow/react/future\";\nimport {\n AppBar,\n AppBarCloseButton,\n AppBarIconButton,\n AppBarLeft,\n AppBarMain,\n AppBarRight,\n} from \"seed-design/ui/app-bar\";\nimport { AppScreen, AppScreenContent } from \"seed-design/ui/app-screen\";\nimport { Flex } from \"seed-design/ui/layout\";\n\ndeclare module \"@stackflow/config\" {\n interface Register {\n AppScreenPreview: unknown;\n }\n}\n\nconst AppScreenPreviewActivity: ActivityComponentType<\"AppScreenPreview\"> = () => {\n return (\n \n \n \n \n \n Preview\n \n \n \n \n \n \n \n \n Preview\n \n \n \n );\n};\n\nexport default AppScreenPreviewActivity;", + "app-screen-transparent-bar": "import { IconBellFill } from \"@daangn/react-monochrome-icon\";\nimport type { ActivityComponentType } from \"@stackflow/react/future\";\nimport {\n AppBar,\n AppBarCloseButton,\n AppBarIconButton,\n AppBarLeft,\n AppBarMain,\n AppBarRight,\n} from \"seed-design/ui/app-bar\";\nimport { AppScreen, AppScreenContent } from \"seed-design/ui/app-screen\";\nimport { Flex } from \"seed-design/ui/layout\";\n\ndeclare module \"@stackflow/config\" {\n interface Register {\n AppScreenTransparentBar: unknown;\n }\n}\n\nconst AppScreenTransparentBarActivity: ActivityComponentType<\"AppScreenTransparentBar\"> = () => {\n return (\n \n \n \n \n \n Preview\n \n \n \n \n \n \n \n \n Preview\n \n \n \n );\n};\n\nexport default AppScreenTransparentBarActivity;", "avatar-badge": "import { IdentityPlaceholder } from \"seed-design/ui/identity-placeholder\";\nimport { Avatar, AvatarBadge } from \"seed-design/ui/avatar\";\n\nexport default function AvatarWithBadge() {\n return (\n }\n >\n \n
\n \n \n );\n}", "avatar-preview": "import { Avatar, AvatarBadge } from \"seed-design/ui/avatar\";\nimport { IdentityPlaceholder } from \"seed-design/ui/identity-placeholder\";\nimport { Flex } from \"seed-design/ui/layout\";\n\nexport default function AvatarPreview() {\n return (\n \n }\n >\n \n
\n \n \n } />\n \n );\n}", "avatar-size": "import { Avatar } from \"seed-design/ui/avatar\";\nimport { Flex } from \"seed-design/ui/layout\";\n\nexport default function AvatarSize() {\n return (\n \n \n \n \n \n \n \n \n \n );\n}", @@ -78,6 +78,7 @@ "control-chip-preview": "import { ControlChip } from \"seed-design/ui/control-chip\";\n\nexport default function ControlChipPreview() {\n return 라벨;\n}", "control-chip-small": "import { ControlChip } from \"seed-design/ui/control-chip\";\n\nexport default function ActionChipSmall() {\n return 라벨;\n}", "control-chip-suffix-icon": "import { IconChevronDownFill } from \"@daangn/react-monochrome-icon\";\nimport { ControlChip } from \"seed-design/ui/control-chip\";\n\nexport default function ControlChipSuffixIcon() {\n return }>라벨;\n}", + "demo-activity": "import { useMemo, useState } from \"react\";\nimport type { ActivityComponentType } from \"@stackflow/react/future\";\nimport { IconChevronDownFill, IconPenHorizlineFill } from \"@daangn/react-monochrome-icon\";\n\nimport { AppBar, AppBarCloseButton, AppBarMain, AppBarRight } from \"seed-design/ui/app-bar\";\nimport { AppScreen, AppScreenContent } from \"seed-design/ui/app-screen\";\nimport { Flex, Inline, Stack } from \"seed-design/ui/layout\";\nimport { ControlChip } from \"seed-design/ui/control-chip\";\nimport { Tabs, TabTrigger, TabTriggerList } from \"seed-design/ui/tabs\";\nimport { IdentityPlaceholder } from \"seed-design/ui/identity-placeholder\";\nimport { Avatar } from \"seed-design/ui/avatar\";\nimport { Text } from \"seed-design/ui/text\";\nimport { Badge } from \"seed-design/ui/badge\";\nimport { ErrorState } from \"seed-design/ui/error-state\";\nimport {\n BottomSheetBody,\n BottomSheetRoot,\n BottomSheetContent,\n BottomSheetFooter,\n BottomSheetTrigger,\n} from \"seed-design/ui/bottom-sheet\";\nimport { ActionButton } from \"seed-design/ui/action-button\";\nimport { Snackbar, SnackbarProvider, useSnackbarAdapter } from \"seed-design/ui/snackbar\";\nimport { ExtendedFab } from \"seed-design/ui/extended-fab\";\n\ndeclare module \"@stackflow/config\" {\n interface Register {\n Demo: unknown;\n }\n}\n\nexport type Article = {\n id: number;\n title: string;\n content: string;\n author: string;\n categoryId: string;\n createdAt: string;\n isPopular?: boolean;\n};\n\nexport type Category = {\n id: string;\n name: string;\n};\n\nconst TABS = [\n { label: \"추천\", value: \"recommendations\" },\n { label: \"구독\", value: \"subscriptions\" },\n] as const satisfies {\n label: string;\n value: string;\n}[];\n\ntype Tab = (typeof TABS)[number][\"value\"];\n\nconst FILTERS = [\n { label: \"카테고리\", value: \"category\" },\n { label: \"동네\", value: \"location\" },\n { label: \"작성자\", value: \"author\" },\n { label: \"작성 시간\", value: \"createdAt\" },\n] as const satisfies {\n label: string;\n value: string;\n}[];\n\ntype Filter = (typeof FILTERS)[number][\"value\"];\n\nconst DemoActivity: ActivityComponentType<\"Demo\"> = () => {\n const [tab, setTab] = useState(\"recommendations\");\n\n return (\n \n \n dangerouslySetInnerHTML={{\n __html: \"::-webkit-scrollbar{display:none}\",\n }}\n />\n \n \n Foo\n \n \n \n \n \n setTab(value as Tab)}\n layout=\"fill\"\n size=\"medium\"\n fixTriggerList\n style={{ height: \"100%\", overflowY: \"auto\" }}\n >\n \n {TABS.map(({ label, value }) => (\n \n {label}\n \n ))}\n \n {tab === \"recommendations\" && }\n {tab === \"subscriptions\" && (\n setTab(\"recommendations\"),\n }}\n />\n )}\n \n \n \n \n );\n};\n\nexport function HomeTab() {\n const [currentFilterBottomSheet, setCurrentFilterBottomSheet] = useState(null);\n\n const defaultFilters = useMemo(\n () => ({\n category: [],\n location: [],\n author: [],\n createdAt: [],\n }),\n [],\n );\n\n const [selectedFilters, setSelectedFilters] = useState>(defaultFilters);\n\n const adapter = useSnackbarAdapter();\n\n const onUnavailableFilterClick = () =>\n adapter.create({\n render: () => (\n \n ),\n });\n\n const filteredArticles = useMemo(() => {\n let filtered = ARTICLES;\n\n if (selectedFilters.category?.length) {\n filtered = ARTICLES.filter((article) =>\n selectedFilters.category?.includes(article.categoryId),\n );\n }\n\n // XXX: Add more filters if needed\n\n return filtered;\n }, [selectedFilters]);\n\n const handleFilterConfirm = (filter: Filter, values: string[]) => {\n setSelectedFilters((prev) => ({ ...prev, [filter]: values }));\n };\n\n return (\n \n }\n style={{ position: \"fixed\", insetBlockEnd: \"16px\", insetInlineEnd: \"16px\" }}\n >\n 글쓰기\n \n \n {FILTERS.map(({ label, value }) => (\n setCurrentFilterBottomSheet(open ? value : null)}\n >\n {value === \"category\" ? (\n \n }\n onClick={value !== \"category\" ? onUnavailableFilterClick : undefined}\n >\n {selectedFilters[value]?.length\n ? selectedFilters[value]\n .map((id) => CATEGORIES.find((c) => c.id === id)?.name)\n .join(\", \") || label\n : label}\n \n \n ) : (\n }\n onClick={onUnavailableFilterClick}\n >\n {label}\n \n )}\n setCurrentFilterBottomSheet(null)}\n onConfirm={(values) => handleFilterConfirm(value, values)}\n />\n \n ))}\n \n \n {filteredArticles\n .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())\n .map((article) => (\n \n ))}\n \n \n );\n}\n\nexport function FilterBottomSheet({\n filter,\n currentFilter,\n onClose,\n onConfirm,\n}: {\n filter: Filter;\n currentFilter: string[];\n onClose: () => void;\n onConfirm: (values: string[]) => void;\n}) {\n const options = useMemo(() => {\n switch (filter) {\n case \"category\":\n return CATEGORIES;\n // Add more cases for other filters if needed\n default:\n return [];\n }\n }, [filter]);\n\n const [selectedOptions, setSelectedOptions] = useState(currentFilter);\n\n return (\n f.value === filter)?.label}>\n \n \n {options.map((option) => (\n {\n setSelectedOptions((prev) =>\n checked ? [...prev, option.id] : prev.filter((id) => id !== option.id),\n );\n }}\n >\n {option.name}\n \n ))}\n \n \n \n {\n onConfirm(selectedOptions);\n onClose();\n }}\n >\n 완료\n \n \n \n );\n}\n\ntype ArticleProps = Article & {};\n\nexport function ArticleListItem({\n title,\n categoryId,\n content,\n author,\n createdAt,\n isPopular,\n}: ArticleProps) {\n const relativeDate = new Date(createdAt).toLocaleDateString(\"ko-KR\", {\n month: \"long\",\n day: \"numeric\",\n });\n\n const categoryName = CATEGORIES.find((c) => c.id === categoryId)?.name;\n\n return (\n \n \n \n }\n size=\"20\"\n // FIXME\n style={{ zIndex: -1 }}\n />\n \n {author}\n \n \n \n \n \n \n {title}\n \n \n {content}\n \n \n \n {isPopular && (\n \n 인기\n \n )}\n \n {categoryName} ⸱ 서초2동 ⸱ {relativeDate}\n \n \n \n \n );\n}\n\nexport const CATEGORIES: Category[] = [\n { id: \"travel\", name: \"여행\" },\n { id: \"food\", name: \"음식\" },\n { id: \"lifestyle\", name: \"라이프스타일\" },\n];\n\nexport const ARTICLES: Article[] = [\n {\n id: 1,\n title: \"산토리니의 일몰\",\n content:\n \"에게해의 푸른 바다와 하얀 건물들 사이로 지는 석양이 만드는 황홍빛 풍경. 산토리니에서 가장 로맨틱한 순간을 만날 수 있다.\",\n author: \"mollit\",\n categoryId: \"travel\",\n createdAt: \"2023-02-15T09:30:00Z\",\n },\n\n {\n id: 2,\n title: \"교토의 대나무 숲\",\n content:\n \"아라시야마의 대나무 숲길을 걸으며 느끼는 고요함. 바람에 흔들리는 대나무 소리가 마음을 평온하게 만든다.\",\n author: \"sunt\",\n categoryId: \"travel\",\n createdAt: \"2023-05-22T14:15:00Z\",\n isPopular: true,\n },\n\n {\n id: 3,\n title: \"알프스 하이킹 가이드\",\n content:\n \"스위스 알프스의 초보자용 트레킹 코스. 그린델발트에서 시작하여 아이거 북벽을 감상하며 2시간 코스로 즐길 수 있다.\",\n author: \"ullamco\",\n categoryId: \"travel\",\n createdAt: \"2023-10-15T12:10:00Z\",\n },\n {\n id: 4,\n title: \"홈메이드 파스타의 비밀\",\n content:\n \"신선한 면과 소스만 있다면 레스토랑 급 파스타를 만들 수 있다. 올리브오일과 마늘의 양이 맛을 결정한다.\",\n author: \"dolore\",\n categoryId: \"food\",\n createdAt: \"2023-03-30T16:20:00Z\",\n },\n\n {\n id: 5,\n title: \"제철 딸기 고르는 법\",\n content:\n \"봄철 딸기는 꼭지가 선명한 초록색이어야 한다. 윤기 나는 빨간색과 은은한 향기가 좋은 딸기의 조건.\",\n author: \"aliqua\",\n categoryId: \"food\",\n createdAt: \"2023-06-18T10:05:00Z\",\n },\n\n {\n id: 6,\n title: \"커피 브루잉 팁\",\n content:\n \"물 온도 92도, 분쇄도는 중간, 추출 시간 3분이 기본. 원두 양은 물 200ml당 15g이 황금비율이다.\",\n author: \"consectetur\",\n categoryId: \"food\",\n createdAt: \"2023-09-25T13:40:00Z\",\n },\n\n {\n id: 7,\n title: \"미니멀 홈 데코\",\n content:\n \"불필요한 소품은 과감히 정리하고, 화이트 톤의 기본 가구만으로 깔끔한 공간을 연출할 수 있다.\",\n author: \"adipisicing\",\n categoryId: \"lifestyle\",\n createdAt: \"2023-04-12T15:55:00Z\",\n isPopular: true,\n },\n\n {\n id: 8,\n title: \"아침 루틴의 힘\",\n content:\n \"하루를 5분 일찍 시작하는 것부터. 따뜻한 물 한잔과 스트레칭으로 활기찬 아침을 맞이하자.\",\n author: \"elit\",\n categoryId: \"lifestyle\",\n createdAt: \"2023-07-28T08:25:00Z\",\n isPopular: true,\n },\n\n {\n id: 9,\n title: \"실내 공기 정화 식물\",\n content:\n \"스투키, 스파티필름, 산세베리아는 관리가 쉽고 공기정화 능력이 뛰어난 대표적인 실내식물이다.\",\n author: \"exercitation\",\n categoryId: \"lifestyle\",\n createdAt: \"2023-08-07T11:45:00Z\",\n },\n\n {\n id: 10,\n title: \"독서 습관 만들기\",\n content:\n \"하루 10페이지부터 시작하자. 취침 전 20분 독서는 수면의 질도 높여주는 일석이조 습관이다.\",\n author: \"magna\",\n categoryId: \"lifestyle\",\n createdAt: \"2023-12-03T17:35:00Z\",\n isPopular: true,\n },\n];\n\nexport default DemoActivity;", "error-state-basement": "import { Stack } from \"@seed-design/react\";\nimport { ErrorState } from \"seed-design/ui/error-state\";\n\nexport default function ErrorStateBasement() {\n return (\n \n \n \n );\n}", "error-state-preview": "import { Stack } from \"@seed-design/react\";\nimport { ErrorState } from \"seed-design/ui/error-state\";\n\nexport default function ErrorStatePreview() {\n return (\n \n \n \n );\n}", "extended-action-sheet-preview": "import { IconEyeSlashLine } from \"@daangn/react-monochrome-icon\";\nimport { ActionButton } from \"seed-design/ui/action-button\";\nimport {\n ExtendedActionSheetContent,\n ExtendedActionSheetGroup,\n ExtendedActionSheetItem,\n ExtendedActionSheetRoot,\n ExtendedActionSheetTrigger,\n} from \"seed-design/ui/extended-action-sheet\";\n\nconst ExtendedActionSheetPreview = () => {\n return (\n \n \n Open\n \n \n \n } label=\"Action 1\" />\n } label=\"Action 2\" />\n } label=\"Action 3\" />\n \n \n } label=\"Action 4\" />\n }\n label=\"Action 5\"\n />\n \n \n \n );\n};\n\nexport default ExtendedActionSheetPreview;", diff --git a/docs/content/docs/react/index.mdx b/docs/content/docs/react/index.mdx index 0f2d692ff..f09e0767e 100644 --- a/docs/content/docs/react/index.mdx +++ b/docs/content/docs/react/index.mdx @@ -2,4 +2,4 @@ title: V3 --- - + diff --git a/docs/public/__registry__/ui/app-screen.json b/docs/public/__registry__/ui/app-screen.json index 694330eb1..72571bfb0 100644 --- a/docs/public/__registry__/ui/app-screen.json +++ b/docs/public/__registry__/ui/app-screen.json @@ -1,13 +1,19 @@ { "name": "app-screen", "dependencies": [ + "@seed-design/react", "@seed-design/stackflow" ], "registries": [ { "name": "app-screen.tsx", "type": "ui", - "content": "\"use client\";\n\nimport {\n IconChevronLeftLine,\n IconXmarkLine,\n} from \"@daangn/react-monochrome-icon\";\nimport {\n AppBar as SeedAppBar,\n AppScreen as SeedAppScreen,\n} from \"@seed-design/stackflow\";\nimport { useActions, useActivity } from \"@stackflow/react\";\nimport { forwardRef, useCallback } from \"react\";\n\nexport type AppBarProps = SeedAppBar.RootProps;\n\nexport type AppScreenProps = SeedAppScreen.RootProps;\n\nexport const AppBar = SeedAppBar.Root;\n\nexport const Left = SeedAppBar.Left;\n\nexport const Right = SeedAppBar.Right;\n\nexport const Title = SeedAppBar.Title;\n\nexport const IconButton = SeedAppBar.IconButton;\n\nexport const BackButton = forwardRef<\n HTMLButtonElement,\n SeedAppBar.IconButtonProps\n>(({ children = , onClick, ...otherProps }, ref) => {\n const activity = useActivity();\n const actions = useActions();\n\n const handleOnClick = useCallback(\n (e: React.MouseEvent) => {\n onClick?.(e);\n\n if (!e.defaultPrevented) {\n actions.pop();\n }\n },\n [actions],\n );\n\n if (!activity) {\n return null;\n }\n if (activity.isRoot) {\n return null;\n }\n\n return (\n \n {children}\n \n );\n});\nBackButton.displayName = \"BackButton\";\n\nexport const CloseButton = forwardRef<\n HTMLButtonElement,\n SeedAppBar.IconButtonProps\n>(({ children = , onClick, ...otherProps }, ref) => {\n const activity = useActivity();\n\n const handleOnClick = useCallback(\n (e: React.MouseEvent) => {\n onClick?.(e);\n\n if (!e.defaultPrevented) {\n // you can do something here\n }\n },\n [],\n );\n\n const isRoot = !activity || activity.isRoot;\n\n if (!isRoot) {\n return null;\n }\n\n return (\n \n {children}\n \n );\n});\nCloseButton.displayName = \"CloseButton\";\n\nexport const AppScreen = forwardRef(\n ({ children, ...otherProps }, ref) => {\n return (\n \n \n {children}\n \n \n );\n },\n);\nAppScreen.displayName = \"AppScreen\";\n" + "content": "\"use client\";\n\nimport { PullToRefresh } from \"@seed-design/react/primitive\";\nimport { AppScreen as SeedAppScreen } from \"@seed-design/stackflow\";\nimport { useActions } from \"@stackflow/react\";\nimport { forwardRef } from \"react\";\nimport { ProgressCircle } from \"../ui/progress-circle\";\n\nexport interface AppScreenProps extends SeedAppScreen.RootProps {}\n\nexport const AppScreen = forwardRef(\n ({ children, onSwipeBackEnd, ...otherProps }, ref) => {\n const { pop } = useActions();\n\n return (\n {\n if (swiped) {\n pop();\n }\n onSwipeBackEnd?.({ swiped });\n }}\n {...otherProps}\n >\n \n {children}\n \n \n );\n },\n);\nAppScreen.displayName = \"AppScreen\";\n\nexport interface AppScreenContentProps extends SeedAppScreen.LayerProps {\n ptr?: boolean;\n\n onPtrReady?: () => void;\n\n onPtrRefresh?: () => Promise;\n}\n\nexport const AppScreenContent = forwardRef<\n HTMLDivElement,\n AppScreenContentProps\n>(({ children, ptr, onPtrReady, onPtrRefresh, ...otherProps }, ref) => {\n if (!ptr) {\n return (\n \n {children}\n \n );\n }\n\n return (\n \n \n \n {(props) => }\n \n {children}\n \n \n );\n});\n" + }, + { + "name": "app-bar.tsx", + "type": "ui", + "content": "\"use client\";\n\nimport {\n IconChevronLeftLine,\n IconXmarkLine,\n} from \"@daangn/react-monochrome-icon\";\nimport { Stack } from \"@seed-design/react\";\nimport {\n AppBar as SeedAppBar,\n type AppBarIconButtonProps,\n} from \"@seed-design/stackflow\";\nimport { useActions, useActivity } from \"@stackflow/react\";\nimport * as React from \"react\";\nimport { forwardRef } from \"react\";\n\nexport interface AppBarProps extends SeedAppBar.RootProps {}\n\nexport const AppBar = SeedAppBar.Root;\n\nexport interface AppBarLeftProps extends SeedAppBar.LeftProps {}\n\nexport const AppBarLeft = SeedAppBar.Left;\n\nexport interface AppBarRightProps extends SeedAppBar.RightProps {}\n\nexport const AppBarRight = SeedAppBar.Right;\n\nexport interface AppBarMainProps extends Omit {\n /**\n * The title of the app bar.\n * If children is provided as ReactElement, this prop will be ignored.\n */\n title?: string;\n\n /**\n * The subtitle of the app bar.\n * If children is provided as ReactElement, this prop will be ignored.\n */\n subtitle?: string;\n}\n\nexport const AppBarMain = forwardRef(\n ({ title, subtitle, children, ...otherProps }, ref) => {\n if (React.isValidElement(children)) {\n return (\n \n {children}\n \n );\n }\n\n return (\n \n \n {children ?? title}\n {subtitle ? (\n {subtitle}\n ) : null}\n \n \n );\n },\n);\nAppBarMain.displayName = \"AppBarMain\";\n\nexport const AppBarIconButton = SeedAppBar.IconButton;\n\nexport const AppBarBackButton = forwardRef<\n HTMLButtonElement,\n AppBarIconButtonProps\n>(({ children = , onClick, ...otherProps }, ref) => {\n const activity = useActivity();\n const actions = useActions();\n\n const handleOnClick = (e: React.MouseEvent) => {\n onClick?.(e);\n\n if (!e.defaultPrevented) {\n actions.pop();\n }\n };\n\n if (!activity) {\n return null;\n }\n if (activity.isRoot) {\n return null;\n }\n\n return (\n \n {children}\n \n );\n});\nAppBarBackButton.displayName = \"AppBarBackButton\";\n\nexport const AppBarCloseButton = forwardRef<\n HTMLButtonElement,\n AppBarIconButtonProps\n>(({ children = , onClick, ...otherProps }, ref) => {\n const activity = useActivity();\n\n const handleOnClick = (e: React.MouseEvent) => {\n onClick?.(e);\n\n if (!e.defaultPrevented) {\n // you can do something here\n }\n };\n\n const isRoot = !activity || activity.isRoot;\n\n if (!isRoot) {\n return null;\n }\n\n return (\n \n {children}\n \n );\n});\nAppBarCloseButton.displayName = \"AppBarCloseButton\";\n" } ] } \ No newline at end of file diff --git a/docs/public/__registry__/ui/index.json b/docs/public/__registry__/ui/index.json index 7ca8eba42..27abc752d 100644 --- a/docs/public/__registry__/ui/index.json +++ b/docs/public/__registry__/ui/index.json @@ -2,9 +2,11 @@ { "name": "app-screen", "files": [ - "ui:app-screen.tsx" + "ui:app-screen.tsx", + "ui:app-bar.tsx" ], "dependencies": [ + "@seed-design/react", "@seed-design/stackflow" ] },