Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: stackflow integration #497

Merged
merged 14 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
47 changes: 47 additions & 0 deletions docs/components/example/app-screen-preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { Flex } from "@/registry/ui/layout";
import { IconBellFill } from "@daangn/react-monochrome-icon";
import type { ActivityComponentType } from "@stackflow/react/future";
import {
AppBar,
AppScreen,
CloseButton,
IconButton,
Left,
Right,
Title,
} from "seed-design/ui/app-screen";

declare module "@stackflow/config" {
interface Register {
AppScreenPreview: unknown;
}
}

const AppScreenPreviewActivity: ActivityComponentType<"AppScreenPreview"> = () => {
return (
<AppScreen
theme="cupertino"
appBar={
<AppBar>
<Left>
<CloseButton />
</Left>
<Title>Preview</Title>
<Right>
<IconButton aria-label="Notification">
<IconBellFill />
</IconButton>
</Right>
</AppBar>
}
>
<Flex height="full" justifyContent="center" alignItems="center">
Preview
</Flex>
</AppScreen>
);
};

export default AppScreenPreviewActivity;
47 changes: 47 additions & 0 deletions docs/components/example/app-screen-transparent-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { Flex } from "@/registry/ui/layout";
import { IconBellFill } from "@daangn/react-monochrome-icon";
import type { ActivityComponentType } from "@stackflow/react/future";
import {
AppBar,
AppScreen,
CloseButton,
IconButton,
Left,
Right,
Title,
} from "seed-design/ui/app-screen";

declare module "@stackflow/config" {
interface Register {
AppScreenTransparentBar: unknown;
}
}

const AppScreenTransparentBarActivity: ActivityComponentType<"AppScreenTransparentBar"> = () => {
return (
<AppScreen
theme="cupertino"
appBar={
<AppBar tone="transparent" border={false}>
<Left>
<CloseButton />
</Left>
<Title>Transparent Bar</Title>
<Right>
<IconButton aria-label="Notification">
<IconBellFill />
</IconButton>
</Right>
</AppBar>
}
>
<Flex height="full">
<img src="/penguin.webp" alt="Penguin" />
</Flex>
</AppScreen>
);
};

