Toast#
์ฌ์ฉ์์๊ฒ ๊ฐ๋จํ ํผ๋๋ฐฑ ๋ฉ์์ง๋ฅผ ์ผ์์ ์ผ๋ก ํ์ํ๋ ํ ์คํธ ์ปดํฌ๋ํธ์ ๋๋ค.
Live Preview#
class ToastDefaultExample extends StatelessComponent {
const ToastDefaultExample({super.key});
@override
Component build(BuildContext context) {
return Toast(
message: 'Success',
description: 'Your changes have been saved.',
onDismiss: () {},
);
}
}
class ToastDefaultExample extends StatefulWidget {
const ToastDefaultExample({super.key});
@override
State<ToastDefaultExample> createState() => _ToastDefaultExampleState();
}
class _ToastDefaultExampleState extends State<ToastDefaultExample> {
@override
Widget build(BuildContext context) {
return Toast(
message: 'Success',
description: 'Your changes have been saved.',
onDismiss: () {},
);
}
}
class ToastDestructiveExample extends StatelessComponent {
const ToastDestructiveExample({super.key});
@override
Component build(BuildContext context) {
return Toast(
message: 'Error',
description: 'Something went wrong.',
variant: CoreToastVariant.destructive,
onDismiss: () {},
);
}
}
class ToastDestructiveExample extends StatefulWidget {
const ToastDestructiveExample({super.key});
@override
State<ToastDestructiveExample> createState() =>
_ToastDestructiveExampleState();
}
class _ToastDestructiveExampleState extends State<ToastDestructiveExample> {
@override
Widget build(BuildContext context) {
return Toast(
message: 'Error',
description: 'Something went wrong.',
variant: CoreToastVariant.destructive,
onDismiss: () {},
);
}
}
class ToastChainExample extends StatelessComponent {
const ToastChainExample({super.key});
@override
Component build(BuildContext context) {
return Toast(
message: 'Success',
description: 'Your changes have been saved.',
onDismiss: () {},
).withStyle(
const CoreToastStyle(
panelBorderRadius: CoreBorderRadius.all(CoreRadius.radius4),
panelBorderColor: CoreColor.token(CoreColors.primary),
panelBorderWidth: CoreStrokeWidth.stroke2,
panelPaddingWithDismiss: CoreEdgeInsets.directional(
start: CoreSpace.space16,
top: CoreSpace.space16,
end: CoreSpace.space32,
bottom: CoreSpace.space16,
),
messageDescriptionGapStyle: CoreGapStyle(size: CoreSpace.space8),
),
);
}
}
class ToastChainExample extends StatelessWidget {
const ToastChainExample({super.key});
@override
Widget build(BuildContext context) {
return Toast(
message: 'Success',
description: 'Your changes have been saved.',
onDismiss: () {},
).withStyle(
const CoreToastStyle(
panelBorderRadius: CoreBorderRadius.all(CoreRadius.radius4),
panelBorderColor: CoreColor.token(CoreColors.primary),
panelBorderWidth: CoreStrokeWidth.stroke2,
panelPaddingWithDismiss: CoreEdgeInsets.directional(
start: CoreSpace.space16,
top: CoreSpace.space16,
end: CoreSpace.space32,
bottom: CoreSpace.space16,
),
messageDescriptionGapStyle: CoreGapStyle(size: CoreSpace.space8),
),
);
}
}
์ฌ์ฉ ์๊ธฐ (When to Use)#
์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ธ์:
- ์์ ๊ฒฐ๊ณผ(์ฑ๊ณต/์คํจ)๋ฅผ ๊ฐ๋จํ ์๋ฆด ๋
- ๋๋๋ฆฌ๊ธฐ(undo) ์ก์ ์ ์ ๊ณตํ ๋
- ์ฌ์ฉ์ ํ๋ฆ์ ๋ฐฉํดํ์ง ์๋ ๋น์นจ์ ์ ์๋ฆผ์ด ํ์ํ ๋
๋์ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ธ์:
Dialog: ์ฌ์ฉ์ ํ์ธ์ด ๋ฐ๋์ ํ์ํ ์ค์ํ ์๋ฆผ์ผ ๋Alert/Banner: ํ์ด์ง์ ๊ณ ์ ๋ ์๊ตฌ์ ์๋ฆผ์ด ํ์ํ ๋Tooltip: ํน์ ์์์ ๋ํ ์ค๋ช ์ด ํ์ํ ๋
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ (Basic Usage)#
// ๊ธฐ๋ณธ ํ ์คํธ ์นด๋
Toast(
message: '์ ์ฅ๋์์ต๋๋ค.',
description: '๋ณ๊ฒฝ์ฌํญ์ด ์ฑ๊ณต์ ์ผ๋ก ์ ์ฅ๋์์ต๋๋ค.',
onDismiss: () {},
)
// destructive ๋ณํ
Toast(
message: '์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.',
description: '๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด ์ฃผ์ธ์.',
variant: CoreToastVariant.destructive,
onDismiss: () {},
)
// showToast์ ํจ๊ป ์ฌ์ฉ
showToast(
context: context,
builder: (context, overlay) => Toast(
message: 'ํ์ผ์ด ์
๋ก๋๋์์ต๋๋ค.',
onDismiss: overlay.close,
),
)
// ๊ธฐ๋ณธ ํ ์คํธ (default ๋ณํ)
Toast(
message: '์ ์ฅ๋์์ต๋๋ค.',
description: '๋ณ๊ฒฝ์ฌํญ์ด ์ฑ๊ณต์ ์ผ๋ก ์ ์ฅ๋์์ต๋๋ค.',
onDismiss: handleDismiss,
)
// destructive ๋ณํ
Toast(
message: '์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.',
description: '๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด ์ฃผ์ธ์.',
variant: CoreToastVariant.destructive,
onDismiss: handleDismiss,
)
Props / Parameters#
| ์์ฑ | ํ์ | ๊ธฐ๋ณธ๊ฐ | ์ค๋ช |
|---|---|---|---|
message | String | ํ์ | ํ ์คํธ ๋ฉ์์ง |
description |
String? |
null |
๋ณด์กฐ ์ค๋ช ํ ์คํธ |
variant |
CoreToastVariant |
CoreToastVariant.defaultVariant (neutral) |
์๊ฐ์ ๋ณํ (์๋งจํฑ โ ์์ ฏ ํ๋ผ๋ฏธํฐ) |
onDismiss |
VoidCallback? / CoreVoidCallback? |
null |
๋ซ๊ธฐ ๋ฒํผ ์ฝ๋ฐฑ (null์ด๋ฉด ๋ซ๊ธฐ ๋ฒํผ ์์) |
action |
Widget? / Component? |
null |
์ก์ ์ฌ๋กฏ ์์ ฏ |
toastStyle |
CoreToastStyle? |
null |
panel chrome / nested ์ฌ๋กฏ ๋ฌถ์ |
๋น ๋ฅธ ์ค๋ฒ๋ผ์ด๋ (Chain)#
์ด๋ฏธ ๋ง๋ Toast ์ธ์คํด์ค์ ์คํ์ผ์ ๋น ๋ฅด๊ฒ ๋ง๋ถ์ด๊ณ ์ถ๋ค๋ฉด withStyle ์ฒด์ธ์ ์ธ ์ ์์ต๋๋ค. ์์ฑ์์ style ์ฌ๋กฏ ์ธ์์ ๋์ผํ๊ฒ ๋์ํ์ง๋ง, ์ด๋ฏธ ๊ตฌ์ฑ๋ ์์ ฏ ์์์ ๋ฐ๋ก ์ด์ด ์ธ ์ ์์ต๋๋ค.
class ToastChainExample extends StatelessWidget {
const ToastChainExample({super.key});
@override
Widget build(BuildContext context) {
return Toast(
message: 'Success',
description: 'Your changes have been saved.',
onDismiss: () {},
).withStyle(
const CoreToastStyle(
panelBorderRadius: CoreBorderRadius.all(CoreRadius.radius4),
panelBorderColor: CoreColor.token(CoreColors.primary),
panelBorderWidth: CoreStrokeWidth.stroke2,
panelPaddingWithDismiss: CoreEdgeInsets.directional(
start: CoreSpace.space16,
top: CoreSpace.space16,
end: CoreSpace.space32,
bottom: CoreSpace.space16,
),
messageDescriptionGapStyle: CoreGapStyle(size: CoreSpace.space8),
),
);
}
}
class ToastChainExample extends StatelessComponent {
const ToastChainExample({super.key});
@override
Component build(BuildContext context) {
return Toast(
message: 'Success',
description: 'Your changes have been saved.',
onDismiss: () {},
).withStyle(
const CoreToastStyle(
panelBorderRadius: CoreBorderRadius.all(CoreRadius.radius4),
panelBorderColor: CoreColor.token(CoreColors.primary),
panelBorderWidth: CoreStrokeWidth.stroke2,
panelPaddingWithDismiss: CoreEdgeInsets.directional(
start: CoreSpace.space16,
top: CoreSpace.space16,
end: CoreSpace.space32,
bottom: CoreSpace.space16,
),
messageDescriptionGapStyle: CoreGapStyle(size: CoreSpace.space8),
),
);
}
}
์คํ์ผ ์์คํ
โ toastStyle#
Toast ์ panel chrome / ์ฌ๋กฏ ๋ฏธ์ธ ์กฐ์ ์ ๋จ์ผ toastStyle
(CoreToastStyle) ์ผ๋ก ํ๋ฆ
๋๋ค. ์๋งจํฑ enum (variant) ์ ์์ ฏ ํ๋ผ๋ฏธํฐ.
Toast(
message: '์ ์ฅ ์๋ฃ',
description: '๋ณ๊ฒฝ์ฌํญ์ด ์ ์ฉ๋์์ต๋๋ค',
variant: CoreToastVariant.destructive,
onDismiss: handleDismiss,
action: Button(
variant: .ghost,
onPressed: undo,
child: Text('๋๋๋ฆฌ๊ธฐ'),
),
toastStyle: CoreToastStyle(
panelBackgroundColor: CoreColor.token(CoreColors.surface),
panelBorderColor: CoreColor.token(CoreColors.outline),
panelBorderRadius: CoreBorderRadius.all(CoreRadius.radius12),
panelPadding: CoreEdgeInsets.all(CoreSpace.space16),
contentActionGapStyle: CoreGapStyle(size: CoreSpace.space12),
messageStyle: CoreTextStyle.token(
CoreTextStyles.labelLarge,
).copyWith(fontWeight: CoreFontWeight.semiBold),
descriptionStyle: CoreTextStyle.token(
CoreTextStyles.bodySmall,
color: CoreColor.token(CoreColors.onSurfaceVariant),
),
// actionButtonStyle ์ action ์์ Button ์ด ์๋ ์ ์ฉ ๊ฐ๋ฅ
// (์์ฒ๋ผ variant ์ง์ ์ฃผ์
โ asChild)
),
)
CoreToastStyle ํ๋#
| ํ๋ | ํ์ | ์ค๋ช |
|---|---|---|
panelBackgroundColor |
CoreColor? |
Panel background fill colour override. |
panelBorderColor |
CoreColor? |
Panel border stroke colour override. |
panelBorderRadius |
CoreBorderRadius? |
Panel border radius override. |
panelBorderWidth |
double? |
Panel border stroke width override (logical px). When null, defers to [defaultPanelBorderWidth] (1). |
panelPadding |
CoreEdgeInsets? |
Panel content padding override (no dismiss button). When null, defers to [defaultPanelPadding]. |
panelPaddingWithDismiss |
CoreEdgeInsets? |
Panel content padding override when a dismiss button is present โ the resolver selects this (instead of [panelPadding]) so the trailing edge can leave room for the close button. When null, defers to [defaultPanelPaddingWithDismiss]. |
panelBoxShadow |
List<CoreShadowLayer>? |
Panel elevation shadow override. When null, defers to [defaultPanelBoxShadow]. |
minHeight |
double? |
Panel minimum height override (logical px). When null, defers to [defaultMinHeight]. |
contentActionGapStyle |
CoreGapStyle? |
Gap style between the message + description column and the action slot โ nested [CoreGapStyle] slot forwarded to the
Gap
widget that separates the content column from the action.
null
defers to [defaultContentActionGapStyle].
|
iconStyle |
CoreIconStyle? |
Leading-glyph style override.
null
defers to [defaultIconStyle]. Size and any other icon chrome only โ the colour comes from the variant, because the glyph is what tells the three variants apart.
|
iconContentGapStyle |
CoreGapStyle? |
Gap between the leading glyph and the content column.
null
defers to [defaultIconContentGapStyle]. Reached only by the variants that draw a glyph; a toast with none starts at its content.
|
messageDescriptionGapStyle |
CoreGapStyle? |
Gap style between the message and description rows inside the content column โ nested [CoreGapStyle] slot forwarded to the
Gap
widget that separates message from description.
null
defers to [defaultMessageDescriptionGapStyle].
|
closeInset |
CoreEdgeInsets? |
Close-button inset from the top-right corner โ four-sided so the caller can shift the dismiss button on any axis. This is panel layout (it positions the dismiss button inside the panel padding region), not button chrome, so it stays flat; the button's own chrome lives in the nested [closeButtonStyle] slot. When null, defers to [defaultCloseInset]. |
transitionDuration |
Duration? |
Panel chrome transition duration override. When null, defers to [defaultTransitionDuration]. |
messageStyle |
CoreTextStyle? |
Message (primary text) style override. |
descriptionStyle |
CoreTextStyle? |
Description (secondary text) style override. |
actionButtonStyle |
CoreButtonStyle? |
Action button style override. To change the action's
variant
, inject the action widget directly via
Toast(action: Button(variant: outline,...))
rather than packing variant into this style โ ์์น 8 (asChild pattern). No
default*
on this field, deliberately โ a flat default would name one colour for three variants; what the resolver injects underneath this slot is instead the VARIANT's own foreground, the same way [defaultCloseButtonStyle] is tinted with
closeForeground
:
text CoreButtonStyle(foregroundColor: variant.foregroundColor, hoverForegroundColor: variant.foregroundColor).merge(actionButtonStyle) // this slot.merge(action.buttonStyle) // the caller's own Button, wins
It used to inject nothing, on the reasoning that the action is the caller's widget and its chrome should pass through untouched. That reasoning is exactly what made the action unreadable: a
Button
carries a page-surface foreground, and the toast is
inverse-surface
, so
plain
/
link
measured 2.1 : 1 against the slab and
outline
/
ghost
/
text
โ whose
onSurface
is the very same token as the slab โ 1.0 : 1, invisible. A toast is the one surface a caller's button was never designed against, so the toast has to say what legible means there; the caller's explicit style still wins by merge order, and the caller's own
variant
is still theirs (asChild).
|
closeButtonStyle |
CoreButtonStyle? |
Close (dismiss) button style override. The single entry point for the dismiss-button chrome โ padding / close-glyph (
leadingIconStyle
) / hover transition (
animationDuration
) / focus-ring width (
focusOutlineStyle.borderWidth
). Merged onto [defaultCloseButtonStyle] and raw-forwarded to
Button(variant:.plain, buttonStyle: โฆ)
(the
plain
variant owns the resting-opacity / hover-fade / foreground semantics).
|
CoreToastStyle ๋ณํ๋ณ ๊ธฐ๋ณธ๊ฐ (CoreToastVariantStyle)#
| ํ๋ | success |
destructive |
neutral |
|---|---|---|---|
backgroundColor |
inverseSurface | inverseSurface | inverseSurface |
closeForeground |
inverseOnSurface (opacity defaultCloseForegroundOpacity) | inverseOnSurface (opacity defaultCloseForegroundOpacity) | inverseOnSurface (opacity defaultCloseForegroundOpacity) |
closeHoverForeground |
inverseOnSurface | inverseOnSurface | inverseOnSurface |
foregroundColor |
inverseOnSurface | inverseOnSurface | inverseOnSurface |
borderColor |
inverseSurface | inverseSurface | inverseSurface |
descriptionForeground |
inverseOnSurface (opacity defaultDescriptionForegroundOpacity) | inverseOnSurface (opacity defaultDescriptionForegroundOpacity) | inverseOnSurface (opacity defaultDescriptionForegroundOpacity) |
iconColor | success | error | โ |
asChild ํจํด โ action ๋ฒํผ ๋ณ๊ฒฝ#
action ๋ฒํผ์ ์๋งจํฑ (variant) ๋ณ๊ฒฝ์ด ํ์ํ ๋๋ action ์ฌ๋กฏ์
์ง์ ์์ ฏ์ ์ฃผ์
ํฉ๋๋ค:
Toast(
message: '...',
action: Button(variant: .ghost, child: Text('๋๋๋ฆฌ๊ธฐ')), // โ variant ์ง์
)
toastStyle.actionButtonStyle ์ chrome ๋ฏธ์ธ ์กฐ์ ์ฉ (paddingH /
labelStyle ๋ฑ).
Resolve chain#
design system default for variant
โ CoreToastTheme.style // ํ๋ก์ ํธ ๊ณตํต
โ CoreToastTheme.variantStyles[widget.variant] // variant ๋ณ
โ ๋ถ๋ชจ ์ปดํฌ๋ํธ ์ฌ๋กฏ ์ค๋ฒ๋ผ์ด๋
โ widget.toastStyle // ์ธ์คํด์ค๋ณ
๋ณํ (Variants)#
์ธ ๋ณํ์ ์ ํ ๊ธ๋ฆฌํ ํ๋๋ก๋ง ๊ฐ๋ฆฝ๋๋ค. ๋ฐฐ๊ฒฝ ยท ์ ๊ฒฝ ยท ๋ณด๋๋ ์ ๋ค ๊ฐ์ inverse-surface ์์ด๋ผ(์ ๋ณํ๋ณ ๊ธฐ๋ณธ๊ฐ ํ ์ฐธ์กฐ), ๋ณํ์ ๊ณ ๋ฅด๋ ๊ฒ์ ์์ ๊ณ ๋ฅด๋ ๊ฒ์ด ์๋๋ผ ๋ฌด์์ ์ฃผ์ฅํ๋์ง๋ฅผ ๊ณ ๋ฅด๋ ๊ฒ์ ๋๋ค.
neutral#
๊ธฐ๋ณธ๊ฐ์ ๋๋ค. ๊ธ๋ฆฌํ๊ฐ ์์ต๋๋ค โ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํ์ง ์๋ ์๋ฆผ.
variant ๋ฅผ ์ ์ง ์์ผ๋ฉด ์ด๊ฒ์ด ๊ทธ๋ ค์ง๋๋ค. ์๋ฌด ๋ณํ๋ ์ด๋ฆํ์ง ์์ ํธ์ถ์๋ ์ด๋ค ๊ฒฐ๊ณผ๋ ์ฃผ์ฅํ์ง ์์ ๊ฒ์ด๊ณ , ๊ธ๋ฆฌํ ์๋ ํ ์คํธ๊ฐ ๊ทธ ์ํ์ ๋ชจ์ต์
๋๋ค.
Toast(
message: 'New version available',
description: 'Reload to get the latest.',
onDismiss: handleDismiss,
)
success#
์ด๋ก ์ฑ๊ณต ์ฒดํฌ๋ฅผ ๋ต๋๋ค. ๋ฌด์ธ๊ฐ ์ฑ๊ณตํ์ ๋๋ง ์๋๋ค.
Toast(
message: 'Saved',
description: 'Your changes have been saved.',
variant: CoreToastVariant.success,
onDismiss: handleDismiss,
)
default_๋ ์ด ๋ณํ์ ์ ์ด๋ฆ์ด๊ณ deprecated ๋ณ์นญ์ผ๋ก ๋จ์ ์์ต๋๋ค. ๊ฐ์ผ๋ก ์ฐ๋ ์ฝ๋๋ ๊ทธ๋๋ก ์ปดํ์ผ๋์ง๋ง,switch์ exhaustive arm ์success๋ก ๋ฐ๊ฟ์ผ ํฉ๋๋ค.
destructive#
์ค๋ฅ ์ ๊ธ๋ฆฌํ๋ฅผ ๋ต๋๋ค. ์คํจ๋ฅผ ๋ณด๊ณ ํ ๋ ์๋๋ค.
Toast(
message: 'Error',
description: 'Something went wrong.',
variant: CoreToastVariant.destructive,
onDismiss: handleDismiss,
)
Flutter ์ธํ๋ผ (ToastLayer)#
Flutter์์๋ ToastLayer์ showToast()๋ก ํ ์คํธ ํ์/์คํํน/์ ๋๋ฉ์ด์
์ ๊ด๋ฆฌํฉ๋๋ค.
// ์ฑ ์ต์๋จ์ ToastLayer ๋ฐฐ์น
ToastLayer(
maxStackedEntries: 3,
expandMode: ExpandMode.expandOnHover,
child: MyApp(),
)
// ํ ์คํธ ํ์
final overlay = showToast(
context: context,
builder: (context, overlay) => Toast(
message: '์ฒ๋ฆฌ ์๋ฃ',
onDismiss: overlay.close,
),
location: ToastLocation.bottomRight,
showDuration: Duration(seconds: 5),
);
// ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ๋ซ๊ธฐ
overlay.close();
ํ ๋ง ์ปค์คํฐ๋ง์ด์ง#
CoreComponentTheme(
toast: CoreToastTheme(
style: CoreToastStyle(
panelBorderRadius: CoreBorderRadius.all(CoreRadius.radius12),
),
),
)
์ฌ์ฉ ๊ฐ์ด๋๋ผ์ธ (Usage Guidelines)#
โ Do#
action ์ ์๋งจํฑ(variant)์ ์ฌ๋กฏ์ ์ง์ ์ฃผ์ (asChild)
Toast(
message: '์ญ์ ๋์์ต๋๋ค.',
action: Button(variant: .ghost, onPressed: undo, child: Text('๋๋๋ฆฌ๊ธฐ')),
onDismiss: handleDismiss,
)
์ด์ : toastStyle.actionButtonStyle์ paddingยท๋ผ๋ฒจ ์คํ์ผ ๊ฐ์ chrome ๋ฏธ์ธ ์กฐ์ ์ ์ฉ์
๋๋ค. ๋ฒํผ์ variant ์์ฒด๋ฅผ ๋ฐ๊พธ๋ ค๋ฉด action ์ฌ๋กฏ์ ์ํ๋ Button์ ์ง์ ๋ฃ์ด์ผ ํฉ๋๋ค.
โ Don't#
Toast ๋ฅผ ToastLayer/showToast() ์์ด ํ๋ฉด์ ์ง์ ๋ฐฐ์นํ์ง ์๊ธฐ (Flutter)
// โ Toast ์นด๋๋ง ํธ๋ฆฌ ์ด๋๊ฐ์ ๊ทธ๋๋ก ๋ โ ์คํํน/์๋ ๋ซํ/์ ๋๋ฉ์ด์
์์
Column(children: [Toast(message: '์ ์ฅ๋์์ต๋๋ค.', onDismiss: () {})])
์ด์ : Flutter์์ ํ ์คํธ์ ํ์ยท์คํํน(maxStackedEntries)ยท๋ฑ์ฅ/ํด์ฅ ์ ๋๋ฉ์ด์
์ ToastLayer + showToast() ์ธํ๋ผ๊ฐ ๊ด๋ฆฌํฉ๋๋ค. Toast ์นด๋๋ง ํธ๋ฆฌ์ ๋๋ฉด ์ฌ๋ฌ ํ ์คํธ๊ฐ ๊ฒน์น๊ฑฐ๋, ์์น(ToastLocation)์ ์ง์ ์๊ฐ(showDuration)์ด ์ ํ ๋ฐ์๋์ง ์์ต๋๋ค.
์ ๊ทผ์ฑ (Accessibility)#
์คํฌ๋ฆฐ ๋ฆฌ๋#
- Flutter:
Semantics(liveRegion: true)์ ์ฉ์ผ๋ก ์คํฌ๋ฆฐ ๋ฆฌ๋์ ์๋ ์๋ฆผ - Web:
role="status",aria-live="polite"์ ์ฉ์ผ๋ก ๋น์นจ์ ์ ์๋ฆผ
๋ซ๊ธฐ ๋ฒํผ#
onDismiss๊ฐ ์ ๊ณต๋๋ฉดaria-label="Close"์์ฑ์ด ์๋ ๋ซ๊ธฐ ๋ฒํผ ๋ ๋๋ง
ํฌ๋ก์ค ํ๋ซํผ ์ฐจ์ด์ (Platform Differences)#
| ํญ๋ชฉ | Flutter | Web |
|---|---|---|
| ํ ์คํธ ์นด๋ | Toast (ํต์ผ) | Toast (ํต์ผ) |
| ์ธํ๋ผ | ToastLayer + showToast() | ์ธ๋ถ ๊ด๋ฆฌ |
| ์คํํน | ๋ด์ฅ (hover ํ์ฅ, ์ต๋ ๊ฐ์) | ์ธ๋ถ ๊ด๋ฆฌ |
| ๋ซ๊ธฐ ๋ฐฉ์ | ์ค์์ดํ + ํด๋ฆญ + ํ๋ก๊ทธ๋๋ฐ | ๋ซ๊ธฐ ๋ฒํผ ํด๋ฆญ |