AppBar
화면 상단에서 현재 화면의 제목과 탐색 액션을 보여주는 내비게이션 바 컴포넌트입니다.
import { IconBellLine, IconChevronLeftLine } from "@karrotmarket/react-monochrome-icon";
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { Box, Flex } from "@seed-design/react";
import { seedPlugin } from "@seed-design/stackflow";
import { stackflow } from "@stackflow/react";
import {
AppBar,
AppBarIconButton,
AppBarLeft,
AppBarMain,
AppBarRight,
} from "seed-design/ui/app-bar";
import { AppScreen, AppScreenContent } from "seed-design/ui/app-screen";
function Activity() {
return (
<AppScreen theme="cupertino">
<AppBar>
<AppBarLeft>
<AppBarIconButton aria-label="뒤로">
<IconChevronLeftLine />
</AppBarIconButton>
</AppBarLeft>
<AppBarMain title="동네생활" />
<AppBarRight>
<AppBarIconButton aria-label="알림">
<IconBellLine />
</AppBarIconButton>
</AppBarRight>
</AppBar>
<AppScreenContent>
<Flex height="full" align="center" justify="center" color="fg.neutralMuted">
화면 콘텐츠
</Flex>
</AppScreenContent>
</AppScreen>
);
}
const { Stack } = stackflow({
activities: { Activity },
initialActivity: () => "Activity",
plugins: [basicRendererPlugin(), seedPlugin({ theme: "cupertino" })],
transitionDuration: 0,
});
export default function Preview() {
return (
<Box
position="relative"
width="375px"
maxWidth="100%"
height="375px"
overflowX="hidden"
overflowY="hidden"
borderWidth={1}
borderColor="stroke.neutralWeak"
borderRadius="r4"
>
<Stack />
</Box>
);
}Installation
npx @seed-design/cli@latest add ui:app-screenpnpm dlx @seed-design/cli@latest add ui:app-screenyarn dlx @seed-design/cli@latest add ui:app-screenbun x @seed-design/cli@latest add ui:app-screen의존성 설치
npm install @karrotmarket/react-monochrome-icon @seed-design/react @seed-design/stackflow @stackflow/reactyarn add @karrotmarket/react-monochrome-icon @seed-design/react @seed-design/stackflow @stackflow/reactpnpm add @karrotmarket/react-monochrome-icon @seed-design/react @seed-design/stackflow @stackflow/reactbun add @karrotmarket/react-monochrome-icon @seed-design/react @seed-design/stackflow @stackflow/react아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:app-screen
* @requires @seed-design/react@^2.0.0
* @requires @seed-design/css@^2.0.0
**/
"use client";
import { PullToRefreshRoot, PullToRefreshContent, PullToRefreshIndicator } from "./pull-to-refresh";
import { AppScreen as SeedAppScreen } from "@seed-design/stackflow";
import { useActions, useActivity } from "@stackflow/react";
import { forwardRef } from "react";
export interface AppScreenProps extends SeedAppScreen.RootProps {
preventSwipeBack?: boolean;
}
export const AppScreen = forwardRef<HTMLDivElement, AppScreenProps>(
({ children, onSwipeBackEnd, preventSwipeBack, ...otherProps }, ref) => {
const { pop } = useActions();
const { isRoot } = useActivity();
const shouldSwipeBack = !isRoot && !preventSwipeBack;
return (
<SeedAppScreen.Root
ref={ref}
onSwipeBackEnd={({ swiped }) => {
if (swiped) {
pop();
}
onSwipeBackEnd?.({ swiped });
}}
{...otherProps}
>
<SeedAppScreen.Dim />
{children}
{shouldSwipeBack && <SeedAppScreen.Edge />}
</SeedAppScreen.Root>
);
},
);
AppScreen.displayName = "AppScreen";
export interface AppScreenContentProps extends SeedAppScreen.LayerProps {
ptr?: boolean;
onPtrReady?: () => void;
onPtrRefresh?: () => Promise<void>;
}
export const AppScreenContent = forwardRef<HTMLDivElement, AppScreenContentProps>(
({ children, ptr, onPtrReady, onPtrRefresh, ...otherProps }, ref) => {
if (!ptr) {
return (
<SeedAppScreen.Layer ref={ref} {...otherProps}>
{children}
</SeedAppScreen.Layer>
);
}
return (
<PullToRefreshRoot asChild onPtrReady={onPtrReady} onPtrRefresh={onPtrRefresh}>
<SeedAppScreen.Layer ref={ref} {...otherProps}>
<PullToRefreshIndicator />
<PullToRefreshContent asChild>{children}</PullToRefreshContent>
</SeedAppScreen.Layer>
</PullToRefreshRoot>
);
},
);
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
/**
* @file ui:app-screen
* @requires @seed-design/react@^2.0.0
* @requires @seed-design/css@^2.0.0
**/
"use client";
import { IconChevronLeftLine, IconXmarkLine } from "@karrotmarket/react-monochrome-icon"; // "@daangn/react-monochrome-icon"과 동일합니다.
import { VStack } from "@seed-design/react";
import { AppBar as SeedAppBar } from "@seed-design/stackflow";
import { useActions, useActivity } from "@stackflow/react";
import * as React from "react";
import { forwardRef } from "react";
export interface AppBarProps extends SeedAppBar.RootProps {}
export const AppBar = SeedAppBar.Root;
export interface AppBarLeftProps extends SeedAppBar.LeftProps {}
export const AppBarLeft = SeedAppBar.Left;
export interface AppBarRightProps extends SeedAppBar.RightProps {}
export const AppBarRight = SeedAppBar.Right;
export interface AppBarSlotProps extends SeedAppBar.SlotProps {}
export const AppBarSlot = SeedAppBar.Slot;
export interface AppBarMainProps extends Omit<SeedAppBar.MainProps, "asChild"> {
/**
* The title of the app bar.
* If children is provided as ReactElement, this prop will be ignored.
*/
title?: string;
/**
* The subtitle of the app bar.
* If children is provided as ReactElement, this prop will be ignored.
*/
subtitle?: string;
}
export const AppBarMain = forwardRef<HTMLDivElement, AppBarMainProps>(
({ title, subtitle, children, ...otherProps }, ref) => {
if (React.isValidElement(children)) {
return (
<SeedAppBar.Main {...otherProps} ref={ref}>
{children}
</SeedAppBar.Main>
);
}
return (
<SeedAppBar.Main layout={subtitle ? "withSubtitle" : "titleOnly"} {...otherProps} ref={ref}>
<VStack overflowX="auto">
<SeedAppBar.Title>{children ?? title}</SeedAppBar.Title>
{subtitle ? <SeedAppBar.Subtitle>{subtitle}</SeedAppBar.Subtitle> : null}
</VStack>
</SeedAppBar.Main>
);
},
);
AppBarMain.displayName = "AppBarMain";
export interface AppBarIconButtonProps extends SeedAppBar.IconButtonProps {}
export const AppBarIconButton = SeedAppBar.IconButton;
export const AppBarBackButton = forwardRef<HTMLButtonElement, AppBarIconButtonProps>(
({ children = <IconChevronLeftLine />, onClick, ...otherProps }, ref) => {
const activity = useActivity();
const actions = useActions();
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(e);
if (!e.defaultPrevented) {
actions.pop();
}
};
if (!activity) {
return null;
}
if (activity.isRoot) {
return null;
}
return (
<SeedAppBar.IconButton
ref={ref}
aria-label="뒤로"
type="button"
onClick={handleOnClick}
{...otherProps}
>
{children}
</SeedAppBar.IconButton>
);
},
);
AppBarBackButton.displayName = "AppBarBackButton";
export const AppBarCloseButton = forwardRef<HTMLButtonElement, AppBarIconButtonProps>(
({ children = <IconXmarkLine />, onClick, ...otherProps }, ref) => {
const activity = useActivity();
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(e);
if (!e.defaultPrevented) {
// you can do something here
}
};
const isRoot = !activity || activity.isRoot;
if (!isRoot) {
return null;
}
return (
<AppBarIconButton
ref={ref}
aria-label="닫기"
type="button"
onClick={handleOnClick}
{...otherProps}
>
{children}
</AppBarIconButton>
);
},
);
AppBarCloseButton.displayName = "AppBarCloseButton";
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
React Registry는 AppBar를 AppScreen과 함께 설치합니다. AppBar는 Stackflow Activity 안에서 AppScreen과 함께 사용하세요. Stackflow 설정은 Getting Started를 참고하세요.
Props
AppBar
Prop
Type
background?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefinedAppBarLeft
Prop
Type
AppBarMain
Prop
Type
AppBarRight
Prop
Type
AppBarSlot
Prop
Type
AppBarIconButton
Prop
Type
Usage
설치한 snippet은 Root와 주요 슬롯을 AppBar, AppBarLeft, AppBarMain, AppBarRight로 내보냅니다. AppBarBackButton은 현재 Activity가 루트가 아닐 때 나타나며, 클릭하면 이전 Activity로 이동합니다.
import {
AppBar,
AppBarBackButton,
AppBarLeft,
AppBarMain,
AppBarRight,
} from "@/components/ui/app-bar";
export function Header() {
return (
<AppBar>
<AppBarLeft>
<AppBarBackButton />
</AppBarLeft>
<AppBarMain title="동네생활" />
<AppBarRight />
</AppBar>
);
}Title / Subtitle
AppBarMain에 title, subtitle을 전달하면 제목과 부제목을 조립합니다. subtitle이 있으면 layout="withSubtitle"을 자동으로 적용합니다.
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { Box, Flex } from "@seed-design/react";
import { seedPlugin } from "@seed-design/stackflow";
import { stackflow } from "@stackflow/react";
import { AppBar, AppBarMain } from "seed-design/ui/app-bar";
import { AppScreen, AppScreenContent } from "seed-design/ui/app-screen";
function Activity() {
return (
<AppScreen theme="cupertino">
<AppBar>
<AppBarMain title="관심 목록" subtitle="3개의 새 소식" />
</AppBar>
<AppScreenContent>
<Flex height="full" align="center" justify="center" color="fg.neutralMuted">
제목과 부제목을 함께 표시한 AppBar
</Flex>
</AppScreenContent>
</AppScreen>
);
}
const { Stack } = stackflow({
activities: { Activity },
initialActivity: () => "Activity",
plugins: [basicRendererPlugin(), seedPlugin({ theme: "cupertino" })],
transitionDuration: 0,
});
export default function TitleAndSubtitle() {
return (
<Box
position="relative"
width="375px"
maxWidth="100%"
height="375px"
overflowX="hidden"
overflowY="hidden"
borderWidth={1}
borderColor="stroke.neutralWeak"
borderRadius="r4"
>
<Stack />
</Box>
);
}Left / Right Action
왼쪽과 오른쪽에는 아이콘 버튼이나 커스텀 슬롯을 배치할 수 있습니다. Cupertino theme에서는 좌우 슬롯 폭을 기준으로 가운데 제목의 padding을 조정합니다.
"use client";
import { IconChevronLeftLine, IconXmarkLine } from "@karrotmarket/react-monochrome-icon";
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { Box, Flex } from "@seed-design/react";
import { seedPlugin } from "@seed-design/stackflow";
import { stackflow } from "@stackflow/react";
import {
AppBar,
AppBarIconButton,
AppBarLeft,
AppBarMain,
AppBarRight,
AppBarSlot,
} from "seed-design/ui/app-bar";
import { AppScreen, AppScreenContent } from "seed-design/ui/app-screen";
import { useState } from "react";
function Activity() {
const [lastAction, setLastAction] = useState("없음");
return (
<AppScreen theme="cupertino">
<AppBar>
<AppBarLeft>
<AppBarIconButton aria-label="뒤로" onClick={() => setLastAction("뒤로")}>
<IconChevronLeftLine />
</AppBarIconButton>
</AppBarLeft>
<AppBarMain title="작성하기" />
<AppBarRight>
<AppBarSlot>
<button type="button">완료</button>
</AppBarSlot>
<AppBarIconButton aria-label="닫기" onClick={() => setLastAction("닫기")}>
<IconXmarkLine />
</AppBarIconButton>
</AppBarRight>
</AppBar>
<AppScreenContent>
<Flex height="full" align="center" justify="center" color="fg.neutralMuted">
마지막 액션: {lastAction}
</Flex>
</AppScreenContent>
</AppScreen>
);
}
const { Stack } = stackflow({
activities: { Activity },
initialActivity: () => "Activity",
plugins: [basicRendererPlugin(), seedPlugin({ theme: "cupertino" })],
transitionDuration: 0,
});
export default function LeftAndRightActions() {
return (
<Box
position="relative"
width="375px"
maxWidth="100%"
height="375px"
overflowX="hidden"
overflowY="hidden"
borderWidth={1}
borderColor="stroke.neutralWeak"
borderRadius="r4"
>
<Stack />
</Box>
);
}Accessibility
아이콘만 있는 버튼에는 동작을 설명하는 aria-label을 지정하세요. AppBarBackButton과 AppBarCloseButton은 각각 "뒤로", "닫기"를 기본값으로 제공합니다.
Theme
theme은 "cupertino" 또는 "android"를 사용할 수 있습니다. AppScreen과 같은 theme을 사용하세요.
import { IconChevronLeftLine, IconXmarkLine } from "@karrotmarket/react-monochrome-icon";
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { Box, HStack, VStack } from "@seed-design/react";
import { seedPlugin } from "@seed-design/stackflow";
import { stackflow } from "@stackflow/react";
import {
AppBar,
AppBarIconButton,
AppBarLeft,
AppBarMain,
AppBarRight,
} from "seed-design/ui/app-bar";
import { AppScreen } from "seed-design/ui/app-screen";
import type { ReactNode } from "react";
function CupertinoActivity() {
return (
<AppScreen theme="cupertino">
<AppBar theme="cupertino">
<AppBarLeft>
<AppBarIconButton aria-label="뒤로">
<IconChevronLeftLine />
</AppBarIconButton>
</AppBarLeft>
<AppBarMain title="화면 제목" subtitle="보조 제목" />
<AppBarRight>
<AppBarIconButton aria-label="닫기">
<IconXmarkLine />
</AppBarIconButton>
</AppBarRight>
</AppBar>
</AppScreen>
);
}
function AndroidActivity() {
return (
<AppScreen theme="android">
<AppBar theme="android">
<AppBarLeft>
<AppBarIconButton aria-label="뒤로">
<IconChevronLeftLine />
</AppBarIconButton>
</AppBarLeft>
<AppBarMain title="화면 제목" subtitle="보조 제목" />
<AppBarRight>
<AppBarIconButton aria-label="닫기">
<IconXmarkLine />
</AppBarIconButton>
</AppBarRight>
</AppBar>
</AppScreen>
);
}
const { Stack: CupertinoStack } = stackflow({
activities: { CupertinoActivity },
initialActivity: () => "CupertinoActivity",
plugins: [basicRendererPlugin(), seedPlugin({ theme: "cupertino" })],
transitionDuration: 0,
});
const { Stack: AndroidStack } = stackflow({
activities: { AndroidActivity },
initialActivity: () => "AndroidActivity",
plugins: [basicRendererPlugin(), seedPlugin({ theme: "android" })],
transitionDuration: 0,
});
function ExampleFrame({ children }: { children: ReactNode }) {
return (
<Box
position="relative"
width="375px"
maxWidth="100%"
height="375px"
overflowX="hidden"
overflowY="hidden"
borderWidth={1}
borderColor="stroke.neutralWeak"
borderRadius="r4"
>
{children}
</Box>
);
}
export default function PlatformLayouts() {
return (
<HStack gap="x6" flexWrap="wrap" justify="center">
<VStack gap="x3">
<span>Cupertino</span>
<ExampleFrame>
<CupertinoStack />
</ExampleFrame>
</VStack>
<VStack gap="x3">
<span>Android</span>
<ExampleFrame>
<AndroidStack />
</ExampleFrame>
</VStack>
</HStack>
);
}Last updated on