export default AppScreenTransparentBarActivity;
2 changes: 2 additions & 0 deletions docs/components/example/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"actionable-inline-banner-with-icon": "import { ActionableInlineBanner, InlineBannerDescription } from \"seed-design/ui/inline-banner\";\nimport { IconILowercaseSerifCircleFill } from \"@daangn/react-monochrome-icon\";\n\nexport default function ActionableInlineBannerWithIcon() {\n return (\n <ActionableInlineBanner\n onClick={() => window.alert(\"Hello World\")}\n variant=\"informativeWeak\"\n icon={<IconILowercaseSerifCircleFill />}\n >\n <InlineBannerDescription>다른 사람과 예약된 물품이 있어요.</InlineBannerDescription>\n </ActionableInlineBanner>\n );\n}",
"alert-dialog-default-activity": "import * as React from \"react\";\n\nimport { AppScreen } from \"@stackflow/plugin-basic-ui\";\nimport { type ActivityComponentType, useStepFlow, useStack } from \"@stackflow/react/future\";\n\nimport { ActionButton } from \"seed-design/ui/action-button\";\nimport { AlertDialog as UIAlertDialog } from \"seed-design/ui/alert-dialog\";\n\ndeclare module \"@stackflow/config\" {\n interface Register {\n AlertDialogDefault: {\n alert: boolean;\n };\n }\n}\n\nconst AlertDialogDefaultActivity: ActivityComponentType<\"AlertDialogDefault\"> = ({ params }) => {\n const { alert } = params;\n const stack = useStack();\n const { pushStep, popStep } = useStepFlow(\"AlertDialogDefault\");\n\n const appBarLeft = () => <div>Left</div>;\n const appBarRight = () => <div>Right</div>;\n\n const onInteractOutside = () => {\n popStep();\n };\n\n const onButtonClick = () => {\n pushStep({\n alert: true,\n });\n };\n\n const mainActivitySteps = stack.activities[0].steps;\n\n return (\n <AppScreen\n appBar={{\n renderLeft: appBarLeft,\n renderRight: appBarRight,\n }}\n >\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n width: \"100%\",\n }}\n >\n <ActionButton onClick={onButtonClick}>Open</ActionButton>\n {alert && (\n <UIAlertDialog\n onInteractOutside={onInteractOutside}\n title=\"Title\"\n description=\"Description\"\n />\n )}\n </div>\n\n <div\n style={{\n position: \"fixed\",\n bottom: \"0\",\n right: \"0\",\n padding: \"8px\",\n }}\n >\n <span\n style={{\n fontSize: \"12px\",\n color: \"var(--seed-semantic-color-divider-3)\",\n }}\n >\n Steps\n </span>\n {mainActivitySteps.map((step) => (\n <div\n style={{\n fontSize: \"12px\",\n width: \"1rem\",\n height: \"1rem\",\n borderRadius: \"50%\",\n border: \"1px solid var(--seed-semantic-color-divider-3)\",\n margin: \"8px\",\n }}\n key={step.id}\n />\n ))}\n </div>\n\n <div\n style={{\n position: \"fixed\",\n bottom: \"0\",\n left: \"0\",\n padding: \"8px\",\n zIndex: 1000,\n }}\n >\n <ActionButton onClick={() => popStep}>Back</ActionButton>\n </div>\n </AppScreen>\n );\n};\n\nexport default AlertDialogDefaultActivity;\n\nAlertDialogDefaultActivity.displayName = \"AlertDialogDefaultActivity\";",
"alert-dialog-preview": "import * as React from \"react\";\n\nimport { AlertDialog } from \"seed-design/ui/alert-dialog\";\n\nexport default function AlertDialogPreview() {\n return <AlertDialog title=\"Title\" description=\"Description\" />;\n}",
"app-screen-preview": "import { Flex } from \"@/registry/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 <AppScreen\n theme=\"cupertino\"\n appBar={\n <AppBar>\n <Left>\n <CloseButton />\n </Left>\n <Title>Preview</Title>\n <Right>\n <IconButton aria-label=\"Notification\">\n <IconBellFill />\n </IconButton>\n </Right>\n </AppBar>\n }\n >\n <Flex height=\"full\" justifyContent=\"center\" alignItems=\"center\">\n Preview\n </Flex>\n </AppScreen>\n );\n};\n\nexport default AppScreenPreviewActivity;",
"app-screen-transparent-bar": "import { Flex } from \"@/registry/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 <AppScreen\n theme=\"cupertino\"\n appBar={\n <AppBar tone=\"transparent\" border={false}>\n <Left>\n <CloseButton />\n </Left>\n <Title>Transparent Bar</Title>\n <Right>\n <IconButton aria-label=\"Notification\">\n <IconBellFill />\n </IconButton>\n </Right>\n </AppBar>\n }\n >\n <Flex height=\"full\">\n <img src=\"/penguin.webp\" alt=\"Penguin\" />\n </Flex>\n </AppScreen>\n );\n};\n\nexport default AppScreenTransparentBarActivity;",
"avatar-badge": "import { IdentityPlaceholder } from \"seed-design/ui/identity-placeholder\";\nimport { Avatar, AvatarBadge, AvatarFallback, AvatarImage } from \"seed-design/ui/avatar\";\n\nexport default function AvatarWithBadge() {\n return (\n <Avatar size=\"80\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>\n <IdentityPlaceholder />\n </AvatarFallback>\n <AvatarBadge>\n <div style={{ background: \"green\", width: 20, height: 20, borderRadius: 9999 }} />\n </AvatarBadge>\n </Avatar>\n );\n}",
"avatar-preview": "import { Flex } from \"seed-design/ui/layout\";\nimport { IdentityPlaceholder } from \"seed-design/ui/identity-placeholder\";\nimport { Avatar, AvatarBadge, AvatarFallback, AvatarImage } from \"seed-design/ui/avatar\";\n\nexport default function AvatarPreview() {\n return (\n <Flex gap=\"s4\">\n <Avatar size=\"80\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>\n <IdentityPlaceholder />\n </AvatarFallback>\n <AvatarBadge>\n <div style={{ background: \"green\", width: 20, height: 20, borderRadius: 9999 }} />\n </AvatarBadge>\n </Avatar>\n <Avatar size=\"80\">\n <AvatarImage src={undefined} />\n <AvatarFallback>\n <IdentityPlaceholder />\n </AvatarFallback>\n </Avatar>\n </Flex>\n );\n}",
"avatar-size": "import { Flex } from \"seed-design/ui/layout\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"seed-design/ui/avatar\";\n\nexport default function AvatarSize() {\n return (\n <Flex gap=\"s4\">\n <Avatar size=\"20\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>L</AvatarFallback>\n </Avatar>\n <Avatar size=\"24\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>L</AvatarFallback>\n </Avatar>\n <Avatar size=\"36\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>L</AvatarFallback>\n </Avatar>\n <Avatar size=\"48\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>L</AvatarFallback>\n </Avatar>\n <Avatar size=\"64\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>L</AvatarFallback>\n </Avatar>\n <Avatar size=\"80\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>L</AvatarFallback>\n </Avatar>\n <Avatar size=\"96\">\n <AvatarImage src=\"https://avatars.githubusercontent.com/u/54893898?v=4\" />\n <AvatarFallback>L</AvatarFallback>\n </Avatar>\n </Flex>\n );\n}",
Expand Down
1 change: 0 additions & 1 deletion docs/components/stackflow/ActivityLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AppScreen } from "@stackflow/plugin-basic-ui";

