From 0145632eab04a39d7e46b4c887ce4afb88092b49 Mon Sep 17 00:00:00 2001 From: curogom Date: Fri, 28 Nov 2025 14:15:27 +0900 Subject: [PATCH 1/3] docs: add Korean localization --- .gitignore | 5 +- docs/cli.mdx | 2 +- docs/docs.json | 469 ++++++++++++------ docs/introduction.mdx | 2 +- docs/ko/actions/delay_action.mdx | 21 + docs/ko/actions/dialog.mdx | 54 ++ docs/ko/actions/form_validate.mdx | 35 ++ docs/ko/actions/get_form_value.mdx | 21 + docs/ko/actions/modal_bottom_sheet.mdx | 80 +++ docs/ko/actions/multi_action.mdx | 79 +++ docs/ko/actions/navigate.mdx | 92 ++++ docs/ko/actions/network_request.mdx | 68 +++ docs/ko/actions/none.mdx | 12 + docs/ko/actions/snack_bar.mdx | 57 +++ docs/ko/cli.mdx | 216 ++++++++ docs/ko/concepts/custom_actions.mdx | 135 +++++ docs/ko/concepts/custom_widgets.mdx | 193 +++++++ docs/ko/concepts/rendering_stac_widgets.mdx | 181 +++++++ docs/ko/concepts/theming.mdx | 58 +++ docs/ko/get_started.mdx | 84 ++++ docs/ko/introduction.mdx | 103 ++++ docs/ko/project_structure.mdx | 22 + docs/ko/quickstart.mdx | 231 +++++++++ docs/ko/sdui.mdx | 60 +++ docs/ko/styles/border.mdx | 135 +++++ docs/ko/styles/border_radius.mdx | 43 ++ docs/ko/styles/border_side.mdx | 30 ++ docs/ko/styles/box_fit.mdx | 25 + docs/ko/styles/clip_behavior.mdx | 22 + docs/ko/styles/colors.mdx | 55 ++ docs/ko/styles/stac_alignment_directional.mdx | 27 + docs/ko/styles/table_border.mdx | 33 ++ docs/ko/styles/table_column_width.mdx | 22 + docs/ko/widgets/alert_dialog.mdx | 59 +++ docs/ko/widgets/align.mdx | 43 ++ docs/ko/widgets/app_bar.mdx | 41 ++ docs/ko/widgets/aspect_ratio.mdx | 28 ++ docs/ko/widgets/auto_complete.mdx | 32 ++ docs/ko/widgets/backdrop_filter.mdx | 103 ++++ docs/ko/widgets/bottom_navigation_bar.mdx | 76 +++ docs/ko/widgets/card.mdx | 40 ++ docs/ko/widgets/carousel_view.mdx | 44 ++ docs/ko/widgets/center.mdx | 26 + docs/ko/widgets/check_box.mdx | 42 ++ docs/ko/widgets/chip.mdx | 58 +++ docs/ko/widgets/circle_avatar.mdx | 29 ++ .../widgets/circular_progress_indicator.mdx | 30 ++ docs/ko/widgets/clip_oval.mdx | 29 ++ docs/ko/widgets/clip_rrect.mdx | 66 +++ docs/ko/widgets/colored_box.mdx | 24 + docs/ko/widgets/column.mdx | 36 ++ docs/ko/widgets/container.mdx | 56 +++ docs/ko/widgets/custom_scroll_view.mdx | 41 ++ docs/ko/widgets/drawer.mdx | 45 ++ docs/ko/widgets/dropdown_menu.mdx | 47 ++ docs/ko/widgets/dynamic_view.mdx | 133 +++++ docs/ko/widgets/elevated_button.mdx | 33 ++ docs/ko/widgets/expanded.mdx | 26 + docs/ko/widgets/filled_button.mdx | 30 ++ docs/ko/widgets/fitted_box.mdx | 30 ++ docs/ko/widgets/flexible.mdx | 28 ++ docs/ko/widgets/floating_action_button.mdx | 38 ++ docs/ko/widgets/form.mdx | 46 ++ docs/ko/widgets/fractionally_sized_box.mdx | 34 ++ docs/ko/widgets/gesture_detector.mdx | 87 ++++ docs/ko/widgets/grid_view.mdx | 44 ++ docs/ko/widgets/icon.mdx | 32 ++ docs/ko/widgets/icon_button.mdx | 49 ++ docs/ko/widgets/image.mdx | 32 ++ docs/ko/widgets/ink_well.mdx | 47 ++ docs/ko/widgets/limited_box.mdx | 29 ++ docs/ko/widgets/linear_progress_indicator.mdx | 32 ++ docs/ko/widgets/list_tile.mdx | 42 ++ docs/ko/widgets/listview.mdx | 54 ++ docs/ko/widgets/network_widget.mdx | 27 + docs/ko/widgets/opacity.mdx | 25 + docs/ko/widgets/outlined_button.mdx | 25 + docs/ko/widgets/padding.mdx | 32 ++ docs/ko/widgets/page_view.mdx | 35 ++ docs/ko/widgets/placeholder.mdx | 25 + docs/ko/widgets/positioned.mdx | 70 +++ docs/ko/widgets/radio_group.mdx | 60 +++ docs/ko/widgets/refresh_indicator.mdx | 31 ++ docs/ko/widgets/row.mdx | 33 ++ docs/ko/widgets/safe_area.mdx | 28 ++ docs/ko/widgets/scaffold.mdx | 51 ++ docs/ko/widgets/single_child_scroll_view.mdx | 33 ++ docs/ko/widgets/sized_box.mdx | 23 + docs/ko/widgets/slider.mdx | 35 ++ docs/ko/widgets/sliver_app_bar.mdx | 48 ++ docs/ko/widgets/spacer.mdx | 21 + docs/ko/widgets/stack.mdx | 32 ++ docs/ko/widgets/switch.mdx | 32 ++ docs/ko/widgets/tab_bar.mdx | 84 ++++ docs/ko/widgets/table.mdx | 56 +++ docs/ko/widgets/table_cell.mdx | 27 + docs/ko/widgets/table_row.mdx | 21 + docs/ko/widgets/text.mdx | 36 ++ docs/ko/widgets/text_button.mdx | 42 ++ docs/ko/widgets/text_field.mdx | 41 ++ docs/ko/widgets/text_form_field.mdx | 54 ++ docs/ko/widgets/vertical_divider.mdx | 24 + docs/ko/widgets/visibility.mdx | 30 ++ docs/ko/widgets/webview.mdx | 40 ++ docs/ko/widgets/wrap.mdx | 37 ++ docs/quickstart.mdx | 2 +- docs/sdui.mdx | 2 +- 107 files changed, 5532 insertions(+), 143 deletions(-) create mode 100644 docs/ko/actions/delay_action.mdx create mode 100644 docs/ko/actions/dialog.mdx create mode 100644 docs/ko/actions/form_validate.mdx create mode 100644 docs/ko/actions/get_form_value.mdx create mode 100644 docs/ko/actions/modal_bottom_sheet.mdx create mode 100644 docs/ko/actions/multi_action.mdx create mode 100644 docs/ko/actions/navigate.mdx create mode 100644 docs/ko/actions/network_request.mdx create mode 100644 docs/ko/actions/none.mdx create mode 100644 docs/ko/actions/snack_bar.mdx create mode 100644 docs/ko/cli.mdx create mode 100644 docs/ko/concepts/custom_actions.mdx create mode 100644 docs/ko/concepts/custom_widgets.mdx create mode 100644 docs/ko/concepts/rendering_stac_widgets.mdx create mode 100644 docs/ko/concepts/theming.mdx create mode 100644 docs/ko/get_started.mdx create mode 100644 docs/ko/introduction.mdx create mode 100644 docs/ko/project_structure.mdx create mode 100644 docs/ko/quickstart.mdx create mode 100644 docs/ko/sdui.mdx create mode 100644 docs/ko/styles/border.mdx create mode 100644 docs/ko/styles/border_radius.mdx create mode 100644 docs/ko/styles/border_side.mdx create mode 100644 docs/ko/styles/box_fit.mdx create mode 100644 docs/ko/styles/clip_behavior.mdx create mode 100644 docs/ko/styles/colors.mdx create mode 100644 docs/ko/styles/stac_alignment_directional.mdx create mode 100644 docs/ko/styles/table_border.mdx create mode 100644 docs/ko/styles/table_column_width.mdx create mode 100644 docs/ko/widgets/alert_dialog.mdx create mode 100644 docs/ko/widgets/align.mdx create mode 100644 docs/ko/widgets/app_bar.mdx create mode 100644 docs/ko/widgets/aspect_ratio.mdx create mode 100644 docs/ko/widgets/auto_complete.mdx create mode 100644 docs/ko/widgets/backdrop_filter.mdx create mode 100644 docs/ko/widgets/bottom_navigation_bar.mdx create mode 100644 docs/ko/widgets/card.mdx create mode 100644 docs/ko/widgets/carousel_view.mdx create mode 100644 docs/ko/widgets/center.mdx create mode 100644 docs/ko/widgets/check_box.mdx create mode 100644 docs/ko/widgets/chip.mdx create mode 100644 docs/ko/widgets/circle_avatar.mdx create mode 100644 docs/ko/widgets/circular_progress_indicator.mdx create mode 100644 docs/ko/widgets/clip_oval.mdx create mode 100644 docs/ko/widgets/clip_rrect.mdx create mode 100644 docs/ko/widgets/colored_box.mdx create mode 100644 docs/ko/widgets/column.mdx create mode 100644 docs/ko/widgets/container.mdx create mode 100644 docs/ko/widgets/custom_scroll_view.mdx create mode 100644 docs/ko/widgets/drawer.mdx create mode 100644 docs/ko/widgets/dropdown_menu.mdx create mode 100644 docs/ko/widgets/dynamic_view.mdx create mode 100644 docs/ko/widgets/elevated_button.mdx create mode 100644 docs/ko/widgets/expanded.mdx create mode 100644 docs/ko/widgets/filled_button.mdx create mode 100644 docs/ko/widgets/fitted_box.mdx create mode 100644 docs/ko/widgets/flexible.mdx create mode 100644 docs/ko/widgets/floating_action_button.mdx create mode 100644 docs/ko/widgets/form.mdx create mode 100644 docs/ko/widgets/fractionally_sized_box.mdx create mode 100644 docs/ko/widgets/gesture_detector.mdx create mode 100644 docs/ko/widgets/grid_view.mdx create mode 100644 docs/ko/widgets/icon.mdx create mode 100644 docs/ko/widgets/icon_button.mdx create mode 100644 docs/ko/widgets/image.mdx create mode 100644 docs/ko/widgets/ink_well.mdx create mode 100644 docs/ko/widgets/limited_box.mdx create mode 100644 docs/ko/widgets/linear_progress_indicator.mdx create mode 100644 docs/ko/widgets/list_tile.mdx create mode 100644 docs/ko/widgets/listview.mdx create mode 100644 docs/ko/widgets/network_widget.mdx create mode 100644 docs/ko/widgets/opacity.mdx create mode 100644 docs/ko/widgets/outlined_button.mdx create mode 100644 docs/ko/widgets/padding.mdx create mode 100644 docs/ko/widgets/page_view.mdx create mode 100644 docs/ko/widgets/placeholder.mdx create mode 100644 docs/ko/widgets/positioned.mdx create mode 100644 docs/ko/widgets/radio_group.mdx create mode 100644 docs/ko/widgets/refresh_indicator.mdx create mode 100644 docs/ko/widgets/row.mdx create mode 100644 docs/ko/widgets/safe_area.mdx create mode 100644 docs/ko/widgets/scaffold.mdx create mode 100644 docs/ko/widgets/single_child_scroll_view.mdx create mode 100644 docs/ko/widgets/sized_box.mdx create mode 100644 docs/ko/widgets/slider.mdx create mode 100644 docs/ko/widgets/sliver_app_bar.mdx create mode 100644 docs/ko/widgets/spacer.mdx create mode 100644 docs/ko/widgets/stack.mdx create mode 100644 docs/ko/widgets/switch.mdx create mode 100644 docs/ko/widgets/tab_bar.mdx create mode 100644 docs/ko/widgets/table.mdx create mode 100644 docs/ko/widgets/table_cell.mdx create mode 100644 docs/ko/widgets/table_row.mdx create mode 100644 docs/ko/widgets/text.mdx create mode 100644 docs/ko/widgets/text_button.mdx create mode 100644 docs/ko/widgets/text_field.mdx create mode 100644 docs/ko/widgets/text_form_field.mdx create mode 100644 docs/ko/widgets/vertical_divider.mdx create mode 100644 docs/ko/widgets/visibility.mdx create mode 100644 docs/ko/widgets/webview.mdx create mode 100644 docs/ko/widgets/wrap.mdx diff --git a/.gitignore b/.gitignore index 6b1cefc3..32472601 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,11 @@ mason-lock.json # Local Netlify folder .netlify +# Docusaurus build artifacts +website/ + # Cursor Files .cursor/ # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ diff --git a/docs/cli.mdx b/docs/cli.mdx index 572cde53..4d34e801 100644 --- a/docs/cli.mdx +++ b/docs/cli.mdx @@ -215,4 +215,4 @@ stac deploy | --------------- | ------------------------------ | | `-v, --verbose` | Show additional command output | | `--version` | Print tool version | -| `--help` | Print usage information | \ No newline at end of file +| `--help` | Print usage information | diff --git a/docs/docs.json b/docs/docs.json index 8214a5a3..ae763172 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -18,166 +18,359 @@ "library": "lucide" }, "navigation": { - "tabs": [ + "languages": [ { - "tab": "Overview", - "groups": [ + "language": "en", + "tabs": [ { - "group": "Get Started", - "icon": "rocket", - "pages": [ - "introduction", - "quickstart", - "sdui", - "cli", - "project_structure" + "tab": "Overview", + "groups": [ + { + "group": "Get Started", + "icon": "rocket", + "pages": [ + "introduction", + "quickstart", + "sdui", + "cli", + "project_structure" + ] + }, + { + "group": "Concepts", + "icon": "book", + "pages": [ + "concepts/rendering_stac_widgets", + "concepts/caching", + "concepts/custom_widgets", + "concepts/custom_actions" + ] + } ] }, { - "group": "Concepts", - "icon": "book", - "pages": [ - "concepts/rendering_stac_widgets", - "concepts/caching", - "concepts/custom_widgets", - "concepts/custom_actions" - ] - } - ] - }, - { - "tab": "Widgets", - "groups": [ - { - "group": "Layout Widgets", - "pages": [ - "widgets/align", - "widgets/aspect_ratio", - "widgets/center", - "widgets/column", - "widgets/container", - "widgets/expanded", - "widgets/flexible", - "widgets/fitted_box", - "widgets/fractionally_sized_box", - "widgets/limited_box", - "widgets/padding", - "widgets/positioned", - "widgets/row", - "widgets/safe_area", - "widgets/sized_box", - "widgets/spacer", - "widgets/stack", - "widgets/wrap" + "tab": "Widgets", + "groups": [ + { + "group": "Layout Widgets", + "pages": [ + "widgets/align", + "widgets/aspect_ratio", + "widgets/center", + "widgets/column", + "widgets/container", + "widgets/expanded", + "widgets/flexible", + "widgets/fitted_box", + "widgets/fractionally_sized_box", + "widgets/limited_box", + "widgets/padding", + "widgets/positioned", + "widgets/row", + "widgets/safe_area", + "widgets/sized_box", + "widgets/spacer", + "widgets/stack", + "widgets/wrap" + ] + }, + { + "group": "Display Widgets", + "pages": [ + "widgets/backdrop_filter", + "widgets/card", + "widgets/carousel_view", + "widgets/circle_avatar", + "widgets/clip_oval", + "widgets/clip_rrect", + "widgets/colored_box", + "widgets/icon", + "widgets/image", + "widgets/opacity", + "widgets/placeholder", + "widgets/visibility" + ] + }, + { + "group": "Interactive Widgets", + "pages": [ + "widgets/alert_dialog", + "widgets/app_bar", + "widgets/auto_complete", + "widgets/bottom_navigation_bar", + "widgets/check_box", + "widgets/chip", + "widgets/circular_progress_indicator", + "widgets/drawer", + "widgets/dropdown_menu", + "widgets/dynamic_view", + "widgets/elevated_button", + "widgets/filled_button", + "widgets/floating_action_button", + "widgets/form", + "widgets/gesture_detector", + "widgets/icon_button", + "widgets/ink_well", + "widgets/linear_progress_indicator", + "widgets/list_tile", + "widgets/listview", + "widgets/network_widget", + "widgets/outlined_button", + "widgets/page_view", + "widgets/radio_group", + "widgets/refresh_indicator", + "widgets/scaffold", + "widgets/single_child_scroll_view", + "widgets/slider", + "widgets/sliver_app_bar", + "widgets/switch", + "widgets/tab_bar", + "widgets/text_button", + "widgets/text_field", + "widgets/text_form_field", + "widgets/webview" + ] + }, + { + "group": "Data Widgets", + "pages": [ + "widgets/custom_scroll_view", + "widgets/grid_view", + "widgets/table", + "widgets/table_cell", + "widgets/table_row", + "widgets/text", + "widgets/vertical_divider" + ] + } ] }, { - "group": "Display Widgets", - "pages": [ - "widgets/backdrop_filter", - "widgets/card", - "widgets/carousel_view", - "widgets/circle_avatar", - "widgets/clip_oval", - "widgets/clip_rrect", - "widgets/colored_box", - "widgets/icon", - "widgets/image", - "widgets/opacity", - "widgets/placeholder", - "widgets/visibility" - ] - }, - { - "group": "Interactive Widgets", - "pages": [ - "widgets/alert_dialog", - "widgets/app_bar", - "widgets/auto_complete", - "widgets/bottom_navigation_bar", - "widgets/check_box", - "widgets/chip", - "widgets/circular_progress_indicator", - "widgets/drawer", - "widgets/dropdown_menu", - "widgets/dynamic_view", - "widgets/elevated_button", - "widgets/filled_button", - "widgets/floating_action_button", - "widgets/form", - "widgets/gesture_detector", - "widgets/icon_button", - "widgets/ink_well", - "widgets/linear_progress_indicator", - "widgets/list_tile", - "widgets/listview", - "widgets/network_widget", - "widgets/outlined_button", - "widgets/page_view", - "widgets/radio_group", - "widgets/refresh_indicator", - "widgets/scaffold", - "widgets/single_child_scroll_view", - "widgets/slider", - "widgets/sliver_app_bar", - "widgets/switch", - "widgets/tab_bar", - "widgets/text_button", - "widgets/text_field", - "widgets/text_form_field", - "widgets/webview" - ] - }, - { - "group": "Data Widgets", - "pages": [ - "widgets/custom_scroll_view", - "widgets/grid_view", - "widgets/table", - "widgets/table_cell", - "widgets/table_row", - "widgets/text", - "widgets/vertical_divider" + "tab": "Actions", + "groups": [ + { + "group": "Navigation Actions", + "pages": [ + "actions/navigate" + ] + }, + { + "group": "UI Actions", + "pages": [ + "actions/dialog", + "actions/modal_bottom_sheet", + "actions/snack_bar" + ] + }, + { + "group": "Form Actions", + "pages": [ + "actions/form_validate", + "actions/get_form_value" + ] + }, + { + "group": "Network Actions", + "pages": [ + "actions/network_request" + ] + }, + { + "group": "Utility Actions", + "pages": [ + "actions/delay_action", + "actions/multi_action", + "actions/none" + ] + } ] } ] }, { - "tab": "Actions", - "groups": [ - { - "group": "Navigation Actions", - "pages": [ - "actions/navigate" - ] - }, + "language": "ko", + "tabs": [ { - "group": "UI Actions", - "pages": [ - "actions/dialog", - "actions/modal_bottom_sheet", - "actions/snack_bar" + "tab": "Overview", + "groups": [ + { + "group": "시작하기", + "icon": "rocket", + "pages": [ + "ko/introduction", + "ko/quickstart", + "ko/sdui", + "ko/cli", + "ko/project_structure", + "ko/get_started", + "ko/concepts/theming" + ] + }, + { + "group": "핵심 개념", + "icon": "book-open", + "pages": [ + "ko/concepts/rendering_stac_widgets", + "ko/concepts/custom_widgets", + "ko/concepts/custom_actions" + ] + } ] }, { - "group": "Form Actions", - "pages": [ - "actions/form_validate", - "actions/get_form_value" + "tab": "Widgets", + "groups": [ + { + "group": "레이아웃 위젯", + "pages": [ + "ko/widgets/align", + "ko/widgets/aspect_ratio", + "ko/widgets/center", + "ko/widgets/column", + "ko/widgets/container", + "ko/widgets/expanded", + "ko/widgets/flexible", + "ko/widgets/fitted_box", + "ko/widgets/fractionally_sized_box", + "ko/widgets/limited_box", + "ko/widgets/padding", + "ko/widgets/positioned", + "ko/widgets/row", + "ko/widgets/safe_area", + "ko/widgets/sized_box", + "ko/widgets/spacer", + "ko/widgets/stack", + "ko/widgets/wrap" + ] + }, + { + "group": "디스플레이 위젯", + "pages": [ + "ko/widgets/backdrop_filter", + "ko/widgets/card", + "ko/widgets/carousel_view", + "ko/widgets/circle_avatar", + "ko/widgets/clip_oval", + "ko/widgets/clip_rrect", + "ko/widgets/colored_box", + "ko/widgets/icon", + "ko/widgets/image", + "ko/widgets/opacity", + "ko/widgets/placeholder" + ] + }, + { + "group": "인터랙티브 위젯", + "pages": [ + "ko/widgets/alert_dialog", + "ko/widgets/app_bar", + "ko/widgets/auto_complete", + "ko/widgets/bottom_navigation_bar", + "ko/widgets/check_box", + "ko/widgets/chip", + "ko/widgets/circular_progress_indicator", + "ko/widgets/drawer", + "ko/widgets/dropdown_menu", + "ko/widgets/dynamic_view", + "ko/widgets/elevated_button", + "ko/widgets/filled_button", + "ko/widgets/floating_action_button", + "ko/widgets/form", + "ko/widgets/gesture_detector", + "ko/widgets/icon_button", + "ko/widgets/ink_well", + "ko/widgets/linear_progress_indicator", + "ko/widgets/list_tile", + "ko/widgets/listview", + "ko/widgets/network_widget", + "ko/widgets/opacity", + "ko/widgets/outlined_button", + "ko/widgets/page_view", + "ko/widgets/radio_group", + "ko/widgets/refresh_indicator", + "ko/widgets/scaffold", + "ko/widgets/single_child_scroll_view", + "ko/widgets/slider", + "ko/widgets/sliver_app_bar", + "ko/widgets/switch", + "ko/widgets/tab_bar", + "ko/widgets/text_button", + "ko/widgets/text_field", + "ko/widgets/text_form_field", + "ko/widgets/webview" + ] + }, + { + "group": "데이터 위젯", + "pages": [ + "ko/widgets/custom_scroll_view", + "ko/widgets/grid_view", + "ko/widgets/table", + "ko/widgets/table_row", + "ko/widgets/table_cell", + "ko/widgets/text", + "ko/widgets/vertical_divider" + ] + } ] }, { - "group": "Network Actions", - "pages": [ - "actions/network_request" + "tab": "Actions", + "groups": [ + { + "group": "내비게이션 액션", + "pages": [ + "ko/actions/navigate" + ] + }, + { + "group": "UI 액션", + "pages": [ + "ko/actions/dialog", + "ko/actions/modal_bottom_sheet", + "ko/actions/snack_bar" + ] + }, + { + "group": "폼 액션", + "pages": [ + "ko/actions/form_validate", + "ko/actions/get_form_value" + ] + }, + { + "group": "네트워크 액션", + "pages": [ + "ko/actions/network_request" + ] + }, + { + "group": "유틸리티 액션", + "pages": [ + "ko/actions/delay_action", + "ko/actions/multi_action", + "ko/actions/none" + ] + } ] }, { - "group": "Utility Actions", - "pages": [ - "actions/delay_action", - "actions/multi_action", - "actions/none" + "tab": "Styles", + "groups": [ + { + "group": "스타일 레퍼런스", + "pages": [ + "ko/styles/border", + "ko/styles/border_radius", + "ko/styles/border_side", + "ko/styles/box_fit", + "ko/styles/clip_behavior", + "ko/styles/colors", + "ko/styles/stac_alignment_directional", + "ko/styles/table_border", + "ko/styles/table_column_width" + ] + } ] } ] @@ -220,4 +413,4 @@ "linkedin": "https://www.linkedin.com/company/stacdev" } } -} \ No newline at end of file +} diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 56143462..f574634b 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -100,4 +100,4 @@ Built by developers, for developers. Stac is open source and community-driven. --- -Ready to build your first server-driven UI app? [Get started now →](/quickstart) \ No newline at end of file +Ready to build your first server-driven UI app? [Get started now →](/quickstart) diff --git a/docs/ko/actions/delay_action.mdx b/docs/ko/actions/delay_action.mdx new file mode 100644 index 00000000..1339aa6d --- /dev/null +++ b/docs/ko/actions/delay_action.mdx @@ -0,0 +1,21 @@ +--- +title: "Delay 액션" +description: "지정된 시간만큼 대기하는 액션" +--- + +`StacDelayAction`은 주어진 시간 동안 아무 동작도 하지 않고 대기할 때 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| milliseconds | `int` | 대기할 시간(밀리초) | + +## 예제 + +```json +{ + "actionType": "delay", + "milliseconds": 1000 +} +``` diff --git a/docs/ko/actions/dialog.mdx b/docs/ko/actions/dialog.mdx new file mode 100644 index 00000000..ab82c840 --- /dev/null +++ b/docs/ko/actions/dialog.mdx @@ -0,0 +1,54 @@ +--- +title: "Dialog 액션" +description: "다이얼로그를 JSON으로 호출하는 방법" +--- + +`StacDialogAction`은 Stac 앱에서 다이얼로그를 띄우는 액션입니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| widget | `Map?` | 다이얼로그 안에 표시할 위젯 | +| request | `StacNetworkRequest?` | 다이얼로그를 띄우기 전 수행할 네트워크 요청 | +| assetPath | `String?` | 다이얼로그 콘텐츠가 정의된 에셋 경로 | +| barrierDismissible | `bool` | 배리어를 탭해 닫을 수 있는지(기본값 `true`) | +| barrierColor | `String?` | 배리어 색상 | +| barrierLabel | `String?` | 접근성용 라벨 | +| useSafeArea | `bool` | SafeArea를 적용할지(기본값 `true`) | +| traversalEdgeBehavior | `TraversalEdgeBehavior?` | 포커스 이동 방식 | + +## 예제 + +### 위젯 JSON으로 다이얼로그 표시 + +```json +{ + "actionType": "showDialog", + "widget": { + "type": "text", + "data": "Hello, World!" + } +} +``` + +### 네트워크 요청 결과를 활용해 표시 + +```json +{ + "actionType": "showDialog", + "request": { + "url": "https://example.com/api", + "method": "get" + } +} +``` + +### 에셋 JSON 사용 + +```json +{ + "actionType": "showDialog", + "assetPath": "assets/dialog.json" +} +``` diff --git a/docs/ko/actions/form_validate.mdx b/docs/ko/actions/form_validate.mdx new file mode 100644 index 00000000..d7f41d70 --- /dev/null +++ b/docs/ko/actions/form_validate.mdx @@ -0,0 +1,35 @@ +--- +title: "Form Validate 액션" +description: "폼 유효성 검사를 수행하는 액션" +--- + +`StacFormValidateAction`은 폼이 유효한지 검사하고 결과에 따라 다른 액션을 실행합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| isValid | `Map` | 폼이 유효할 때 실행할 액션 | +| isNotValid | `Map` | 폼이 유효하지 않을 때 실행할 액션 | + +## 예제 + +```json +{ + "actionType": "validateForm", + "isValid": { + "actionType": "showDialog", + "widget": { + "type": "text", + "data": "Form is valid!" + } + }, + "isNotValid": { + "actionType": "showDialog", + "widget": { + "type": "text", + "data": "Form is not valid!" + } + } +} +``` diff --git a/docs/ko/actions/get_form_value.mdx b/docs/ko/actions/get_form_value.mdx new file mode 100644 index 00000000..9edd11dd --- /dev/null +++ b/docs/ko/actions/get_form_value.mdx @@ -0,0 +1,21 @@ +--- +title: "Get Form Value 액션" +description: "폼 필드 값을 가져오는 액션" +--- + +`StacGetFormValueAction`은 특정 폼 필드의 값을 읽어 다른 액션에서 사용할 때 활용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| id | `String` | 값을 조회할 폼 필드의 ID | + +## 예제 + +```json +{ + "actionType": "getFormValue", + "id": "username" +} +``` diff --git a/docs/ko/actions/modal_bottom_sheet.mdx b/docs/ko/actions/modal_bottom_sheet.mdx new file mode 100644 index 00000000..7234326c --- /dev/null +++ b/docs/ko/actions/modal_bottom_sheet.mdx @@ -0,0 +1,80 @@ +--- +title: "Modal Bottom Sheet 액션" +description: "모달 바텀시트를 띄우는 액션" +--- + +`StacModalBottomSheetAction`은 Flutter의 `showModalBottomSheet`를 JSON으로 제어할 수 있게 해 줍니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| widget | `Map?` | 바텀시트 내부에 표시할 위젯 | +| request | `StacNetworkRequest?` | 바텀시트를 띄우기 전 실행할 네트워크 요청 | +| assetPath | `String?` | 에셋에 있는 JSON 경로 | +| backgroundColor | `String?` | 배경색 | +| barrierLabel | `String?` | 배리어의 접근성 라벨 | +| elevation | `double?` | 바텀시트 고도 | +| shape | `StacBorder?` | 모서리 모양 | +| constraints | `StacBoxConstraints?` | 크기 제약 | +| barrierColor | `String?` | 배리어 색상 | +| isScrollControlled | `bool` | 전체 높이를 사용할지 여부(기본값 `false`) | +| useRootNavigator | `bool` | 루트 내비게이터 사용 여부(기본값 `false`) | +| isDismissible | `bool` | 외부 탭으로 닫히도록 할지(기본값 `true`) | +| enableDrag | `bool` | 드래그로 닫기 가능 여부(기본값 `true`) | +| showDragHandle | `bool?` | 드래그 핸들을 표시할지 | +| useSafeArea | `bool` | SafeArea 적용 여부(기본값 `false`) | + +## 예제 + +```json +{ + "actionType": "showModalBottomSheet", + "widget": { + "type": "container", + "height": 200, + "color": "amber", + "child": { + "type": "center", + "child": { + "type": "column", + "mainAxisAlignment": "center", + "mainAxisSize": "min", + "children": [ + { + "type": "text", + "data": "Modal BottomSheet" + }, + { + "type": "elevatedButton", + "child": { + "type": "text", + "data": "Close BottomSheet" + }, + "onPressed": { + "actionType": "pop" + } + } + ] + } + } + } +} +``` + +```json +{ + "actionType": "showModalBottomSheet", + "assetPath": "assets/widgets/modal_bottom_sheet.json" +} +``` + +```json +{ + "actionType": "showModalBottomSheet", + "request": { + "url": "https://example.com/api", + "method": "get" + } +} +``` diff --git a/docs/ko/actions/multi_action.mdx b/docs/ko/actions/multi_action.mdx new file mode 100644 index 00000000..8fba866e --- /dev/null +++ b/docs/ko/actions/multi_action.mdx @@ -0,0 +1,79 @@ +--- +title: "Multi Action" +description: "여러 액션을 순차/병렬로 실행" +--- + +`StacMultiAction`은 여러 액션을 한 번에 정의하고 순차(`sync: true`) 또는 병렬(`sync: false`)로 실행할 수 있게 해 줍니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| actions | `List?>?` | 실행할 액션 목록 | +| sync | `bool` | `true`면 순차 실행, `false`면 병렬 실행(기본값 `false`) | + +## 예제 + +```json +{ + "actionType": "multiAction", + "sync": true, + "actions": [ + { + "actionType": "showSnackBar", + "content": { + "type": "text", + "data": "Executing request..." + }, + "action": { + "label": "Done", + "textColor": "#73C2FB", + "onPressed": {} + }, + "behavior": "floating" + }, + { + "actionType": "networkRequest", + "url": "https://example.com/api", + "method": "get", + "queryParameters": { + "page": 1 + }, + "headers": { + "Authorization": "Bearer token" + }, + "contentType": "application/json", + "body": { + "data": "example" + }, + "results": [ + { + "statusCode": 200, + "action": { + "actionType": "none" + } + }, + { + "statusCode": 404, + "action": { + "actionType": "none" + } + } + ] + }, + { + "actionType": "showSnackBar", + "content": { + "type": "text", + "data": "Request executed" + }, + "action": { + "label": "Done", + "textColor": "#73C2FB", + "onPressed": {} + }, + "behavior": "floating" + } + ] +} +``` diff --git a/docs/ko/actions/navigate.mdx b/docs/ko/actions/navigate.mdx new file mode 100644 index 00000000..c045548f --- /dev/null +++ b/docs/ko/actions/navigate.mdx @@ -0,0 +1,92 @@ +--- +title: "Navigate 액션" +description: "JSON으로 Flutter 내비게이션을 제어하는 방법" +--- + +Stac `navigate` 액션은 JSON에서 화면 전환을 정의할 수 있게 해 줍니다. Flutter 내비게이션 기본 개념은 [Navigator 문서](https://api.flutter.dev/flutter/widgets/Navigator-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| request | `StacNetworkRequest?` | 화면 이동 전에 실행할 네트워크 요청 | +| widgetJson | `Map?` | 이동할 화면의 JSON 정의 | +| assetPath | `String?` | 에셋에 저장된 화면 JSON 경로 | +| routeName | `String?` | 이동할 라우트 이름 | +| navigationStyle | `NavigationStyle?` | push / pop 등 내비게이션 방식 | +| result | `Map?` | `pop` 시 반환할 결과 | +| arguments | `Map?` | 라우트에 전달할 인자 | + +### NavigationStyle + +| 값 | 설명 | +|----|------| +| `push` | 새 라우트를 스택에 추가 | +| `pop` | 현재 라우트를 제거 | +| `pushReplacement` | 현재 라우트를 새 라우트로 교체 | +| `pushAndRemoveAll` | 새 라우트를 추가하고 기존 스택을 모두 제거 | +| `popAll` | 모든 라우트를 제거 | +| `pushNamed` | 이름 기반 라우트를 push | +| `pushNamedAndRemoveAll` | 이름 기반 라우트를 push하고 이전 라우트를 모두 제거 | +| `pushReplacementNamed` | 현재 라우트를 이름 기반 라우트로 교체 | + +## 예제 + +### 네트워크 요청 후 이동 + +```json +{ + "actionType": "navigate", + "request": { + "url": "https://example.com/api", + "method": "get" + }, + "navigationStyle": "push" +} +``` + +### 위젯 JSON으로 이동 + +```json +{ + "actionType": "navigate", + "widgetJson": { + "type": "scaffold", + "appBar": { + "type": "appBar", + "title": { + "type": "text", + "data": "My App" + } + }, + "body": { + "type": "center", + "child": { + "type": "text", + "data": "Hello, World!" + } + } + }, + "navigationStyle": "push" +} +``` + +### 에셋 파일로 이동 + +```json +{ + "actionType": "navigate", + "assetPath": "assets/widgets/my_widget.json", + "navigationStyle": "push" +} +``` + +### 라우트 이름으로 이동 + +```json +{ + "actionType": "navigate", + "routeName": "/home", + "navigationStyle": "pushNamed" +} +``` diff --git a/docs/ko/actions/network_request.mdx b/docs/ko/actions/network_request.mdx new file mode 100644 index 00000000..2cbdd92a --- /dev/null +++ b/docs/ko/actions/network_request.mdx @@ -0,0 +1,68 @@ +--- +title: "Network Request 액션" +description: "HTTP 요청을 JSON으로 정의" +--- + +`NetworkRequestAction`은 Stac에서 HTTP 요청을 수행하고 응답 코드에 따라 후속 액션을 실행할 때 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| url | `String` | 요청을 보낼 URL | +| method | `Method` | HTTP 메서드(`get` 기본값) | +| queryParameters | `Map?` | URL에 추가할 쿼리 파라미터 | +| headers | `Map?` | 요청 헤더 | +| contentType | `String?` | Content-Type 값 | +| body | `dynamic` | 요청 본문 | +| results | `List` | 상태 코드별 후속 액션 목록 | + +### Method + +| 값 | 설명 | +|----|------| +| `get` | GET 요청 | +| `post` | POST 요청 | +| `put` | PUT 요청 | +| `delete` | DELETE 요청 | + +### StacNetworkResult + +| 속성 | 타입 | 설명 | +|------|------|------| +| statusCode | `int` | 처리할 상태 코드 | +| action | `Action` | 해당 코드 수신 시 실행할 액션 | + +## 예제 + +```json +{ + "actionType": "networkRequest", + "url": "https://example.com/api", + "method": "get", + "queryParameters": { + "page": 1 + }, + "headers": { + "Authorization": "Bearer token" + }, + "contentType": "application/json", + "body": { + "data": "example" + }, + "results": [ + { + "statusCode": 200, + "action": { + "actionType": "none" + } + }, + { + "statusCode": 404, + "action": { + "actionType": "none" + } + } + ] +} +``` diff --git a/docs/ko/actions/none.mdx b/docs/ko/actions/none.mdx new file mode 100644 index 00000000..f0e90c50 --- /dev/null +++ b/docs/ko/actions/none.mdx @@ -0,0 +1,12 @@ +--- +title: "None 액션" +description: "아무 동작도 수행하지 않는 기본 액션" +--- + +`none` 액션은 다른 액션을 대체할 자리 표시자로 사용하거나 조건 분기에서 아무 것도 하지 않을 때 활용합니다. + +```json +{ + "actionType": "none" +} +``` diff --git a/docs/ko/actions/snack_bar.mdx b/docs/ko/actions/snack_bar.mdx new file mode 100644 index 00000000..40677c0b --- /dev/null +++ b/docs/ko/actions/snack_bar.mdx @@ -0,0 +1,57 @@ +--- +title: "SnackBar 액션" +description: "Flutter SnackBar를 JSON으로 표시" +--- + +Stac `showSnackBar`는 Flutter `SnackBar`를 JSON으로 구성해 보여 줍니다. 자세한 위젯 설명은 [공식 문서](https://api.flutter.dev/flutter/material/SnackBar-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| content | `Map` | 스낵바에 표시할 본문 위젯 | +| backgroundColor | `String` | 배경 색상 | +| elevation | `double` | 그림자 깊이 | +| margin | `StacEdgeInsets` | 바깥 여백 | +| padding | `StacEdgeInsets` | 내부 여백 | +| width | `double` | 스낵바 너비 | +| shape | `StacShapeBorder` | 모양 | +| hitTestBehavior | `HitTestBehavior` | 스낵바 영역의 히트 테스트 방식 | +| behavior | `SnackBarBehavior` | 배치/동작 방식 (`fixed`, `floating` 등) | +| action | `StacSnackBarAction` | 사용자 액션 버튼 | +| actionOverflowThreshold | `double` | 액션 너비가 줄바꿈되는 임계값 | +| showCloseIcon | `bool` | 닫기 아이콘 표시 여부 | +| closeIconColor | `String` | 닫기 아이콘 색상 | +| duration | `StacDuration` | 표시 지속 시간 | +| onVisible | `Map` | 처음 표시될 때 실행할 액션 | +| dismissDirection | `DismissDirection` | 스와이프로 닫을 수 있는 방향 | +| clipBehavior | `Clip` | 클리핑 동작 | + +### SnackBarAction 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| textColor | `String` | 버튼 텍스트 색 | +| disabledTextColor | `String` | 비활성 텍스트 색 | +| backgroundColor | `String` | 버튼 배경색 | +| disabledBackgroundColor | `String` | 비활성 배경색 | +| label | `String` | 버튼 라벨 | +| onPressed | `Map` | 버튼 탭 시 실행할 액션 | + +## 예제 + +```json +{ + "actionType": "showSnackBar", + "content": { + "type": "text", + "data": "This is a Snackbar" + }, + "action": { + "label": "Done", + "textColor": "#73C2FB", + "onPressed": {} + }, + "behavior": "floating" +} +``` diff --git a/docs/ko/cli.mdx b/docs/ko/cli.mdx new file mode 100644 index 00000000..99da40d7 --- /dev/null +++ b/docs/ko/cli.mdx @@ -0,0 +1,216 @@ +--- +title: "Stac CLI" +description: "Stac CLI로 서버 기반 UI 프로젝트를 초기화하고 빌드하며 클라우드에 배포하세요." +--- + +## 설치 + + + + ```bash + # Stac CLI 설치 + curl -fsSL https://raw.githubusercontent.com/StacDev/install/main/install.sh | bash + + # 설치 확인 + stac --version + ``` + + + ```bash + # Stac CLI 설치 + irm https://raw.githubusercontent.com/StacDev/install/main/install.ps1 | iex + + # 설치 확인 + stac --version + ``` + + + +## 사용 가능한 명령 + +| 명령 | 설명 | 인증 필요 | +| --------------- | -------------------------------------- | --------- | +| `login` | Google OAuth로 인증 | 아니오 | +| `logout` | 저장된 인증 토큰 삭제 | 아니오 | +| `status` | 현재 인증 상태 확인 | 아니오 | +| `init` | 프로젝트에 Stac 초기화 | 예 | +| `build` | Dart 위젯을 JSON으로 변환 | 아니오 | +| `deploy` | 빌드 후 Stac Cloud로 배포 | 예 | +| `project list` | Cloud 프로젝트 목록 확인 | 예 | +| `project create`| 새 Cloud 프로젝트 생성 | 예 | + +## 인증 + +대부분의 명령을 사용하려면 먼저 Stac Cloud에 인증해야 합니다. + +### 로그인 + +```bash +# Google OAuth로 인증 +# +# 명령 실행 시 브라우저가 열립니다. +# 인증 정보는 로컬에 안전하게 보관됩니다. +stac login +``` + +### 상태 확인 + +```bash +# 현재 인증 상태 확인 +stac status +``` + +### 로그아웃 + +```bash +# 저장된 토큰 삭제 +stac logout +``` + +## Stac 초기화 + +기존 Flutter/Dart 프로젝트에서 Stac을 사용할 수 있도록 설정합니다. 로컬 앱과 Stac Cloud 프로젝트를 연결하고 필요한 파일을 생성합니다. + +```bash +# 현재 프로젝트에 Stac 초기화 +stac init +``` + +#### 수행 작업 + +- Stac DSL 위젯을 위한 `stac/` 폴더 생성 +- `lib/default_stac_options.dart` 생성(프로젝트 ID 등 `StacOptions` 포함) +- `pubspec.yaml`에 `stac` 및 관련 의존성 추가 +- 필요 시 기존 Stac Cloud 프로젝트와 연결 + +#### 생성되는 파일 + +```bash +|- Flutter project +├── lib/ +│ ├── default_stac_options.dart +│ └── main.dart +├── stac/ +│ └── stac_widget.dart +└── pubspec.yaml +``` + +## 위젯 빌드 + +### Dart를 JSON으로 변환 + +```bash +# 현재 프로젝트의 모든 위젯 빌드 +stac build + +# 특정 프로젝트 디렉터리 지정 +stac build --project /path/to/project + +# 검증 활성화(기본값) +stac build --validate + +# 자세한 로그 출력 +stac build --verbose +``` + +#### 빌드 옵션 + +| 옵션 | 설명 | 기본값 | +| --------------- | ---------------------------- | ------------------ | +| `-p, --project` | 프로젝트 디렉터리 경로 | 현재 디렉터리 | +| `--validate` | 생성된 JSON 검증 | `true` | +| `-v, --verbose` | 상세 빌드 로그 | `false` | + +`stac build`는 `stac/` 폴더의 위젯 정의를 JSON으로 변환해 `build/` 폴더에 저장합니다. + +## 배포 + +### Stac Cloud로 배포 + +```bash +# 빌드 후 Stac Cloud로 배포 +stac deploy + +# 특정 프로젝트 디렉터리 지정 +stac deploy --project /path/to/project + +# 빌드를 건너뛰고 기존 파일만 배포 +stac deploy --skip-build + +# 상세 로그 출력 +stac deploy --verbose +``` + +### 배포 옵션 + +| 옵션 | 설명 | 기본값 | +| --------------- | ---------------------------------- | ------------------ | +| `-p, --project` | 프로젝트 디렉터리 경로 | 현재 디렉터리 | +| `--skip-build` | 배포 전 빌드 생략 | `false` | +| `-v, --verbose` | 상세 배포 로그 | `false` | + + +기본적으로 `stac deploy`는 배포 전에 `stac build`를 실행합니다. 기존 빌드 결과를 그대로 업로드하려면 `--skip-build`를 사용하세요. + + +## Cloud 프로젝트 관리 + +### 프로젝트 목록 확인 + +```bash +# 자신의 Stac Cloud 프로젝트 목록 +stac project list + +# JSON 형식으로 출력 +stac project list --json +``` + +출력에는 다음 정보가 포함됩니다. + +- 프로젝트 이름 및 ID +- 프로젝트 설명 +- 생성/수정 시각 + +### 새 프로젝트 생성 + +```bash +# Stac Cloud에 새 프로젝트 생성 +stac project create --name "My App" --description "My SDUI app" + +# 단축형 +stac project create -n "My App" -d "My SDUI app" +``` + +프로젝트를 만든 뒤 `stac init`을 실행해 로컬과 연결하세요. + +## 개발 워크플로 + +### 일반적인 순서 + +```bash +# 1. Stac Cloud 인증(최초 1회) +stac login + +# 2. 프로젝트 목록 확인 +stac project list + +# 3. Flutter/Dart 앱에서 초기화 +cd your-flutter-project +stac init + +# 4. stac/ 폴더에서 위젯 작성 +# 예: stac/your_screen.dart 수정 + +# 5. Stac Cloud로 배포 +stac deploy +``` + +## 명령 참조 + +### 전역 옵션 + +| 옵션 | 설명 | +| --------------- | --------------------- | +| `-v, --verbose` | 상세 로그 출력 | +| `--version` | CLI 버전 출력 | +| `--help` | 사용법 요약 출력 | diff --git a/docs/ko/concepts/custom_actions.mdx b/docs/ko/concepts/custom_actions.mdx new file mode 100644 index 00000000..c133d561 --- /dev/null +++ b/docs/ko/concepts/custom_actions.mdx @@ -0,0 +1,135 @@ +--- +title: "커스텀 액션 만들기" +description: "StacAction을 확장해 비즈니스 로직에 맞는 동작을 정의하고 파서에 연결합니다." +--- + +Stac은 내비게이션, 다이얼로그, 네트워크 요청 등 다양한 기본 액션을 제공하지만, 서비스별 요구에 맞춘 동작이 필요할 수 있습니다. 이 문서는 `StacAction` 서브클래스를 작성하고 JSON 직렬화·파서 시스템과 연동하는 방법을 소개합니다. + +## 커스텀 StacAction이란? + +- `StacAction`을 상속하며 +- JSON 직렬화/역직렬화를 지원하고 +- 파서를 통해 실행 로직을 연결하며 +- JSON 정의에서 위젯의 `onPressed`, `onTap` 등으로 호출할 수 있는 클래스입니다. + +이를 활용하면 서드파티 API 연동, 비즈니스 전용 로직, 재사용 가능한 액션을 구축할 수 있습니다. + +## 준비 사항 + +1. **의존성**: `stac_core`, `json_annotation` +2. **코드 생성**: `build_runner` +3. **액션 파서**: 커스텀 액션을 실행할 파서 + +## 만들기 순서 + +### 1단계: 액션 클래스 정의 + +`lib/actions/stac_share_action.dart`와 같이 파일을 만들고 클래스를 작성합니다. + +```dart +@JsonSerializable() +class StacShareAction extends StacAction { + const StacShareAction({ + required this.text, + this.subject, + this.title, + }); + + final String text; + final String? subject; + final String? title; + + @override + String get actionType => 'share'; + + factory StacShareAction.fromJson(Map json) => + _$StacShareActionFromJson(json); + + @override + Map toJson() => _$StacShareActionToJson(this); +} +``` + +필수 요소: + +1. `part 'stac_share_action.g.dart';` +2. `@JsonSerializable()` (필요 시 `explicitToJson: true`) +3. 고유한 `actionType` +4. 생성자 +5. `fromJson` / `toJson` + +### 2단계: 코드 생성 + +```bash +flutter pub run build_runner build --delete-conflicting-outputs +``` + +### 3단계: 액션 파서 작성 + +```dart +class StacShareActionParser implements StacActionParser { + const StacShareActionParser(); + + @override + String get actionType => 'share'; + + @override + StacShareAction getModel(Map json) => + StacShareAction.fromJson(json); + + @override + FutureOr onCall(BuildContext context, StacShareAction model) async { + return Share.share( + model.text, + subject: model.subject, + ); + } +} +``` + +### 4단계: 파서 등록 + +```dart +await Stac.initialize( + options: defaultStacOptions, + actionParsers: const [ + StacShareActionParser(), + ], +); +``` + +## 심화 팁 + +- 위젯과 마찬가지로 `@DoubleConverter()` 등 컨버터를 활용해 타입 변환을 단순화합니다. +- Stac 고유 타입을 사용하면 직렬화가 일관됩니다. + +## 사용 예시 + +```dart +StacElevatedButton( + onPressed: StacShareAction( + text: 'Check out this article!', + subject: 'Article', + ).toJson(), + child: StacText(data: 'Share'), +) +``` + +`stac build` 이후 생성되는 JSON: + +```json +{ + "type": "elevatedButton", + "onPressed": { + "actionType": "share", + "text": "Check out this article!", + "subject": "Article" + }, + "child": { + "type": "text", + "data": "Share" + } +} +``` + +커스텀 액션은 버튼, 제스처, 폼 등 다양한 콜백에 연결해 재사용 가능한 동작을 정의할 수 있습니다. diff --git a/docs/ko/concepts/custom_widgets.mdx b/docs/ko/concepts/custom_widgets.mdx new file mode 100644 index 00000000..74a051c6 --- /dev/null +++ b/docs/ko/concepts/custom_widgets.mdx @@ -0,0 +1,193 @@ +--- +title: "커스텀 위젯 만들기" +description: "StacWidget을 확장해 JSON 직렬화·파서 시스템에 통합하는 방법을 알아봅니다." +--- + +Stac에는 70개 이상의 기본 위젯이 있지만, 서비스 고유의 UI를 위해 커스텀 위젯이 필요할 때가 있습니다. 이 문서는 Stac의 직렬화 및 파서 구조와 호환되는 `StacWidget` 서브클래스를 만드는 과정을 단계별로 안내합니다. + +## 커스텀 StacWidget이란? + +커스텀 StacWidget은 아래 조건을 만족하는 Dart 클래스입니다. + +- `StacWidget`을 상속한다. +- JSON 직렬화/역직렬화가 가능하다. +- 파서 시스템과 연동되어 Flutter 위젯으로 변환된다. +- `/stac` 폴더에서 사용해 Stac Cloud에 배포할 수 있다. + +이를 통해: + +- 서드파티 Flutter 패키지를 감싸거나 +- 앱에 특화된 재사용 컴포넌트를 만들고 +- 기본 위젯을 넘어선 기능을 확장하며 +- 비즈니스 로직에 맞춘 도메인 위젯을 구성할 수 있습니다. + +## 준비 사항 + +커스텀 위젯을 만들기 전 아래를 갖춰야 합니다. + +1. **의존성**: `stac_core`, `json_annotation` +2. **코드 생성**: `build_runner` +3. **파서**: 위젯을 렌더링할 커스텀 파서 + +## 만들기 순서 + +### 1단계: 위젯 클래스 정의 + +`lib/widgets/stac_custom_badge.dart`와 같은 파일을 만들고 클래스를 정의합니다. + +```dart +import 'package:json_annotation/json_annotation.dart'; +import 'package:stac_core/core/stac_widget.dart'; +import 'package:stac_core/foundation/foundation.dart'; + +part 'stac_custom_badge.g.dart'; + +@JsonSerializable() +class StacCustomBadge extends StacWidget { + const StacCustomBadge({ + required this.text, + this.color, + this.size, + this.child, + }); + + final String text; + final StacColor? color; + final double? size; + final StacWidget? child; + + @override + String get type => 'customBadge'; + + factory StacCustomBadge.fromJson(Map json) => + _$StacCustomBadgeFromJson(json); + + @override + Map toJson() => _$StacCustomBadgeToJson(this); +} +``` + +### 2단계: 필수 구성 요소 + +모든 커스텀 위젯은 다음 요소를 포함해야 합니다. + +1. **part 파일 선언** – `part 'stac_custom_badge.g.dart';` +2. **`@JsonSerializable()`** – 중첩 위젯이 있다면 `explicitToJson: true` +3. **`type` 게터** – JSON에서 식별자로 사용 +4. **`fromJson` 팩토리** +5. **`toJson` 메서드** + +### 3단계: 코드 생성 + +```bash +flutter pub run build_runner build --delete-conflicting-outputs +``` + +실행 후 직렬화 코드가 담긴 `*.g.dart`가 생성됩니다. + +### 4단계: 파서 작성 + +```dart +import 'package:flutter/material.dart'; +import 'package:stac_framework/stac_framework.dart'; +import 'package:your_app/widgets/stac_custom_badge.dart'; + +class StacCustomBadgeParser extends StacParser { + const StacCustomBadgeParser(); + + @override + String get type => 'customBadge'; + + @override + StacCustomBadge getModel(Map json) => + StacCustomBadge.fromJson(json); + + @override + Widget parse(BuildContext context, StacCustomBadge model) { + return Container( + padding: EdgeInsets.all(model.size ?? 8), + decoration: BoxDecoration( + color: model.color?.toColor() ?? Colors.blue, + borderRadius: BorderRadius.circular(4), + ), + child: Text(model.text), + ); + } +} +``` + +### 5단계: 파서 등록 + +```dart +void main() async { + await Stac.initialize( + options: defaultStacOptions, + parsers: const [ + StacCustomBadgeParser(), + ], + ); + runApp(const MyApp()); +} +``` + +## 심화 패턴 + +### 컨버터 활용 + +Stac은 숫자, 위젯 등 특수 타입을 위한 컨버터를 제공합니다. + +```dart +@DoubleConverter() +final double? elevation; +``` + +또는 자식 위젯이 있을 땐 `@StacWidgetConverter()`를 사용하세요. + +### Stac 타입 사용 + +가능하면 Flutter 고유 타입 대신 `StacColor`, `StacEdgeInsets`, `StacAlignment` 등 Stac 타입을 사용해 직렬화를 단순화합니다. + +## 커스텀 위젯 사용하기 + +```dart +@StacScreen(screenName: 'profile') +StacWidget profileScreen() { + return StacScaffold( + body: StacColumn( + children: [ + StacCustomBadge( + text: 'New', + color: StacColors.red, + size: 12.0, + ), + StacText(data: 'User Profile'), + ], + ), + ); +} +``` + +`stac build` 또는 `stac deploy` 후 생성되는 JSON은 다음과 같습니다. + +```json +{ + "type": "scaffold", + "body": { + "type": "column", + "children": [ + { + "type": "customBadge", + "text": "New", + "color": "#FFFF0000", + "size": 12.0 + }, + { + "type": "text", + "data": "User Profile" + } + ] + } +} +``` + +이 과정을 통해 자신의 도메인에 맞는 위젯을 자유롭게 설계하고 Stac Cloud에 배포할 수 있습니다. diff --git a/docs/ko/concepts/rendering_stac_widgets.mdx b/docs/ko/concepts/rendering_stac_widgets.mdx new file mode 100644 index 00000000..1d4bb0da --- /dev/null +++ b/docs/ko/concepts/rendering_stac_widgets.mdx @@ -0,0 +1,181 @@ +--- +title: "Stac 위젯 렌더링" +description: "Stac Cloud, 로컬 JSON, 에셋, 네트워크 요청 등 다양한 렌더링 방법을 살펴봅니다." +--- + +Stac은 JSON에서 위젯을 렌더링하는 여러 방법을 제공합니다. 상황에 맞는 렌더링 전략을 이해하면 앱에서 SDUI를 훨씬 유연하게 사용할 수 있습니다. + +## 사전 준비 + +Stac 위젯을 렌더링하기 전에 애플리케이션에서 Stac을 초기화해야 합니다. + +```dart +import 'package:stac/stac.dart'; +import 'package:your_app/default_stac_options.dart'; + +void main() async { + await Stac.initialize(options: defaultStacOptions); + runApp(const MyApp()); +} +``` + +## 렌더링 방법 + +### 1. Stac Cloud (`Stac` 위젯) + +가장 흔한 서버 기반 UI 방식은 `Stac` 위젯으로 Stac Cloud에서 화면을 불러오는 것입니다. + +#### 사용법 + +```dart +Stac(routeName: 'home_screen') +``` + +`routeName`과 매핑된 화면 JSON을 Stac Cloud에서 가져와 렌더링합니다. + +#### Dart 소스 코드 + +`/stac` 폴더에 있는 `home_screen` 예시는 다음과 같습니다. + +```dart +import 'package:stac_core/stac_core.dart'; + +@StacScreen(screenName: 'home_screen') +StacWidget homeScreen() { + return StacScaffold( + appBar: StacAppBar(title: StacText(data: 'Home')), + body: StacColumn( + children: [ + StacText(data: 'Welcome to Stac!'), + StacElevatedButton( + onPressed: { + 'actionType': 'navigate', + 'routeName': 'details' + }, + child: StacText(data: 'Go to Details'), + ), + ], + ), + ); +} +``` + +`stac deploy`를 실행하면 위 코드가 JSON으로 변환되어 Stac Cloud에 업로드되며, `Stac(routeName: 'home_screen')`에서 사용할 수 있습니다. + +#### 주요 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| `routeName` | `String` | Stac Cloud에 등록된 화면 이름 | +| `loadingWidget` | `Widget?` | 데이터를 불러오는 동안 표시할 위젯 | +| `errorWidget` | `Widget?` | 오류가 발생했을 때 표시할 위젯 | + +#### 예제 + +```dart +import 'package:flutter/material.dart'; +import 'package:stac/stac.dart'; + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Stac Demo', + home: Stac( + routeName: 'hello_world', + loadingWidget: const Center(child: CircularProgressIndicator()), + errorWidget: const Center(child: Text('Failed to load screen')), + ), + ); + } +} +``` + +#### 사용 시점 + +- ✅ 실제 서비스에서 Stac Cloud 화면을 사용할 때 +- ✅ 서버에서 콘텐츠가 자주 변경될 때 +- ✅ A/B 테스트 및 실험을 수행할 때 +- ✅ 스토어 심사 없이 즉시 업데이트가 필요할 때 + +### 2. JSON에서 직접 렌더링 (`Stac.fromJson`) + +메모리에 있는 JSON 맵을 바로 렌더링합니다. 테스트나 프로토타이핑에 유용합니다. + +```dart +Stac.fromJson(jsonMap, context) +``` + +| 파라미터 | 타입 | 설명 | +|----------|------|------| +| `json` | `Map?` | 렌더링할 위젯 JSON | +| `context` | `BuildContext` | 빌드 컨텍스트 | + +예제: + +```dart +final json = { + 'type': 'scaffold', + 'body': { + 'type': 'center', + 'child': { + 'type': 'text', + 'data': 'Hello from JSON!' + } + } +}; + +return Stac.fromJson(json, context) ?? const SizedBox(); +``` + +- ✅ 하드코딩된 JSON 테스트 +- ✅ 빠른 프로토타입 +- ✅ 기존 JSON 데이터를 UI로 바꿀 때 + +### 3. 에셋에서 렌더링 (`Stac.fromAssets`) + +앱에 번들된 JSON 파일을 불러옵니다. 오프라인 콘텐츠나 정적인 화면에 적합합니다. + +```dart +Stac.fromAssets( + 'assets/screens/home.json', + loadingWidget: (context) => const CircularProgressIndicator(), + errorWidget: (context, error) => Text('Error: $error'), +) +``` + +`pubspec.yaml`에 JSON을 등록해야 합니다. + +```yaml +flutter: + assets: + - assets/screens/home.json +``` + +- ✅ 오프라인/저대역폭 환경 +- ✅ 앱에 내장된 기본 화면 + +### 4. 네트워크에서 렌더링 (`Stac.fromNetwork`) + +외부 API나 사내 서버에서 JSON을 받아 렌더링합니다. + +```dart +Stac.fromNetwork( + request: StacNetworkRequest(url: 'https://example.com/ui.json'), + context: context, +) +``` + +| 파라미터 | 설명 | +|----------|------| +| `request` | `StacNetworkRequest` 객체. 인증 헤더나 쿼리를 지정할 수 있습니다. | +| `context` | 빌드 컨텍스트 | + +- ✅ 자체 백엔드에서 JSON을 제공할 때 +- ✅ CDN/캐시 레이어와 통합할 때 + +--- + +이 네 가지 패턴을 적절히 조합하면 온라인/오프라인, 개발/운영 환경에 맞춰 유연하게 Stac 화면을 제공할 수 있습니다. diff --git a/docs/ko/concepts/theming.mdx b/docs/ko/concepts/theming.mdx new file mode 100644 index 00000000..4c121bb7 --- /dev/null +++ b/docs/ko/concepts/theming.mdx @@ -0,0 +1,58 @@ +--- +title: "Theming" +description: "JSON 기반으로 앱 전반 테마를 제어" +--- + +Stac 테마는 Flutter 테마와 동일한 개념을 JSON으로 노출합니다. `StacTheme`를 통해 색상, 폰트, 밝기 등을 일괄 관리할 수 있으며, 서버에서 JSON을 교체해 앱 스타일을 즉시 갱신할 수 있습니다. + +## 적용 방법 + +1. `MaterialApp` 대신 `StacApp`을 사용합니다. +2. `StacTheme.fromJson`으로 생성한 테마를 `StacApp`의 `theme` 파라미터에 전달합니다. + +```dart +import 'package:flutter/material.dart'; +import 'package:stac/stac.dart'; + +void main() async { + await Stac.initialize(); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return StacApp( + theme: StacTheme.fromJson(themeJson), + homeBuilder: (context) => const HomeScreen(), + ); + } + + Map themeJson = { + "brightness": "light", + "disabledColor": "#60FEF7FF", + "fontFamily": "Handjet", + "colorScheme": { + "brightness": "light", + "primary": "#6750a4", + "onPrimary": "#FFFFFF", + "secondary": "#615B6F", + "onSecondary": "#FFFFFF", + "surface": "#FEFBFF", + "onSurface": "#1C1B1E", + "background": "#FEFBFF", + "onBackground": "#1C1B1E", + "surfaceVariant": "#E6E0EA", + "onSurfaceVariant": "#48454D", + "error": "#AB2D25", + "onError": "#FFFFFF", + "success": "#27BA62", + "onSuccess": "#FFFFFF" + } + }; +} +``` + +자세한 구조는 [StacTheme 클래스](https://github.com/StacDev/stac/blob/dev/packages/stac/lib/src/parsers/theme/stac_theme/stac_theme.dart)를 참고하세요. diff --git a/docs/ko/get_started.mdx b/docs/ko/get_started.mdx new file mode 100644 index 00000000..16269b18 --- /dev/null +++ b/docs/ko/get_started.mdx @@ -0,0 +1,84 @@ +--- +title: "시작하기 🚀" +description: "Stac은 Flutter용 Server-Driven UI 프레임워크입니다." +--- + +**Stac**(구 Mirai)은 JSON을 통해 실시간으로 UI를 구성하여, 앱스토어 심사 없이도 UI를 최신 상태로 유지할 수 있게 합니다. + +## 패키지 구성 + +| 패키지 | 설명 | 링크 | +|--------|------|------| +| [stac](https://github.com/StacDev/stac/tree/dev/packages/stac) | 핵심 SDUI 렌더러 | [![pub package](https://img.shields.io/pub/v/stac.svg)](https://pub.dev/packages/stac) | +| [stac_framework](https://github.com/StacDev/stac/tree/dev/packages/stac_framework) | 확장 기능/도우미 제공 | [![pub package](https://img.shields.io/pub/v/stac_framework.svg)](https://pub.dev/packages/stac_framework) | +| [stac_webview](https://github.com/StacDev/stac/tree/dev/packages/stac_webview) | WebView 렌더링 지원 | [![pub package](https://img.shields.io/pub/v/stac_webview.svg)](https://pub.dev/packages/stac_webview) | + +## 설치 + +1. 의존성 추가 + +```bash +flutter pub add stac +``` + +또는 `pubspec.yaml`에 직접 추가: + +```yaml +dependencies: + stac: ^ +``` + +최신 버전은 [pub.dev](https://pub.dev/packages/stac)에서 확인하세요. + +2. 패키지 설치 + +```bash +flutter pub get +``` + +3. 패키지 import + +```dart +import 'package:stac/stac.dart'; +``` + +## 사용 방법 + +### 1. 초기화 + +```dart +void main() async { + await Stac.initialize(); + runApp(const MyApp()); +} +``` + +### 2. JSON 렌더링 + +```dart +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Stac.fromJson(json, context), + ); + } +} +``` + +### Assets에서 로드 + +```dart +home: Stac.fromAsset('assets/ui.json', context), +``` + +### 네트워크에서 로드 + +```dart +home: Stac.fromNetwork( + request: StacNetworkRequest(url: 'https://example.com/ui.json'), + context: context, +), +``` + +이제 Stac을 이용해 JSON 기반 동적 UI를 Flutter 프로젝트에 통합할 수 있습니다. diff --git a/docs/ko/introduction.mdx b/docs/ko/introduction.mdx new file mode 100644 index 00000000..0df9810f --- /dev/null +++ b/docs/ko/introduction.mdx @@ -0,0 +1,103 @@ +--- +title: "소개" +description: "Stac 공식 문서를 한국어로 만나보세요." +--- + +Stac: Server-Driven UI Framework for Flutter + +# Stac란? + +**Stac**은 **Flutter를 위한 서버 기반 UI(Server-Driven UI, SDUI) 프레임워크**로, 앱을 다시 배포하지 않고도 동적인 사용자 인터페이스를 구축하고 업데이트할 수 있게 해줍니다. +모든 위젯을 코드로 고정하는 대신, 런타임에 JSON으로 렌더링되는 **Stac 위젯**을 정의합니다. + +이 방식은 표현 계층과 비즈니스 로직을 분리하여 다음을 가능하게 합니다. + +- **즉시 업데이트 배포.** Stac 위젯을 수정해 Stac Cloud에 푸시하면 됩니다. +- **기능 실험**(A/B 테스트, 개인화 등)을 새 릴리스 없이 진행. +- 단일 스키마로 여러 플랫폼에서 **일관된 UI 유지**. +- 디자이너, PM 등 **비개발자도 레이아웃과 콘텐츠를 제어**. + +## 문서 길잡이 + + + + 처음부터 설치, 구성, 핵심 기능 사용까지 단계별로 살펴봅니다. + + + Stac CLI로 프로젝트를 생성하고 빌드하며 Stac Cloud와 연동하는 방법을 알아보세요. + + + 70개 이상의 기본 위젯을 예제와 함께 살펴보고 커스터마이징 옵션을 확인하세요. + + + 내비게이션, 다이얼로그, API 호출 등 내장 액션 핸들러를 사용하는 방법을 익힙니다. + + + Stac의 내부 구조를 이해하고 커스텀 위젯, 액션, 파서를 만드는 방법을 배웁니다. + + + 색상, 타이포그래피, 컴포넌트 테마를 실시간으로 제어하세요. + + + +## 커뮤니티에 참여하기 + +다른 Stac 개발자와 소통하고 질문하며 프로젝트를 공유하세요. + + + 디스코드 서버에서 도움을 받고 아이디어를 나누며 최신 소식을 확인하세요. + + +## 오픈 소스와 성장 + +Stac은 개발자 주도의 오픈 소스 프로젝트입니다. + + + + GitHub에서 스타를 눌러 주세요. 더 나은 서버 기반 UI를 함께 만들어갑니다. + + + PR 제출, 버그 신고, 기능 제안 등 모든 기여를 환영합니다. + + + +--- + +지금 바로 첫 번째 서버 기반 UI 앱을 만들어 보세요. [시작하기 →](/ko/quickstart) diff --git a/docs/ko/project_structure.mdx b/docs/ko/project_structure.mdx new file mode 100644 index 00000000..f7b6d5a1 --- /dev/null +++ b/docs/ko/project_structure.mdx @@ -0,0 +1,22 @@ +--- +title: "프로젝트 구조" +description: "일반적인 Stac 기반 Flutter 앱이 어떻게 구성되는지 알아보세요." +--- + +이 가이드는 Stac 프로젝트 전반 구조를 설명합니다. UI 정의 위치, 파서 구성, 테마와 라우트가 어떻게 어우러지는지 살펴봅니다. + +``` +├─ flutter_project +├── lib/ +│ ├── app/ # 기능별 코드(선택 사항) +│ ├── default_stac_options.dart # 프로젝트 기본 StacOptions +│ └── main.dart # Stac 초기화 진입점 +├── stac/ +│ └── hello_world.dart # Stac 위젯 +├── pubspec.lock +└── pubspec.yaml +``` + +- **default_stac_options.dart**: 프로젝트의 `StacOptions`를 정의합니다. +- **main.dart**: Flutter 앱과 Stac을 초기화하는 진입점입니다. +- **stac/**: 모든 Stac 위젯을 보관하는 곳입니다. diff --git a/docs/ko/quickstart.mdx b/docs/ko/quickstart.mdx new file mode 100644 index 00000000..a6a893a3 --- /dev/null +++ b/docs/ko/quickstart.mdx @@ -0,0 +1,231 @@ +--- +title: "퀵스타트" +description: "5분 안에 첫 번째 Stac 앱을 실행해 봅시다. CLI 설치, Stac Cloud 연동, 서버 기반 UI 화면 만들기까지 순서대로 안내합니다." +--- + +## 사전 준비 + +- Flutter SDK: `>=3.35.0` +- Dart SDK: `>=3.0.0 <4.0.0` + +## Flutter 앱 만들기(필요한 경우) + +아직 Flutter 앱이 없다면 다음 명령으로 새 프로젝트를 만든 뒤 디렉터리로 이동하세요. + +```bash +flutter create stac_demo +cd stac_demo +``` + +## 가입하기 + +먼저 Stac 계정이 필요합니다. 무료이며 몇 초면 끝납니다. + +[console.stac.dev](https://console.stac.dev)에서 콘솔에 접속한 뒤 Google 계정으로 로그인해 프로젝트를 생성하세요. + +## Stac CLI 설치 + +Stac CLI는 프로젝트 초기화, 화면 관리, Stac Cloud 배포를 도와줍니다. + + + +터미널에서 다음을 실행합니다. + +```bash +curl -fsSL https://raw.githubusercontent.com/StacDev/install/main/install.sh | bash +``` + + + +PowerShell을 열고 실행합니다. +```bash +irm https://raw.githubusercontent.com/StacDev/install/main/install.ps1 | iex +``` + + + + +설치가 완료되면 다음 명령으로 버전을 확인하세요. + +```bash +stac --version +``` + +## Stac 로그인 + +로그인 명령을 실행하면 브라우저가 열리고 Google 인증이 진행됩니다. + +```bash +stac login +``` + +## 프로젝트에 Stac 초기화 + +Flutter 앱 루트에서 `stac init`을 실행하세요. + +```bash +stac init +``` + +기존 프로젝트를 선택하거나 새 프로젝트를 만들 수 있습니다. + +```bash +stac_demo % stac init +... (ASCII 아트 생략) +? Please select an option: › +❯ Use an existing project + Create a new project + Don't set up a default project +``` + +여기서는 "Create a new project"를 선택하고 이름을 `stac demo`로 입력하겠습니다. + +```bash +✔ Please select an option: · Create a new project +✔ Enter project name: · stac demo +✔ Enter project description (optional): · +[INFO] Initializing project: stac demo +``` + +다른 옵션이 확실하지 않다면 엔터를 눌러 기본값을 선택해도 됩니다. + +초기화가 완료되면 다음 메시지가 표시됩니다. + +```bash +[SUCCESS] ✓ Added dependency: stac_core +[SUCCESS] ✓ Project initialized successfully! +[INFO] Next steps: +[INFO] 1. Add your Stac widgets definitions to /stac +[INFO] 2. Run "stac build" to convert Dart to JSON +[INFO] 3. Run "stac deploy" to deploy to cloud +``` + +`stac init`이 수행하는 작업: + +- `stac/hello_world.dart` 생성 – 모든 Stac 위젯은 `stac` 폴더에 위치합니다. +- `pubspec.yaml`에 `stac`, `stac_core` 의존성을 추가합니다. +- 프로젝트 이름과 ID를 정의하는 `default_stac_options.dart` 를 생성합니다. + +## Stac 위젯 만들기 + +`stac/hello_world.dart` 파일에서 화면을 구성합니다. `@StacScreen` 어노테이션으로 화면임을 표시하세요. + +```dart +import 'package:stac_core/stac_core.dart'; + +@StacScreen(screenName: 'hello_world') +StacWidget helloWorld() { + return StacScaffold( + body: StacCenter(child: StacText(data: 'Hello, world!')), + ); +} +``` + +Stac 위젯은 Flutter 패턴을 그대로 따릅니다. 예를 들어 Flutter의 `Scaffold`는 `StacScaffold`로 대응됩니다. + +## Stac 위젯 배포 + +위젯을 작성했다면 Stac Cloud로 배포합니다. + +```bash +stac deploy +``` + +`stac` 폴더의 모든 위젯을 찾아 빌드하고 Stac Cloud에 업로드합니다. + +```bash +stac_demo % stac deploy +[INFO] Building project before deployment... +[INFO] Building Stac project... +[INFO] Source directory: stac +[INFO] Output directory: stac/.build +[INFO] Found 1 .dart file(s) to process +[INFO] Found 1 @StacScreen annotated function(s) in stac/hello_world.dart +[INFO] ✓ Generated: hello_world.json +[SUCCESS] ✓ Build completed successfully! +[INFO] Processed 1 function(s) and generated JSON files +[INFO] Build completed. Starting deployment... +[INFO] Deploying screens to cloud... +[INFO] Uploading: hello_world.json +[SUCCESS] ✓ Uploaded: hello_world.json +[SUCCESS] ✓ Deployment completed successfully! +[INFO] Deployed 1 file(s) +``` + +## Stac 초기화 + +프로젝트가 준비되었으니 `main.dart`에서 Stac을 초기화합니다. + +```dart +import 'package:stac_demo/default_stac_options.dart'; +import 'package:flutter/material.dart'; +import 'package:stac/stac.dart'; + +void main() async { + await Stac.initialize(options: defaultStacOptions); + runApp(const MyApp()); +} +``` + +이제 앱이 서버 기반 UI를 로드할 준비가 되었습니다. + +`Stac` 위젯으로 `hello_world` 화면을 렌더링할 수 있습니다. + +```dart +Stac(routeName: 'hello_world'), +``` + +전체 예제는 다음과 같습니다. + +```dart +import 'package:example_app/default_stac_options.dart'; +import 'package:flutter/material.dart'; +import 'package:stac/stac.dart'; + +void main() { + Stac.initialize(options: defaultStacOptions); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + ), + home: const Stac(routeName: 'hello_world'), + ); + } +} +``` + +이렇게 몇 줄만으로 SDUI 앱이 동작합니다. + +Hello World + +`stac/hello_world.dart`를 수정한 뒤 `stac deploy`를 다시 실행하면 앱이 즉시 갱신됩니다. 스토어 심사도 필요 없습니다. + +## Stac Cloud + +배포한 화면은 Stac Cloud 대시보드에서 확인할 수 있습니다: [console.stac.dev](https://console.stac.dev) + +Stac Cloud + + +검증: 배포 후 앱을 실행하면 `hello_world` 화면이 표시되어야 합니다. 콘솔에서도 프로젝트의 Screens 목록에서 동일한 파일을 확인할 수 있습니다. + + +--- + +이제 준비가 끝났습니다. 다음 단계로 위젯과 액션을 탐색하거나 CLI 가이드를 살펴보세요. + +- [위젯](/widgets/align) +- [액션](/actions/navigate) +- [CLI](/ko/cli) + +도움이 필요하면 [Discord](https://discord.com/invite/vTGsVRK86V) 커뮤니티에 참여하거나 [GitHub 이슈](https://github.com/StacDev/stac/issues)를 열어주세요. diff --git a/docs/ko/sdui.mdx b/docs/ko/sdui.mdx new file mode 100644 index 00000000..ad48aa14 --- /dev/null +++ b/docs/ko/sdui.mdx @@ -0,0 +1,60 @@ +--- +title: "SDUI란?" +description: "정의, 장단점, 그리고 SDUI를 도입한 회사 사례" +--- + +Server-Driven UI(SDUI)는 **서버가 화면의 레이아웃과 동작을 정의하고 앱은 이를 렌더링만 하는 방식**입니다. 화면을 로드할 때 앱은 어떤 컴포넌트를 보여줄지, 어떻게 배치할지, 어떤 액션을 수행할지를 설명한 스키마를 서버에서 받아옵니다. + +## 기존 방식과 무엇이 다른가요? + +전통적인 클라이언트 주도 UI에서는 앱의 UI가 코드에 강하게 묶여 있습니다. 모든 레이아웃, 비즈니스 로직, 렌더링을 클라이언트가 담당합니다. 버튼 스타일을 바꾸거나 새 기능을 추가할 때마다 코드를 수정하고 테스트한 뒤 앱 스토어에 새 버전을 제출해야 합니다. 이 때문에 릴리스 주기가 길어지고 유연성과 확장성이 떨어집니다. + +SDUI는 이 모델을 뒤집습니다. UI를 앱 내부에 하드코딩하는 대신 **서버가 UI를 결정**하고, 클라이언트는 서버가 내려주는 스키마를 렌더링합니다. + +웹 브라우저가 HTML을 렌더링하는 방식과 비슷합니다. 브라우저는 페이지 내용을 미리 알지 못하지만 HTML을 해석하고 표시하는 방법을 알고 있습니다. SDUI에서도 앱은 미리 정의된 컴포넌트를 렌더링할 수 있는 능력만 가지며, **어떤 컴포넌트가 언제 표시되는지는 서버가 제어**합니다. + +### 동작 방식 + +1. 서버가 경량 포맷(JSON 등)으로 UI 구조를 정의합니다. +2. 앱은 이 스키마를 받아 동적으로 렌더링합니다. +3. UI를 바꾸고 싶다면 서버의 스키마만 수정하면 되며, 앱 스토어 심사가 필요 없습니다. + +Server가 JSON을 전송하면 앱이 위젯을 렌더링하는 SDUI 흐름 + +### 장점 + +- 앱 스토어 승인 없이 즉시 배포 가능 +- 앱 용량과 복잡도 감소 +- 플랫폼 간 일관성 및 빠른 기능 동등화 +- 개발 속도 향상과 비용 절감 + +### 단점 + +- 초기 구현 난이도가 존재(하지만 Stac이 크게 완화) +- 완전 오프라인 앱에는 제한적 + +## 적합한 활용 사례 + +- **홈/탐색 섹션:** 동적 캐러셀, 프로모션 모듈, 에디토리얼 스택 +- **온보딩 플로우:** 단계 순서 변경, 폼 요소 제어 +- **결제/프로모션 화면:** 지역별 요금제, 기간 한정 제안, 다이내믹 가격표 +- **실험/플래그:** 실시간 A/B 테스트, 타깃 롤아웃 + +## SDUI를 사용하는 기업 + +최근 여러 스타트업과 대규모 기업이 SDUI를 적극적으로 도입하고 있습니다. + +- **Netflix** +- **Airbnb** +- **Delivery Hero** +- **Uber** +- **Shopify** +- **Meta** +- **Spotify** +- **PhonePe** + +## 다음 단계 + +- [퀵스타트](./ko/quickstart)에서 첫 번째 서버 기반 화면을 렌더링해 보세요. +- [Stac 위젯 렌더링](./concepts/rendering_stac_widgets)을 읽고 내부 동작을 이해하세요. +- [위젯](./widgets/) 카탈로그를 살펴보고 어떤 컴포넌트가 있는지 확인하세요. diff --git a/docs/ko/styles/border.mdx b/docs/ko/styles/border.mdx new file mode 100644 index 00000000..bc4e1a3c --- /dev/null +++ b/docs/ko/styles/border.mdx @@ -0,0 +1,135 @@ +--- +title: "Border" +description: "StacBorder로 테두리를 정의하는 방법" +--- + +`StacBorder`는 Flutter `Border` 클래스를 JSON으로 표현할 수 있게 해 줍니다. 전체 테두리를 한 번에 지정하거나 각 변을 개별적으로 설정할 수 있습니다. Flutter 위젯 자체 설명은 [공식 문서](https://api.flutter.dev/flutter/painting/Border-class.html)를 참고하세요. + +## 특징 + +- **균일 테두리**: 네 면에 동일한 설정 적용 +- **개별 테두리**: 상·우·하·좌 각각 다른 설정 지정 +- **자동 전환**: 개별 속성이 하나라도 있으면 자동으로 개별 모드로 전환 + +## 속성 + +### 균일 테두리 + +| 속성 | 타입 | 설명 | +|------|------|------| +| color | `String?` | 네 면 모두에 적용할 색상 | +| width | `StacDouble?` | 테두리 두께(logical px) | +| borderStyle | `BorderStyle?` | `solid` 또는 `none`(기본값 `solid`) | +| strokeAlign | `StacDouble?` | 선이 안/밖 어디에 위치할지(-1.0 ~ 1.0) | + +### 개별 테두리 + +| 속성 | 타입 | 설명 | +|------|------|------| +| top/right/bottom/left | `StacBorderSide?` | 각 면의 설정 | + +:::note +`top/right/bottom/left` 중 하나라도 지정하면 균일 속성(`color`, `width`, `borderStyle`, `strokeAlign`)은 무시됩니다. +::: + +## 예제 + +### 균일 테두리 + +```json +{ + "border": { + "color": "#FF0000", + "width": 2.0, + "borderStyle": "solid", + "strokeAlign": 0.0 + } +} +``` + +### 개별 테두리 + +```json +{ + "border": { + "top": { + "color": "#FF0000", + "width": 2.0, + "borderStyle": "solid", + "strokeAlign": 0.0 + }, + "right": { + "color": "#00FF00", + "width": 1.0, + "borderStyle": "solid" + }, + "bottom": { + "color": "#0000FF", + "width": 3.0, + "borderStyle": "solid" + }, + "left": { + "color": "#FFFF00", + "width": 1.5, + "borderStyle": "solid" + } + } +} +``` + +### 일부 면만 지정 + +```json +{ + "border": { + "top": { + "color": "#FF0000", + "width": 2.0, + "borderStyle": "solid" + }, + "bottom": { + "color": "#0000FF", + "width": 2.0, + "borderStyle": "solid" + } + } +} +``` + +### Container 예시 + +```json +{ + "type": "container", + "width": 200, + "height": 100, + "decoration": { + "color": "#F0F0F0", + "border": { + "top": { "color": "#FF0000", "width": 3.0, "borderStyle": "solid" }, + "right": { "color": "#00FF00", "width": 2.0, "borderStyle": "solid" }, + "bottom": { "color": "#0000FF", "width": 3.0, "borderStyle": "solid" }, + "left": { "color": "#FFFF00", "width": 2.0, "borderStyle": "solid" } + }, + "borderRadius": { + "topLeft": 8.0, + "topRight": 8.0, + "bottomLeft": 8.0, + "bottomRight": 8.0 + } + }, + "child": { + "type": "center", + "child": { + "type": "text", + "data": "Custom Borders", + "style": { "fontSize": 16, "fontWeight": "bold" } + } + } +} +``` + +## 같이 보기 + +- [Border Side](./border_side) – `StacBorderSide` 속성 +- [Border Radius](./border_radius) – 모서리 둥글기 설정 diff --git a/docs/ko/styles/border_radius.mdx b/docs/ko/styles/border_radius.mdx new file mode 100644 index 00000000..3a7f0733 --- /dev/null +++ b/docs/ko/styles/border_radius.mdx @@ -0,0 +1,43 @@ +--- +title: "Border Radius" +description: "StacBorderRadius로 모서리를 둥글게 만들기" +--- + +`StacBorderRadius`는 Flutter `BorderRadius`를 JSON으로 정의할 수 있게 합니다. [공식 문서](https://api.flutter.dev/flutter/painting/BorderRadius-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| radius | `double?` | 네 모서리에 동일한 반경 적용 | +| radius | `Map?` | 각 모서리를 명시적으로 지정 | +| radius | `List?` | `[topLeft, topRight, bottomRight, bottomLeft]` 순으로 지정 | + +:::note +위 세 방법 중 하나만 사용하면 됩니다. +::: + +## 예제 + +```json +{ + "borderRadius": 16.0 +} +``` + +```json +{ + "borderRadius": { + "topLeft": 16.0, + "topRight": 16.0, + "bottomLeft": 16.0, + "bottomRight": 16.0 + } +} +``` + +```json +{ + "borderRadius": [16.0, 16.0, 16.0, 16.0] +} +``` diff --git a/docs/ko/styles/border_side.mdx b/docs/ko/styles/border_side.mdx new file mode 100644 index 00000000..1a4c04f7 --- /dev/null +++ b/docs/ko/styles/border_side.mdx @@ -0,0 +1,30 @@ +--- +title: "Border Side" +description: "StacBorderSide 속성 설명" +--- + +`StacBorderSide`는 Flutter `BorderSide`를 JSON으로 정의할 수 있게 해 줍니다. [StacBorder](./border)에서 개별 면을 사용할 때 이 구성을 참조합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| color | `Color?` | 선 색상 | +| width | `double?` | 선 두께(logical px) | +| style | `BorderStyle?` | `solid` 또는 `none`(기본 `solid`) | +| strokeAlign | `double?` | 선 위치(-1.0: 안쪽, 1.0: 바깥) | + +## 예제 + +```json +{ + "color": "#428AF5", + "width": 1.0, + "style": "solid", + "strokeAlign": 1.0 +} +``` + +## 참고 + +- [Border](./border) – 개별 면을 사용하는 방법 diff --git a/docs/ko/styles/box_fit.mdx b/docs/ko/styles/box_fit.mdx new file mode 100644 index 00000000..d4293003 --- /dev/null +++ b/docs/ko/styles/box_fit.mdx @@ -0,0 +1,25 @@ +--- +title: "BoxFit" +description: "이미지/위젯을 어떻게 맞출지 정의" +--- + +`fit` 속성에 사용할 수 있는 값: + +| 값 | 설명 | +|----|------| +| `fill` | 비율을 무시하고 상자를 꽉 채움 | +| `contain` | 비율을 유지하며 상자 안에 모두 들어가도록 축소/확대 | +| `cover` | 상자를 완전히 덮도록 확대(일부 잘릴 수 있음) | +| `fitWidth` | 너비를 기준으로 맞추며 세로는 넘칠 수 있음 | +| `fitHeight` | 높이를 기준으로 맞추며 가로는 넘칠 수 있음 | +| `none` | 스케일 없이 배치(넘칠 수 있음) | +| `scaleDown` | 자식이 클 때만 축소하여 상자 안에 맞춤 | + +## 예제 + +```json +{ + "type": "fittedBox", + "fit": "contain" +} +``` diff --git a/docs/ko/styles/clip_behavior.mdx b/docs/ko/styles/clip_behavior.mdx new file mode 100644 index 00000000..a9dac811 --- /dev/null +++ b/docs/ko/styles/clip_behavior.mdx @@ -0,0 +1,22 @@ +--- +title: "Clip" +description: "clipBehavior 값 설명" +--- + +`clipBehavior`는 위젯 영역 밖의 콘텐츠를 어떻게 처리할지 결정합니다. + +| 값 | 설명 | +|----|------| +| `none` | 클리핑 없음(기본값) | +| `hardEdge` | 안티에일리어싱 없이 잘라냄 | +| `antiAlias` | 안티에일리어싱을 적용해 부드러운 가장자리 | +| `antiAliasWithSaveLayer` | 안티에일리어싱 + 오프스크린 버퍼 사용 | + +## 예제 + +```json +{ + "type": "fittedBox", + "clipBehavior": "hardEdge" +} +``` diff --git a/docs/ko/styles/colors.mdx b/docs/ko/styles/colors.mdx new file mode 100644 index 00000000..315c41c7 --- /dev/null +++ b/docs/ko/styles/colors.mdx @@ -0,0 +1,55 @@ +--- +title: "Color" +description: "색상을 정의하는 세 가지 방법" +--- + +텍스트, 배경, 테두리 등 대부분의 속성은 `Color` 값을 받습니다. Stac에서는 Theme 색상, Hex 색상, 이름 색상을 지원합니다. Flutter 색상 자체는 [공식 문서](https://api.flutter.dev/flutter/material/Colors-class.html)를 참고하세요. + +## 색상 유형 + +### Theme 색상 + +테마에 정의된 색상을 문자열로 참조합니다. 사용 가능한 키: `primary`, `onPrimary`, `primaryContainer`, `onPrimaryContainer`, `secondary`, `onSecondary`, `secondaryContainer`, `onSecondaryContainer`, `tertiary`, `onTertiary`, `tertiaryContainer`, `onTertiaryContainer`, `error`, `onError`, `errorContainer`, `onErrorContainer`, `background`, `onBackground`, `surface`, `onSurface`, `surfaceVariant`, `onSurfaceVariant`, `outline`, `outlineVariant`, `shadow`, `scrim`, `inverseSurface`, `onInverseSurface`, `inversePrimary`, `surfaceTint`, `scaffoldBackgroundColor`. + +### Hex 색상 + +`#RRGGBB`(6자리)나 `#AARRGGBB`(8자리)로 임의 색상을 지정합니다. 앞 2자리는 불투명도입니다. + +### 이름 색상 + +`amber`, `blueAccent`, `black45`, `white70` 등 Flutter 이름 규칙을 그대로 사용할 수 있습니다. 흑백 계열은 뒤에 투명도 값을 붙여 사용합니다. + +- 흰색 사용 가능 투명도: `10`, `12`, `24`, `30`, `38`, `54`, `60`, `70` +- 검정 사용 가능 투명도: `12`, `26`, `38`, `45`, `54`, `87` + +## 예제 + +```json +{ + "type": "text", + "data": "Hello World!", + "style": { + "color": "primary" + } +} +``` + +```json +{ + "type": "text", + "data": "Hello World!", + "style": { + "color": "#000000" + } +} +``` + +```json +{ + "type": "text", + "data": "Hello World!", + "style": { + "color": "black45" + } +} +``` diff --git a/docs/ko/styles/stac_alignment_directional.mdx b/docs/ko/styles/stac_alignment_directional.mdx new file mode 100644 index 00000000..cf37b5e7 --- /dev/null +++ b/docs/ko/styles/stac_alignment_directional.mdx @@ -0,0 +1,27 @@ +--- +title: "StacAlignmentDirectional" +description: "정렬 값을 JSON으로 지정" +--- + +`alignment` 속성은 자식을 상자 안 어디에 배치할지 결정합니다. + +| 값 | 설명 | +|----|------| +| `topStart` | 좌측 상단(또는 RTL일 때 우측 상단) | +| `topCenter` | 상단 중앙 | +| `topEnd` | 우측 상단(RTL 기준 반대) | +| `centerStart` | 가운데 좌측 | +| `center` | 중앙 | +| `centerEnd` | 가운데 우측 | +| `bottomStart` | 좌측 하단 | +| `bottomCenter` | 하단 중앙 | +| `bottomEnd` | 우측 하단 | + +## 예제 + +```json +{ + "type": "fittedBox", + "alignment": "center" +} +``` diff --git a/docs/ko/styles/table_border.mdx b/docs/ko/styles/table_border.mdx new file mode 100644 index 00000000..3b7bbf6c --- /dev/null +++ b/docs/ko/styles/table_border.mdx @@ -0,0 +1,33 @@ +--- +title: "Table Border" +description: "표 테두리를 JSON으로 정의" +--- + +`StacTableBorder`는 Flutter `TableBorder`를 JSON으로 설정할 때 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| color | `String` | 테두리 색상 | +| width | `double` | 테두리 두께 | +| style | `BorderStyle` | 테두리 스타일 | +| borderRadius | `StacBorderRadius` | 모서리 둥글기 | + +## 예제 + +```json +{ + "tableBorder": { + "color": "#000000", + "width": 1.0, + "style": "solid", + "borderRadius": { + "topLeft": 16.0, + "topRight": 16.0, + "bottomLeft": 16.0, + "bottomRight": 16.0 + } + } +} +``` diff --git a/docs/ko/styles/table_column_width.mdx b/docs/ko/styles/table_column_width.mdx new file mode 100644 index 00000000..d31bc4e9 --- /dev/null +++ b/docs/ko/styles/table_column_width.mdx @@ -0,0 +1,22 @@ +--- +title: "Table Column Width" +description: "표 컬럼 폭 설정" +--- + +`StacTableColumnWidth`는 Flutter `TableColumnWidth`를 JSON으로 지정합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| type | `StacTableColumnWidth?` | `fixedColumnWidth`, `flexColumnWidth`, `fractionColumnWidth`, `intrinsicColumnWidth` 중 하나 | +| value | `double?` | 컬럼 폭 값. 예를 들어 `fixedColumnWidth`일 때 `200.0`이면 200px | + +## 예제 + +```json +{ + "type": "fixedColumnWidth", + "value": 200.0 +} +``` diff --git a/docs/ko/widgets/alert_dialog.mdx b/docs/ko/widgets/alert_dialog.mdx new file mode 100644 index 00000000..cf3765c6 --- /dev/null +++ b/docs/ko/widgets/alert_dialog.mdx @@ -0,0 +1,59 @@ +--- +title: "AlertDialog" +description: "경고 대화 상자 구성" +--- + +`alertDialog`는 Flutter `AlertDialog`를 JSON으로 정의합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| icon | `Map?` | 다이얼로그 상단 아이콘 | +| iconPadding / iconColor | `StacEdgeInsets?` / `String?` | 아이콘 여백과 색상 | +| title / titlePadding / titleTextStyle | `Map` / `StacEdgeInsets?` / `StacTextStyle?` | 제목 영역 구성 | +| content / contentPadding / contentTextStyle | `Map` / `StacEdgeInsets?` / `StacTextStyle?` | 본문 영역 구성 | +| actions | `List>?` | 하단 버튼 목록 | +| actionsPadding / buttonPadding | `StacEdgeInsets?` | 액션 주변 여백 | +| actionsAlignment / actionsOverflowAlignment / actionsOverflowDirection | `MainAxisAlignment?` / `OverflowBarAlignment?` / `VerticalDirection?` | 버튼 정렬과 오버플로 동작 | +| actionsOverflowButtonSpacing | `double?` | 넘칠 때 버튼 간격 | +| backgroundColor / shadowColor / surfaceTintColor | `String?` | 배경 및 그림자 색 | +| elevation | `double?` | 그림자 깊이 | +| semanticLabel | `String?` | 접근성 라벨 | +| insetPadding | `StacEdgeInsets` | 화면과의 기본 여백(기본 좌우 40, 상 24, 하 24) | +| clipBehavior | `Clip` | 클리핑 옵션 | +| shape | `StacShapeBorder?` | 모서리 모양 | +| alignment | `StacAlignmentGeometry?` | 다이얼로그 위치 | +| scrollable | `bool` | 내용 스크롤 허용 여부 | + +## 예제 + +```json +{ + "type": "alertDialog", + "content": { + "type": "padding", + "padding": { "top": 0, "left": 12, "right": 12, "bottom": 8 }, + "child": { + "type": "text", + "data": "Discard draft?", + "align": "center", + "style": { "fontSize": 14 } + } + }, + "actions": [ + { + "type": "textButton", + "child": { "type": "text", "data": "CANCEL" }, + "onPressed": { "actionType": "navigate", "navigationStyle": "pop" } + }, + { "type": "sizedBox", "width": 8 }, + { + "type": "textButton", + "child": { "type": "text", "data": "DISCARD" }, + "onPressed": { "actionType": "navigate", "navigationStyle": "pop" } + }, + { "type": "sizedBox", "width": 12 } + ] +} +``` diff --git a/docs/ko/widgets/align.mdx b/docs/ko/widgets/align.mdx new file mode 100644 index 00000000..e34ad469 --- /dev/null +++ b/docs/ko/widgets/align.mdx @@ -0,0 +1,43 @@ +--- +title: "Align" +description: "Align 위젯을 JSON으로 구성하는 방법" +--- + +Stac `align`은 Flutter의 `Align` 위젯을 JSON으로 정의할 수 있게 해 줍니다. Flutter 위젯 자체에 대한 자세한 설명은 [공식 문서](https://api.flutter.dev/flutter/widgets/Align-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| alignment | `StacAlignment` | 자식 위젯을 정렬하는 위치 | +| widthFactor | `double` | 지정하면 자식 너비에 이 값을 곱한 크기로 정렬 박스를 결정합니다. | +| heightFactor | `double` | 지정하면 자식 높이에 이 값을 곱한 크기로 정렬 박스를 결정합니다. | +| child | `Map` | Align 아래에 위치할 자식 위젯 | + +## 예제 + +```json +{ + "type": "align", + "alignment": "topEnd", + "child": { + "type": "container", + "color": "#FC5632", + "clipBehavior": "hardEdge", + "height": 250, + "width": 200, + "child": { + "type": "align", + "alignment": "bottomCenter", + "child": { + "type": "text", + "data": "Flutter", + "style": { + "fontSize": 23, + "fontWeight": "w600" + } + } + } + } +} +``` diff --git a/docs/ko/widgets/app_bar.mdx b/docs/ko/widgets/app_bar.mdx new file mode 100644 index 00000000..53406440 --- /dev/null +++ b/docs/ko/widgets/app_bar.mdx @@ -0,0 +1,41 @@ +--- +title: "AppBar" +description: "상단 앱 바 구성" +--- + +Stac `appBar`는 Flutter `AppBar`를 JSON으로 정의합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| leading | `Map?` | 타이틀 앞에 놓일 위젯 | +| title | `Map?` | 제목 위젯 | +| titleTextStyle / toolbarTextStyle | `StacTextStyle?` | 텍스트 스타일 | +| shadowColor / backgroundColor / foregroundColor / surfaceTintColor | `String?` | 색상 설정 | +| actions | `List>` | 타이틀 뒤에 나열할 액션 | +| bottom | `Map?` | AppBar 하단에 붙는 위젯(예: TabBar) | +| titleSpacing | `double?` | 타이틀 주변 간격 | +| toolbarOpacity / bottomOpacity | `double` | Toolbar/Bottom 투명도 | +| toolbarHeight / leadingWidth | `double?` | Toolbar 높이 및 leading 폭 | +| primary | `bool` | Scaffold의 기본 AppBar인지(기본 `true`) | +| centerTitle | `bool?` | 타이틀 중앙 정렬 여부 | +| elevation / scrolledUnderElevation | `double?` | 그림자 높이 | + +## 예제 + +```json +{ + "type": "appBar", + "title": { + "type": "text", + "data": "App Bar Title" + }, + "backgroundColor": "#FFFFFF", + "foregroundColor": "#000000", + "actions": [ + { "type": "iconButton", "icon": { "type": "icon", "icon": "search" } }, + { "type": "iconButton", "icon": { "type": "icon", "icon": "settings" } } + ] +} +``` diff --git a/docs/ko/widgets/aspect_ratio.mdx b/docs/ko/widgets/aspect_ratio.mdx new file mode 100644 index 00000000..a17ac413 --- /dev/null +++ b/docs/ko/widgets/aspect_ratio.mdx @@ -0,0 +1,28 @@ +--- +title: "AspectRatio" +description: "가로세로 비율을 강제하는 위젯" +--- + +Stac `aspectRatio`는 Flutter `AspectRatio`를 JSON으로 정의합니다. [공식 문서](https://api.flutter.dev/flutter/widgets/AspectRatio-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| aspectRatio | `double` | 자식의 가로/세로 비율. 기본값 1(정사각형) | +| child | `Map?` | 비율을 적용할 자식 위젯 | + +## 예제 + +```json +{ + "type": "aspectRatio", + "aspectRatio": 1.33, + "child": { + "type": "container", + "color": "#FF5733", + "width": 100, + "height": 100 + } +} +``` diff --git a/docs/ko/widgets/auto_complete.mdx b/docs/ko/widgets/auto_complete.mdx new file mode 100644 index 00000000..d047a72e --- /dev/null +++ b/docs/ko/widgets/auto_complete.mdx @@ -0,0 +1,32 @@ +--- +title: "AutoComplete" +description: "자동완성 입력 필드" +--- + +`autoComplete`는 Flutter `Autocomplete` 위젯을 JSON으로 구성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| options | `List` | 제안 목록 | +| onSelected | `Map?` | 항목 선택 시 실행할 액션 | +| optionsMaxHeight | `double` | 목록 최대 높이(기본 200) | +| optionsViewOpenDirection | `OptionsViewOpenDirection` | 목록이 펼쳐질 방향(`down` 기본) | +| initialValue | `String?` | 초기 값 | + +## 예제 + +```json +{ + "type": "autoComplete", + "options": ["Option 1", "Option 2", "Option 3"], + "onSelected": { + "type": "callback", + "name": "onOptionSelected" + }, + "optionsMaxHeight": 250, + "optionsViewOpenDirection": "up", + "initialValue": "Option 1" +} +``` diff --git a/docs/ko/widgets/backdrop_filter.mdx b/docs/ko/widgets/backdrop_filter.mdx new file mode 100644 index 00000000..e5f1609a --- /dev/null +++ b/docs/ko/widgets/backdrop_filter.mdx @@ -0,0 +1,103 @@ +--- +title: "BackdropFilter" +description: "기존 컨텐츠에 블러/필터 적용" +--- + +`backdropFilter`는 이미 그려진 화면을 필터링한 뒤 자식을 렌더링합니다. 블러·서리 낀 유리 효과 등 다양한 비주얼을 만들 수 있습니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| filter | `Map` | 기존 픽셀에 적용할 이미지 필터 | +| child | `Map?` | 필터 적용 후 그릴 자식 | +| enabled | `bool` | 필터 적용 여부(기본 `true`) | +| blendMode | `String?` | 기존 내용과 필터를 혼합할 때 사용할 BlendMode (`BlendMode.srcOver` 기본) | + +## 필터 타입 + +### Blur + +| 키 | 타입 | 설명 | +|----|------|------| +| type | `"blur"` | 필터 종류 | +| sigmaX / sigmaY | `double` | 가로/세로 표준편차(기본 10) | + +### Dilate / Erode + +| 키 | 타입 | 설명 | +|----|------|------| +| type | `"dilate"` 또는 `"erode"` | 팽창/침식 필터 | +| radiusX / radiusY | `double` | 가로/세로 반경 | + +### Compose + +| 키 | 타입 | 설명 | +|----|------|------| +| type | `"compose"` | 두 필터를 조합 | +| inner | `Map` | 먼저 적용할 필터 | +| outer | `Map` | 이후 적용할 필터 | + +## 사용 팁 + +1. `stack`을 사용해 필터를 적용할 컨텐츠 위에 올립니다. +2. `clipRRect`로 영역을 제한하면 가장자리 번짐을 막을 수 있습니다. +3. 자식으로 반투명 컨테이너를 두면 효과가 더 잘 보입니다. +4. `fit: expand`를 통해 전체 영역을 덮도록 합니다. + +## 예제 + +### 블러(서리낀 유리) + +```json +{ + "type": "clipRRect", + "borderRadius": { "all": 16 }, + "child": { + "type": "sizedBox", + "height": 200, + "width": 300, + "child": { + "type": "stack", + "fit": "expand", + "children": [ + { + "type": "container", + "decoration": { + "color": "#FF0000" + } + }, + { + "type": "backdropFilter", + "filter": { "type": "blur", "sigmaX": 15.0, "sigmaY": 15.0 }, + "child": { "type": "container", "decoration": { "color": "#80FFFFFF" } } + } + ] + } + } +} +``` + +### Dilate + +```json +{ + "type": "backdropFilter", + "filter": { "type": "dilate", "radiusX": 2.0, "radiusY": 2.0 }, + "child": { "type": "container", "decoration": { "color": "#80FFFFFF" } } +} +``` + +### Compose(Blur + Dilate) + +```json +{ + "type": "backdropFilter", + "filter": { + "type": "compose", + "outer": { "type": "blur", "sigmaX": 5.0, "sigmaY": 5.0 }, + "inner": { "type": "dilate", "radiusX": 2.0, "radiusY": 2.0 } + }, + "child": { "type": "container", "decoration": { "color": "#80FFFFFF" } } +} +``` diff --git a/docs/ko/widgets/bottom_navigation_bar.mdx b/docs/ko/widgets/bottom_navigation_bar.mdx new file mode 100644 index 00000000..86982ccd --- /dev/null +++ b/docs/ko/widgets/bottom_navigation_bar.mdx @@ -0,0 +1,76 @@ +--- +title: "BottomNavigationBar" +description: "하단 탭 바 + 컨트롤러" +--- + +`bottomNavigationBar`는 Flutter `BottomNavigationBar`를 JSON으로 구성합니다. 여러 페이지를 전환하려면 `defaultBottomNavigationController`와 `bottomNavigationView`를 함께 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| items | `List` | 표시할 탭 목록 | +| elevation | `double?` | 높이(그림자) | +| bottomNavigationBarType | `BottomNavigationBarType?` | `fixed` / `shifting` | +| fixedColor / selectedItemColor / unselectedItemColor | `String?` | 선택/비선택 색상 | +| backgroundColor | `String?` | 배경색 | +| iconSize | `double` | 아이콘 크기(기본 24) | +| selectedFontSize / unselectedFontSize | `double` | 라벨 폰트 크기 | +| selectedLabelStyle / unselectedLabelStyle | `StacTextStyle?` | 라벨 스타일 | +| showSelectedLabels / showUnselectedLabels | `bool?` | 라벨 표시 여부 | +| enableFeedback | `bool?` | 탭 피드백 | +| landscapeLayout | `BottomNavigationBarLandscapeLayout?` | 가로 모드 레이아웃 | + +### DefaultBottomNavigationController + +| 속성 | 타입 | 설명 | +|------|------|------| +| length | `int` | 아이템 수 | +| initialIndex | `int?` | 초기 선택 인덱스 | +| child | `Map` | 하위 위젯 | + +### BottomNavigationBarItem + +| 속성 | 타입 | 설명 | +|------|------|------| +| icon / activeIcon | `Map` | 기본/선택 상태 아이콘 | +| label | `String` | 라벨 | +| backgroundColor | `String?` | 항목 배경 | +| tooltip | `String?` | 길게 누를 때 표시되는 텍스트 | + +### BottomNavigationView + +선택된 탭에 대응하는 자식을 보여주는 뷰입니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| children | `List>` | 각 탭에 매핑될 위젯 | + +## 예제 + +```json +{ + "type": "defaultBottomNavigationController", + "length": 3, + "child": { + "type": "scaffold", + "appBar": { "type": "appBar", "title": { "type": "text", "data": "Bottom Navigation Screen" } }, + "body": { + "type": "bottomNavigationView", + "children": [ + { "type": "center", "child": { "type": "text", "data": "Home", "style": { "fontSize": 24 } } }, + { "type": "center", "child": { "type": "text", "data": "Search", "style": { "fontSize": 24 } } }, + { "type": "center", "child": { "type": "text", "data": "Profile", "style": { "fontSize": 24 } } } + ] + }, + "bottomNavigationBar": { + "type": "bottomNavigationBar", + "items": [ + { "type": "navigationBarItem", "label": "Home", "icon": { "type": "icon", "icon": "home" } }, + { "type": "navigationBarItem", "label": "Search", "icon": { "type": "icon", "icon": "search" } }, + { "type": "navigationBarItem", "label": "Profile", "icon": { "type": "icon", "icon": "account_circle" } } + ] + } + } +} +``` diff --git a/docs/ko/widgets/card.mdx b/docs/ko/widgets/card.mdx new file mode 100644 index 00000000..2edbe2e3 --- /dev/null +++ b/docs/ko/widgets/card.mdx @@ -0,0 +1,40 @@ +--- +title: "Card" +description: "카드 형태 컨테이너" +--- + +`card`는 그림자와 모서리 등을 가진 컨테이너를 만들 때 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| color / shadowColor / surfaceTintColor | `String?` | 배경, 그림자, 표면 틴트 색상 | +| elevation | `double?` | 그림자 깊이 | +| shape | `StacShapeBorder?` | 카드 모양 | +| borderOnForeground | `bool` | 테두리를 자식보다 위에 그릴지(기본 `true`) | +| margin | `StacEdgeInsets?` | 카드 주변 여백 | +| clipBehavior | `Clip?` | 자식 잘라내기 방식 | +| child | `Map?` | 카드 내용 | +| semanticContainer | `bool` | 접근성 컨테이너로 처리할지(기본 `true`) | + +## 예제 + +```json +{ + "type": "card", + "color": "#FFFFFF", + "shadowColor": "#000000", + "surfaceTintColor": "#FF0000", + "elevation": 5.0, + "shape": { + "type": "roundedRectangle", + "borderRadius": 10.0 + }, + "borderOnForeground": true, + "margin": { "left": 10, "top": 20, "right": 10, "bottom": 20 }, + "clipBehavior": "antiAlias", + "child": { "type": "text", "data": "This is a card." }, + "semanticContainer": true +} +``` diff --git a/docs/ko/widgets/carousel_view.mdx b/docs/ko/widgets/carousel_view.mdx new file mode 100644 index 00000000..1075fe1a --- /dev/null +++ b/docs/ko/widgets/carousel_view.mdx @@ -0,0 +1,44 @@ +--- +title: "CarouselView" +description: "여러 아이템을 슬라이드로 보여주기" +--- + +`carouselView`는 가로/세로로 스크롤되는 캐러셀을 생성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| carouselType | `StacCarouselViewType` | `regular`, `weighted` 등 | +| padding | `StacEdgeInsets?` | 캐러셀 주변 여백 | +| backgroundColor / overlayColor | `String?` | 배경 및 오버레이 색 | +| elevation | `double?` | 그림자 높이 | +| itemSnapping | `bool` | 아이템 스냅 여부 | +| shrinkExtent | `double` | 수축 정도(기본 0) | +| scrollDirection | `Axis` | 스크롤 방향(기본 horizontal) | +| reverse | `bool` | 역방향 스크롤 | +| onTap | `Map?` | 아이템 탭 시 콜백 | +| enableSplash | `bool` | 스플래시 효과(기본 true) | +| itemExtent | `double?` | 각 아이템 폭/높이 | +| flexWeights | `List?` | 가중치 기반 레이아웃일 때 비율 | +| children | `List>` | 캐러셀 항목 | + +## 예제 + +```json +{ + "type": "carouselView", + "carouselType": "weighted", + "padding": 12, + "backgroundColor": "#FFFFFF", + "itemSnapping": true, + "scrollDirection": "horizontal", + "itemExtent": 300, + "flexWeights": [1, 7, 1], + "children": [ + { "type": "image", "height": 400, "fit": "cover", "src": "https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png" }, + { "type": "image", "height": 400, "fit": "cover", "src": "https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_2.png" }, + { "type": "image", "height": 400, "fit": "cover", "src": "https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_3.png" } + ] +} +``` diff --git a/docs/ko/widgets/center.mdx b/docs/ko/widgets/center.mdx new file mode 100644 index 00000000..a70cdf93 --- /dev/null +++ b/docs/ko/widgets/center.mdx @@ -0,0 +1,26 @@ +--- +title: "Center" +description: "자식을 중앙에 배치" +--- + +Stac `center`는 Flutter `Center` 위젯과 동일하게 자식을 중앙에 배치합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| widthFactor | `double?` | 자식 너비에 곱할 비율 | +| heightFactor | `double?` | 자식 높이에 곱할 비율 | +| child | `Map?` | 중앙에 둘 자식 | + +## 예제 + +```json +{ + "type": "center", + "child": { + "type": "text", + "data": "Hello, World!" + } +} +``` diff --git a/docs/ko/widgets/check_box.mdx b/docs/ko/widgets/check_box.mdx new file mode 100644 index 00000000..72c9cbf5 --- /dev/null +++ b/docs/ko/widgets/check_box.mdx @@ -0,0 +1,42 @@ +--- +title: "CheckBox" +description: "체크박스 입력" +--- + +`checkBox`는 Flutter `Checkbox`를 JSON으로 정의합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| id | `String?` | 체크박스 식별자 | +| value | `bool?` | 현재 값 | +| tristate | `bool` | 세 가지 상태(null 포함) 사용 여부 | +| onChanged | `Map?` | 값 변경 시 실행할 액션 | +| mouseCursor | `StacMouseCursor?` | 마우스 커서 스타일 | +| activeColor / fillColor / checkColor | `String?` / `StacMaterialColor?` / `String?` | 체크 시 색상 | +| focusColor / hoverColor / overlayColor | `String?` / `String?` / `StacMaterialColor?` | 포커스/호버/오버레이 색 | +| splashRadius | `double?` | 터치 스플래시 반경 | +| materialTapTargetSize | `MaterialTapTargetSize?` | 탭 영역 최소 크기 | +| autofocus | `bool` | 자동 포커스 여부 | +| isError | `bool` | 에러 상태 표시 | + +## 예제 + +```json +{ + "type": "checkBox", + "id": "checkbox_1", + "value": true, + "mouseCursor": "click", + "activeColor": "#FF0000", + "fillColor": { "type": "materialColor", "color": "#00FF00" }, + "checkColor": "#0000FF", + "focusColor": "#FFFF00", + "hoverColor": "#FF00FF", + "splashRadius": 20.0, + "materialTapTargetSize": "padded", + "autofocus": true, + "isError": false +} +``` diff --git a/docs/ko/widgets/chip.mdx b/docs/ko/widgets/chip.mdx new file mode 100644 index 00000000..806acfa5 --- /dev/null +++ b/docs/ko/widgets/chip.mdx @@ -0,0 +1,58 @@ +--- +title: "Chip" +description: "태그/배지 형태 위젯" +--- + +`chip`은 아바타, 라벨, 삭제 아이콘 등을 포함한 작은 토큰을 만듭니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| avatar | `Map?` | 라벨 앞에 표시할 위젯 | +| label | `Map` | Chip의 본문 | +| labelStyle / labelPadding | `StacTextStyle?` / `StacEdgeInsets?` | 라벨 스타일과 여백 | +| deleteIcon / onDeleted / deleteIconColor / deleteButtonTooltipMessage | `Map` / `Map` / `String?` / `String?` | 삭제 아이콘 및 동작 | +| side / shape | `StacBorderSide?` / `StacRoundedRectangleBorder?` | 테두리 스타일 | +| clipBehavior | `Clip` | 잘라내기 방식 | +| autofocus | `bool` | 자동 포커스 여부 | +| color / backgroundColor | `String?` | 전경/배경 색상 | +| padding | `StacEdgeInsets?` | Chip 전체 여백 | +| visualDensity | `StacVisualDensity?` | 공간 밀도 | +| materialTapTargetSize | `MaterialTapTargetSize?` | 탭 영역 최소 크기 | +| elevation / shadowColor / surfaceTintColor | `double?` / `String?` / `String?` | 그림자 및 틴트 | +| iconTheme | `StacIconThemeData?` | 아이콘 테마 | +| avatarBoxConstraints / deleteIconBoxConstraints | `StacBoxConstraints?` | 아바타/삭제 아이콘 제약 | + +## 예제 + +```json +{ + "type": "chip", + "avatar": { + "type": "circleAvatar", + "backgroundColor": "#FF0000", + "child": { "type": "text", "data": "A" } + }, + "label": { "type": "text", "data": "Chip Label" }, + "labelStyle": { "color": "#000000", "fontSize": 14 }, + "labelPadding": { "left": 8, "top": 4, "right": 8, "bottom": 4 }, + "deleteIcon": { "type": "icon", "icon": "delete" }, + "deleteIconColor": "#FF0000", + "deleteButtonTooltipMessage": "Delete", + "side": { "color": "#000000", "width": 1.0 }, + "shape": { "type": "roundedRectangle", "borderRadius": 8.0 }, + "clipBehavior": "antiAlias", + "color": "#FFFFFF", + "backgroundColor": "#EEEEEE", + "padding": { "left": 8, "top": 4, "right": 8, "bottom": 4 }, + "visualDensity": { "horizontal": 0.0, "vertical": 0.0 }, + "materialTapTargetSize": "padded", + "elevation": 2.0, + "shadowColor": "#000000", + "surfaceTintColor": "#FFFFFF", + "iconTheme": { "color": "#000000", "size": 24.0 }, + "avatarBoxConstraints": { "minWidth": 24.0, "minHeight": 24.0 }, + "deleteIconBoxConstraints": { "minWidth": 24.0, "minHeight": 24.0 } +} +``` diff --git a/docs/ko/widgets/circle_avatar.mdx b/docs/ko/widgets/circle_avatar.mdx new file mode 100644 index 00000000..c4e79601 --- /dev/null +++ b/docs/ko/widgets/circle_avatar.mdx @@ -0,0 +1,29 @@ +--- +title: "CircleAvatar" +description: "원형 프로필 아바타" +--- + +`circleAvatar`는 원형 배경 위에 이미지나 텍스트를 표시합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| child | `Map?` | 아바타 내부 위젯 | +| backgroundColor / foregroundColor | `String?` | 배경/전경 색상 | +| backgroundImage / foregroundImage | `String?` | 배경/전경 이미지 URL | +| onBackgroundImageError / onForegroundImageError | `Map?` | 이미지 로드 실패 시 액션/위젯 | +| radius / minRadius / maxRadius | `double?` | 반지름 설정 | + +## 예제 + +```json +{ + "type": "circleAvatar", + "backgroundColor": "#FF0000", + "foregroundColor": "#FFFFFF", + "backgroundImage": "https://raw.githubusercontent.com/StacDev/stac/refs/heads/dev/assets/companies/bettrdo.jpg", + "radius": 50, + "child": { "type": "text", "data": "A" } +} +``` diff --git a/docs/ko/widgets/circular_progress_indicator.mdx b/docs/ko/widgets/circular_progress_indicator.mdx new file mode 100644 index 00000000..ad3fa28e --- /dev/null +++ b/docs/ko/widgets/circular_progress_indicator.mdx @@ -0,0 +1,30 @@ +--- +title: "CircularProgressIndicator" +description: "원형 진행 표시기" +--- + +`circularProgressIndicator`는 로딩 상태를 원형으로 표시합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| value | `double` | 진행 값(0~1, null이면 무한 애니메이션) | +| backgroundColor | `String` | 트랙 색상 | +| color | `String` | 진행선 색상 | +| strokeWidth | `double` | 선 두께 | +| strokeAlign | `double` | 선이 차지할 위치 | +| strokeCap | `StrokeCap` | 선 끝 모양 | +| semanticsLabel / semanticsValue | `String` | 접근성 텍스트 | + +## 예제 + +```json +{ + "type": "circularProgressIndicator", + "color": "#541204", + "strokeWidth": 6, + "backgroundColor": "#FFD700", + "strokeCap": "round" +} +``` diff --git a/docs/ko/widgets/clip_oval.mdx b/docs/ko/widgets/clip_oval.mdx new file mode 100644 index 00000000..b2d80b38 --- /dev/null +++ b/docs/ko/widgets/clip_oval.mdx @@ -0,0 +1,29 @@ +--- +title: "ClipOval" +description: "자식을 타원 형태로 잘라내기" +--- + +`clipOval`은 자식을 타원 또는 원 모양으로 잘라낼 때 사용합니다. + +## 속성 + +| 속성 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| clipBehavior | `Clip` | `antiAlias` | 영역을 벗어난 내용을 어떻게 처리할지 | +| child | `Map` | 필수 | 타원 모양으로 잘라낼 자식 | + +## 예제 + +```json +{ + "type": "clipOval", + "clipBehavior": "antiAlias", + "child": { + "type": "image", + "src": "https://placehold.co/600x400", + "width": 200, + "height": 200, + "fit": "cover" + } +} +``` diff --git a/docs/ko/widgets/clip_rrect.mdx b/docs/ko/widgets/clip_rrect.mdx new file mode 100644 index 00000000..b074d369 --- /dev/null +++ b/docs/ko/widgets/clip_rrect.mdx @@ -0,0 +1,66 @@ +--- +title: "ClipRRect" +description: "사각형 모서리를 둥글게 잘라내기" +--- + +`clipRRect`는 자식을 둥근 사각형 형태로 클리핑합니다. + +## 속성 + +| 속성 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| borderRadius | `StacBorderRadius` | 0 | 둥글기. 단일 값, 배열, 객체로 지정 가능 | +| clipBehavior | `Clip` | `antiAlias` | 잘라낼 때 어떤 방식으로 처리할지 | +| child | `Map` | 필수 | 둥글게 자를 위젯 | + +### BorderRadius 지정 방법 + +- 단일 값: 전체 모서리에 동일하게 적용 +- 리스트 `[topLeft, topRight, bottomLeft, bottomRight]` +- 객체 `{ "topLeft": 8.0, ... }` + +### clipBehavior 값 + +- `antiAlias` (기본) +- `hardEdge` +- `antiAliasWithSaveLayer` +- `none` + +## 예제 + +```json +{ + "type": "clipRRect", + "borderRadius": 12.0, + "child": { + "type": "container", + "color": "#2196F3", + "height": 100, + "width": 200 + } +} +``` + +```json +{ + "type": "clipRRect", + "borderRadius": { + "topLeft": 20.0, + "topRight": 20.0, + "bottomLeft": 0.0, + "bottomRight": 0.0 + }, + "child": { + "type": "container", + "color": "#FFFFFF", + "padding": 16.0, + "child": { + "type": "column", + "children": [ + { "type": "text", "data": "Card Title", "style": { "fontSize": 18.0, "fontWeight": "bold" } }, + { "type": "text", "data": "Card content goes here" } + ] + } + } +} +``` diff --git a/docs/ko/widgets/colored_box.mdx b/docs/ko/widgets/colored_box.mdx new file mode 100644 index 00000000..53604148 --- /dev/null +++ b/docs/ko/widgets/colored_box.mdx @@ -0,0 +1,24 @@ +--- +title: "ColoredBox" +description: "배경색만 가진 박스" +--- + +`coloredBox`는 배경색을 지정하는 가장 단순한 컨테이너입니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| color | `String` | 배경색(필수) | +| child | `Map?` | 내부 자식 | + +## 예제 + +```json +{ + "type": "coloredBox", + "color": "#FF0000", + "child": { + "type": "text", + "data": "Hello, World!" + } +} +``` diff --git a/docs/ko/widgets/column.mdx b/docs/ko/widgets/column.mdx new file mode 100644 index 00000000..0a45ae35 --- /dev/null +++ b/docs/ko/widgets/column.mdx @@ -0,0 +1,36 @@ +--- +title: "Column" +description: "세로 방향으로 자식을 배치" +--- + +Stac `column`은 Flutter `Column`을 JSON으로 표현합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| mainAxisAlignment | `MainAxisAlignment` | 주축(세로)에서 자식 배치 방식 | +| crossAxisAlignment | `CrossAxisAlignment` | 교차축(가로) 정렬 | +| mainAxisSize | `MainAxisSize` | Column이 차지할 공간 크기 | +| textDirection | `TextDirection?` | 정렬 계산 시 사용할 방향 | +| verticalDirection | `VerticalDirection` | 자식을 위→아래 또는 아래→위 순서로 배치 | +| spacing | `double` | 자식 간 간격 | +| children | `List>` | 자식 위젯 목록 | + +## 예제 + +```json +{ + "type": "column", + "mainAxisAlignment": "center", + "crossAxisAlignment": "start", + "mainAxisSize": "min", + "textDirection": "ltr", + "verticalDirection": "up", + "spacing": 10, + "children": [ + { "type": "text", "data": "Hello, World!" }, + { "type": "container", "width": 100, "height": 100, "color": "#FF0000" } + ] +} +``` diff --git a/docs/ko/widgets/container.mdx b/docs/ko/widgets/container.mdx new file mode 100644 index 00000000..b9e44517 --- /dev/null +++ b/docs/ko/widgets/container.mdx @@ -0,0 +1,56 @@ +--- +title: "Container" +description: "Container 위젯을 JSON으로 정의하는 방법" +--- + +Stac `container`는 Flutter `Container`를 JSON 구성으로 만들 수 있게 해 줍니다. 위젯 자체에 대한 자세한 설명은 [Flutter 공식 문서](https://api.flutter.dev/flutter/widgets/Container-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| alignment | `StacAlignment` | 자식 정렬 방식 | +| padding | `StacEdgeInsets` | 자식 주변 여백 | +| decoration | `StacBoxDecoration` | 자식 뒤 배경/테두리 데코레이션 | +| foregroundDecoration | `StacBoxDecoration` | 자식 앞에 그려질 데코레이션 | +| color | `String` | 배경 색상(hex) | +| width | `double` | 컨테이너 너비 | +| height | `double` | 컨테이너 높이 | +| constraints | `StacBoxConstraints` | 추가 제약 조건 | +| margin | `StacEdgeInsets` | 바깥쪽 여백 | +| child | `Map` | 자식 위젯 | +| clipBehavior | `Clip` | 잘라내기 동작 | + +## 예제 + +```json +{ + "type": "container", + "alignment": "center", + "padding": { + "top": 16.0, + "bottom": 16.0, + "left": 16.0, + "right": 16.0 + }, + "decoration": { + "color": "#FF5733", + "borderRadius": { + "topLeft": 16.0, + "topRight": 16.0, + "bottomLeft": 16.0, + "bottomRight": 16.0 + } + }, + "width": 200.0, + "height": 200.0, + "child": { + "type": "text", + "data": "Hello, World!", + "style": { + "color": "#FFFFFF", + "fontSize": 24.0 + } + } +} +``` diff --git a/docs/ko/widgets/custom_scroll_view.mdx b/docs/ko/widgets/custom_scroll_view.mdx new file mode 100644 index 00000000..624ce9fd --- /dev/null +++ b/docs/ko/widgets/custom_scroll_view.mdx @@ -0,0 +1,41 @@ +--- +title: "CustomScrollView" +description: "Sliver 기반 스크롤 뷰" +--- + +`customScrollView`는 Sliver 들을 조합해 복잡한 스크롤 레이아웃을 구성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| slivers | `List>` | 뷰포트에 배치할 슬리버 목록 | +| scrollDirection | `Axis` | 스크롤 방향(기본 vertical) | +| reverse | `bool` | 역방향 스크롤 | +| padding | `StacEdgeInsets?` | 전체 패딩 | +| primary | `bool?` | 부모와 연결된 기본 스크롤 뷰인지 | +| physics | `StacScrollPhysics?` | 스크롤 물리 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 동작(기본 start) | +| clipBehavior | `Clip` | 클리핑 방식(기본 hardEdge) | +| restorationId | `String?` | 상태 복원 ID | +| keyboardDismissBehavior | `ScrollViewKeyboardDismissBehavior` | 키보드 해제 방식 | + +## 예제 + +```json +{ + "type": "customScrollView", + "slivers": [ + { + "type": "sliverAppBar", + "title": { "type": "text", "data": "SliverAppBar" }, + "leading": { + "type": "iconButton", + "icon": { "type": "icon", "icon": "menu" }, + "onPressed": {} + }, + "backgroundColor": "primary" + } + ] +} +``` diff --git a/docs/ko/widgets/drawer.mdx b/docs/ko/widgets/drawer.mdx new file mode 100644 index 00000000..df4c45bf --- /dev/null +++ b/docs/ko/widgets/drawer.mdx @@ -0,0 +1,45 @@ +--- +title: "Drawer" +description: "사이드 내비게이션 서랍" +--- + +`drawer`는 Scaffold의 좌우에서 열리는 네비게이션 서랍입니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| backgroundColor / shadowColor / surfaceTintColor | `String?` | 배경과 그림자 색상 | +| elevation | `double?` | 그림자 높이 | +| shape | `StacShapeBorder?` | 모양 | +| width | `double?` | Drawer 너비 | +| child | `Map?` | 내부 콘텐츠 | +| semanticLabel | `String?` | 접근성 라벨 | +| clipBehavior | `Clip?` | 클리핑 방식 | + +## 예제 + +```json +{ + "type": "drawer", + "backgroundColor": "#FFFFFF", + "elevation": 16.0, + "shadowColor": "#000000", + "surfaceTintColor": "#F2F2F2", + "shape": { + "type": "roundedRectangleBorder", + "borderRadius": 16 + }, + "width": 304.0, + "semanticLabel": "Navigation Drawer", + "clipBehavior": "antiAlias", + "child": { + "type": "column", + "children": [ + { "type": "text", "data": "Drawer Header" }, + { "type": "text", "data": "Item 1" }, + { "type": "text", "data": "Item 2" } + ] + } +} +``` diff --git a/docs/ko/widgets/dropdown_menu.mdx b/docs/ko/widgets/dropdown_menu.mdx new file mode 100644 index 00000000..c2ca2d8e --- /dev/null +++ b/docs/ko/widgets/dropdown_menu.mdx @@ -0,0 +1,47 @@ +--- +title: "DropdownMenu" +description: "선택형 드롭다운" +--- + +`dropdownMenu`는 Material 3 드롭다운 메뉴를 구성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| dropdownMenuEntries | `List` | 항목 목록 | +| initialSelection | `dynamic` | 초기 선택 값 | +| enabled | `bool` | 사용 가능 여부(기본 `true`) | +| label | `Map?` | 라벨 위젯 | +| leadingIcon / trailingIcon | `Map?` | 앞/뒤 아이콘 | +| hintText / errorText | `String?` | 힌트/에러 텍스트 | +| width / menuHeight | `double?` | 메뉴 너비/높이 | +| inputDecorationTheme | `StacInputDecorationTheme?` | InputDecoration 테마 | +| textStyle | `StacTextStyle?` | 텍스트 스타일 | +| enableFilter / enableSearch / requestFocusOnTap | `bool` | 필터/검색/포커스 옵션(기본 true) | + +### DropdownMenuEntry + +| 속성 | 타입 | 설명 | +|------|------|------| +| value | `dynamic` | 항목 값 | +| label | `String` | 표시 라벨 | +| enabled | `bool` | 활성화 여부(기본 `true`) | +| leadingIcon / trailingIcon | `Map?` | 아이콘 | +| style | `StacButtonStyle?` | 개별 스타일 | + +## 예제 + +```json +{ + "type": "dropdownMenu", + "label": { "type": "text", "data": "Select an option" }, + "hintText": "Please select", + "width": 200, + "dropdownMenuEntries": [ + { "value": "option1", "label": "Option 1", "leadingIcon": { "type": "icon", "icon": "home" } }, + { "value": "option2", "label": "Option 2", "leadingIcon": { "type": "icon", "icon": "settings" } }, + { "value": "option3", "label": "Option 3", "leadingIcon": { "type": "icon", "icon": "favorite" } } + ] +} +``` diff --git a/docs/ko/widgets/dynamic_view.mdx b/docs/ko/widgets/dynamic_view.mdx new file mode 100644 index 00000000..855f305d --- /dev/null +++ b/docs/ko/widgets/dynamic_view.mdx @@ -0,0 +1,133 @@ +--- +title: "DynamicView" +description: "API 데이터를 템플릿으로 렌더링" +--- + +`dynamicView`는 REST API 응답을 가져와 템플릿에 바인딩합니다. 별도 코드 없이 데이터 기반 UI를 만들 수 있습니다. + +## 특징 + +- 임의의 REST 엔드포인트 호출 +- `{{placeholder}}` 구문으로 템플릿 바인딩 +- 점 표기/배열 인덱스로 중첩 데이터 접근 +- 단일 객체/리스트 모두 처리 +- `itemTemplate`로 반복 렌더링 +- 로딩/에러 상태 커스터마이징 +- 응답 내 특정 경로(`targetPath`) 지정 가능 + +## 속성 + +| 속성 | 타입 | 필수 | 설명 | +|------|------|------|------| +| request | `StacNetworkRequest` | ✔ | API 요청 설정 | +| template | `Map` | ✔ | 응답 데이터를 적용할 템플릿 | +| targetPath | `String` | ✖ | 응답에서 원하는 경로만 추출 | +| resultTarget | `String` | ✖ | 템플릿에서 사용할 키 이름 | +| loaderWidget / errorWidget | `Map` | ✖ | 로딩/에러 상태 UI | +| itemTemplate | `Map` | ✖ | 리스트 항목 템플릿 | +| emptyTemplate | `Map` | ✖ | 빈 리스트일 때 UI | + +## 데이터 바인딩 + +- `{{user.name}}` 같이 점 표기로 중첩 접근 +- 배열은 `{{users[0].name}}` 또는 `{{users.0.name}}` + +## 예제 + +### 기본 사용 + +```json +{ + "type": "dynamicView", + "request": { "url": "https://api.example.com/user/1", "method": "get" }, + "template": { "type": "text", "data": "Hello, {{name}}!" } +} +``` + +### 로딩/에러 상태 + +```json +{ + "type": "dynamicView", + "request": { "url": "https://dummyjson.com/users/1", "method": "get" }, + "loaderWidget": { + "type": "center", + "child": { + "type": "column", + "children": [ + { "type": "text", "data": "Loading..." }, + { "type": "circularProgressIndicator" } + ] + } + }, + "errorWidget": { + "type": "center", + "child": { "type": "text", "data": "Error fetching user profile" } + }, + "template": { + "type": "column", + "children": [ + { + "type": "container", + "padding": 16, + "child": { + "type": "column", + "crossAxisAlignment": "start", + "children": [ + { "type": "image", "src": "{{image}}", "width": 100, "height": 100 }, + { "type": "text", "style": { "fontSize": 24, "fontWeight": "w700" }, "data": "{{firstName}} {{lastName}}" }, + { "type": "text", "style": { "fontSize": 16, "color": "#666666" }, "data": "Email: {{email}}" } + ] + } + } + ] + } +} +``` + +### 리스트 렌더링 + +```json +{ + "type": "dynamicView", + "request": { "url": "https://dummyjson.com/users", "method": "get" }, + "targetPath": "users", + "template": { + "type": "listView", + "itemTemplate": { + "type": "listTile", + "title": { "type": "text", "data": "{{firstName}} {{lastName}}" }, + "subtitle": { "type": "text", "data": "{{email}}" }, + "leading": { "type": "circleAvatar", "backgroundImage": "{{image}}" } + } + } +} +``` + +### 빈 상태 + +```json +{ + "type": "dynamicView", + "request": { "url": "https://api.example.com/products", "method": "get" }, + "targetPath": "products", + "template": { + "type": "gridView", + "crossAxisCount": 2, + "itemTemplate": { + "type": "card", + "child": { + "type": "column", + "children": [ + { "type": "image", "src": "{{image_url}}" }, + { "type": "text", "data": "{{title}}" } + ] + } + }, + "emptyTemplate": { + "type": "center", + "child": { "type": "text", "data": "No products found" } + } + } +} +``` diff --git a/docs/ko/widgets/elevated_button.mdx b/docs/ko/widgets/elevated_button.mdx new file mode 100644 index 00000000..59228d00 --- /dev/null +++ b/docs/ko/widgets/elevated_button.mdx @@ -0,0 +1,33 @@ +--- +title: "ElevatedButton" +description: "높이/그림자가 있는 기본 버튼" +--- + +`elevatedButton`은 Flutter `ElevatedButton`을 JSON으로 정의합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| onPressed / onLongPress / onHover / onFocusChange | `Map?` | 각 이벤트에 대한 액션 | +| style | `StacButtonStyle?` | 배경/전경 색, 테두리 등 스타일 | +| autofocus | `bool` | 빌드시 자동 포커스 | +| clipBehavior | `Clip` | 콘텐츠 클리핑 | +| child | `Map` | 버튼 내용 | + +## 예제 + +```json +{ + "type": "elevatedButton", + "onPressed": {}, + "style": { + "backgroundColor": "primary", + "foregroundColor": "#FFFFFF" + }, + "child": { + "type": "text", + "data": "Click Me!" + } +} +``` diff --git a/docs/ko/widgets/expanded.mdx b/docs/ko/widgets/expanded.mdx new file mode 100644 index 00000000..8d608bd7 --- /dev/null +++ b/docs/ko/widgets/expanded.mdx @@ -0,0 +1,26 @@ +--- +title: "Expanded" +description: "Row/Column에서 남은 공간 채우기" +--- + +`expanded`는 Flutter `Expanded`와 동일하게 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| flex | `int` | 가중치(기본값 1) | +| child | `Map?` | 공간을 차지할 자식 | + +## 예제 + +```json +{ + "type": "expanded", + "flex": 2, + "child": { + "type": "text", + "data": "Hello, World!" + } +} +``` diff --git a/docs/ko/widgets/filled_button.mdx b/docs/ko/widgets/filled_button.mdx new file mode 100644 index 00000000..1dc73e7f --- /dev/null +++ b/docs/ko/widgets/filled_button.mdx @@ -0,0 +1,30 @@ +--- +title: "FilledButton" +description: "Material 3 스타일의 채워진 버튼" +--- + +`filledButton`은 M3 FilledButton을 JSON으로 구성합니다. 속성은 ElevatedButton과 동일합니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| onPressed / onLongPress / onHover / onFocusChange | `Map?` | 이벤트 콜백 | +| style | `StacButtonStyle?` | 색상/모양/패딩 등 | +| autofocus | `bool` | 자동 포커스 | +| clipBehavior | `Clip` | 클리핑 | +| child | `Map` | 버튼 내부 위젯 | + +## 예제 + +```json +{ + "type": "filledButton", + "style": { + "backgroundColor": "#FFC107", + "foregroundColor": "#000000" + }, + "child": { + "type": "text", + "data": "Click Me!" + } +} +``` diff --git a/docs/ko/widgets/fitted_box.mdx b/docs/ko/widgets/fitted_box.mdx new file mode 100644 index 00000000..f61cd652 --- /dev/null +++ b/docs/ko/widgets/fitted_box.mdx @@ -0,0 +1,30 @@ +--- +title: "FittedBox" +description: "자식을 비율에 맞춰 스케일링" +--- + +`fittedBox`는 `BoxFit` 규칙에 따라 자식을 축소/확대합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| fit | `BoxFit` | 자식을 어떻게 맞출지(`contain` 기본) | +| alignment | `StacAlignmentDirectional` | 자식 정렬 | +| clipBehavior | `Clip` | 자른 방식 | +| child | `Map?` | 스케일링할 자식 | + +## 예제 + +```json +{ + "type": "fittedBox", + "fit": "contain", + "alignment": "center", + "child": { + "type": "text", + "data": "Hello, World!", + "style": { "fontSize": 20, "color": "#000000" } + } +} +``` diff --git a/docs/ko/widgets/flexible.mdx b/docs/ko/widgets/flexible.mdx new file mode 100644 index 00000000..089b5c79 --- /dev/null +++ b/docs/ko/widgets/flexible.mdx @@ -0,0 +1,28 @@ +--- +title: "Flexible" +description: "남은 공간을 유연하게 차지" +--- + +`flexible`은 `Expanded`와 유사하지만 `fit`으로 자식 크기 제어가 가능합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| child | `Map?` | 내부 자식 | +| flex | `int` | 가중치(기본 1) | +| fit | `FlexFit` | 공간 사용 방식(`loose`/`tight`) | + +## 예제 + +```json +{ + "type": "flexible", + "flex": 2, + "fit": "tight", + "child": { + "type": "text", + "data": "Hello, World!" + } +} +``` diff --git a/docs/ko/widgets/floating_action_button.mdx b/docs/ko/widgets/floating_action_button.mdx new file mode 100644 index 00000000..0c67396d --- /dev/null +++ b/docs/ko/widgets/floating_action_button.mdx @@ -0,0 +1,38 @@ +--- +title: "FloatingActionButton" +description: "부동 액션 버튼" +--- + +`floatingActionButton`은 둥근 플로팅 버튼을 정의합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| onPressed | `Map?` | 탭 시 액션 | +| buttonType | `FloatingActionButtonType` | `small`, `regular`, `large`, `extended` | +| icon / child | `Map?` / `Map` | 내부 콘텐츠 | +| textStyle / extendedTextStyle | `StacTextStyle?` | 라벨 스타일 | +| backgroundColor / foregroundColor / focusColor / hoverColor / splashColor | `String?` | 상태별 색상 | +| elevation / focusElevation / hoverElevation / disabledElevation / highlightElevation | `double?` | 다양한 상태별 높이 | +| extendedIconLabelSpacing | `double?` | 확장형일 때 아이콘-라벨 간격 | +| enableFeedback | `bool?` | 피드백 여부 | +| tooltip | `String?` | 롱프레스 텍스트 | +| heroTag | `Object?` | 히어로 애니메이션 태그 | +| autofocus | `bool` | 자동 포커스 | + +## 예제 + +```json +{ + "type": "floatingActionButton", + "onPressed": {}, + "buttonType": "small", + "icon": { "type": "icon", "icon": "add" }, + "backgroundColor": "#FFC107", + "foregroundColor": "#000000", + "tooltip": "Add Item", + "heroTag": "fab1", + "child": { "type": "text", "data": "Add" } +} +``` diff --git a/docs/ko/widgets/form.mdx b/docs/ko/widgets/form.mdx new file mode 100644 index 00000000..c63ef86f --- /dev/null +++ b/docs/ko/widgets/form.mdx @@ -0,0 +1,46 @@ +--- +title: "Form" +description: "검증 가능한 입력 폼 컨테이너" +--- + +`form`은 자식 입력 위젯들을 그룹화하고 검증을 수행할 수 있게 합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| autovalidateMode | `AutovalidateMode?` | `always`, `onUserInteraction`, `disabled` 등 | +| child | `Map` | 폼 내부 위젯(일반적으로 `column`) | + +## 예제 + +```json +{ + "type": "form", + "autovalidateMode": "always", + "child": { + "type": "column", + "children": [ + { "type": "textFormField", "id": "username", "decoration": { "labelText": "Username" } }, + { "type": "textFormField", "id": "password", "decoration": { "labelText": "Password" } }, + { + "type": "filledButton", + "child": { "type": "text", "data": "Submit" }, + "onPressed": { + "actionType": "validateForm", + "isValid": { + "actionType": "networkRequest", + "url": "https://dummyjson.com/auth/login", + "method": "post", + "contentType": "application/json", + "body": { + "username": { "actionType": "getFormValue", "id": "username" }, + "password": { "actionType": "getFormValue", "id": "password" } + } + } + } + } + ] + } +} +``` diff --git a/docs/ko/widgets/fractionally_sized_box.mdx b/docs/ko/widgets/fractionally_sized_box.mdx new file mode 100644 index 00000000..3aa5f5cc --- /dev/null +++ b/docs/ko/widgets/fractionally_sized_box.mdx @@ -0,0 +1,34 @@ +--- +title: "FractionallySizedBox" +description: "부모의 비율만큼 공간 차지" +--- + +`fractionallySizedBox`는 부모 크기의 일부만 사용하도록 설정합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| alignment | `StacAlignment?` | 자식 정렬 | +| widthFactor | `double?` | 부모 너비 대비 비율 | +| heightFactor | `double?` | 부모 높이 대비 비율 | +| child | `Map?` | 내부 자식 | + +## 예제 + +```json +{ + "type": "fractionallySizedBox", + "alignment": "center", + "widthFactor": 0.5, + "heightFactor": 0.5, + "child": { + "type": "container", + "color": "#FF5733", + "child": { + "type": "text", + "data": "Hello, World!" + } + } +} +``` diff --git a/docs/ko/widgets/gesture_detector.mdx b/docs/ko/widgets/gesture_detector.mdx new file mode 100644 index 00000000..71033d1e --- /dev/null +++ b/docs/ko/widgets/gesture_detector.mdx @@ -0,0 +1,87 @@ +--- +title: "GestureDetector" +description: "다양한 제스처 이벤트 감지" +--- + +`gestureDetector`는 자식 위젯을 감싸 다양한 터치/마우스 제스처를 JSON으로 처리합니다. + +## 사용 예 + +```json +{ + "type": "gestureDetector", + "child": { "type": "container", "color": "#2196F3", "width": 200, "height": 200, + "alignment": "center", + "child": { "type": "text", "data": "Tap me!", "style": { "color": "#FFFFFF", "fontSize": 20 } } + }, + "onTap": { + "actionType": "showSnackBar", + "content": { "type": "text", "data": "This is a Snackbar" } + } +} +``` + +## 주요 속성 + +- `child`: 제스처를 감지할 위젯 +- **탭 관련**: `onTapDown`, `onTapUp`, `onTap`, `onTapCancel`, `onDoubleTap*` +- **보조/3차 탭**: `onSecondaryTap*`, `onTertiaryTap*` +- **롱 프레스**: `onLongPress*`, `onSecondaryLongPress*`, `onTertiaryLongPress*` +- **드래그**: `onVerticalDrag*`, `onHorizontalDrag*` +- **Force Press**: `onForcePressStart`, `onForcePressUpdate`, 등 +- 기타: `onHover`, `onHighlightChanged`, `onFocusChange`, `mouseCursor`, `dragStartBehavior`, `excludeFromSemantics` + +각 속성은 `Map` 액션을 받아 특정 제스처 시 실행됩니다. + +## 예제 모음 + +### 탭으로 네비게이션 + +```json +{ + "type": "gestureDetector", + "onTap": { "actionType": "navigate", "routeName": "/details" }, + "child": { + "type": "container", + "padding": 16, + "color": "#E0E0E0", + "child": { "type": "text", "data": "Navigate to Details" } + } +} +``` + +### 드래그 제스처 + +```json +{ + "type": "gestureDetector", + "onHorizontalDragEnd": { "actionType": "handleSwipe" }, + "child": { + "type": "container", + "width": 200, + "height": 200, + "color": "#F5F5F5", + "alignment": "center", + "child": { "type": "text", "data": "Swipe horizontally" } + } +} +``` + +### 롱프레스 + +```json +{ + "type": "gestureDetector", + "onLongPress": { + "actionType": "showDialog", + "title": "Long Press Detected", + "content": "You performed a long press on the widget." + }, + "child": { + "type": "container", + "padding": 16, + "decoration": { "color": "#4CAF50", "borderRadius": 8 }, + "child": { "type": "text", "data": "Long press me", "style": { "color": "#FFFFFF" } } + } +} +``` diff --git a/docs/ko/widgets/grid_view.mdx b/docs/ko/widgets/grid_view.mdx new file mode 100644 index 00000000..12e675c3 --- /dev/null +++ b/docs/ko/widgets/grid_view.mdx @@ -0,0 +1,44 @@ +--- +title: "GridView" +description: "격자형 리스트" +--- + +`gridView`는 행과 열로 항목을 배치합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| scrollDirection / reverse | `Axis` / `bool` | 스크롤 방향과 역스크롤 여부 | +| primary | `bool` | 부모의 기본 스크롤 뷰인지 | +| physics | `StacScrollPhysics?` | 스크롤 물리 | +| shrinkWrap | `bool` | 콘텐츠 크기에 맞춰 축소할지 | +| padding | `StacEdgeInsets?` | 전체 패딩 | +| crossAxisCount | `int?` | 교차축 항목 수 | +| mainAxisSpacing / crossAxisSpacing | `double` | 항목 간 간격 | +| childAspectRatio | `double` | 가로/세로 비율 | +| mainAxisExtent | `double?` | 주축 길이 고정 | +| addAutomaticKeepAlives / addRepaintBoundaries / addSemanticIndexes | `bool` | 성능/접근성 옵션 | +| cacheExtent | `double?` | 캐시 범위 | +| children | `List>` | 항목들 | +| semanticChildCount | `int?` | 접근성용 항목 수 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 방식 | +| keyboardDismissBehavior | `ScrollViewKeyboardDismissBehavior` | 키보드 닫기 방식 | +| restorationId | `String?` | 스크롤 위치 복원 ID | +| clipBehavior | `Clip` | 클리핑 방식 | + +## 예제 + +```json +{ + "type": "gridView", + "padding": { "left": 10, "top": 10, "right": 10, "bottom": 10 }, + "crossAxisCount": 2, + "mainAxisSpacing": 10.0, + "crossAxisSpacing": 10.0, + "children": [ + { "type": "text", "data": "Item 1" }, + { "type": "text", "data": "Item 2" } + ] +} +``` diff --git a/docs/ko/widgets/icon.mdx b/docs/ko/widgets/icon.mdx new file mode 100644 index 00000000..00a2a2bf --- /dev/null +++ b/docs/ko/widgets/icon.mdx @@ -0,0 +1,32 @@ +--- +title: "Icon" +description: "아이콘 위젯" +--- + +`icon`은 Material/Cupertino 아이콘을 렌더링합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| icon | `String` | 아이콘 이름 | +| iconType | `IconType` | `material` 또는 `cupertino`(기본 `material`) | +| size | `double?` | 아이콘 크기 | +| color | `String?` | 색상 | +| semanticLabel | `String?` | 접근성 라벨 | +| textDirection | `TextDirection?` | 텍스트 방향(일부 아이콘에 영향) | + +아이콘 목록은 [icon_utils.dart](https://github.com/StacDev/stac/blob/dev/packages/stac/lib/src/utils/icon_utils.dart)를 참고하세요. + +## 예제 + +```json +{ + "type": "icon", + "icon": "home", + "size": 24.0, + "color": "#000000", + "semanticLabel": "Home Icon", + "textDirection": "ltr" +} +``` diff --git a/docs/ko/widgets/icon_button.mdx b/docs/ko/widgets/icon_button.mdx new file mode 100644 index 00000000..9e423460 --- /dev/null +++ b/docs/ko/widgets/icon_button.mdx @@ -0,0 +1,49 @@ +--- +title: "IconButton" +description: "아이콘을 눌러 동작시키기" +--- + +`iconButton`은 아이콘을 포함한 버튼입니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| icon / selectedIcon | `Map?` | 기본/선택 상태 아이콘 | +| iconSize | `double?` | 아이콘 크기 | +| padding | `StacEdgeInsets?` | 내부 여백 | +| alignment | `StacAlignment?` | 아이콘 정렬 | +| splashRadius | `double?` | 스플래시 반경 | +| color / focusColor / hoverColor / highlightColor / splashColor / disabledColor | `String?` | 다양한 상태별 색상 | +| onPressed | `Map?` | 탭 시 액션 | +| autofocus | `bool` | 자동 포커스 여부 | +| tooltip | `String?` | 롱프레스 툴팁 | +| enableFeedback | `bool?` | 음향/진동 피드백 | +| constraints | `StacBoxConstraints?` | 버튼 최소 크기 등 | +| style | `StacButtonStyle?` | 배경/전경 스타일 | +| isSelected | `bool?` | 선택 상태 | + +## 예제 + +```json +{ + "type": "iconButton", + "iconSize": 24.0, + "padding": { "left": 8.0, "top": 8.0, "right": 8.0, "bottom": 8.0 }, + "alignment": "center", + "splashRadius": 20.0, + "color": "#000000", + "focusColor": "#FFC107", + "hoverColor": "#FF9800", + "highlightColor": "#FF5722", + "splashColor": "#FFEB3B", + "disabledColor": "#BDBDBD", + "onPressed": {}, + "tooltip": "Add Item", + "enableFeedback": true, + "constraints": { "minWidth": 48.0, "minHeight": 48.0 }, + "style": { "backgroundColor": "#FFC107", "foregroundColor": "#000000" }, + "selectedIcon": { "type": "icon", "icon": "check" }, + "icon": { "type": "icon", "icon": "add" } +} +``` diff --git a/docs/ko/widgets/image.mdx b/docs/ko/widgets/image.mdx new file mode 100644 index 00000000..c29f8aa9 --- /dev/null +++ b/docs/ko/widgets/image.mdx @@ -0,0 +1,32 @@ +--- +title: "Image" +description: "다양한 소스의 이미지를 표시" +--- + +`image` 위젯은 네트워크/파일/에셋 이미지를 보여 줍니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| src | `String` | 이미지 소스(URL, 파일 경로, 에셋 경로 등) | +| alignment | `StacAlignment` | 컨테이너에서 정렬(기본 `center`) | +| imageType | `StacImageType` | `network`, `file`, `asset` 중 선택(기본 `network`) | +| color | `String?` | 이미지에 블렌딩할 색상 | +| width / height | `double?` | 논리 픽셀 단위 크기 | +| fit | `BoxFit?` | 이미지를 공간에 맞추는 방식 | + +## 예제 + +```json +{ + "type": "image", + "src": "https://example.com/image.png", + "alignment": "center", + "imageType": "network", + "color": "#FFFFFF", + "width": 200.0, + "height": 100.0, + "fit": "contain" +} +``` diff --git a/docs/ko/widgets/ink_well.mdx b/docs/ko/widgets/ink_well.mdx new file mode 100644 index 00000000..e1f02ba5 --- /dev/null +++ b/docs/ko/widgets/ink_well.mdx @@ -0,0 +1,47 @@ +--- +title: "InkWell" +description: "리플 효과가 있는 터치 영역" +--- + +`inkWell`은 터치/호버 이벤트에 반응하며 리플 애니메이션을 제공합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| child | `Map?` | 내부 위젯 | +| onTap / onDoubleTap / onLongPress / onTapDown / onTapUp / onTapCancel | `Map?` | 다양한 탭 제스처 콜백 | +| onSecondaryTap* | `Map?` | 데스크톱 우클릭 등 보조 탭 콜백 | +| onHighlightChanged / onHover / onFocusChange | `Map?` | 상태 변화 감지 | +| mouseCursor | `StacMouseCursor?` | 호버 시 커서 | +| focusColor / hoverColor / highlightColor / overlayColor / splashColor | `String?` | 시각 효과 색상 | +| radius | `double?` | 리플 반경 | +| borderRadius | `StacBorderRadius?` | 둥근 모서리 | +| customBorder | `StacBorder?` | 커스텀 모양 | +| enableFeedback | `bool` | 소리/진동 피드백(기본 `true`) | +| excludeFromSemantics | `bool` | 접근성 제외 여부 | +| canRequestFocus | `bool` | 포커스 요청 가능 여부 | +| autofocus | `bool` | 자동 포커스 여부 | +| hoverDuration | `StacDuration?` | 호버 애니메이션 시간 | + +## 예제 + +```json +{ + "type": "inkWell", + "child": { + "type": "padding", + "padding": { "top": 20, "bottom": 20, "right": 20, "left": 20 }, + "child": { + "type": "text", + "data": "Hello, World! from Inkwell", + "textAlign": "center" + } + }, + "splashColor": "#E1BEE7", + "borderRadius": { "topLeft": 20, "topRight": 20, "bottomLeft": 20, "bottomRight": 20 }, + "radius": 20, + "hoverDuration": { "seconds": 10 }, + "onTap": {} +} +``` diff --git a/docs/ko/widgets/limited_box.mdx b/docs/ko/widgets/limited_box.mdx new file mode 100644 index 00000000..a23b01b3 --- /dev/null +++ b/docs/ko/widgets/limited_box.mdx @@ -0,0 +1,29 @@ +--- +title: "LimitedBox" +description: "상위 제약이 없을 때 최대 크기 제한" +--- + +`limitedBox`는 부모가 무한대 크기를 제공할 때 자식의 최대 너비/높이를 제한하는 위젯입니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| maxHeight | `double` | 부모 높이가 무한대일 때 적용할 최대 높이 | +| maxWidth | `double` | 부모 너비가 무한대일 때 적용할 최대 너비 | +| child | `Map?` | 제한을 받을 자식 | + +## 예제 + +```json +{ + "type": "limitedBox", + "maxHeight": 200.0, + "maxWidth": 300.0, + "child": { + "type": "text", + "data": "Hello, World! from Limited Box", + "style": { "fontSize": 16, "color": "#000000" } + } +} +``` diff --git a/docs/ko/widgets/linear_progress_indicator.mdx b/docs/ko/widgets/linear_progress_indicator.mdx new file mode 100644 index 00000000..045f0c72 --- /dev/null +++ b/docs/ko/widgets/linear_progress_indicator.mdx @@ -0,0 +1,32 @@ +--- +title: "LinearProgressIndicator" +description: "수평 진행 표시기" +--- + +`linearProgressIndicator`는 가로 막대 형태로 진행 상황을 표시합니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| value | `double` | 진행 값(0~1, null이면 무한 애니메이션) | +| backgroundColor | `String` | 트랙 색상 | +| color | `String` | 진행선 색상 | +| minHeight | `double` | 막대 높이 | +| borderRadius | `StacBorderRadius` | 모서리 둥글기 | +| semanticsLabel / semanticsValue | `String` | 접근성 설명 | + +## 예제 + +```json +{ + "type": "linearProgressIndicator", + "color": "#541204", + "minHeight": 6, + "backgroundColor": "#FFD700", + "borderRadius": { + "topLeft": 10, + "topRight": 10, + "bottomLeft": 10, + "bottomRight": 10 + } +} +``` diff --git a/docs/ko/widgets/list_tile.mdx b/docs/ko/widgets/list_tile.mdx new file mode 100644 index 00000000..2d613924 --- /dev/null +++ b/docs/ko/widgets/list_tile.mdx @@ -0,0 +1,42 @@ +--- +title: "ListTile" +description: "Leading/Title/Subtitle/Trailing 구조의 행" +--- + +`listTile`은 아이콘, 제목, 부제, 트레일링 위젯을 조합해 하나의 행을 구성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| onTap / onLongPress | `Map` | 탭/롱탭 시 액션 | +| leading / title / subtitle / trailing | `Map` | 각 영역에 표시할 위젯 | +| isThreeLine / dense | `bool` | 3줄 표시 여부, 촘촘한 레이아웃 | +| style | `ListTileStyle` | 제목 텍스트 스타일 | +| selectedColor / iconColor / textColor | `String` | 선택/아이콘/텍스트 색상 | +| contentPadding | `StacEdgeInsets` | 내부 패딩 | +| enabled / selected / enableFeedback | `bool` | 상호작용 여부, 선택 상태, 피드백 | +| focusColor / hoverColor | `String` | 포커스/호버 색상 | +| autoFocus | `bool` | 초기 포커스 여부 | +| tileColor / selectedTileColor | `String` | 배경 색상 | +| horizontalTitleGap / minVerticalPadding / minLeadingWidth | `double` | 레이아웃 간격 설정 | + +## 예제 + +```json +{ + "type": "listTile", + "leading": { "type": "image", "src": "https://cdn-icons-png.flaticon.com/512/3135/3135715.png" }, + "title": { + "type": "text", + "data": "Andrew Symonds", + "style": { "fontSize": 18 } + }, + "subtitle": { + "type": "text", + "data": "Australian international cricketer...", + "style": { "fontSize": 14 } + }, + "trailing": { "type": "icon", "icon": "more_vert", "size": 24 } +} +``` diff --git a/docs/ko/widgets/listview.mdx b/docs/ko/widgets/listview.mdx new file mode 100644 index 00000000..865ace1a --- /dev/null +++ b/docs/ko/widgets/listview.mdx @@ -0,0 +1,54 @@ +--- +title: "ListView" +description: "스크롤 가능한 리스트" +--- + +`listView`는 단일 열/행으로 항목을 스크롤합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| scrollDirection / reverse | `Axis` / `bool` | 스크롤 방향과 역스크롤 | +| primary | `bool` | 부모의 기본 스크롤 뷰인지 | +| physics | `StacScrollPhysics` | 스크롤 물리 | +| shrinkWrap | `bool` | 콘텐츠 크기에 맞춰 축소 | +| padding | `StacEdgeInsets` | 전체 패딩 | +| addAutomaticKeepAlives / addRepaintBoundaries / addSemanticIndexes | `bool` | 성능/접근성 설정 | +| cacheExtent | `double` | 사전 캐시 범위 | +| children | `List>` | 리스트 항목 | +| separator | `Map` | 각 항목 사이에 표시할 위젯 | +| semanticChildCount | `int` | 접근성용 항목 수 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 방식 | +| keyboardDismissBehavior | `ScrollViewKeyboardDismissBehavior` | 키보드 닫기 방식 | +| restorationId | `String` | 스크롤 위치 복원 ID | +| clipBehavior | `Clip` | 클리핑 방식 | + +## 예제 + +```json +{ + "type": "listView", + "shrinkWrap": true, + "separator": { "type": "container", "height": 10 }, + "children": [ + { + "type": "listTile", + "leading": { + "type": "container", + "height": 50, + "width": 50, + "color": "#165FC7", + "child": { + "type": "text", + "data": "1", + "style": { "fontSize": 21 } + } + }, + "title": { "type": "text", "data": "Item 1", "style": { "fontSize": 18 } }, + "subtitle": { "type": "text", "data": "Item description", "style": { "fontSize": 14 } }, + "trailing": { "type": "icon", "icon": "more_vert", "size": 24 } + } + ] +} +``` diff --git a/docs/ko/widgets/network_widget.mdx b/docs/ko/widgets/network_widget.mdx new file mode 100644 index 00000000..5dfa8d36 --- /dev/null +++ b/docs/ko/widgets/network_widget.mdx @@ -0,0 +1,27 @@ +--- +title: "NetworkWidget" +description: "네트워크 요청 결과를 렌더링" +--- + +`networkWidget`은 `StacNetworkRequest`를 실행해 응답에 따라 내부 위젯을 렌더링합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| request | `StacNetworkRequest` | 요청 설정(URL, method, headers 등) | + +## 예제 + +```json +{ + "type": "networkWidget", + "request": { + "url": "https://api.example.com/data", + "method": "get", + "headers": { + "Authorization": "Bearer token" + } + } +} +``` diff --git a/docs/ko/widgets/opacity.mdx b/docs/ko/widgets/opacity.mdx new file mode 100644 index 00000000..c615f0b1 --- /dev/null +++ b/docs/ko/widgets/opacity.mdx @@ -0,0 +1,25 @@ +--- +title: "Opacity" +description: "자식의 투명도 조절" +--- + +`opacity`는 자식 위젯의 투명도를 0~1 사이 값으로 제어합니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| opacity | `double` | 0.0은 완전 투명, 1.0은 불투명 | +| child | `Map` | 투명도를 적용할 자식 | + +## 예제 + +```json +{ + "type": "opacity", + "opacity": 0.5, + "child": { + "type": "text", + "data": "Opacity Widget", + "style": { "fontSize": 23, "fontWeight": "w600" } + } +} +``` diff --git a/docs/ko/widgets/outlined_button.mdx b/docs/ko/widgets/outlined_button.mdx new file mode 100644 index 00000000..2f96c7e0 --- /dev/null +++ b/docs/ko/widgets/outlined_button.mdx @@ -0,0 +1,25 @@ +--- +title: "OutlinedButton" +description: "외곽선 버튼" +--- + +`outlinedButton`은 테두리만 있는 버튼입니다. + +속성과 사용법은 `elevatedButton`과 동일하며, `style`에서 `side`/`foregroundColor` 등을 설정해 외곽선을 제어합니다. + +## 예제 + +```json +{ + "type": "outlinedButton", + "onPressed": {}, + "style": { + "backgroundColor": "#FFFFFF", + "foregroundColor": "#000000" + }, + "child": { + "type": "text", + "data": "Click Me!" + } +} +``` diff --git a/docs/ko/widgets/padding.mdx b/docs/ko/widgets/padding.mdx new file mode 100644 index 00000000..56e61e65 --- /dev/null +++ b/docs/ko/widgets/padding.mdx @@ -0,0 +1,32 @@ +--- +title: "Padding" +description: "자식 주변 여백 추가" +--- + +`padding`은 Flutter `Padding`과 동일하게 작동합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| padding | `StacEdgeInsets` | 자식 주변 여백. `12`처럼 숫자 하나로 전체를 지정하거나 `{ "left": 0, "right": 0 }`, `[8,12,8,12]`처럼 세부 지정 가능 | +| child | `Map` | 여백 안에 들어갈 자식 | + +## 예제 + +```json +{ + "type": "padding", + "padding": { + "left": 0, + "right": 0 + }, + "child": { + "type": "container", + "color": "#672BFF", + "clipBehavior": "hardEdge", + "height": 75, + "width": 700 + } +} +``` diff --git a/docs/ko/widgets/page_view.mdx b/docs/ko/widgets/page_view.mdx new file mode 100644 index 00000000..cf1662e3 --- /dev/null +++ b/docs/ko/widgets/page_view.mdx @@ -0,0 +1,35 @@ +--- +title: "PageView" +description: "페이지 단위로 스크롤" +--- + +`pageView`는 각 페이지를 좌우/위아래로 넘기며 보여줍니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| scrollDirection / reverse | `Axis` / `bool` | 스크롤 방향과 역스크롤 | +| physics | `StacScrollPhysics?` | 스크롤 물리 | +| pageSnapping | `bool` | 페이지 경계에 스냅(기본 `true`) | +| onPageChanged | `Map?` | 페이지 전환 시 콜백 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 방식 | +| allowImplicitScrolling | `bool` | 암시적 스크롤 허용 | +| restorationId | `String?` | 스크롤 위치 복원 ID | +| clipBehavior | `Clip` | 클리핑 방식 | +| padEnds | `bool` | 양 끝 여백 추가 | +| initialPage | `int` | 초기 페이지 | +| keepPage | `bool` | 현재 페이지 저장 | +| viewportFraction | `double` | 뷰포트 대비 페이지 비율 | +| children | `List>` | 페이지 목록 | + +## 예제 + +```json +{ + "type": "pageView", + "children": [ + { "type": "container", "color": "#D9D9D9", "child": { "type": "center", "child": { "type": "text", "data": "Page 1" } } }, + { "type": "container", "color": "#FC3F1B", "child": { "type": "center", "child": { "type": "text", "data": "Page 2" } } }, + { "type": "container", "color": "#D9D9D9", "child": { "type": "center", "child": { "type": "text", "data": "Page 3" } } } + ] +} +``` diff --git a/docs/ko/widgets/placeholder.mdx b/docs/ko/widgets/placeholder.mdx new file mode 100644 index 00000000..2d93dec3 --- /dev/null +++ b/docs/ko/widgets/placeholder.mdx @@ -0,0 +1,25 @@ +--- +title: "Placeholder" +description: "임시 자리 표시자" +--- + +`placeholder`는 아직 위젯이 준비되지 않은 영역을 표시하거나 레이아웃 디버깅에 사용됩니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| color | `String?` | 배경/선 색상(기본 `#455A64`) | +| strokeWidth | `double?` | 선 두께(기본 2) | +| fallbackWidth / fallbackHeight | `double?` | 부모가 무한 크기를 줄 때 사용할 기본 폭/높이(기본 400) | +| child | `Map?` | 내부에 표시할 자식 | + +## 예제 + +```json +{ + "type": "placeholder", + "color": "#455A64", + "strokeWidth": 2.0, + "fallbackWidth": 400.0, + "fallbackHeight": 400.0 +} +``` diff --git a/docs/ko/widgets/positioned.mdx b/docs/ko/widgets/positioned.mdx new file mode 100644 index 00000000..5f47d367 --- /dev/null +++ b/docs/ko/widgets/positioned.mdx @@ -0,0 +1,70 @@ +--- +title: "Positioned" +description: "Stack 안에서 절대 위치 지정" +--- + +`positioned`는 `Stack` 자식의 좌표를 직접 지정합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| positionedType | `StacPositionedType?` | `directional`, `fill`, `fromRect` 지원 | +| left/top/right/bottom | `double?` | 부모 기준 거리 | +| width/height | `double?` | 자식 크기 | +| start/end | `double?` | `directional`일 때 사용 | +| textDirection | `TextDirection` | `start/end` 계산에 필요한 방향 | +| rect | `StacRect?` | `fromRect`에서 사용할 사각형 | +| child | `Map?` | 배치할 자식 | + +## 예제 + +```json +{ + "type": "positioned", + "left": 10, + "top": 20, + "right": 30, + "bottom": 40, + "child": { "type": "text", "data": "Hello, World!" } +} +``` + +```json +{ + "type": "positioned", + "positionedType": "directional", + "start": 10, + "top": 20, + "width": 100, + "height": 50, + "textDirection": "ltr", + "child": { "type": "text", "data": "Hello, World!" } +} +``` + +```json +{ + "type": "positioned", + "positionedType": "fill", + "left": 10, + "top": 20, + "right": 30, + "bottom": 40, + "child": { "type": "text", "data": "Hello, World!" } +} +``` + +```json +{ + "type": "positioned", + "positionedType": "fromRect", + "rect": { + "left": 10, + "top": 20, + "right": 110, + "bottom": 70 + }, + "child": { "type": "text", "data": "Hello, World!" } +} +``` diff --git a/docs/ko/widgets/radio_group.mdx b/docs/ko/widgets/radio_group.mdx new file mode 100644 index 00000000..7d9c77e9 --- /dev/null +++ b/docs/ko/widgets/radio_group.mdx @@ -0,0 +1,60 @@ +--- +title: "RadioGroup" +description: "라디오 버튼 그룹" +--- + +`radioGroup`은 여러 Radio 중 하나만 선택되도록 관리합니다. + +## RadioGroup 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| id | `String` | 선택된 값을 저장할 식별자 | +| groupValue | `dynamic` | 현재 선택 값 | +| child | `Map` | 라디오 항목들을 포함한 위젯 | + +## Radio 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| radioType | `StacRadioType` | `adaptive`, `material`, `cupertino` 등 | +| value | `dynamic` | 이 라디오가 대표하는 값 | +| mouseCursor | `StacMouseCursor` | 마우스 커서 | +| toggleable | `bool` | 선택된 상태에서 탭하면 해제 가능 | +| activeColor / inactiveColor / fillColor / focusColor / hoverColor / overlayColor / backgroundColor | `String` | 다양한 상태 색상 | +| splashRadius | `double` | 리플 반경 | +| materialTapTargetSize | `MaterialTapTargetSize` | 탭 영역 크기 | +| visualDensity | `StacVisualDensity` | 레이아웃 밀도 | +| autofocus | `bool` | 자동 포커스 | +| useCheckmarkStyle / useCupertinoCheckmarkStyle | `bool` | 체크박스 스타일 표시 여부 | +| enabled | `bool` | 활성화 여부 | +| side | `StacBorderSide` | 테두리 | +| innerRadius | `double` | 내부 반지름 | + +## 예제 + +```json +{ + "type": "radioGroup", + "child": { + "type": "column", + "children": [ + { + "type": "listTile", + "leading": { "type": "radio", "radioType": "adaptive", "value": "1", "groupValue": "1" }, + "title": { "type": "text", "data": "Male", "style": { "fontSize": 21 } } + }, + { + "type": "listTile", + "leading": { "type": "radio", "radioType": "adaptive", "value": "2", "groupValue": "1" }, + "title": { "type": "text", "data": "Female", "style": { "fontSize": 21 } } + }, + { + "type": "listTile", + "leading": { "type": "radio", "radioType": "adaptive", "value": "3", "groupValue": "1" }, + "title": { "type": "text", "data": "Other", "style": { "fontSize": 21 } } + } + ] + } +} +``` diff --git a/docs/ko/widgets/refresh_indicator.mdx b/docs/ko/widgets/refresh_indicator.mdx new file mode 100644 index 00000000..c47b9a0c --- /dev/null +++ b/docs/ko/widgets/refresh_indicator.mdx @@ -0,0 +1,31 @@ +--- +title: "RefreshIndicator" +description: "당겨서 새로고침" +--- + +`refreshIndicator`는 리스트를 아래로 당겨 데이터 새로고침을 트리거합니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| child | `Map?` | 감싸는 스크롤 위젯 | +| displacement | `double` | 인디케이터 시작 위치(기본 40) | +| edgeOffset | `double` | 시작 오프셋(기본 0) | +| onRefresh | `Map?` | 충분히 끌어내렸을 때 호출할 액션 | +| color / backgroundColor | `String?` | 인디케이터 전경/배경 색 | +| semanticsLabel / semanticsValue | `String?` | 접근성 텍스트 | +| strokeWidth | `double` | 원형 스트로크 두께 | +| triggerMode | `RefreshIndicatorTriggerMode` | 트리거 조건(`onEdge` 등) | + +## 예제 + +```json +{ + "type": "refreshIndicator", + "onRefresh": { + "actionType": "request", + "url": "https://raw.githubusercontent.com/StacDev/stac/main/examples/stac_gallery/assets/json/list_view_example.json", + "method": "get", + "contentType": "application/json" + } +} +``` diff --git a/docs/ko/widgets/row.mdx b/docs/ko/widgets/row.mdx new file mode 100644 index 00000000..5be291f6 --- /dev/null +++ b/docs/ko/widgets/row.mdx @@ -0,0 +1,33 @@ +--- +title: "Row" +description: "가로로 자식을 배치" +--- + +Stac `row`는 Flutter `Row` 위젯과 동일합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| mainAxisAlignment | `MainAxisAlignment` | 주축(가로) 정렬 | +| crossAxisAlignment | `CrossAxisAlignment` | 교차축(세로) 정렬 | +| mainAxisSize | `MainAxisSize` | 얼마나 공간을 차지할지 | +| textDirection | `TextDirection` | 자식 배치 순서 | +| spacing | `double` | 자식 간 간격 | +| children | `List>` | 자식 목록 | + +## 예제 + +```json +{ + "type": "row", + "mainAxisAlignment": "center", + "crossAxisAlignment": "center", + "spacing": 12, + "children": [ + { "type": "image", "src": "https://...", "width": 100 }, + { "type": "image", "src": "https://...", "width": 100 }, + { "type": "image", "src": "https://...", "width": 100 } + ] +} +``` diff --git a/docs/ko/widgets/safe_area.mdx b/docs/ko/widgets/safe_area.mdx new file mode 100644 index 00000000..ed9d1f78 --- /dev/null +++ b/docs/ko/widgets/safe_area.mdx @@ -0,0 +1,28 @@ +--- +title: "SafeArea" +description: "시스템 영역을 피하도록 패딩 추가" +--- + +`safeArea`는 노치·상태바 등을 피하기 위해 자동으로 패딩을 추가합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| child | `Map` | SafeArea 안에 들어갈 자식 | +| left/top/right/bottom | `bool` | 해당 방향에서 시스템 침범을 피할지 | +| minimum | `StacEdgeInsets` | 최소 패딩 | +| maintainBottomViewPadding | `bool` | 키보드 등으로 인한 `viewPadding.bottom`을 유지할지 | + +## 예제 + +```json +{ + "type": "safeArea", + "child": { + "type": "text", + "data": "Hello, World!", + "style": { "color": "#FFFFFF", "fontSize": 24.0 } + } +} +``` diff --git a/docs/ko/widgets/scaffold.mdx b/docs/ko/widgets/scaffold.mdx new file mode 100644 index 00000000..5bfe3ff7 --- /dev/null +++ b/docs/ko/widgets/scaffold.mdx @@ -0,0 +1,51 @@ +--- +title: "Scaffold" +description: "기본 Material 페이지 구조" +--- + +`scaffold`는 AppBar, Body, Drawer, FAB 등을 포함하는 페이지 골격입니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| appBar / body / floatingActionButton / drawer / endDrawer / bottomNavigationBar / bottomSheet | `Map?` | 각 영역 위젯 | +| floatingActionButtonLocation | `StacFloatingActionButtonLocation?` | FAB 위치 | +| persistentFooterButtons | `List>?` | 고정 푸터 버튼 | +| backgroundColor | `String?` | 배경색 | +| resizeToAvoidBottomInset | `bool?` | 키보드 등 하단 인셋 회피 | +| primary | `bool` | 기본 Scaffold 여부 | +| drawerDragStartBehavior | `DragStartBehavior` | 드로어 드래그 시작 방식 | +| extendBody / extendBodyBehindAppBar | `bool` | Body를 아래/위로 확장 | +| drawerScrimColor | `String?` | Drawer 위 오버레이 색 | +| drawerEdgeDragWidth | `double?` | 드래그 인식 영역 너비 | +| drawerEnableOpenDragGesture / endDrawerEnableOpenDragGesture | `bool` | 제스처 허용 | +| restorationId | `String?` | 상태 복원 ID | + +## 예제 + +```json +{ + "type": "scaffold", + "appBar": { "type": "appBar", "title": { "type": "text", "data": "App Bar Title" } }, + "body": { + "type": "center", + "child": { "type": "text", "data": "Hello, World!" } + }, + "floatingActionButton": { + "type": "floatingActionButton", + "child": { "type": "icon", "icon": "add" }, + "onPressed": { "type": "function", "name": "onFabPressed" } + }, + "drawer": { + "type": "drawer", + "child": { + "type": "column", + "children": [ + { "type": "text", "data": "Drawer Item 1" }, + { "type": "text", "data": "Drawer Item 2" } + ] + } + } +} +``` diff --git a/docs/ko/widgets/single_child_scroll_view.mdx b/docs/ko/widgets/single_child_scroll_view.mdx new file mode 100644 index 00000000..c89dd36a --- /dev/null +++ b/docs/ko/widgets/single_child_scroll_view.mdx @@ -0,0 +1,33 @@ +--- +title: "SingleChildScrollView" +description: "단일 자식을 스크롤" +--- + +`singleChildScrollView`는 하나의 자식을 감싸 스크롤 가능하게 만듭니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| scrollDirection / reverse | `Axis` / `bool` | 스크롤 방향과 역방향 여부 | +| padding | `StacEdgeInsets?` | 주변 여백 | +| primary | `bool?` | 기본 스크롤 뷰인지 | +| physics | `StacScrollPhysics?` | 스크롤 물리 | +| child | `Map?` | 스크롤할 자식 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 방식 | +| clipBehavior | `Clip` | 클리핑 방식 | +| restorationId | `String?` | 상태 복원 ID | +| keyboardDismissBehavior | `ScrollViewKeyboardDismissBehavior` | 키보드 닫기 방식 | + +## 예제 + +```json +{ + "type": "singleChildScrollView", + "child": { + "type": "column", + "children": [ + { "type": "text", "data": "Hello World!" }, + { "type": "text", "data": "This is a SingleChildScrollView widget." } + ] + } +} +``` diff --git a/docs/ko/widgets/sized_box.mdx b/docs/ko/widgets/sized_box.mdx new file mode 100644 index 00000000..f3552c07 --- /dev/null +++ b/docs/ko/widgets/sized_box.mdx @@ -0,0 +1,23 @@ +--- +title: "SizedBox" +description: "고정 크기 또는 빈 공간 생성" +--- + +`sizedBox`는 고정된 너비/높이를 갖는 상자를 만들거나 단순히 공간을 채우는 용도로 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| width | `double?` | 상자 너비 | +| height | `double?` | 상자 높이 | +| child | `Map?` | 들어갈 자식(없으면 빈 공간) | + +## 예제 + +```json +{ + "type": "sizedBox", + "height": 25 +} +``` diff --git a/docs/ko/widgets/slider.mdx b/docs/ko/widgets/slider.mdx new file mode 100644 index 00000000..99f9faf4 --- /dev/null +++ b/docs/ko/widgets/slider.mdx @@ -0,0 +1,35 @@ +--- +title: "Slider" +description: "값을 드래그해 선택하는 슬라이더" +--- + +`slider`는 연속/분할 값을 선택할 수 있는 막대입니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| id | `String` | 선택 값을 저장할 식별자 | +| sliderType | `StacSliderType` | `material`, `adaptive` 등 | +| value | `double` | 현재 값 | +| secondaryTrackValue | `double` | 보조 트랙 값 | +| onChanged / onChangeStart / onChangeEnd | `Map` | 드래그 중/시작/종료 콜백 | +| min / max | `double` | 선택 가능한 최소/최대 | +| divisions | `int` | 분할 수 | +| label | `String` | 활성 상태에서 표시할 라벨 | +| activeColor / inactiveColor / secondaryActiveColor | `String` | 트랙 색상 | +| thumbColor / overlayColor | `String` | 썸/오버레이 색 | +| mouseCursor | `StacMouseCursor` | 커서 스타일 | +| autofocus | `bool` | 자동 포커스 | +| allowedInteraction | `SliderInteraction` | 허용되는 상호작용(탭, 드래그 등) | + +## 예제 + +```json +{ + "type": "slider", + "id": "example_slider", + "sliderType": "material", + "value": 20, + "max": 100, + "divisions": 5 +} +``` diff --git a/docs/ko/widgets/sliver_app_bar.mdx b/docs/ko/widgets/sliver_app_bar.mdx new file mode 100644 index 00000000..59c3f331 --- /dev/null +++ b/docs/ko/widgets/sliver_app_bar.mdx @@ -0,0 +1,48 @@ +--- +title: "SliverAppBar" +description: "스크롤 위치에 따라 동작하는 AppBar" +--- + +`sliverAppBar`는 `CustomScrollView` 내에서 확장/축소/스냅되는 앱바입니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| leading / title / titleTextStyle / toolbarTextStyle | `Map` / `StacTextStyle?` | 기본 AppBar 속성과 동일 | +| actions / bottom / actionsPadding | `List` / `Map?` | 액션 영역 | +| backgroundColor / foregroundColor / surfaceTintColor / shadowColor | `String?` | 색상 | +| toolbarHeight / leadingWidth / titleSpacing | `double?` | 레이아웃 크기 | +| toolbarOpacity / bottomOpacity | `double` | 투명도 | +| primary | `bool` | 기본 AppBar인지 | +| centerTitle | `bool?` | 제목 중앙 정렬 | +| elevation / scrolledUnderElevation | `double?` | 그림자 높이 | +| flexibleSpace | `Map?` | Toolbar 뒤에 겹쳐질 위젯 | +| expandedHeight / collapsedHeight | `double?` | 확장/축소 시 높이 | +| floating / pinned / snap / stretch | `bool` | 스크롤 동작 설정 | +| stretchTriggerOffset | `double?` | 스트레치 트리거 오프셋 | +| shape | `Map?` | 모양 | +| iconTheme / actionsIconTheme | `Map?` | 아이콘 테마 | +| actionsPadding | `Map?` | 액션 패딩 | + +## 예제 + +```json +{ + "type": "sliverAppBar", + "title": { "type": "text", "data": "SliverAppBar" }, + "leading": { + "type": "iconButton", + "icon": { "type": "icon", "icon": "menu" }, + "onPressed": {} + }, + "backgroundColor": "primary", + "actions": [ + { + "type": "iconButton", + "icon": { "type": "icon", "iconType": "cupertino", "icon": "heart_solid" }, + "onPressed": {} + } + ] +} +``` diff --git a/docs/ko/widgets/spacer.mdx b/docs/ko/widgets/spacer.mdx new file mode 100644 index 00000000..912ee599 --- /dev/null +++ b/docs/ko/widgets/spacer.mdx @@ -0,0 +1,21 @@ +--- +title: "Spacer" +description: "Row/Column에서 빈 공간 채우기" +--- + +`spacer`는 `Flexible`처럼 동작하지만 자식이 없는 빈 공간을 생성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| flex | `int` | 차지할 비율(기본 1) | + +## 예제 + +```json +{ + "type": "spacer", + "flex": 2 +} +``` diff --git a/docs/ko/widgets/stack.mdx b/docs/ko/widgets/stack.mdx new file mode 100644 index 00000000..f179176f --- /dev/null +++ b/docs/ko/widgets/stack.mdx @@ -0,0 +1,32 @@ +--- +title: "Stack" +description: "위젯을 겹쳐 배치" +--- + +`stack`은 자식을 겹쳐 배치하며 `positioned`와 함께 사용합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| alignment | `StacAlignmentDirectional` | 비배치 자식 정렬 | +| clipBehavior | `Clip` | 잘라내기 방식 | +| fit | `StackFit` | 비배치 자식 크기(`loose` 기본) | +| textDirection | `TextDirection?` | 정렬 계산 시 방향 | +| children | `List>` | 자식 목록 | + +## 예제 + +```json +{ + "type": "stack", + "alignment": "center", + "clipBehavior": "antiAlias", + "fit": "expand", + "textDirection": "ltr", + "children": [ + { "type": "text", "data": "Hello, World!" }, + { "type": "container", "width": 100, "height": 100, "color": "#FF0000" } + ] +} +``` diff --git a/docs/ko/widgets/switch.mdx b/docs/ko/widgets/switch.mdx new file mode 100644 index 00000000..4b04c5f4 --- /dev/null +++ b/docs/ko/widgets/switch.mdx @@ -0,0 +1,32 @@ +--- +title: "Switch" +description: "켜짐/꺼짐 토글 스위치" +--- + +`switch`는 Flutter Switch를 JSON으로 구성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| switchType | `StacSwitchType` | `material`, `cupertino`, `adaptive` | +| value | `bool` | 현재 상태 | +| onChanged | `Map` | 상태 변경 시 콜백 | +| autofocus | `bool` | 자동 포커스 | +| activeThumbColor / activeTrackColor | `String` | 켜졌을 때 썸/트랙 색 | +| inactiveThumbColor / inactiveTrackColor | `String` | 꺼졌을 때 색 | +| focusColor / hoverColor / overlayColor | `String` 또는 `StacMaterialColor` | 포커스/호버/오버레이 색 | +| thumbColor / trackColor / trackOutlineColor | `StacMaterialColor` | 커스텀 색 | +| onLabelColor / offLabelColor | `String` | 접근성 라벨 색 | +| splashRadius | `double` | 리플 반경 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 방식 | +| materialTapTargetSize | `MaterialTapTargetSize` | 탭 영역 크기 | +| thumbIcon | `Map` | 썸에 표시할 아이콘 | +| activeThumbImage / inactiveThumbImage | `String` | 썸 이미지 | +| applyTheme / applyCupertinoTheme | `bool` | 테마 적용 여부 | + +## 예제 + +```json +{ "type": "switch", "switchType": "cupertino", "value": true } +``` diff --git a/docs/ko/widgets/tab_bar.mdx b/docs/ko/widgets/tab_bar.mdx new file mode 100644 index 00000000..55013907 --- /dev/null +++ b/docs/ko/widgets/tab_bar.mdx @@ -0,0 +1,84 @@ +--- +title: "TabBar" +description: "탭과 뷰 전환" +--- + +`tabBar`/`tabBarView`/`defaultTabController`를 조합해 탭 전환 UI를 만들 수 있습니다. + +## TabBar 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| tabs | `List>` | 탭 목록 | +| initialIndex | `int` | 초기 선택 인덱스 | +| isScrollable | `bool` | 스크롤 가능한 탭 | +| padding | `StacEdgeInsets?` | 전체 패딩 | +| indicatorColor / indicatorWeight / indicatorPadding / indicator | `String?` / `double` / `StacEdgeInsets?` / `StacBoxDecoration?` | 인디케이터 스타일 | +| indicatorSize | `TabBarIndicatorSize?` | 인디케이터 크기 | +| labelColor / labelStyle / labelPadding | `String?` / `StacTextStyle?` / `StacEdgeInsets?` | 선택된 탭 스타일 | +| unselectedLabelColor / unselectedLabelStyle | `String?` / `StacTextStyle?` | 미선택 스타일 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 방식 | +| enableFeedback | `bool?` | 피드백 | +| physics | `StacScrollPhysics?` | 스크롤 물리 | +| tabAlignment | `TabAlignment?` | 탭 정렬 | + +## DefaultTabController 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| length | `int` | 탭 수 | +| initialIndex | `int` | 초기 인덱스 | +| child | `Map` | 하위 위젯 | + +## Tab 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| text | `String?` | 텍스트 | +| icon | `Map?` | 아이콘 | +| iconMargin | `StacEdgeInsets?` | 아이콘 여백 | +| height | `double?` | 높이 | +| child | `Map?` | 커스텀 위젯 | + +## TabBarView 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| children | `List>` | 각 탭 콘텐츠 | +| initialIndex | `int` | 초기 인덱스 | +| dragStartBehavior | `DragStartBehavior` | 드래그 시작 방식 | +| physics | `StacScrollPhysics?` | 스크롤 물리 | +| viewportFraction | `double` | 페이지가 차지할 비율 | +| clipBehavior | `Clip` | 클리핑 | + +## 예제 + +```json +{ + "type": "defaultTabController", + "length": 3, + "child": { + "type": "scaffold", + "appBar": { + "type": "appBar", + "title": { "type": "text", "data": "Tabbar" }, + "bottom": { + "type": "tabBar", + "tabs": [ + { "type": "tab", "text": "Red" }, + { "type": "tab", "text": "Green" }, + { "type": "tab", "text": "Blue" } + ] + } + }, + "body": { + "type": "tabBarView", + "children": [ + { "type": "container", "color": "#D9D9D9" }, + { "type": "container", "color": "#FC3F1B" }, + { "type": "container", "color": "#D9D9D9" } + ] + } + } +} +``` diff --git a/docs/ko/widgets/table.mdx b/docs/ko/widgets/table.mdx new file mode 100644 index 00000000..34746c0e --- /dev/null +++ b/docs/ko/widgets/table.mdx @@ -0,0 +1,56 @@ +--- +title: "Table" +description: "행과 열로 구성된 표" +--- + +`table`은 `tableRow`와 `tableCell`을 조합해 표를 구성합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| columnWidths | `Map?` | 열별 너비. 지정하지 않으면 `defaultColumnWidth` 사용 | +| defaultColumnWidth | `StacTableColumnWidth?` | 전체 기본 열 너비(`flexColumnWidth` 기본) | +| textDirection | `TextDirection?` | 열 순서(`ltr`/`rtl`) | +| border | `StacTableBorder?` | 테이블 테두리 | +| defaultVerticalAlignment | `TableCellVerticalAlignment?` | 셀 기본 세로 정렬 | +| textBaseLine | `TextBaseline?` | `baseline` 정렬 시 기준선 | +| children | `List` | 행 리스트 | + +## 예제 + +```json +{ + "type": "table", + "columnWidths": { + "1": { "type": "fixedColumnWidth", "value": 200 } + }, + "defaultColumnWidth": { "type": "flexColumnWidth", "value": 1 }, + "textDirection": "ltr", + "defaultVerticalAlignment": "bottom", + "border": { + "type": "tableBorder", + "top": { "color": "#428AF5", "width": 1.0 }, + "right": { "color": "#428AF5", "width": 1.0 }, + "bottom": { "color": "#428AF5", "width": 1.0 }, + "left": { "color": "#428AF5", "width": 1.0 }, + "borderRadius": 16 + }, + "children": [ + { + "type": "tableRow", + "children": [ + { "type": "tableCell", "child": { "type": "text", "data": "Header 1" } }, + { "type": "tableCell", "child": { "type": "text", "data": "Header 2" } } + ] + }, + { + "type": "tableRow", + "children": [ + { "type": "tableCell", "child": { "type": "text", "data": "Row 1, Cell 1" } }, + { "type": "tableCell", "child": { "type": "text", "data": "Row 1, Cell 2" } } + ] + } + ] +} +``` diff --git a/docs/ko/widgets/table_cell.mdx b/docs/ko/widgets/table_cell.mdx new file mode 100644 index 00000000..bb346295 --- /dev/null +++ b/docs/ko/widgets/table_cell.mdx @@ -0,0 +1,27 @@ +--- +title: "TableCell" +description: "표의 셀 정의" +--- + +| 속성 | 타입 | 설명 | +|------|------|------| +| verticalAlignment | `TableCellVerticalAlignment?` | `top`, `middle`, `bottom`, `baseline`, `fill`, `intrinsicHeight` 등 | +| child | `Map` | 셀 내부 자식 | + +## 예제 + +```json +{ + "type": "tableCell", + "verticalAlignment": "top", + "child": { + "type": "container", + "color": "#40000000", + "height": 50.0, + "child": { + "type": "center", + "child": { "type": "text", "data": "Header 1" } + } + } +} +``` diff --git a/docs/ko/widgets/table_row.mdx b/docs/ko/widgets/table_row.mdx new file mode 100644 index 00000000..ee966c6a --- /dev/null +++ b/docs/ko/widgets/table_row.mdx @@ -0,0 +1,21 @@ +--- +title: "TableRow" +description: "표의 한 행" +--- + +| 속성 | 타입 | 설명 | +|------|------|------| +| decoration | `StacDecoration?` | 행 배경/테두리 등 장식 | +| children | `List>` | 셀 목록 (`tableCell` 사용) | + +## 예제 + +```json +{ + "type": "tableRow", + "children": [ + { "type": "tableCell", "child": { "type": "text", "data": "Header 1" } }, + { "type": "tableCell", "child": { "type": "text", "data": "Header 2" } } + ] +} +``` diff --git a/docs/ko/widgets/text.mdx b/docs/ko/widgets/text.mdx new file mode 100644 index 00000000..dc3a08ee --- /dev/null +++ b/docs/ko/widgets/text.mdx @@ -0,0 +1,36 @@ +--- +title: "Text" +description: "Text 위젯 속성과 예제를 한국어로 안내합니다." +--- + +Stac `text`는 Flutter `Text` 위젯을 JSON으로 정의할 수 있게 해 줍니다. 자세한 설명은 [공식 문서](https://api.flutter.dev/flutter/widgets/Text-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| data | `String` | 표시할 문자열 | +| children | `List` | 리치 텍스트 구성 시 사용할 스팬 목록 | +| style | `StacTextStyle?` | 텍스트 스타일 | +| textAlign | `TextAlign?` | 정렬 방식 | +| textDirection | `TextDirection?` | 텍스트 방향 | +| softWrap | `bool?` | 줄바꿈 허용 여부 | +| overflow | `TextOverflow?` | 영역을 넘을 때 처리 방식 | +| textScaleFactor | `double?` | 폰트 스케일 | +| maxLines | `int?` | 최대 줄 수 | +| semanticsLabel | `String?` | 접근성 라벨 | +| textWidthBasis | `TextWidthBasis?` | 텍스트 폭 기준 | +| selectionColor | `String?` | 선택 영역 색상 | + +## 예제 + +```json +{ + "type": "text", + "data": "Hello, World!", + "style": { + "color": "#FFFFFF", + "fontSize": 24.0 + } +} +``` diff --git a/docs/ko/widgets/text_button.mdx b/docs/ko/widgets/text_button.mdx new file mode 100644 index 00000000..375f4686 --- /dev/null +++ b/docs/ko/widgets/text_button.mdx @@ -0,0 +1,42 @@ +--- +title: "TextButton" +description: "TextButton 위젯을 JSON으로 구성하는 방법" +--- + +Stac `textButton`은 Flutter `TextButton`을 JSON으로 정의할 수 있습니다. 자세한 내용은 [공식 문서](https://api.flutter.dev/flutter/material/TextButton-class.html)를 참고하세요. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| onPressed | `Map?` | 버튼을 탭했을 때 실행될 액션 | +| onLongPress | `Map?` | 길게 눌렀을 때 실행될 액션 | +| onHover | `Map?` | 호버 시 실행될 액션 | +| onFocusChange | `Map?` | 포커스 변경 시 실행될 액션 | +| style | `StacButtonStyle?` | 버튼 스타일 | +| autofocus | `bool` | 페이지 로드 시 자동 포커스 여부(기본값 `false`) | +| clipBehavior | `Clip` | 콘텐츠를 어떻게 잘라낼지(기본값 `Clip.none`) | +| isSemanticButton | `bool` | 접근성 트리에서 버튼으로 인식할지(기본값 `true`) | +| child | `Map` | 버튼 내부에 표시할 위젯 | + +## 예제 + +```json +{ + "type": "textButton", + "onPressed": {}, + "onLongPress": {}, + "onHover": {}, + "onFocusChange": {}, + "style": { + "backgroundColor": "#FFC107", + "foregroundColor": "#000000" + }, + "autofocus": false, + "clipBehavior": "none", + "child": { + "type": "text", + "data": "Click Me!" + } +} +``` diff --git a/docs/ko/widgets/text_field.mdx b/docs/ko/widgets/text_field.mdx new file mode 100644 index 00000000..f6b404f5 --- /dev/null +++ b/docs/ko/widgets/text_field.mdx @@ -0,0 +1,41 @@ +--- +title: "TextField" +description: "단순 텍스트 입력 필드" +--- + +`textField`는 Flutter `TextField`와 동일한 속성을 JSON으로 노출합니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| decoration | `StacInputDecoration?` | 힌트/라벨/테두리 등 InputDecoration | +| initialValue | `String` | 초기 값 | +| keyboardType | `StacTextInputType?` | 키보드 타입 | +| textInputAction | `TextInputAction?` | 키보드 액션 버튼 | +| textCapitalization | `TextCapitalization` | 자동 대소문자 | +| style | `StacTextStyle?` | 입력 텍스트 스타일 | +| textAlign / textAlignVertical / textDirection | `TextAlign` / `StacTextAlignVertical?` / `TextDirection?` | 정렬 | +| readOnly | `bool` | 읽기 전용 | +| showCursor | `bool?` | 커서 표시 | +| expands | `bool` | 부모 크기 채우기 | +| autofocus | `bool` | 자동 포커스 | +| obscuringCharacter | `String` | 비밀번호 대체 문자 | +| maxLines / minLines | `int?` | 줄 수 제한 | +| maxLength | `int?` | 최대 글자 수 | +| obscureText | `bool` | 텍스트 숨김 | +| enableSuggestions | `bool` | 입력 제안 사용 | +| enabled | `bool?` | 입력 가능 여부 | +| cursorWidth / cursorHeight / cursorColor | `double` / `double?` / `String?` | 커서 스타일 | +| inputFormatters | `List` | 입력 포매터 | + +## 예제 + +```json +{ + "type": "textField", + "initialValue": "Enter text here", + "decoration": { "hintText": "Enter your name" }, + "style": { "color": "#000000", "fontSize": 16.0 }, + "textAlign": "center", + "maxLength": 50 +} +``` diff --git a/docs/ko/widgets/text_form_field.mdx b/docs/ko/widgets/text_form_field.mdx new file mode 100644 index 00000000..7c18b6bd --- /dev/null +++ b/docs/ko/widgets/text_form_field.mdx @@ -0,0 +1,54 @@ +--- +title: "TextFormField" +description: "검증 가능한 텍스트 입력 필드" +--- + +`textFormField`는 `Form`과 함께 사용되며, `id`를 통해 값 저장 및 검증을 지원합니다. + +| 속성 | 타입 | 설명 | +|------|------|------| +| id / compareId | `String?` | 값 저장 및 다른 필드와 비교할 ID | +| decoration | `StacInputDecoration?` | InputDecoration | +| initialValue | `String?` | 초기 값 | +| keyboardType / textInputAction / textCapitalization | `StacTextInputType?` / `TextInputAction?` / `TextCapitalization` | 키보드 설정 | +| style | `StacTextStyle?` | 텍스트 스타일 | +| textAlign / textAlignVertical / textDirection | `TextAlign` / `StacTextAlignVertical?` / `TextDirection?` | 정렬 | +| readOnly / showCursor / autofocus | `bool` / `bool?` / `bool` | 동작 제어 | +| obscuringCharacter / obscureText | `String` / `bool?` | 비밀번호 표시 | +| maxLines / minLines / maxLength | `int?` | 줄 수, 글자 수 | +| autocorrect / enableSuggestions / enableIMEPersonalizedLearning | `bool` | 입력 보조 기능 | +| smartDashesType / smartQuotesType | `SmartDashesType?` / `SmartQuotesType?` | 스마트 문자 | +| maxLengthEnforcement | `MaxLengthEnforcement?` | 길이 제한 방식 | +| expands | `bool` | 부모 크기 채우기 | +| keyboardAppearance | `Brightness?` | 키보드 테마 | +| scrollPadding | `StacEdgeInsets` | 스크롤 시 여백 | +| restorationId | `String?` | 상태 복원 | +| enabled | `bool?` | 입력 가능 여부 | +| cursorWidth / cursorHeight / cursorColor | `double` / `double?` / `String?` | 커서 스타일 | +| autovalidateMode | `AutovalidateMode?` | 자동 검증 모드 | +| inputFormatters | `List` | 입력 포매터 | +| validatorRules | `List` | 검증 규칙 | + +## 예제 + +```json +{ + "type": "textFormField", + "id": "email", + "autovalidateMode": "onUserInteraction", + "validatorRules": [ + { "rule": "isEmail", "message": "Please enter a valid email" } + ], + "style": { "fontSize": 16, "fontWeight": "w400", "height": 1.5 }, + "decoration": { + "hintText": "Email", + "filled": true, + "fillColor": "#FFFFFF", + "border": { + "type": "outlineInputBorder", + "borderRadius": 8, + "color": "#24151D29" + } + } +} +``` diff --git a/docs/ko/widgets/vertical_divider.mdx b/docs/ko/widgets/vertical_divider.mdx new file mode 100644 index 00000000..6f4323ef --- /dev/null +++ b/docs/ko/widgets/vertical_divider.mdx @@ -0,0 +1,24 @@ +--- +title: "VerticalDivider" +description: "세로 구분선" +--- + +| 속성 | 타입 | 설명 | +|------|------|------| +| width | `double?` | 전체 폭 | +| thickness | `double?` | 선 두께 | +| indent / endIndent | `double?` | 위/아래 간격 | +| color | `String?` | 선 색상 | + +## 예제 + +```json +{ + "type": "verticalDivider", + "width": 20, + "thickness": 4, + "indent": 10, + "endIndent": 10, + "color": "#21814C" +} +``` diff --git a/docs/ko/widgets/visibility.mdx b/docs/ko/widgets/visibility.mdx new file mode 100644 index 00000000..1d2311c9 --- /dev/null +++ b/docs/ko/widgets/visibility.mdx @@ -0,0 +1,30 @@ +--- +title: "Visibility" +description: "위젯의 표시 여부를 제어" +--- + +| 속성 | 타입 | 설명 | +|------|------|------| +| child | `Map` | 기본 표시 위젯 | +| replacement | `Map?` | `visible=false`일 때 대신 렌더링할 위젯(기본 `SizedBox.shrink`) | +| visible | `bool` | 표시 여부(기본 `true`) | +| maintainState / maintainAnimation / maintainSize / maintainSemantics / maintainInteractivity | `bool` | 숨김 상태에서도 상태/애니메이션/크기/접근성/인터랙션을 유지할지 | + +## 예제 + +```json +{ + "type": "visibility", + "child": { "type": "text", "data": "I am visible!" }, + "visible": true +} +``` + +```json +{ + "type": "visibility", + "child": { "type": "text", "data": "I am hidden!" }, + "replacement": { "type": "container", "color": "#FF5733", "width": 50, "height": 50 }, + "visible": false +} +``` diff --git a/docs/ko/widgets/webview.mdx b/docs/ko/widgets/webview.mdx new file mode 100644 index 00000000..2b5dec6e --- /dev/null +++ b/docs/ko/widgets/webview.mdx @@ -0,0 +1,40 @@ +--- +title: "WebView" +description: "앱 안에서 웹 페이지 렌더링" +--- + +`webView`는 `stac_webview` 플러그인을 통해 구현되며, 초기화 시 `StacWebViewParser`를 등록해야 합니다. + +## 의존성 추가 + +```bash +flutter pub add stac_webview +``` + +```dart +await Stac.initialize( + parsers: const [ + StacWebViewParser(), + ], +); +``` + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| url | `String` | 로드할 URL | +| javaScriptMode | `JavaScriptMode` | 자바스크립트 실행 모드(기본 `unrestricted`) | +| backgroundColor | `String` | 배경색(기본 `#FFFFFF`) | +| userAgent | `String?` | 사용자 에이전트 | +| enableZoom | `bool` | 줌 허용 여부(기본 `false`) | +| layoutDirection | `TextDirection` | 레이아웃 방향 | + +## 예제 + +```json +{ + "type": "webView", + "url": "https://github.com/StacDev/stac" +} +``` diff --git a/docs/ko/widgets/wrap.mdx b/docs/ko/widgets/wrap.mdx new file mode 100644 index 00000000..80e16da9 --- /dev/null +++ b/docs/ko/widgets/wrap.mdx @@ -0,0 +1,37 @@ +--- +title: "Wrap" +description: "자동 줄바꿈 레이아웃" +--- + +`wrap`은 공간이 부족하면 다음 줄로 넘어가며 자식을 배치합니다. + +## 속성 + +| 속성 | 타입 | 설명 | +|------|------|------| +| axis | `Axis` | 주축 방향(`horizontal` 기본) | +| alignment | `WrapAlignment` | 한 줄 내부에서의 정렬 | +| spacing | `double` | 같은 줄 내 자식 간 간격 | +| runAlignment | `WrapAlignment` | 줄 자체의 정렬 | +| runSpacing | `double` | 줄과 줄 사이 간격 | +| crossAxisAlignment | `WrapCrossAxisAlignment` | 한 줄 내부 교차축 정렬 | +| textDirection | `TextDirection` | 가로 방향 해석 | +| verticalDirection | `VerticalDirection` | 세로 방향 해석 | +| clipBehavior | `Clip` | 클리핑 방식 | +| children | `List>` | 자식 목록 | + +## 예제 + +```json +{ + "type": "wrap", + "spacing": 8.0, + "runSpacing": 4.0, + "children": [ + { "type": "container", "color": "#FFCDD2", "width": 100, "height": 100, "child": { "type": "center", "child": { "type": "text", "data": "1", "style": { "color": "#FFFFFF" }}} }, + { "type": "container", "color": "#F8BBD0", "width": 100, "height": 100, "child": { "type": "center", "child": { "type": "text", "data": "2", "style": { "color": "#FFFFFF" }}} }, + { "type": "container", "color": "#E1BEE7", "width": 100, "height": 100, "child": { "type": "center", "child": { "type": "text", "data": "3", "style": { "color": "#FFFFFF" }}} }, + { "type": "container", "color": "#D1C4E9", "width": 100, "height": 100, "child": { "type": "center", "child": { "type": "text", "data": "4", "style": { "color": "#FFFFFF" }}} } + ] +} +``` diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index c9030e67..7700b973 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -240,4 +240,4 @@ You're all set. Next, explore widgets and actions, or jump into the CLI guide: - [Actions](/actions/navigate) - [CLI](/cli) -Need help? Join the community on [Discord](https://discord.com/invite/vTGsVRK86V) or open an issue on [GitHub](https://github.com/StacDev/stac/issues). \ No newline at end of file +Need help? Join the community on [Discord](https://discord.com/invite/vTGsVRK86V) or open an issue on [GitHub](https://github.com/StacDev/stac/issues). diff --git a/docs/sdui.mdx b/docs/sdui.mdx index 8e05098d..18be8571 100644 --- a/docs/sdui.mdx +++ b/docs/sdui.mdx @@ -57,4 +57,4 @@ Server-Driven UI has gained significant traction in recent years. Both startups - Try the quick start in [Quickstart](./quickstart) to render your first server‑driven screen. - Read how Stac renders widgets in [Rendering Stac Widgets](./concepts/rendering_stac_widgets). -- Explore the widget catalog under [Widgets](./widgets/). \ No newline at end of file +- Explore the widget catalog under [Widgets](./widgets/). From 55d3f6979d0f9313321a3d273ed33bffaf022538 Mon Sep 17 00:00:00 2001 From: curogom Date: Fri, 28 Nov 2025 14:41:43 +0900 Subject: [PATCH 2/3] docs(ko): add caching concept translation --- docs/docs.json | 1 + docs/ko/concepts/caching.mdx | 231 +++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 docs/ko/concepts/caching.mdx diff --git a/docs/docs.json b/docs/docs.json index ae763172..1a6419c6 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -211,6 +211,7 @@ "icon": "book-open", "pages": [ "ko/concepts/rendering_stac_widgets", + "ko/concepts/caching", "ko/concepts/custom_widgets", "ko/concepts/custom_actions" ] diff --git a/docs/ko/concepts/caching.mdx b/docs/ko/concepts/caching.mdx new file mode 100644 index 00000000..c061e2fd --- /dev/null +++ b/docs/ko/concepts/caching.mdx @@ -0,0 +1,231 @@ +--- +title: "캐싱 & 오프라인 지원" +description: "Stac이 화면을 캐싱해 오프라인 접근, 빠른 로딩, 효율적인 네트워크 사용을 제공하는 방법" +--- + +Stac에는 화면을 로컬에 저장해 오프라인 접근, 즉시 표시, 네트워크 사용량 절감을 동시에 얻을 수 있는 강력한 캐싱 시스템이 내장돼 있습니다. `Stac` 위젯을 사용할 때 자동으로 작동하며, 다양한 사용 사례에 맞게 동작을 세밀하게 제어할 수 있습니다. + +## 캐싱 동작 방식 + +`Stac` 위젯으로 Stac Cloud에서 화면을 가져오면 다음 순서로 처리됩니다. + +1. **최초 로드**: 네트워크에서 화면을 내려받아 로컬 캐시에 저장합니다. +2. **이후 로드**: 선택한 전략에 따라 캐시 데이터를 우선 사용하거나 새 데이터를 받아옵니다. +3. **버전 추적**: 각 캐시 항목에는 버전이 포함되어 있어 변경 사항을 감지합니다. +4. **백그라운드 갱신**: UI를 막지 않고 백그라운드에서 최신 데이터를 받아와 캐시를 갱신할 수 있습니다. + +## 캐시 전략 + +Stac은 다섯 가지 전략을 제공하며 상황에 따라 골라 쓸 수 있습니다. + +### 1. Optimistic (기본) + +캐시 데이터를 즉시 반환하면서 백그라운드에서 최신 데이터를 가져옵니다. 체감 속도가 가장 빠릅니다. + +```dart +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.optimistic, + ), +) +``` + +**동작:** +- ✅ 만료 여부와 관계없이 캐시를 바로 표시 +- ✅ 백그라운드에서 최신 데이터 요청 +- ✅ 결과를 캐시에 저장해 다음 로드를 준비 +- ⚡ 가장 빠른 체감 속도 + +**권장 용도:** UI 레이아웃, 컨텐츠 화면 등 최신성보다 즉시 표시가 중요한 경우. + +### 2. Cache First + +캐시가 유효하면 그대로 사용하고, 만료됐거나 없을 때만 네트워크 요청을 수행합니다. + +```dart +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.cacheFirst, + maxAge: Duration(hours: 24), + ), +) +``` + +**동작:** +- ✅ 만료되지 않은 캐시를 우선 사용 +- ✅ 캐시가 없거나 만료되면 네트워크 요청 +- ✅ 필요 시 백그라운드 새로고침 +- 📱 오프라인 친화적인 UX 제공 + +**권장 용도:** 오프라인 우선 앱, 자주 변하지 않는 컨텐츠, 네트워크 절약이 필요한 화면. + +### 3. Network First + +항상 네트워크를 먼저 시도하고 실패할 경우 캐시로 폴백합니다. + +```dart +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.networkFirst, + ), +) +``` + +**동작:** +- ✅ 항상 최신 데이터를 우선 요청 +- ✅ 네트워크 오류 시 캐시로 대체 +- ✅ 온라인일 때 최신 컨텐츠 보장 +- 🌐 네트워크 연결이 중요 + +**권장 용도:** 실시간 데이터, 자주 변하는 화면, 최신성이 중요한 콘텐츠. + +### 4. Cache Only + +캐시만 사용하고 네트워크 요청은 절대 수행하지 않습니다. 캐시가 없으면 오류가 발생합니다. + +```dart +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.cacheOnly, + ), +) +``` + +**동작:** +- ✅ 네트워크 요청 없음 +- ✅ 캐시에서 즉시 로드 +- ❌ 캐시가 없으면 실패 +- 📴 완전한 오프라인 모드에 적합 + +**권장 용도:** 기내 모드, 완전 오프라인 모드, 사전 캐시된 화면. + +### 5. Network Only + +항상 네트워크에서 데이터를 가져오고 캐시는 사용하거나 업데이트하지 않습니다. + +```dart +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.networkOnly, + ), +) +``` + +**동작:** +- ✅ 항상 최신 데이터 +- ❌ 네트워크 없으면 실패 +- ❌ 오프라인 지원 없음 +- 🔒 민감 정보를 캐시하지 않을 때 유용 + +**권장 용도:** 캐시하면 안 되는 민감 데이터, 실시간 대시보드, 인증 관련 화면. + +## 캐시 구성 + +### StacCacheConfig 속성 + +| 속성 | 타입 | 기본값 | 설명 | +|------|------|--------|------| +| `strategy` | `StacCacheStrategy` | `optimistic` | 사용할 캐싱 전략 | +| `maxAge` | `Duration?` | `null` | 캐시 만료까지 허용 시간. `null`이면 시간 기반 만료 없음 | +| `refreshInBackground` | `bool` | `true` | 캐시가 유효할 때도 백그라운드에서 최신 데이터를 가져올지 여부 | +| `staleWhileRevalidate` | `bool` | `false` | 만료된 캐시를 보여 주면서 새 데이터를 받아올지 여부 | + +### 캐시 유효 기간 지정 + +캐시를 얼마나 오래 유효하게 유지할지 제어할 수 있습니다. + +```dart +// 1시간 동안 유효한 캐시 +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.cacheFirst, + maxAge: Duration(hours: 1), + ), +) + +// 7일 동안 유효한 캐시 +Stac( + routeName: '/settings', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.cacheFirst, + maxAge: Duration(days: 7), + ), +) + +// 만료되지 않는 캐시 (버전 기반 갱신만 사용) +Stac( + routeName: '/static-page', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.cacheFirst, + maxAge: null, // 시간 기반 만료 없음 + ), +) +``` + +### 백그라운드 새로고침 + +UI를 막지 않고 캐시를 최신 상태로 유지합니다. + +```dart +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.cacheFirst, + maxAge: Duration(hours: 1), + refreshInBackground: true, // 조용히 새로고침 + ), +) +``` + +`refreshInBackground`가 `true`이면: +- 유효한 캐시를 즉시 반환 +- 백그라운드에서 최신 데이터를 가져옴 +- 결과를 캐시에 저장해 다음 로드를 준비 +- 사용자는 즉시 화면을 보고, 데이터는 결국 최신 상태로 맞춰짐 + +### Stale-While-Revalidate + +만료된 캐시를 보여 주면서 새 데이터를 가져옵니다. + +```dart +Stac( + routeName: '/home', + cacheConfig: StacCacheConfig( + strategy: StacCacheStrategy.cacheFirst, + maxAge: Duration(hours: 1), + staleWhileRevalidate: true, // 만료된 캐시를 사용하며 갱신 + ), +) +``` + +다음과 같은 상황에서 유용합니다. +- 로딩 스피너 대신 기존 화면을 보여 주고 싶을 때 +- 일정 시간 동안 콘텐츠가 다소 오래돼도 괜찮을 때 +- 네트워크가 느리거나 불안정할 때 + +## 전략 비교 + +| 전략 | 최초 로드 | 이후 로드 | 오프라인 지원 | 최적의 용도 | +|------|-----------|-----------|---------------|--------------| +| `optimistic` | 네트워크 → 캐시 | 캐시(백그라운드 갱신) | ✅ 가능 | 빠른 UX | +| `cacheFirst` | 캐시 또는 네트워크 | 캐시 | ✅ 가능 | 오프라인 앱 | +| `networkFirst` | 네트워크 | 네트워크(캐시 폴백) | ⚠️ 폴백만 | 최신 데이터 | +| `cacheOnly` | 캐시만 | 캐시만 | ✅ 가능 | 오프라인 모드 | +| `networkOnly` | 네트워크만 | 네트워크만 | ❌ 불가 | 민감 데이터 | + +## 버전 기반 업데이트 + +Stac은 각 캐시된 화면의 버전 번호를 관리합니다. Stac Cloud에 새 화면을 배포하면 다음 순서로 업데이트됩니다. + +1. 서버가 새로운 버전 번호를 부여합니다. +2. 백그라운드 요청이 새 버전을 감지합니다. +3. 캐시가 최신 콘텐츠로 교체됩니다. +4. 다음 로드에서 업데이트된 화면을 표시합니다. + +이 과정을 통해 사용자는 배포 없이도 빠른 로딩을 유지하고, 변경 사항 역시 자연스럽게 전달받을 수 있습니다. From 87cb4f6193d5d5016778f5efb44b7271686042a8 Mon Sep 17 00:00:00 2001 From: curogom Date: Fri, 28 Nov 2025 14:54:40 +0900 Subject: [PATCH 3/3] docs(ko): polish localization docs --- docs/ko/concepts/theming.mdx | 2 +- docs/ko/sdui.mdx | 2 +- docs/ko/widgets/bottom_navigation_bar.mdx | 96 ++++++++++++++++--- docs/ko/widgets/chip.mdx | 1 + docs/ko/widgets/filled_button.mdx | 2 + docs/ko/widgets/linear_progress_indicator.mdx | 2 + docs/ko/widgets/opacity.mdx | 2 + docs/ko/widgets/webview.mdx | 55 +++++++---- 8 files changed, 127 insertions(+), 35 deletions(-) diff --git a/docs/ko/concepts/theming.mdx b/docs/ko/concepts/theming.mdx index 4c121bb7..4f04c02c 100644 --- a/docs/ko/concepts/theming.mdx +++ b/docs/ko/concepts/theming.mdx @@ -55,4 +55,4 @@ class MyApp extends StatelessWidget { } ``` -자세한 구조는 [StacTheme 클래스](https://github.com/StacDev/stac/blob/dev/packages/stac/lib/src/parsers/theme/stac_theme/stac_theme.dart)를 참고하세요. +자세한 구조는 [StacTheme 클래스](https://github.com/StacDev/stac/blob/dev/packages/stac_core/lib/foundation/theme/stac_theme/stac_theme.dart)를 참고하세요. diff --git a/docs/ko/sdui.mdx b/docs/ko/sdui.mdx index ad48aa14..59789462 100644 --- a/docs/ko/sdui.mdx +++ b/docs/ko/sdui.mdx @@ -55,6 +55,6 @@ SDUI는 이 모델을 뒤집습니다. UI를 앱 내부에 하드코딩하는 ## 다음 단계 -- [퀵스타트](./ko/quickstart)에서 첫 번째 서버 기반 화면을 렌더링해 보세요. +- [퀵스타트](./quickstart)에서 첫 번째 서버 기반 화면을 렌더링해 보세요. - [Stac 위젯 렌더링](./concepts/rendering_stac_widgets)을 읽고 내부 동작을 이해하세요. - [위젯](./widgets/) 카탈로그를 살펴보고 어떤 컴포넌트가 있는지 확인하세요. diff --git a/docs/ko/widgets/bottom_navigation_bar.mdx b/docs/ko/widgets/bottom_navigation_bar.mdx index 86982ccd..1205deef 100644 --- a/docs/ko/widgets/bottom_navigation_bar.mdx +++ b/docs/ko/widgets/bottom_navigation_bar.mdx @@ -3,7 +3,7 @@ title: "BottomNavigationBar" description: "하단 탭 바 + 컨트롤러" --- -`bottomNavigationBar`는 Flutter `BottomNavigationBar`를 JSON으로 구성합니다. 여러 페이지를 전환하려면 `defaultBottomNavigationController`와 `bottomNavigationView`를 함께 사용합니다. +`bottomNavigationBar`는 Flutter `BottomNavigationBar`를 JSON으로 구성합니다. 여러 페이지를 전환하려면 `defaultBottomNavigationController`와 `bottomNavigationView`를 함께 사용합니다. Flutter 위젯 자체에 대한 자세한 내용은 [공식 문서](https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html)를 참고하세요. ## 속성 @@ -12,16 +12,22 @@ description: "하단 탭 바 + 컨트롤러" | items | `List` | 표시할 탭 목록 | | elevation | `double?` | 높이(그림자) | | bottomNavigationBarType | `BottomNavigationBarType?` | `fixed` / `shifting` | -| fixedColor / selectedItemColor / unselectedItemColor | `String?` | 선택/비선택 색상 | +| fixedColor | `String?` | `type`이 `BottomNavigationBarType.fixed`일 때 선택된 아이템 색상 | +| selectedItemColor | `String?` | 선택된 아이템 색상 | +| unselectedItemColor | `String?` | 비선택 아이템 색상 | | backgroundColor | `String?` | 배경색 | | iconSize | `double` | 아이콘 크기(기본 24) | -| selectedFontSize / unselectedFontSize | `double` | 라벨 폰트 크기 | -| selectedLabelStyle / unselectedLabelStyle | `StacTextStyle?` | 라벨 스타일 | +| selectedFontSize | `double` | 선택된 라벨 폰트 크기(기본 14) | +| unselectedFontSize | `double` | 비선택 라벨 폰트 크기(기본 12) | +| selectedLabelStyle | `StacTextStyle?` | 선택된 라벨 텍스트 스타일 | +| unselectedLabelStyle | `StacTextStyle?` | 비선택 라벨 텍스트 스타일 | | showSelectedLabels / showUnselectedLabels | `bool?` | 라벨 표시 여부 | | enableFeedback | `bool?` | 탭 피드백 | | landscapeLayout | `BottomNavigationBarLandscapeLayout?` | 가로 모드 레이아웃 | -### DefaultBottomNavigationController +## DefaultBottomNavigationController + +`defaultBottomNavigationController`는 `BottomNavigationController`를 트리에 공유하는 상속 위젯입니다. 이를 통해 `bottomNavigationBar`나 `bottomNavigationView`가 동일한 컨트롤러 상태를 참조해 현재 선택된 탭을 동기화합니다. | 속성 | 타입 | 설명 | |------|------|------| @@ -29,16 +35,19 @@ description: "하단 탭 바 + 컨트롤러" | initialIndex | `int?` | 초기 선택 인덱스 | | child | `Map` | 하위 위젯 | -### BottomNavigationBarItem +## BottomNavigationBarItem + +`navigationBarItem`은 Flutter `BottomNavigationBarItem`을 JSON으로 정의합니다. 각 항목은 아이콘과 라벨을 갖고, 활성/비활성 상태별 아이콘과 배경을 따로 지정할 수 있습니다. Flutter 위젯에 대한 더 많은 정보는 [공식 문서](https://api.flutter.dev/flutter/widgets/BottomNavigationBarItem-class.html)를 참고하세요. | 속성 | 타입 | 설명 | |------|------|------| -| icon / activeIcon | `Map` | 기본/선택 상태 아이콘 | +| icon | `Map` | 기본 상태 아이콘 | +| activeIcon | `Map?` | 선택 상태 아이콘 | | label | `String` | 라벨 | | backgroundColor | `String?` | 항목 배경 | | tooltip | `String?` | 길게 누를 때 표시되는 텍스트 | -### BottomNavigationView +## BottomNavigationView 선택된 탭에 대응하는 자식을 보여주는 뷰입니다. @@ -54,21 +63,78 @@ description: "하단 탭 바 + 컨트롤러" "length": 3, "child": { "type": "scaffold", - "appBar": { "type": "appBar", "title": { "type": "text", "data": "Bottom Navigation Screen" } }, + "appBar": { + "type": "appBar", + "title": { + "type": "text", + "data": "Bottom Navigation Screen" + } + }, "body": { "type": "bottomNavigationView", "children": [ - { "type": "center", "child": { "type": "text", "data": "Home", "style": { "fontSize": 24 } } }, - { "type": "center", "child": { "type": "text", "data": "Search", "style": { "fontSize": 24 } } }, - { "type": "center", "child": { "type": "text", "data": "Profile", "style": { "fontSize": 24 } } } + { + "type": "center", + "child": { + "type": "text", + "data": "Home", + "style": { + "fontSize": 24 + } + } + }, + { + "type": "center", + "child": { + "type": "text", + "data": "Search", + "style": { + "fontSize": 24 + } + } + }, + { + "type": "center", + "child": { + "type": "text", + "data": "Profile", + "style": { + "fontSize": 24 + } + } + } ] }, "bottomNavigationBar": { "type": "bottomNavigationBar", "items": [ - { "type": "navigationBarItem", "label": "Home", "icon": { "type": "icon", "icon": "home" } }, - { "type": "navigationBarItem", "label": "Search", "icon": { "type": "icon", "icon": "search" } }, - { "type": "navigationBarItem", "label": "Profile", "icon": { "type": "icon", "icon": "account_circle" } } + { + "type": "navigationBarItem", + "label": "Home", + "icon": { + "type": "icon", + "iconType": "material", + "icon": "home" + } + }, + { + "type": "navigationBarItem", + "label": "Search", + "icon": { + "type": "icon", + "iconType": "material", + "icon": "search" + } + }, + { + "type": "navigationBarItem", + "label": "Profile", + "icon": { + "type": "icon", + "iconType": "material", + "icon": "account_circle" + } + } ] } } diff --git a/docs/ko/widgets/chip.mdx b/docs/ko/widgets/chip.mdx index 806acfa5..728aa321 100644 --- a/docs/ko/widgets/chip.mdx +++ b/docs/ko/widgets/chip.mdx @@ -43,6 +43,7 @@ description: "태그/배지 형태 위젯" "side": { "color": "#000000", "width": 1.0 }, "shape": { "type": "roundedRectangle", "borderRadius": 8.0 }, "clipBehavior": "antiAlias", + "autofocus": false, "color": "#FFFFFF", "backgroundColor": "#EEEEEE", "padding": { "left": 8, "top": 4, "right": 8, "bottom": 4 }, diff --git a/docs/ko/widgets/filled_button.mdx b/docs/ko/widgets/filled_button.mdx index 1dc73e7f..38926233 100644 --- a/docs/ko/widgets/filled_button.mdx +++ b/docs/ko/widgets/filled_button.mdx @@ -5,6 +5,8 @@ description: "Material 3 스타일의 채워진 버튼" `filledButton`은 M3 FilledButton을 JSON으로 구성합니다. 속성은 ElevatedButton과 동일합니다. +## 속성 + | 속성 | 타입 | 설명 | |------|------|------| | onPressed / onLongPress / onHover / onFocusChange | `Map?` | 이벤트 콜백 | diff --git a/docs/ko/widgets/linear_progress_indicator.mdx b/docs/ko/widgets/linear_progress_indicator.mdx index 045f0c72..434ca1db 100644 --- a/docs/ko/widgets/linear_progress_indicator.mdx +++ b/docs/ko/widgets/linear_progress_indicator.mdx @@ -5,6 +5,8 @@ description: "수평 진행 표시기" `linearProgressIndicator`는 가로 막대 형태로 진행 상황을 표시합니다. +## 속성 + | 속성 | 타입 | 설명 | |------|------|------| | value | `double` | 진행 값(0~1, null이면 무한 애니메이션) | diff --git a/docs/ko/widgets/opacity.mdx b/docs/ko/widgets/opacity.mdx index c615f0b1..9c06c61a 100644 --- a/docs/ko/widgets/opacity.mdx +++ b/docs/ko/widgets/opacity.mdx @@ -5,6 +5,8 @@ description: "자식의 투명도 조절" `opacity`는 자식 위젯의 투명도를 0~1 사이 값으로 제어합니다. +## 속성 + | 속성 | 타입 | 설명 | |------|------|------| | opacity | `double` | 0.0은 완전 투명, 1.0은 불투명 | diff --git a/docs/ko/widgets/webview.mdx b/docs/ko/widgets/webview.mdx index 2b5dec6e..78599e79 100644 --- a/docs/ko/widgets/webview.mdx +++ b/docs/ko/widgets/webview.mdx @@ -3,32 +3,51 @@ title: "WebView" description: "앱 안에서 웹 페이지 렌더링" --- -`webView`는 `stac_webview` 플러그인을 통해 구현되며, 초기화 시 `StacWebViewParser`를 등록해야 합니다. +Stac `webView` 위젯은 [webview_flutter](https://pub.dev/packages/webview_flutter) 플러그인을 기반으로 하며 JSON으로 정의한 WebView를 앱 안에 표시합니다. -## 의존성 추가 +## 사용 방법 -```bash -flutter pub add stac_webview -``` +1. `stac_webview` 의존성 추가 -```dart -await Stac.initialize( - parsers: const [ - StacWebViewParser(), - ], -); -``` + 프로젝트 루트에서 다음 명령을 실행하거나, + + ```bash + flutter pub add stac_webview + ``` + + `pubspec.yaml`에 직접 추가할 수 있습니다. + + ```yaml + dependencies: + stac_webview: + ``` + +2. `StacWebViewParser` 등록 + + `Stac.initialize` 호출 시 파서 목록에 추가해야 JSON에서 `webView` 타입을 사용할 수 있습니다. + + ```dart + void main() async { + await Stac.initialize( + parsers: const [ + StacWebViewParser(), + ], + ); + + runApp(const MyApp()); + } + ``` ## 속성 | 속성 | 타입 | 설명 | |------|------|------| -| url | `String` | 로드할 URL | -| javaScriptMode | `JavaScriptMode` | 자바스크립트 실행 모드(기본 `unrestricted`) | -| backgroundColor | `String` | 배경색(기본 `#FFFFFF`) | -| userAgent | `String?` | 사용자 에이전트 | -| enableZoom | `bool` | 줌 허용 여부(기본 `false`) | -| layoutDirection | `TextDirection` | 레이아웃 방향 | +| `url` | `String` | 로드할 웹 페이지 URL. | +| `javaScriptMode` | `JavaScriptMode` | 자바스크립트 실행 모드. 기본값은 `JavaScriptMode.unrestricted`. | +| `backgroundColor` | `String` | WebView 배경색. 기본값 `#FFFFFF`. | +| `userAgent` | `String?` | 사용자 지정 User-Agent 문자열. | +| `enableZoom` | `bool` | 줌 허용 여부. 기본값 `false`. | +| `layoutDirection` | `TextDirection` | 레이아웃 방향. 기본값 `TextDirection.ltr`. | ## 예제