import {
IconChevronDownFill,
IconChevronDownLine,
IconDot3HorizontalChatbubbleLeftLine,
IconGearLine,
Expand Down
2 changes: 1 addition & 1 deletion docs/components/stackflow/Stackflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const Stackflow: React.FC<StackflowProps> = ({ Activity }) => {
return (
<div
ref={ref}
className={cn()}
className={cn("not-prose")}
style={{
position: "relative",
width: "100%",
Expand Down
25 changes: 25 additions & 0 deletions docs/content/docs/react/components/stackflow/app-screen.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: App Screen
---

<StackflowExample name="app-screen-preview" />

## 설치

<Installation name="app-screen" />

## Props

### AppScreen

<ReactTypeTable path="./registry/ui/app-screen.tsx" name="AppScreenProps" />

### AppBar

<ReactTypeTable path="./registry/ui/app-screen.tsx" name="AppBarProps" />

## 예제

### Transparent app bar

<StackflowExample name="app-screen-transparent-bar" />
13 changes: 13 additions & 0 deletions docs/public/__registry__/ui/app-screen.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "app-screen",
"dependencies": [
"@seed-design/stackflow"
],
"registries": [
{
"name": "app-screen.tsx",
"type": "ui",
"content": "\"use client\";\n\nimport \"@seed-design/stylesheet/screen.css\";\nimport \"@seed-design/stylesheet/topNavigation.css\";\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 = <IconChevronLeftLine />, onClick, ...otherProps }, ref) => {\n const activity = useActivity();\n const actions = useActions();\n\n const handleOnClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\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 <SeedAppBar.IconButton\n ref={ref}\n aria-label=\"Go Back\"\n type=\"button\"\n onClick={handleOnClick}\n {...otherProps}\n >\n {children}\n </SeedAppBar.IconButton>\n );\n});\nBackButton.displayName = \"BackButton\";\n\nexport const CloseButton = forwardRef<\n HTMLButtonElement,\n SeedAppBar.IconButtonProps\n>(({ children = <IconXmarkLine />, onClick, ...otherProps }, ref) => {\n const activity = useActivity();\n\n const handleOnClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\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 <IconButton\n ref={ref}\n aria-label=\"Close\"\n type=\"button\"\n onClick={handleOnClick}\n {...otherProps}\n >\n {children}\n </IconButton>\n );\n});\nCloseButton.displayName = \"CloseButton\";\n\nexport const AppScreen = forwardRef<HTMLDivElement, AppScreenProps>(\n ({ children, ...otherProps }, ref) => {\n return (\n <SeedAppScreen.Root ref={ref} {...otherProps}>\n <SeedAppScreen.Dim />\n <SeedAppScreen.Layer>{children}</SeedAppScreen.Layer>\n <SeedAppScreen.Edge />\n </SeedAppScreen.Root>\n );\n },\n);\nAppScreen.displayName = \"AppScreen\";\n"
}
]
}
9 changes: 9 additions & 0 deletions docs/public/__registry__/ui/index.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"name": "app-screen",
"files": [
"ui:app-screen.tsx"
],
"dependencies": [
"@seed-design/stackflow"
]
},
{
"name": "alert-dialog",
"innerDependencies": [
Expand Down
Binary file added docs/public/penguin.webp
Binary file not shown.
75 changes: 75 additions & 0 deletions docs/public/rootage/components/top-navigation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"kind": "ComponentSpec",
"metadata": {
"id": "top-navigation",
"name": "Top Navigation"
},
"data": {
"theme=cupertino": {
"enabled": {
"root": {
"minHeight": "44px",
"paddingX": "$unit.s4"
},
"title": {
"fontSize": "$font-size.s6-static",
"fontWeight": "$font-weight.bold"
},
"icon": {
"size": "24px",
"targetSize": "40px"
}
}
},
"theme=android": {
"enabled": {
"root": {
"minHeight": "56px",
"paddingX": "$unit.s4"
},
"title": {
"fontSize": "$font-size.s6-static",
"fontWeight": "$font-weight.bold"
},
"icon": {
"size": "24px",
"targetSize": "40px"
}
}
},
"tone=layer": {
"enabled": {
"root": {
"color": "$color.bg.layer-default"
},
"title": {
"color": "$color.fg.neutral"
},
"icon": {
"color": "$color.fg.neutral"
}
}
},
"tone=transparent": {
"enabled": {
"root": {
"color": "#00000000"
},
"title": {
"color": "$color.fg.static-white"
},
"icon": {
"color": "$color.fg.static-white"
}
}
},
"divider=true": {
"enabled": {
"root": {
"strokeColor": "$color.stroke.neutral-muted",
"strokeWidth": "1px"
}
}
}
}
}
5 changes: 5 additions & 0 deletions docs/public/rootage/font-size.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
"values": {
"default": "1.625rem"
}
},
"$font-size.s6-static": {
"values": {
"default": "18px"
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions docs/registry/registry-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import tabsPkg from "@seed-design/react-tabs/package.json";
import popoverPkg from "@seed-design/react-popover/package.json";

export const registryUI: RegistryUI = [
{
name: "app-screen",
files: ["ui:app-screen.tsx"],
dependencies: ["@seed-design/stackflow"],
},
{
name: "alert-dialog",
innerDependencies: ["action-button"],
Expand Down
118 changes: 118 additions & 0 deletions docs/registry/ui/app-screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"use client";

import "@seed-design/stylesheet/screen.css";
import "@seed-design/stylesheet/topNavigation.css";

import {
IconChevronLeftLine,
IconXmarkLine,
} from "@daangn/react-monochrome-icon";
import {
AppBar as SeedAppBar,
AppScreen as SeedAppScreen,
} from "@seed-design/stackflow";
import { useActions, useActivity } from "@stackflow/react";
import { forwardRef, useCallback } from "react";

export type AppBarProps = SeedAppBar.RootProps;

export type AppScreenProps = SeedAppScreen.RootProps;

export const AppBar = SeedAppBar.Root;

export const Left = SeedAppBar.Left;

export const Right = SeedAppBar.Right;

export const Title = SeedAppBar.Title;

export const IconButton = SeedAppBar.IconButton;

export const BackButton = forwardRef<
HTMLButtonElement,
SeedAppBar.IconButtonProps
>(({ children = <IconChevronLeftLine />, onClick, ...otherProps }, ref) => {
const activity = useActivity();
const actions = useActions();

const handleOnClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(e);

if (!e.defaultPrevented) {
actions.pop();
}
},
[actions],
);

if (!activity) {
return null;
}
if (activity.isRoot) {
return null;
}

return (
<SeedAppBar.IconButton
ref={ref}
aria-label="Go Back"
type="button"
onClick={handleOnClick}
{...otherProps}
>
{children}
</SeedAppBar.IconButton>
);
});
BackButton.displayName = "BackButton";

export const CloseButton = forwardRef<
HTMLButtonElement,
SeedAppBar.IconButtonProps
>(({ children = <IconXmarkLine />, onClick, ...otherProps }, ref) => {
const activity = useActivity();

const handleOnClick = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(e);

if (!e.defaultPrevented) {
// you can do something here
}
},
[],
);

const isRoot = !activity || activity.isRoot;

if (!isRoot) {
return null;
}

return (
<IconButton
ref={ref}
aria-label="Close"
type="button"
onClick={handleOnClick}
{...otherProps}
>
{children}
</IconButton>
);
});
CloseButton.displayName = "CloseButton";

export const AppScreen = forwardRef<HTMLDivElement, AppScreenProps>(
({ children, ...otherProps }, ref) => {
return (
<SeedAppScreen.Root ref={ref} {...otherProps}>
<SeedAppScreen.Dim />
<SeedAppScreen.Layer>{children}</SeedAppScreen.Layer>
<SeedAppScreen.Edge />
</SeedAppScreen.Root>
);
},
);
AppScreen.displayName = "AppScreen";
Loading
Loading