SwipeActions | CoUI
๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
LogoCoUI

SwipeActions

์˜†์œผ๋กœ ๋ฐ€๋ฉด ํ–‰ ๋’ค์—์„œ ์•ก์…˜์ด ๋“œ๋Ÿฌ๋‚˜๋Š” ๋ชฉ๋ก ํ–‰ โ€” ๋ณด๊ด€ ยท ๊ณ ์ • ยท ์‚ญ์ œ ๊ฐ™์€ ๋™์ž‘์„ ํ–‰์—์„œ ๋ฐ”๋กœ ์‹คํ–‰

๋ชฉ์ฐจ

SwipeActions ๋Š” ๋ชฉ๋ก ํ–‰์„ ์˜†์œผ๋กœ ๋ฐ€๋ฉด ๊ทธ ๋’ค์—์„œ ์•ก์…˜ ๋ฒ„ํŠผ์ด ๋“œ๋Ÿฌ๋‚˜๋Š” ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. ๋ณด๊ด€ ยท ๊ณ ์ • ยท ์‚ญ์ œ์ฒ˜๋Ÿผ ํ•œ ๋ฒˆ์— ๋๋‚˜๋Š” ๋™์ž‘์„ ํ–‰์„ ์—ด์ง€ ์•Š๊ณ  ๊ณง๋ฐ”๋กœ ์‹คํ–‰ํ•  ๋•Œ ์”๋‹ˆ๋‹ค. ํ–‰์€ child ์ด๊ณ , ์‹œ์ž‘ ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ(leadingActions)์™€ ๋ ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ(trailingActions)์— ์•ก์…˜์„ ๋”ฐ๋กœ ๋‘ก๋‹ˆ๋‹ค.

์•ก์…˜์€ Button ์„ ํ•ฉ์„ฑํ•ด ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •ํ•˜๋Š” ๊ฒƒ์€ ๋ฒ„ํŠผ์ด ๋†“์ด๋Š” ์ž๋ฆฌ์™€ ํ–‰์ด ์›€์ง์ด๋Š” ๊ฑฐ๋ฆฌ๋ฟ์ด๋ผ, ๋ฒ„ํŠผ์˜ ํ…Œ๋งˆ๋ฅผ ๋ฐ”๊พธ๋ฉด ์•ก์…˜๋„ ๊ฐ™์ด ๋ฐ”๋€๋‹ˆ๋‹ค. ํ–‰์ด ์–ด๋””๊นŒ์ง€ ๋”ฐ๋ผ์˜ค๋Š”์ง€ ยท ๋†“์•˜์„ ๋•Œ ์–ด๋””์— ๋ฉˆ์ถ”๋Š”์ง€ ยท ์–ด๋А ์ •๋„ ๋‹น๊ธฐ๋ฉด ์ฒซ ์•ก์…˜์ด ์‹คํ–‰๋˜๋Š”์ง€๋Š” ์ˆ˜์น˜์™€ ํŒ์ •์ด๋ผ Core ๊ฐ€ ํ•œ ๋ฒŒ๋กœ ์ •ํ•˜๊ณ , Flutter ์™€ Web ์ด ๊ฐ™์€ ๋‹ต์„ ์”๋‹ˆ๋‹ค. ๊ฐ™์€ ๋‹น๊น€์€ ๋‘ ํ”Œ๋žซํผ์—์„œ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ƒ…๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ• (Basic Usage)#

SwipeActions(
  trailingActions: [
    CoreSwipeAction(
      label: '๋ณด๊ด€',
      icon: const Icon(LucideIcons.archive),
      onPressed: handleArchive,
    ),
    CoreSwipeAction(
      label: '์‚ญ์ œ',
      icon: const Icon(LucideIcons.trash2),
      variant: CoreSwipeActionVariant.destructive,
      onPressed: handleDelete,
    ),
  ],
  child: Basic(
    leading: const Icon(LucideIcons.mail),
    title: const Text('์ฃผ๊ฐ„ ๋ณด๊ณ ์„œ'),
    subtitle: const Text('์ด๋ฒˆ ์ฃผ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ •๋ฆฌํ–ˆ์–ด์š”'),
  ),
)
SwipeActions(
  trailingActions: [
    CoreSwipeAction(
      label: '๋ณด๊ด€',
      icon: const Icon(LucideIcons.archive),
      onPressed: handleArchive,
    ),
    CoreSwipeAction(
      label: '์‚ญ์ œ',
      icon: const Icon(LucideIcons.trash2),
      variant: CoreSwipeActionVariant.destructive,
      onPressed: handleDelete,
    ),
  ],
  child: Basic(
    leading: const Icon(LucideIcons.mail),
    title: Text('์ฃผ๊ฐ„ ๋ณด๊ณ ์„œ'),
    subtitle: Text('์ด๋ฒˆ ์ฃผ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ •๋ฆฌํ–ˆ์–ด์š”'),
  ),
)

์ฒซ ๋ฒˆ์งธ ์•ก์…˜์ด ๊ฐ€์žฅ ๋ฐ”๊นฅ, ๊ณง ํ™”๋ฉด ๊ฐ€์žฅ์ž๋ฆฌ์— ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์นธ์ž…๋‹ˆ๋‹ค. ์ „์ฒด ์Šค์™€์ดํ”„๊ฐ€ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๋„ ์ด ์•ก์…˜์ž…๋‹ˆ๋‹ค.

๋น ๋ฅธ ์˜ค๋ฒ„๋ผ์ด๋“œ (Chain)#

์ด๋ฏธ ๋งŒ๋“  SwipeActions ์ธ์Šคํ„ด์Šค์— ์Šคํƒ€์ผ์„ ๋น ๋ฅด๊ฒŒ ๋ง๋ถ™์ด๊ณ  ์‹ถ๋‹ค๋ฉด withStyle ์ฒด์ธ์„ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ์ž์˜ swipeActionsStyle ์ธ์ž์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ, ์ด๋ฏธ ๊ตฌ์„ฑ๋œ ์œ„์ ฏ ์œ„์—์„œ ๋ฐ”๋กœ ์ด์–ด ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

class SwipeActionsChainExample extends StatelessWidget {
  const SwipeActionsChainExample({super.key});

  @override
  Widget build(BuildContext context) {
    return SwipeActions(
      trailingActions: [
        CoreSwipeAction(
          label: '๋ณด๊ด€',
          icon: const Icon(LucideIcons.archive),
          onPressed: () => debugPrint('Archived'),
        ),
        CoreSwipeAction(
          label: '์‚ญ์ œ',
          icon: const Icon(LucideIcons.trash2),
          variant: CoreSwipeActionVariant.destructive,
          onPressed: () => debugPrint('Deleted'),
        ),
      ],
      child: Basic(
        leading: const Icon(LucideIcons.mail),
        title: const Text('์ฃผ๊ฐ„ ๋ณด๊ณ ์„œ'),
        subtitle: const Text('์ด๋ฒˆ ์ฃผ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ •๋ฆฌํ–ˆ์–ด์š”'),
      ),
    ).withStyle(
      const CoreSwipeActionsStyle(
        platterColor: CoreColor.token(CoreColors.surfaceContainer),
        fullSwipeScale: 1.08,
      ),
    );
  }
}
class SwipeActionsChainExample extends StatelessComponent {
  const SwipeActionsChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return SwipeActions(
      trailingActions: [
        CoreSwipeAction(
          label: '๋ณด๊ด€',
          icon: const Icon(LucideIcons.archive),
          onPressed: () => print('Archived'),
        ),
        CoreSwipeAction(
          label: '์‚ญ์ œ',
          icon: const Icon(LucideIcons.trash2),
          variant: CoreSwipeActionVariant.destructive,
          onPressed: () => print('Deleted'),
        ),
      ],
      child: Basic(
        leading: const Icon(LucideIcons.mail),
        title: Text('์ฃผ๊ฐ„ ๋ณด๊ณ ์„œ'),
        subtitle: Text('์ด๋ฒˆ ์ฃผ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์ •๋ฆฌํ–ˆ์–ด์š”'),
      ),
    ).withStyle(
      const CoreSwipeActionsStyle(
        platterColor: CoreColor.token(CoreColors.surfaceContainer),
        fullSwipeScale: 1.08,
      ),
    );
  }
}

Live Preview#

ํ–‰์„ ์˜†์œผ๋กœ ๋Œ์–ด ๋ณด์„ธ์š”. ๋‘ ๋ฒˆ์งธ ์˜ˆ์ œ๋Š” SwipeActionScope ๋กœ ๋‘ ํ–‰์„ ๊ฐ์‹ธ, ํ•œ ํ–‰์„ ์—ด๋ฉด ๋‹ค๋ฅธ ํ–‰์ด ๋‹ซํž™๋‹ˆ๋‹ค.

๊ตฌ์กฐ (Anatomy)#

trailingActions ๊ฐ€ ๋“œ๋Ÿฌ๋‚œ ๋ชจ์Šต (ํ–‰์„ ์‹œ์ž‘ ์ชฝ์œผ๋กœ ๋ฐ€์—ˆ์„ ๋•Œ)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ child                โ”‚  (icon)  (icon)  โ”‚   // platter: ํ–‰ ๋’ค์— ๋†“์ธ ๋ 
โ”‚                      โ”‚  label   label   โ”‚   // ํ–‰์ด ๋น„์ผœ ์ค€ ํญ๋งŒ ๋ณด์ธ๋‹ค
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  row โ€” ๋ฐ€๋ฆฐ ๋งŒํผ ์ด๋™     platter
๋ถ€๋ถ„์—ญํ• ์Šฌ๋กฏ
child์˜†์œผ๋กœ ๋ฐ€๋ฆฌ๋Š” ํ–‰ ๋ณธ๋ฌธchild
platter ํ–‰ ๋’ค, ๊ฐ€์žฅ์ž๋ฆฌ์— ๋†“์ธ ๋ . ํ–‰์ด ๋น„์ผœ ์ค€ ํญ๋งŒํผ๋งŒ ๋ณด์ธ๋‹ค leadingActions ยท trailingActions
action ๊ณ ์ • ๋„ˆ๋น„ ์นธ ํ•˜๋‚˜ โ€” ์›ํ˜• ๋ฒ„ํŠผ๊ณผ ๊ทธ ์•„๋ž˜ ๋ผ๋ฒจ CoreSwipeAction
icon์›ํ˜• ๋ฒ„ํŠผ ์•ˆ์˜ ๊ธ€๋ฆฌํ”„CoreSwipeAction.icon
label๋ฒ„ํŠผ ์•„๋ž˜ ํ•œ ์ค„ ์ด๋ฆ„CoreSwipeAction.label

์‚ฌ์šฉ ์‹œ๊ธฐ (When to Use)#

์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

  • ๋ชฉ๋ก์˜ ๊ฐ ํ–‰์—์„œ ๋ณด๊ด€ ยท ๊ณ ์ • ยท ์‚ญ์ œ์ฒ˜๋Ÿผ ํ•œ๋‘ ๋ฒˆ์— ๋๋‚˜๋Š” ๋™์ž‘์ด ๋ฐ˜๋ณต๋  ๋•Œ
  • ํ„ฐ์น˜ ํ™”๋ฉด์—์„œ ํ–‰๋งˆ๋‹ค ๋ฒ„ํŠผ์„ ๋Š˜์–ด๋†“์œผ๋ฉด ๋ชฉ๋ก์ด ์–ด์ˆ˜์„ ํ•ด์งˆ ๋•Œ โ€” ๋™์ž‘์ด ํ–‰ ๋’ค์— ์ˆจ์—ˆ๋‹ค๊ฐ€ ๋ฐ€๋ฉด ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค
  • ๊ฐ€์žฅ ํ”ํ•œ ๋™์ž‘ ํ•˜๋‚˜๋ฅผ ๊ธธ๊ฒŒ ๋ฐ€์–ด ํ•œ ๋ฒˆ์— ๋๋‚ด๊ฒŒ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ(์ „์ฒด ์Šค์™€์ดํ”„)

๋Œ€์‹  ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

  • Button: ๋™์ž‘์ด ๋Š˜ ๋ณด์—ฌ์•ผ ํ•  ๋•Œ. ๋ฐ€๊ธฐ๋Š” ๋ˆˆ์— ๋ณด์ด์ง€ ์•Š๋Š” ์กฐ์ž‘์ด๋ผ ๋งˆ์šฐ์Šค ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐœ๊ฒฌํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ๊ฐ™์€ ๋™์ž‘์˜ ์ง€๋ฆ„๊ธธ์ด๋ฏ€๋กœ, ํ–‰์— ๋ˆˆ์— ๋ณด์ด๋Š” ๊ฒฝ๋กœ๊ฐ€ ๋”ฐ๋กœ ์žˆ๋Š” ํŽธ์ด ์ข‹์Šต๋‹ˆ๋‹ค
  • DropdownMenu ยท ContextMenu: ๋™์ž‘์ด ์„œ๋„ˆ ๊ฐœ๋ฅผ ๋„˜์–ด ํ•œ ์ค„ ๋ฐœํŒ์— ๋‹ด๊ธฐ ์–ด๋ ค์šธ ๋•Œ
  • Swiper: ํ–‰์˜ ๋™์ž‘์ด ์•„๋‹ˆ๋ผ, ํ™”๋ฉด ๊ฐ€์žฅ์ž๋ฆฌ์—์„œ ๋ฐ€์–ด ๋ฉ”๋‰ด๋‚˜ ์‹œํŠธ ๊ฐ™์€ ์˜ค๋ฒ„๋ ˆ์ด ํŒจ๋„์„ ์—ด ๋•Œ

Props / Parameters#

W ๋Š” Flutter์˜ Widget, Web์˜ Jaspr Component ์ž…๋‹ˆ๋‹ค. W? ๋Š” ์„ ํƒ ์Šฌ๋กฏ์ž…๋‹ˆ๋‹ค.

์†์„ฑํƒ€์ž…๊ธฐ๋ณธ๊ฐ’์„ค๋ช…
childWํ•„์ˆ˜์˜†์œผ๋กœ ๋ฐ€๋ฆฌ๋Š” ํ–‰ ๋ณธ๋ฌธ
leadingActions List<CoreSwipeAction<W>> const [] ์‹œ์ž‘ ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์˜ ์•ก์…˜. ํ–‰์„ ๋ ์ชฝ์œผ๋กœ ๋ฐ€๋ฉด ๋“œ๋Ÿฌ๋‚จ. ์ฒซ ํ•ญ๋ชฉ์ด ๊ฐ€์žฅ ๋ฐ”๊นฅ
trailingActions List<CoreSwipeAction<W>> const [] ๋ ์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ์˜ ์•ก์…˜. ํ–‰์„ ์‹œ์ž‘ ์ชฝ์œผ๋กœ ๋ฐ€๋ฉด ๋“œ๋Ÿฌ๋‚จ. ์ฒซ ํ•ญ๋ชฉ์ด ๊ฐ€์žฅ ๋ฐ”๊นฅ
allowsFullSwipe bool true ๊ธธ๊ฒŒ ๋ฐ€๋ฉด ๊ทธ ์ชฝ ์ฒซ ์•ก์…˜์„ ์‹คํ–‰. false ์ด๋ฉด ๋ฐœํŒ์„ ๋„˜๊ธด ๋‹น๊น€์— ์ €ํ•ญ์ด ๊ฑธ๋ฆฌ๊ณ  ๋†“์œผ๋ฉด ์—ด๋ฆผ
fullSwipeThreshold double 0.65 ์ฒซ ์•ก์…˜์ด ์‹คํ–‰๋˜๋Š” ๋‹น๊น€ ๊ฑฐ๋ฆฌ, ํ–‰ ๋„ˆ๋น„์˜ ๋น„์œจ. 0 ์ดˆ๊ณผ 1 ์ดํ•˜
showLabels bool true ๋ฒ„ํŠผ ์•„๋ž˜์— ๋ผ๋ฒจ์„ ๊ทธ๋ฆด์ง€. false ์—ฌ๋„ ๋ผ๋ฒจ์€ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์œผ๋กœ ๋‚จ์Œ
enabled bool? null null ์ด๋ฉด ์ผœ์ง. false ์ด๋ฉด ๋ฐ€๋ฆฌ์ง€ ์•Š๊ณ  ๋Š˜ ๋‹ซํ˜€ ์žˆ์Œ
swipeActionsStyle CoreSwipeActionsStyle? null ๋ชจ๋“  chrome ์ด ์ง€๋‚˜๋Š” ๋‹จ์ผ ์Šฌ๋กฏ (์•„๋ž˜ ํ‘œ ์ฐธ๊ณ )

์•ก์…˜์ด ์—†๋Š” ์ชฝ์œผ๋กœ๋Š” ๋ฐ€๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

CoreSwipeAction ํ•„๋“œ#

์•ก์…˜ ํ•˜๋‚˜๋Š” ์œ„์ ฏ์ด ์•„๋‹ˆ๋ผ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ค์ง€๋Š” ๋ชจ์–‘์€ ํ•ฉ์„ฑ๋œ Button ์ด ์ •ํ•ฉ๋‹ˆ๋‹ค.

ํ•„๋“œํƒ€์ž…๊ธฐ๋ณธ๊ฐ’์„ค๋ช…
label String ํ•„์ˆ˜ ์•ก์…˜์˜ ์ด๋ฆ„. ๋ฒ„ํŠผ ์•„๋ž˜์— ๊ทธ๋ ค์ง€๊ณ (showLabels), ์–ด๋””์„œ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„
onPressed void Function() ํ•„์ˆ˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ ยท ์ „์ฒด ์Šค์™€์ดํ”„๊ฐ€ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ยท ๋ณด์กฐ ๊ธฐ์ˆ ์ด ๊ณ ๋ฅผ ๋•Œ ํ˜ธ์ถœ. ํ˜ธ์ถœ ๋’ค ํ–‰์ด ๋‹ซํž˜
icon W? null ๋ฒ„ํŠผ ์•ˆ์˜ ๊ธ€๋ฆฌํ”„. ๋ผ๋ฒจ์€ ๋ฒ„ํŠผ ์•ˆ์— ๊ทธ๋ ค์ง€์ง€ ์•Š์œผ๋ฏ€๋กœ ์•„์ด์ฝ˜์„ ๋„˜๊น€
variant CoreSwipeActionVariant defaultVariant ์•ก์…˜์˜ ์˜๋ฏธ. defaultVariant ์™€ destructive
enabled bool true false ์ด๋ฉด ๋น„ํ™œ์„ฑ์œผ๋กœ ๊ทธ๋ ค์ง€๊ณ , ์ „์ฒด ์Šค์™€์ดํ”„์˜ "์ฒซ ์•ก์…˜" ์—์„œ ๋น ์ง€๋ฉฐ, ๋ณด์กฐ ๊ธฐ์ˆ ์˜ ์•ก์…˜ ๋ชฉ๋ก์—๋„ ์—†์Œ

SwipeActionScope#

์†์„ฑํƒ€์ž…๊ธฐ๋ณธ๊ฐ’์„ค๋ช…
child W ํ•„์ˆ˜ ๋ฒ”์œ„๋ฅผ ๊ณต์œ ํ•˜๋Š” ํ•˜์œ„ ํŠธ๋ฆฌ. ์•ˆ์˜ SwipeActions ํ–‰๋ผ๋ฆฌ ํ•œ ๋ฒˆ์— ํ•œ ํ–‰๋งŒ ์—ด๋ฆผ

CoreSwipeActionsStyle ํ•„๋“œ#

ํ•„๋“œํƒ€์ž…์„ค๋ช…
actionWidth double? The width of one action cell (logical px). null โ†’ [defaultActionWidth].
actionSpacing double? The space between neighbouring cells (logical px). null โ†’ [defaultActionSpacing].
platterInset double? The space between a platter end and the cell next to it (logical px). null โ†’ [defaultPlatterInset].
platterColor CoreColor? The platter's fill. null โ†’ [defaultPlatterColor].
labelGapStyle CoreGapStyle? The space between a cell's button and its label. null โ†’ [defaultLabelGapStyle].
labelStyle CoreTextStyle? The label's type and colour. null โ†’ [defaultLabelStyle].
fullSwipeScale double? The first action's scale during a full-swipe pull, as a factor. null โ†’ [defaultFullSwipeScale].
actionButtonStyle CoreButtonStyle? Chrome of the composed action buttons, forwarded raw to each Button . Deliberately has no default: each action is a circular Button of the lg rung in the variant its action asks for, and the chrome of that variant is the button's own to design. An empty partial here would only restate it, so null forwards nothing and the button resolves its own defaults.

์Šคํƒ€์ผ ์‹œ์Šคํ…œ (Style System)#

CoreSwipeActionsStyle ์€ ๋ฐœํŒ์„ ๊ทธ๋ฆฌ๋Š” ์ˆซ์ž(actionWidth ยท actionSpacing ยท platterInset ยท platterColor), ๋ผ๋ฒจ(labelGapStyle ยท labelStyle), ์ „์ฒด ์Šค์™€์ดํ”„ ์‹ ํ˜ธ(fullSwipeScale), ๊ทธ๋ฆฌ๊ณ  ํ•ฉ์„ฑ๋œ ๋ฒ„ํŠผ์— ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ๋˜๋Š” actionButtonStyle ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์•ก์…˜์˜ ๋ฒ„ํŠผ ๋ชจ์–‘์€ ์ด ์Šฌ๋กฏ์ด ์•„๋‹ˆ๋ผ Button ์˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ณ€ํ˜•(defaultVariant ยท destructive)์ด ๊ฐ๊ฐ Button ์˜ ๋ณ€ํ˜•์„ ๊ณ ๋ฅด๋ฏ€๋กœ, ์‚ญ์ œ ์•ก์…˜๋งŒ ๋‹ค๋ฅด๊ฒŒ ์น ํ•˜๋ ค๋ฉด Button ์˜ ํ…Œ๋งˆ๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. actionButtonStyle ์€ ์ผ๋ถ€๋Ÿฌ ๊ธฐ๋ณธ๊ฐ’์ด ์—†์Šต๋‹ˆ๋‹ค โ€” null ์ด๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ณ , ๊ฐ ๋ฒ„ํŠผ์ด ์ž๊ธฐ ๋ณ€ํ˜•์˜ chrome ์„ ๊ทธ๋Œ€๋กœ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

Resolve chain#

design system default
  โ†’ CoreSwipeActionsTheme.style
  โ†’ widget.swipeActionsStyle

๋ณ€ํ˜•๋ณ„ ์Šฌ๋กฏ(variantStyles)์€ ์—†์Šต๋‹ˆ๋‹ค. ์•ก์…˜์˜ ๋ณ€ํ˜•์€ Button ์ด ๊ทธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์—ด๋ฆฌ๋Š” ๊ฑฐ๋ฆฌ#

ํ–‰์ด ์—ด๋ฆฌ๋Š” ๊ฑฐ๋ฆฌ๋Š” ์นธ์˜ ์ˆ˜์™€ ์ด ์Šฌ๋กฏ์˜ ์„ธ ์ˆซ์ž๊ฐ€ ์ •ํ•ฉ๋‹ˆ๋‹ค. ์นธ ๋„ˆ๋น„๋ฅผ ๊ธ€์ž๋กœ ์žฌ์ง€ ์•Š๊ณ  ์ˆซ์ž๋กœ ์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋‘ ํ”Œ๋žซํผ์ด ํ…์ŠคํŠธ๋ฅผ ๋ฐฐ์น˜ํ•˜์ง€ ์•Š๊ณ ๋„ ๊ฐ™์€ ๊ฑฐ๋ฆฌ๋ฅผ ์••๋‹ˆ๋‹ค.

๋ฐœํŒ ๋„ˆ๋น„ = 2 ร— platterInset + n ร— actionWidth + (n โˆ’ 1) ร— actionSpacing

๊ธฐ๋ณธ๊ฐ’์—์„œ ์•ก์…˜์ด ํ•˜๋‚˜๋ฉด 72, ๋‘˜์ด๋ฉด 132, ์…‹์ด๋ฉด 192 ์ž…๋‹ˆ๋‹ค. ๋ผ๋ฒจ์€ ์นธ ๋„ˆ๋น„ ์•ˆ์— ํ•œ ์ค„๋กœ ๊ทธ๋ ค์ง€๊ณ  ๋„˜์น˜๋ฉด ๋ง์ค„์ž„ํ‘œ๋กœ ์ž˜๋ฆฌ๋ฏ€๋กœ, ๋ผ๋ฒจ์„ ๊ธธ๊ฒŒ ์“ฐ๋ ค๋ฉด actionWidth ๋ฅผ ํ•จ๊ป˜ ํ‚ค์›๋‹ˆ๋‹ค.

ํ…Œ๋งˆ๋กœ ๋ฎ์–ด์“ฐ๊ธฐ#

ThemeData.fromCore(
  CoreThemePresets.light,
  coreComponentTheme: CoreComponentTheme(
    swipeActions: CoreSwipeActionsTheme(
      style: CoreSwipeActionsStyle(
        platterColor: CoreColor.token(CoreColors.surfaceContainer),
      ),
    ),
  ),
)

๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’#

์•„๋ž˜ ๊ฐ’์€ ์ฝ”๋“œ๊ฐ€ ์ •๋‹ต์„ ๊ฐ€๋ฆฌ์ง€ ๋ชปํ•ด ๋””์ž์ด๋„ˆ๊ฐ€ ํŒ๋‹จํ•œ ๊ฐ’์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ ๊ฐ€์žฅ ๋ณด์ˆ˜์ ์ธ ์ชฝ์ด๊ณ , ๋Œ€์•ˆ์€ ๊ฐ™์€ ์ž๋ฆฌ์—์„œ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š” ๋‹ค๋ฅธ ์„ ํƒ์ž…๋‹ˆ๋‹ค.

๊ฐ’๊ธฐ๋ณธ๊ฐ’๋Œ€์•ˆ๋ฐ”๊พธ๋Š” ๊ณณ
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ๋ฐœํŒ ์ƒ‰ ํˆฌ๋ช… โ€” ๋ฒ„ํŠผ์ด ์ƒ‰์„ ๊ฐ–๋Š”๋‹ค ํ†ค ์žˆ๋Š” ์ปจํ…Œ์ด๋„ˆ ์ƒ‰(CoreColors.surfaceContainer ๋“ฑ) โ€” ์นธ๋“ค์ด ํ•œ ๋ ๋กœ ์ฝํžŒ๋‹ค platterColor
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ๋ผ๋ฒจ ์ž๋ฆฌ ๋ฒ„ํŠผ ์•„๋ž˜ ๋ผ๋ฒจ ์—†์ด ์•„์ด์ฝ˜๋งŒ. ๋ฒ„ํŠผ ์˜†์—๋Š” ๋‘์ง€ ์•Š๋Š”๋‹ค โ€” ๊ณ ์ • ๋„ˆ๋น„ ์นธ์ด ์•„์ด์ฝ˜๊ณผ ๋ผ๋ฒจ์„ ๋‚˜๋ž€ํžˆ ๋‹ด์ง€ ๋ชปํ•˜๊ณ , ๊ธ€์ž์— ๋งž์ถฐ ๋Š˜๋ฆฌ๋ฉด ์นธ ๋„ˆ๋น„๊ฐ€ ๊ธ€๊ผด์— ๋‹ฌ๋ผ์ง„๋‹ค showLabels
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ์ „์ฒด ์Šค์™€์ดํ”„ ํ™•๋Œ€ 1 (ํ™•๋Œ€ ์—†์Œ) 1.08 โ€” ์ฒซ ์•ก์…˜ ์นธ์ด ์ปค์ ธ "๋†“์œผ๋ฉด ์‹คํ–‰๋œ๋‹ค" ๋Š” ์‹ ํ˜ธ๊ฐ€ ๋œ๋‹ค fullSwipeScale
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ์•ก์…˜ ์นธ ํฌ๊ธฐ ์ปจํŠธ๋กค ์‚ฌ๋‹ค๋ฆฌ์˜ lg ๋‹จ, 48 ํ•œ ๋‹จ ์œ„ heightXl, 56 โ€” ์นธ ๋„ˆ๋น„์™€ ๋ฒ„ํŠผ ํฌ๊ธฐ๊ฐ€ ๊ฐ™์€ ๋‹จ์„ ์ฝ์œผ๋ฏ€๋กœ ๋‘˜์„ ํ•จ๊ป˜ ์˜ฎ๊ธด๋‹ค Core ๊ธฐ๋ณธ๊ฐ’
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ์นธ ๊ฐ„๊ฒฉ ยท ๋ฐœํŒ ์•ˆ์ชฝ ์—ฌ๋ฐฑ CoreSpace.space12 4px ๊ฒฉ์ž์˜ ๋‹ค๋ฅธ ๋‹จ(8 ยท 16) actionSpacing ยท platterInset
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ๋А๋‚Œ ์ˆ˜์น˜ ๋˜์ง€๋Š” ์†๋„ 400 px/s ยท ์ „์ฒด ์Šค์™€์ดํ”„ ๋ฌธํ„ฑ ํ–‰ ๋„ˆ๋น„์˜ 0.65 ยท ์ €ํ•ญ 0.25 ๋‹ค๋ฅธ ์ˆ˜์น˜ ๋ฌธํ„ฑ์€ fullSwipeThreshold, ๋‚˜๋จธ์ง€๋Š” Core ์ƒ์ˆ˜
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ์ •์ฐฉ ์‹œ๊ฐ„ CoreDuration.normal, 200 ms CoreDuration.moderate, 300 ms Core ์ƒ์ˆ˜
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ์ „์ฒด ์Šค์™€์ดํ”„ ๊ธฐ๋ณธ ์ผœ์ง ๊บผ์ง โ€” ์ฒซ ์•ก์…˜์ด ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋Š” ๋™์ž‘์ผ ๋•Œ allowsFullSwipe
๋””์ž์ด๋„ˆ ํŒ๋‹จ ๊ฐ’ ๊ธฐ๋ณธ ์•ก์…˜์˜ ๋ฒ„ํŠผ ํ†ค ์žˆ๋Š” secondary ๋ธŒ๋žœ๋“œ ์ฑ„์›€์˜ primary CoreSwipeActionVariant.toButtonVariant

๋ณ€ํ˜• (Variants)#

์•ก์…˜ ๋ณ€ํ˜•#

์•ก์…˜์˜ ์˜๋ฏธ๋ฅผ variant ๋กœ ๋งํ•ฉ๋‹ˆ๋‹ค. ์˜๋ฏธ๊ฐ€ ํ•ฉ์„ฑ๋œ Button ์˜ ๋ณ€ํ˜•์„ ๊ณ ๋ฅด๋ฏ€๋กœ ๋ชจ์–‘์€ Button ํ…Œ๋งˆ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

๋ณ€ํ˜•์˜๋ฏธButton ๋ณ€ํ˜•
CoreSwipeActionVariant.defaultVariant ๋ณด๊ด€ ยท ๊ณ ์ • ยท ํ‘œ์‹œ์ฒ˜๋Ÿผ ์ผ๋ฐ˜์ ์ธ ๋™์ž‘ secondary
CoreSwipeActionVariant.destructive ์‚ญ์ œ์ฒ˜๋Ÿผ ๋ฌด์–ธ๊ฐ€๋ฅผ ์—†์• ๋Š” ๋™์ž‘ destructive

์–‘์ชฝ ๊ฐ€์žฅ์ž๋ฆฌ#

leadingActions ์™€ trailingActions ๋ฅผ ํ•จ๊ป˜ ๋„˜๊ธฐ๋ฉด ํ–‰์„ ์–ด๋А ์ชฝ์œผ๋กœ ๋ฏธ๋А๋ƒ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ฐœํŒ์ด ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค. ๋‘ ๋ฐฉํ–ฅ์€ ์ฝ๊ธฐ ๋ฐฉํ–ฅ์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ์˜ค๋ฅธ์ชฝ์—์„œ ์™ผ์ชฝ์œผ๋กœ ์“ฐ๋Š” ๋กœ์ผ€์ผ์—์„œ๋Š” ์ขŒ์šฐ๊ฐ€ ๋’ค์ง‘ํž™๋‹ˆ๋‹ค.

SwipeActions(
  leadingActions: [
    CoreSwipeAction(
      label: '๊ณ ์ •',
      icon: const Icon(LucideIcons.pin),
      onPressed: handlePin,
    ),
  ],
  trailingActions: [
    CoreSwipeAction(
      label: '์‚ญ์ œ',
      icon: const Icon(LucideIcons.trash2),
      variant: CoreSwipeActionVariant.destructive,
      onPressed: handleDelete,
    ),
  ],
  child: row,
)

๋ผ๋ฒจ ์—†์ด#

showLabels: false ๋Š” ๋ฒ„ํŠผ๋งŒ ๋‚จ๊น๋‹ˆ๋‹ค. ๋ผ๋ฒจ์€ ๊ทธ๋ ค์ง€์ง€ ์•Š์•„๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์œผ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค.

SwipeActions(
  showLabels: false,
  trailingActions: [
    CoreSwipeAction(
      label: '์‚ญ์ œ',
      icon: const Icon(LucideIcons.trash2),
      variant: CoreSwipeActionVariant.destructive,
      onPressed: handleDelete,
    ),
  ],
  child: row,
)

์ „์ฒด ์Šค์™€์ดํ”„ ๋„๊ธฐ#

์ฒซ ์•ก์…˜์ด ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋Š” ๋™์ž‘์ด๋ฉด allowsFullSwipe: false ๋กœ ๊ธธ๊ฒŒ ๋ฐ€์–ด ์‹คํ–‰๋˜๋Š” ๊ธธ์„ ๋ง‰์Šต๋‹ˆ๋‹ค. ๋ฐœํŒ๋ณด๋‹ค ๋” ๋ฐ€๋ฉด ํ–‰์ด ์ €ํ•ญ์„ ๋ฐ›๊ณ , ๋†“์œผ๋ฉด ์—ด๋ฆฝ๋‹ˆ๋‹ค.

SwipeActions(
  allowsFullSwipe: false,
  trailingActions: [
    CoreSwipeAction(
      label: '์‚ญ์ œ',
      icon: const Icon(LucideIcons.trash2),
      variant: CoreSwipeActionVariant.destructive,
      onPressed: handleDelete,
    ),
  ],
  child: row,
)

๋™์ž‘ ์ŠคํŽ™ (Behavior)#

์ˆ˜์น˜๋Š” ๋…ผ๋ฆฌ ํ”ฝ์…€(Web ์€ CSS ํ”ฝ์…€)์ด๊ณ  ์†๋„๋Š” ํ”ฝ์…€/์ดˆ์ž…๋‹ˆ๋‹ค.

์ธํ„ฐ๋ž™์…˜#

  • ๋ฐ€๊ธฐ: ํฌ์ธํ„ฐ๊ฐ€ ๊ฐ€๋กœ๋กœ 18 px ๋„˜๊ฒŒ ์›€์ง์ด๊ณ  ์„ธ๋กœ ์ด๋™์— ๋ฐ€๋ฆฌ์ง€ ์•Š์œผ๋ฉด ํ–‰์ด ํฌ์ธํ„ฐ๋ฅผ ๋”ฐ๋ผ ์›€์ง์ž…๋‹ˆ๋‹ค. ์„ธ๋กœ ์ด๋™์ด ๋จผ์ € ํฌ๋ฉด ๊ทธ ์ž…๋ ฅ์€ ์Šคํฌ๋กค๋Ÿฌ์˜ ๊ฒƒ์ด๋ผ ํ–‰์€ ๋ฐ˜์‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ SwipeActions ๋ฅผ ์Œ“์€ ๋ชฉ๋ก์˜ ์„ธ๋กœ ์Šคํฌ๋กค์„ ๊ฐ€๋กœ์ฑ„์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ๊ฐ€๊ธฐ: ๋ฐœํŒ ๋„ˆ๋น„ ์•ˆ์—์„œ๋Š” ํฌ์ธํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ผ๊ฐ‘๋‹ˆ๋‹ค. ๋„˜์–ด๊ฐ€๋ฉด allowsFullSwipe ๊ฐ€ ์ผœ์ง„ ์ชฝ์€ ํ–‰ ๋„ˆ๋น„๊นŒ์ง€ ๊ณ„์† ๋”ฐ๋ผ๊ฐ€๊ณ , ๊บผ์ง„ ์ชฝ์€ ๋„˜์นœ ๊ฑฐ๋ฆฌ์˜ 25 % ๋งŒ ๋”ฐ๋ผ๊ฐ€ ๋Š˜์–ด๋‚˜๋Š” ๋“ฏ์ด ์ €ํ•ญํ•ฉ๋‹ˆ๋‹ค.
  • ๋†“๊ธฐ: ๋†“์€ ์ˆœ๊ฐ„ ๋‹ค์Œ ๊ทœ์น™์„ ์œ„์—์„œ๋ถ€ํ„ฐ ๋ณด๊ณ  ์ฒ˜์Œ ๋งž๋Š” ๊ฒƒ์ด ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.
    1. ๋‹ซํžŒ ์ž๋ฆฌ(1 px ์ด๋‚ด)์ด๊ฑฐ๋‚˜ ์•ก์…˜์ด ์—†๋Š” ์ชฝ์ด๋ฉด ๋‹ซํž™๋‹ˆ๋‹ค.
    2. ์•ก์…˜์—์„œ ๋ฉ€์–ด์ง€๋Š” ๋ฐฉํ–ฅ์œผ๋กœ 400 px/s ์ด์ƒ์ด๋ฉด ๋‹ซํž™๋‹ˆ๋‹ค. ์ „์ฒด ์Šค์™€์ดํ”„ ๊ฑฐ๋ฆฌ๋ฅผ ๋„˜๊ฒผ์–ด๋„ ๋˜๋Œ๋ฆฌ๋Š” ์†๊ธธ์ด๋ฉด ๋‹ซํžˆ๋ฏ€๋กœ, ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋Š” ์•ก์…˜์ด ๋˜๋Œ๋ฆฌ๋˜ ์ œ์Šค์ฒ˜์—์„œ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    3. ๋ฐœํŒ์„ ๋„˜๊ธฐ๊ณ  ์ „์ฒด ์Šค์™€์ดํ”„ ๊ฑฐ๋ฆฌ ์ด์ƒ์ด๋ฉด(์ผœ์ง„ ์ชฝ) ๊ทธ ์ชฝ ์ฒซ ์•ก์…˜์ด ์‹คํ–‰๋˜๊ณ  ํ–‰์ด ๋‹ซํž™๋‹ˆ๋‹ค. ๊ฑฐ๋ฆฌ๋Š” fullSwipeThreshold ร— ํ–‰ ๋„ˆ๋น„์ด๊ณ , ๋ฐœํŒ๋ณด๋‹ค ์งง์„ ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค.
    4. ์•ก์…˜ ์ชฝ์œผ๋กœ 400 px/s ์ด์ƒ์ด๋ฉด, ์งง๊ฒŒ ๋‹น๊ฒผ์–ด๋„ ์—ด๋ฆฝ๋‹ˆ๋‹ค.
    5. ๊ทธ ๋ฐ–์—๋Š” ๊ฐ€๊นŒ์šด ์ž๋ฆฌ์— ๋ฉˆ์ถฅ๋‹ˆ๋‹ค. ๋ฐœํŒ์˜ ์ ˆ๋ฐ˜ ์ด์ƒ ์—ด๋ ธ์œผ๋ฉด ์—ด๋ฆฌ๊ณ , ์•„๋‹ˆ๋ฉด ๋‹ซํž™๋‹ˆ๋‹ค.
  • ์—ด๋ฆฐ ํ–‰: ์—ด๋ฆฐ ํ–‰์„ ํƒญํ•˜๋ฉด child ์— ๋‹ฟ์ง€ ์•Š๊ณ  ํ–‰์ด ๋‹ซํž™๋‹ˆ๋‹ค. ์—ด๋ฆฐ ํ–‰์—์„œ ๋‹ค์‹œ ๋ฐ€ ์ˆ˜ ์žˆ๊ณ , ๊ทธ ๋“œ๋ž˜๊ทธ๋Š” ์—ด๋ ค ์žˆ๋˜ ์ชฝ์— ๋ฌถ์—ฌ ๋ฐ˜๋Œ€์ชฝ ๋ฐœํŒ์œผ๋กœ ๋„˜์–ด๊ฐ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์•ก์…˜ ๋ˆ„๋ฅด๊ธฐ: ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด onPressed ๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  ํ–‰์ด ๋‹ซํž™๋‹ˆ๋‹ค. ์ „์ฒด ์Šค์™€์ดํ”„๊ฐ€ ์‹คํ–‰ํ•  ๋•Œ๋„ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  • ์ทจ์†Œ: ์‹œ์Šคํ…œ์ด ํฌ์ธํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐ€๋ฉด(์Šคํฌ๋กค ์‹œ์ž‘ ยท ํฌ์ธํ„ฐ ์ทจ์†Œ) ํ–‰์€ ๋“œ๋ž˜๊ทธ๊ฐ€ ์‹œ์ž‘๋œ ์ž๋ฆฌ๋กœ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค. ์ทจ์†Œ๋œ ๋‹น๊น€์€ ๊ฑฐ๋ฆฌ์™€ ์ƒ๊ด€์—†์ด ์•ก์…˜์„ ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ํ˜ธ๋ฒ„ ยท ํฌ์ปค์Šค: ํ–‰ ์ž์ฒด์—๋Š” ํ˜ธ๋ฒ„ ๋ฐ˜์‘์ด ์—†์Šต๋‹ˆ๋‹ค. ์•ก์…˜ ๋ฒ„ํŠผ์€ Button ์˜ ํ˜ธ๋ฒ„ ยท ํฌ์ปค์Šค ๋ฐ˜์‘์„ ๊ทธ๋Œ€๋กœ ๊ฐ–์Šต๋‹ˆ๋‹ค.

์ƒํƒœ ์ „ํ™˜#

  • ํ–‰์€ ๋‹ซํž˜ ยท ์‹œ์ž‘ ์ชฝ ์—ด๋ฆผ ยท ๋ ์ชฝ ์—ด๋ฆผ ์…‹ ์ค‘ ํ•˜๋‚˜์— ๋ฉˆ์ถ”๊ณ , ํ•œ ๋ฒˆ์— ํ•œ ์ชฝ๋งŒ ์—ด๋ฆฝ๋‹ˆ๋‹ค.
  • ์ฒซ ์•ก์…˜์ด ๋น„ํ™œ์„ฑ์ด๋ฉด ๊ทธ ์ชฝ ์ „์ฒด ์Šค์™€์ดํ”„๊ฐ€ ๊บผ์ ธ, ๊ธธ๊ฒŒ ๋ฐ€์–ด๋„ ์—ด๋ฆฌ๊ธฐ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.
  • enabled: false ์ด๋ฉด ํ–‰์€ ๋ฐ€๋ฆฌ์ง€ ์•Š๊ณ  ๋Š˜ ๋‹ซํ˜€ ์žˆ์œผ๋ฉฐ, ์•ก์…˜์€ ์–ด๋А ๊ฒฝ๋กœ๋กœ๋„ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์•ก์…˜์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์—ด๋ฆฐ ํ–‰์€ ๋‹ซํž™๋‹ˆ๋‹ค. ์—ด๋ฆฐ ๊ฑฐ๋ฆฌ๊ฐ€ ์ด์ „ ๊ฐœ์ˆ˜๋กœ ์ •ํ•ด์ง„ ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • SwipeActionScope ์•ˆ์—์„œ๋Š” ํ•œ ํ–‰์ด ์›€์ง์ด๊ธฐ ์‹œ์ž‘ํ•˜๋Š” ์ˆœ๊ฐ„ ์—ด๋ ค ์žˆ๋˜ ๋‹ค๋ฅธ ํ–‰์ด ๋‹ซํž™๋‹ˆ๋‹ค. ํฌ์ปค์Šค๊ฐ€ ๋‹ค๋ฅธ ํ–‰์˜ ๋ฐœํŒ์— ๋‹ฟ์•„ ๊ทธ ํ–‰์ด ์—ด๋ฆด ๋•Œ๋„ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋ฒ”์œ„๊ฐ€ ์—†์œผ๋ฉด ํ–‰์€ ์„œ๋กœ ๋…๋ฆฝ์ž…๋‹ˆ๋‹ค. ์Šคํฌ๋กคํ•˜๊ฑฐ๋‚˜ ํ–‰ ๋ฐ–์„ ํƒญํ•ด๋„ ์—ด๋ฆฐ ํ–‰์ด ๋‹ซํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ํ”„๋กœ๊ทธ๋žจ์—์„œ ํ–‰์„ ์—ด๊ณ  ๋‹ซ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์—†์Šต๋‹ˆ๋‹ค.

์• ๋‹ˆ๋ฉ”์ด์…˜#

  • ์ •์ฐฉ: ๋†“์€ ํ–‰์€ CoreDuration.normal(200 ms)์— ๊ฑธ์ณ ๋ฉˆ์ถœ ์ž๋ฆฌ๋กœ ๊ฐ‘๋‹ˆ๋‹ค. Flutter ๋Š” ๋†“๋Š” ์†๋„๋ฅผ ์ด์–ด๋ฐ›๋Š” ์ž„๊ณ„ ๊ฐ์‡  ์Šคํ”„๋ง(ํŠ•๊น€ ์—†์Œ), Web ์€ CoreEasing.enter ๊ณก์„ ์˜ CSS ์ „ํ™˜(ํ–‰์˜ transform ๊ณผ ๋ฐœํŒ์˜ ๋„ˆ๋น„)์ž…๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ๋„˜์น˜์ง€ ์•Š๊ณ  ๊ฐ™์€ ์‹œ๊ฐ„์— ๋ฉˆ์ถ”์ง€๋งŒ ๊ณก์„ ์ด ๊ฐ™์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์Šคํ”„๋ง์€ ๋†“๋Š” ์†๋„์—์„œ ์ถœ๋ฐœํ•˜๋ฏ€๋กœ ์•ž ์ ˆ๋ฐ˜์ด ๋” ๋น ๋ฆ…๋‹ˆ๋‹ค.
  • ๋“œ๋ž˜๊ทธ ์ค‘: ์ •์ฐฉ ์ „ํ™˜์ด ์—†์Šต๋‹ˆ๋‹ค. ํ–‰์ด ํฌ์ธํ„ฐ๋ฅผ ์ง€์—ฐ ์—†์ด ๋”ฐ๋ผ๊ฐ‘๋‹ˆ๋‹ค.
  • ์ „์ฒด ์Šค์™€์ดํ”„ ์‹ ํ˜ธ: fullSwipeScale ์ด 1 ๋ณด๋‹ค ํฌ๋ฉด ์ฒซ ์•ก์…˜ ์นธ์ด ์ „์ฒด ์Šค์™€์ดํ”„ ๊ฑฐ๋ฆฌ์— ๋“ค์–ด์„œ๋Š” ๋™์•ˆ ๊ทธ ๋ฐฐ์œจ๋กœ ์ปค์ง‘๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’ 1 ์€ ์‹ ํ˜ธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • ๋™์ž‘ ์ค„์ด๊ธฐ: ์ •์ฐฉ ์‹œ๊ฐ„์ด ์ „ํ™˜ ๋‹จ๊ณ„ ํ† ํฐ์ด๋ผ ๋™์ž‘ ์ค„์ด๊ธฐ๋ฅผ ์ผœ๋ฉด ๋‘ ํ”Œ๋žซํผ ๋ชจ๋‘ ๋ฉˆ์ถœ ์ž๋ฆฌ๋กœ ๊ณง์žฅ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ๋“œ๋ž˜๊ทธ๊ฐ€ ํฌ์ธํ„ฐ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋Š” ๊ฒƒ์€ ๊ทธ๋Œ€๋กœ์ž…๋‹ˆ๋‹ค.

์‚ฌ์šฉ ๊ฐ€์ด๋“œ๋ผ์ธ (Usage Guidelines)#

Do#

์ฒซ ๋ฒˆ์งธ ์•ก์…˜์—๋Š” ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ํ”ํ•œ ๋™์ž‘์„ ๋‘๊ธฐ

SwipeActions(
  trailingActions: [
    CoreSwipeAction(
      label: '๋ณด๊ด€',
      icon: const Icon(LucideIcons.archive),
      onPressed: handleArchive,
    ),
    CoreSwipeAction(
      label: '์‚ญ์ œ',
      icon: const Icon(LucideIcons.trash2),
      variant: CoreSwipeActionVariant.destructive,
      onPressed: handleDelete,
    ),
  ],
  child: row,
)

์ด์œ : ๊ธธ๊ฒŒ ๋ฏธ๋Š” ์ œ์Šค์ฒ˜๋Š” ์ฒซ ์•ก์…˜์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ˆ˜๋กœ ์ผ์–ด๋‚˜๋„ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋™์ž‘์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Don't#

๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋Š” ๋™์ž‘์„ ์ „์ฒด ์Šค์™€์ดํ”„์˜ ์ฒซ ์•ก์…˜์œผ๋กœ ๋‘๊ธฐ

// โŒ ๊ธธ๊ฒŒ ๋ฐ€๋ฉด ํ™•์ธ ์—†์ด ์‚ญ์ œ๋œ๋‹ค
SwipeActions(
  trailingActions: [
    CoreSwipeAction(
      label: '์‚ญ์ œ',
      icon: const Icon(LucideIcons.trash2),
      variant: CoreSwipeActionVariant.destructive,
      onPressed: handleDelete,
    ),
  ],
  child: row,
)

์ด์œ : ๊ธธ๊ฒŒ ๋ฏธ๋Š” ๋™์ž‘์€ ํ•œ ์†์œผ๋กœ ์Šคํฌ๋กคํ•˜๋‹ค๊ฐ€๋„ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค. ์ฒซ ์•ก์…˜์ด ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋‹ค๋ฉด allowsFullSwipe: false ๋กœ ๋ง‰๊ฑฐ๋‚˜, ์‚ญ์ œ ๋’ค ์‹คํ–‰ ์ทจ์†Œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Do#

๋ชฉ๋ก์€ SwipeActionScope ๋กœ ๊ฐ์‹ธ ํ•œ ๋ฒˆ์— ํ•œ ํ–‰๋งŒ ์—ด๊ธฐ

SwipeActionScope(
  child: Column(
    children: [
      for (final mail in mails) buildRow(mail),
    ],
  ),
)

์ด์œ : ์—ด๋ฆฐ ํ–‰์ด ์—ฌ๋Ÿฟ์ด๋ฉด ์–ด๋А ํ–‰์˜ ์•ก์…˜์ด ๋ˆŒ๋ฆด์ง€ ์•Œ์•„๋ณด๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๋ฒ”์œ„๊ฐ€ ์—†์œผ๋ฉด ํ–‰์€ ์„œ๋กœ ๋‹ซ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Don't#

๋ผ๋ฒจ์„ ๊ธธ๊ฒŒ ์“ฐ๊ธฐ

// โŒ ์นธ ๋„ˆ๋น„(48) ์•ˆ์— ํ•œ ์ค„๋กœ ๋“ค์–ด๊ฐ€์ง€ ์•Š๋Š”๋‹ค
CoreSwipeAction(
  label: '์ฝ์ง€ ์•Š์Œ์œผ๋กœ ํ‘œ์‹œ',
  icon: const Icon(LucideIcons.mailOpen),
  onPressed: handleMarkUnread,
)

์ด์œ : ์นธ ๋„ˆ๋น„๋Š” ์ˆซ์ž๋กœ ์ •ํ•ด์ ธ ์žˆ์–ด ๋ผ๋ฒจ์ด ๊ธธ๋ฉด ๋ง์ค„์ž„ํ‘œ๋กœ ์ž˜๋ฆฝ๋‹ˆ๋‹ค. ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์€ ์ž˜๋ฆฌ์ง€ ์•Š์ง€๋งŒ ํ™”๋ฉด์—์„œ๋Š” ์ฝ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‘์„ธ ๊ธ€์ž๋กœ ์ค„์ด๊ฑฐ๋‚˜ showLabels: false ์™€ ์•„์ด์ฝ˜์œผ๋กœ ๋งํ•ฉ๋‹ˆ๋‹ค.

Don't#

child ์•ˆ์— ๊ฐ€๋กœ๋กœ ์›€์ง์ด๋Š” ๋‹ค๋ฅธ ์ œ์Šค์ฒ˜๋ฅผ ๋„ฃ๊ธฐ

์ด์œ : ๊ฐ€๋กœ ์Šฌ๋ผ์ด๋” ยท ๊ฐ€๋กœ ์Šคํฌ๋กค ์˜์—ญ์€ ๊ฐ™์€ ๊ฐ€๋กœ ๋“œ๋ž˜๊ทธ๋ฅผ ๋‹คํˆฌ๋ฏ€๋กœ ์–ด๋А ์ชฝ์ด ์ด๊ธธ์ง€ ์˜ˆ์ธกํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ํ–‰ ์ „์ฒด๋ฅผ ๋ฏธ๋Š” ์ œ์Šค์ฒ˜๊ฐ€ ํ•„์š”ํ•˜๋ฉด ํ–‰ ์•ˆ์—๋Š” ๊ฐ€๋กœ ์ œ์Šค์ฒ˜๋ฅผ ๋‘์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ ‘๊ทผ์„ฑ (Accessibility)#

์—ญํ• #

  • Flutter: ํ–‰์— ์•ก์…˜๋งˆ๋‹ค ๋งž์ถค ์‹œ๋งจํ‹ฑ ์•ก์…˜(CustomSemanticsAction)์ด ๊ฑธ๋ฆฝ๋‹ˆ๋‹ค. ์ด๋ฆ„์€ CoreSwipeAction.label ์ด๊ณ  ๋น„ํ™œ์„ฑ ์•ก์…˜์€ ๋น ์ง‘๋‹ˆ๋‹ค. ๋ฐœํŒ์˜ ๋ฒ„ํŠผ์€ button ์—ญํ• ์— ๊ฐ™์€ ๋ผ๋ฒจ์„ ์ด๋ฆ„์œผ๋กœ ๊ฐ–๊ณ , ํ™”๋ฉด์— ๋ณด์ด๋Š” ๋ผ๋ฒจ ๊ธ€์ž๋Š” ์‹œ๋งจํ‹ฑ์—์„œ ์ œ์™ธํ•ด ๋‘ ๋ฒˆ ์ฝํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐœํŒ์€ ๋‹ซํžŒ ํ–‰์—์„œ๋„ ์œ„์ ฏ ํŠธ๋ฆฌ์— ์žˆ์–ด(ํญ 0 ์œผ๋กœ ์ž˜๋ ค ์žˆ์Šต๋‹ˆ๋‹ค) ํ‚ค๋ณด๋“œ๊ฐ€ ๋‹ฟ์ง€๋งŒ, ๊ทธ ๋ฒ„ํŠผ์€ ๊ทธ ์ชฝ์ด ์—ด๋ ค ํ™”๋ฉด์— ๋“œ๋Ÿฌ๋‚˜๊ธฐ ์ „๊นŒ์ง€ ์‹œ๋งจํ‹ฑ ํŠธ๋ฆฌ์—์„œ ๋น ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ํ™”๋ฉด์— ์—†๋Š” ๋ฒ„ํŠผ์„ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ์ฐพ๊ฒŒ ํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด์„œ์ด๊ณ , ๋‹ซํžŒ ํ–‰์˜ ์•ก์…˜์€ ๋งž์ถค ์•ก์…˜์œผ๋กœ ๊ณ ๋ฆ…๋‹ˆ๋‹ค.
  • Web: ๋ฐœํŒ์€ DOM ์— ๋Š˜ ์žˆ๊ณ (display: none ยท visibility: hidden ์ด ์•„๋‹ˆ๋ผ ๊ฐ€๋กœ๋กœ ์ž˜๋ฆด ๋ฟ์ž…๋‹ˆ๋‹ค), ๊ฐ ์•ก์…˜์€ <button> ์ž…๋‹ˆ๋‹ค. ์ด๋ฆ„์€ aria-label ์ด ๋ผ๋ฒจ ๋ฌธ์ž์—ด์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์˜ค๊ณ , ํ™”๋ฉด์— ๋ณด์ด๋Š” ๋ผ๋ฒจ ๊ธ€์ž๋Š” aria-hidden ์ž…๋‹ˆ๋‹ค. enabled: false ์ธ ํ–‰์—๋Š” ๋ฐœํŒ์ด ๋ Œ๋”๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํ‚ค๋ณด๋“œ#

๋‘ ํ”Œ๋žซํผ์˜ ํ‚ค๋ณด๋“œ ๊ฒฝ๋กœ๋Š” ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋ฐ€๊ธฐ๋Š” ํฌ์ธํ„ฐ๋กœ๋งŒ ๋‹ฟ๋Š” ์กฐ์ž‘์ด๋ผ, ํ‚ค๋ณด๋“œ์—๋Š” ์•ก์…˜์— ๋‹ฟ๋Š” ๊ธธ์ด ๋”ฐ๋กœ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ‚ค๋™์ž‘
Tab ยท Shift+Tab ํ™œ์„ฑ ์•ก์…˜์€ ๋ชจ๋‘ ๋‹ซํžŒ ํ–‰์—์„œ๋„ ํฌ์ปค์Šค๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์ˆœ์„œ๋Š” ์‹œ์ž‘ ์ชฝ ์•ก์…˜(์„ ์–ธํ•œ ์ˆœ์„œ) โ†’ ํ–‰ ๋ณธ๋ฌธ โ†’ ๋ ์ชฝ ์•ก์…˜(์„ ์–ธ์˜ ๊ฑฐ๊พธ๋กœ)์ด๊ณ , ์ฝ๊ธฐ ๋ฐฉํ–ฅ์ด ๋ฐ”๋€Œ์–ด๋„ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํฌ์ปค์Šค๊ฐ€ ๋“ค์–ด๊ฐ€๋ฉด ๊ทธ ์ชฝ์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ๋น„ํ™œ์„ฑ ์•ก์…˜์€ ์ •์ง€ ์ง€์ ์ด ์•„๋‹™๋‹ˆ๋‹ค
Enter ยท Spaceํฌ์ปค์Šค๋œ ์•ก์…˜์„ ์‹คํ–‰ํ•˜๊ณ  ํ–‰์„ ๋‹ซ์Šต๋‹ˆ๋‹ค
Escape ํฌ์ปค์Šค๊ฐ€ ํ–‰ ์•ˆ์— ์žˆ์„ ๋•Œ ์—ด๋ฆฐ ํ–‰์„ ๋‹ซ๊ณ , ํฌ์ปค์Šค๊ฐ€ ๋ฐœํŒ ์•ˆ์— ์žˆ์—ˆ๋‹ค๋ฉด ํ–‰์œผ๋กœ ๋Œ๋ ค์ค๋‹ˆ๋‹ค. ๋‹ซ์„ ๊ฒƒ์ด ์—†์œผ๋ฉด ํ‚ค๋ฅผ ๊ฐ€๋กœ์ฑ„์ง€ ์•Š์œผ๋ฏ€๋กœ ํ–‰์„ ๋‘˜๋Ÿฌ์‹ผ ๋‹ค์ด์–ผ๋กœ๊ทธ๋Š” ๊ทธ๋Œ€๋กœ Escape ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค

์ˆœ์„œ๋Š” ์œ„์น˜๊ฐ€ ์•„๋‹ˆ๋ผ ๊ตฌ์กฐ๋กœ ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋‹ซํžŒ ํ–‰์€ ์•ก์…˜์„ ํญ 0 ์œผ๋กœ ์ž˜๋ผ ๋‘๊ณ , ํ–‰๋ณด๋‹ค ์•ก์…˜์ด ๋„“์œผ๋ฉด ๋‘ ๋ฐœํŒ์ด ์„œ๋กœ ๊ฒน์ณ ๋†“์ด๋ฏ€๋กœ ์œ„์น˜๋Š” ์ˆœ์„œ๋ฅผ ๋งํ•ด ์ฃผ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. Web ์€ DOM ์ˆœ์„œ๊ฐ€, Flutter ๋Š” FocusTraversalOrder ๊ฐ€ ๊ฐ™์€ ์ˆœ์„œ๋ฅผ ์ •ํ•ฉ๋‹ˆ๋‹ค. ํ–‰ ๋ณธ๋ฌธ์€ ์ž๊ธฐ ์•ˆ์˜ ์ˆœ์„œ๋ฅผ ๊ทธ๋Œ€๋กœ ์ง€ํ‚ค๊ณ  ์ด ์ˆœ์„œ์—์„œ๋Š” ํ•œ ์ •์ง€ ์ง€์ ์ž…๋‹ˆ๋‹ค.

์Šคํฌ๋ฆฐ ๋ฆฌ๋”#

  • Flutter: ํ–‰์— ๋ถ™์€ ๋งž์ถค ์•ก์…˜์ด ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์˜ ์•ก์…˜ ๋ฉ”๋‰ด์— ๋ผ๋ฒจ๋กœ ๋‚˜์˜ต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋ฅผ ๊ณ ๋ฅด๋ฉด onPressed ๊ฐ€ ์‹คํ–‰๋˜๊ณ  ํ–‰์ด ๋‹ซํž™๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋“œ๋ž˜๊ทธ๋ฅผ ํ•  ์ˆ˜ ์—†๋Š” ์‚ฌ์šฉ์ž๋„ ๋ชจ๋“  ํ™œ์„ฑ ์•ก์…˜์— ๋‹ฟ์Šต๋‹ˆ๋‹ค. ํฌ์ปค์Šค๋ฅผ ๋ฐ›์€ ๋ฒ„ํŠผ์˜ ๋…ธ๋“œ๋Š” ํฌ์ปค์Šค ์ƒํƒœ๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๊ฐ€ ํ‚ค๋ณด๋“œ๋ฅผ ๋”ฐ๋ผ์˜ต๋‹ˆ๋‹ค.
  • Web: ์•ก์…˜ ๋ฒ„ํŠผ์˜ ์ด๋ฆ„์€ ๋ผ๋ฒจ์ด๊ณ , ํฌ์ปค์Šค๊ฐ€ ๋‹ฟ์œผ๋ฉด ๋ฐœํŒ์ด ์—ด๋ ค ๋ฒ„ํŠผ์ด ํ™”๋ฉด์— ๋“œ๋Ÿฌ๋‚ฉ๋‹ˆ๋‹ค.
  • ํ–‰์ด ์—ด๋ฆฌ๊ณ  ๋‹ซํžˆ๋Š” ๊ฒƒ ์ž์ฒด๋Š” ํ†ต๋ณดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

ํฌ์ปค์Šค ๊ด€๋ฆฌ#

  • ์ง„์ž… โ€” ๋ฐœํŒ์˜ ๋ฒ„ํŠผ์œผ๋กœ ํฌ์ปค์Šค๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ๊ทธ ์ชฝ์ด ์—ด๋ฆฌ๊ณ , ํ–‰์€ SwipeActionScope ์˜ ์ž๋ฆฌ๋ฅผ ์ฐจ์ง€ํ•ด ์—ด๋ ค ์žˆ๋˜ ๋‹ค๋ฅธ ํ–‰์„ ๋‹ซ์Šต๋‹ˆ๋‹ค. ํฌ์ปค์Šค๊ฐ€ ๋ณด์ด๋Š” ์ž๋ฆฌ์— ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.
  • ์ดํƒˆ โ€” ํ–‰์ด ๋‹ซํžŒ ์ฑ„ ์‰ฌ๊ฒŒ ๋˜๋ฉด, ๋‹ซํžˆ๋Š” ๋ฐœํŒ์˜ ์•ก์…˜์— ์žˆ๋˜ ํฌ์ปค์Šค๋Š” ํ–‰์œผ๋กœ ๋Œ์•„์˜ต๋‹ˆ๋‹ค. ๋ฌด์—‡์ด ๋‹ซ์•˜๋“ , ์–ด๋А ํ”Œ๋žซํผ์—์„œ๋“  ๊ฐ™์Šต๋‹ˆ๋‹ค. Escape, ์•ก์…˜ ์‹คํ–‰, ๋ฐ€๊ฑฐ๋‚˜ ํŠ•๊ธด ๋’ค ๋†“๊ธฐ, ์—ด๋ฆฐ ํ–‰์˜ ๋ณธ๋ฌธ ํƒญ, SwipeActionScope ์˜ ๋‹ค๋ฅธ ํ–‰์ด ์›€์ง์ด๊ธฐ ์‹œ์ž‘ํ•จ, ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ(์•„๋ž˜)๊ฐ€ ๋ชจ๋‘ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ํฌ์ธํ„ฐ๋Š” ํฌ์ปค์Šค๋ฅผ ์˜ฎ๊ธฐ์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด(Flutter ์˜ ํฌ์ธํ„ฐ, ๋ธŒ๋ผ์šฐ์ €์˜ ํ„ฐ์น˜๋กœ ๋ฐ€๊ธฐ) ๊ทธ๋Œ€๋กœ ๋‘๋ฉด ํฌ์ปค์Šค๊ฐ€ ํญ 0 ์œผ๋กœ ์ž˜๋ฆฐ ๋ฒ„ํŠผ์— ๋‚จ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋ฐœํŒ ๋ฐ–์— ์žˆ๋˜ ํฌ์ปค์Šค(๋‹ค๋ฅธ ํ–‰, ํ–‰ ๋ณธ๋ฌธ์˜ ์ž…๋ ฅ, ์•„๋ฌด ๋ฐ๋„ ์—†๋Š” ๊ฒฝ์šฐ)์™€ ์—ด๋ฆฐ ์ฑ„ ์‰ฌ๋Š” ํ–‰์˜ ํฌ์ปค์Šค๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ , ์•ก์…˜์ด ์‹คํ–‰๋˜๋ฉด์„œ ํฌ์ปค์Šค๋ฅผ ๋‹ค๋ฅธ ๊ณณ(ํŽธ์ง‘ ํ•„๋“œ ๋“ฑ)์œผ๋กœ ๋ณด๋ƒˆ๋‹ค๋ฉด ํ–‰์€ ๊ทธ๊ฒƒ์„ ๋˜๊ฐ€์ ธ๊ฐ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ–‰ ์ž์ฒด๋Š” Tab ์ •์ง€ ์ง€์ ์ด ์•„๋‹ˆ๋ผ ํฌ์ปค์Šค๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ์ž๋ฆฌ์ž…๋‹ˆ๋‹ค(Web ์€ ๋ฃจํŠธ์˜ tabindex="-1", Flutter ๋Š” ํ–‰์˜ ํฌ์ปค์Šค ๋…ธ๋“œ). ํฌ์ปค์Šค๊ฐ€ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ์˜ฎ๊ฒจ ๊ฐ€๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ํ–‰์ด ๋‹ซํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋‹ค์‹œ ๊ทธ๋ ค์„œ ๋‹ซํž ๋•Œ โ€” ์•ก์…˜ ๊ฐœ์ˆ˜๊ฐ€ ๋ฐ”๋€Œ๊ฑฐ๋‚˜, ํ–‰์ด ๋”๋Š” ๋ฐ€ ์ˆ˜ ์—†๊ฒŒ ๋˜์–ด(enabled: false, ์•ก์…˜์ด ํ•˜๋‚˜๋„ ์—†์Œ) ํ–‰์ด ๋‹ซํžˆ๊ฑฐ๋‚˜ ๋ฐœํŒ์ด ์‚ฌ๋ผ์งˆ ๋•Œ๋„ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Œ€๋กœ ๋‘๋ฉด ํฌ์ปค์Šค๊ฐ€ ํญ 0 ์œผ๋กœ ์ž˜๋ฆฐ ๋ฒ„ํŠผ์— ๋‚จ๊ฑฐ๋‚˜, ๋ฒ„ํŠผ์ด ์‚ฌ๋ผ์ง€๋ฉด์„œ ๋ฌธ์„œ๋กœ ๋–จ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์•ก์…˜ ๊ฐœ์ˆ˜๋„ ๋ฐ€ ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋„ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ๋Š” ํฌ์ปค์Šค๋ฅผ ์˜ฎ๊ธฐ์ง€ ์•Š๊ณ , ๊ฐ™์€ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ๋กœ ์‚ฌ๋ผ์ง€๋Š” ํ–‰ ๋ณธ๋ฌธ์˜ ์ž…๋ ฅ์—์„œ๋Š” ํ–‰์ด ํฌ์ปค์Šค๋ฅผ ๊ฐ€์ ธ๊ฐ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Web ์€ ๋‹ค์‹œ ๊ทธ๋ฆฌ๋Š” ๋„์ค‘์— ํฌ์ปค์Šค๋ฅผ ์˜ฎ๊ธฐ์ง€ ์•Š๊ณ  ๊ทธ ๋ณ€๊ฒฝ์ด ๋ฌธ์„œ์— ๋ฐ˜์˜๋œ ๋’ค์— ์˜ฎ๊น๋‹ˆ๋‹ค. ๊ทธ ์‚ฌ์ด ๊ฐ™์€ ๋‹ค์‹œ ๊ทธ๋ฆฌ๊ธฐ๊ฐ€ ํฌ์ปค์Šค๋ฅผ ๋‹ค๋ฅธ ์š”์†Œ์— ์คฌ๋‹ค๋ฉด ๊ทธ ์š”์†Œ๊ฐ€ ๊ฐ–์Šต๋‹ˆ๋‹ค.
  • ๋ฐ€ ์ˆ˜ ์—†๋Š” ํ–‰ โ€” ์•ก์…˜์ด ์—†๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑ์ธ ํ–‰๋„ ํฌ์ปค์Šค๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ์ž๋ฆฌ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค. Web ์€ ๊ทธ ํ–‰์˜ ๋ฃจํŠธ์—๋„ tabindex="-1" ์„ ๋ถ™์ด๊ณ  Flutter ์˜ ํ–‰ ํฌ์ปค์Šค ๋…ธ๋“œ๋Š” ๋Š˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ฏธ ํ–‰์— ์žˆ๋˜ ํฌ์ปค์Šค๋Š” ํ–‰์ด ๋น„ํ™œ์„ฑ์ด ๋˜์–ด๋„ ๊ทธ๋Œ€๋กœ์ž…๋‹ˆ๋‹ค.
  • ํŠธ๋žฉ โ€” ์—†์Œ. ํฌ์ปค์Šค๋Š” ํ–‰ ์•ˆ๊ณผ ๋ฐ–์„ ์ž์œ ๋กญ๊ฒŒ ์˜ค๊ฐ‘๋‹ˆ๋‹ค.

์•Œ๋ ค์ง„ ์ œ์•ฝ#

  • ๋ฐ€๊ธฐ๋Š” ๋ˆˆ์— ๋ณด์ด์ง€ ์•Š๋Š” ์กฐ์ž‘์ž…๋‹ˆ๋‹ค. ๋งˆ์šฐ์Šค ์‚ฌ์šฉ์ž๋Š” ๋ฐœ๊ฒฌํ•˜๊ธฐ ์–ด๋ ค์šฐ๋ฏ€๋กœ ๊ฐ™์€ ๋™์ž‘์˜ ๋ˆˆ์— ๋ณด์ด๋Š” ๊ฒฝ๋กœ๋ฅผ ํ•จ๊ป˜ ๋‘๋Š” ํŽธ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • Escape ๋Š” ํ‚ค๋ณด๋“œ ํฌ์ปค์Šค๊ฐ€ ํ–‰ ์•ˆ์— ์žˆ์„ ๋•Œ๋งŒ ๋‹ฟ์Šต๋‹ˆ๋‹ค. ๋ฐ€์–ด์„œ ์—ฐ ํ–‰์— ํฌ์ปค์Šค๊ฐ€ ์—†์œผ๋ฉด Escape ๋กœ ๋‹ซ์„ ์ˆ˜ ์—†๊ณ , ํƒญํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ํ–‰์„ ๋ฐ€์–ด์„œ ๋‹ซ์Šต๋‹ˆ๋‹ค.
  • ๋‹ซํžŒ ํ–‰์˜ ์•ก์…˜์€ ํญ 0 ์œผ๋กœ ์ž˜๋ฆฐ ์ฑ„ ํฌ์ปค์Šค๋ฅผ ๋ฐ›๋Š” ๋…ธ๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐฉํ–ฅํ‚ค๋กœ ํฌ์ปค์Šค๋ฅผ ์˜ฎ๊ธฐ๋Š” ํ™˜๊ฒฝ(๋ฐ์Šคํฌํ†ฑ ยท TV ์˜ Flutter)์—์„œ๋Š” ๋ฐฉํ–ฅํ‚ค ํƒ์ƒ‰๋„ ๊ฑฐ๊ธฐ์— ๋‹ฟ์Šต๋‹ˆ๋‹ค. ๋‹ฟ๋Š” ์ˆœ๊ฐ„ ๊ทธ ํ–‰์ด ์—ด๋ ค ํฌ์ปค์Šค๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š” ์ž๋ฆฌ์— ๋จธ๋ฌผ์ง€๋Š” ์•Š์ง€๋งŒ, ๋ชฉ๋ก์„ ์•„๋ž˜๋กœ ํ›‘์œผ๋ฉด ํ–‰์ด ํ•˜๋‚˜์”ฉ ์—ด๋ฆฝ๋‹ˆ๋‹ค(SwipeActionScope ์•ˆ์—์„œ๋Š” ์•ž ํ–‰์ด ๋‹ซํž™๋‹ˆ๋‹ค). ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ๋ฐฉํ–ฅํ‚ค๊ฐ€ ํฌ์ปค์Šค๋ฅผ ์˜ฎ๊ธฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ํ–‰์„ ์—ด๊ณ  ๋‹ซ๋Š” ํ”„๋กœ๊ทธ๋žจ API ๊ฐ€ ์—†๊ณ , ์Šคํฌ๋กค์ด๋‚˜ ํ–‰ ๋ฐ– ํƒญ์œผ๋กœ ์—ด๋ฆฐ ํ–‰์ด ๋‹ซํžˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—ด๋ฆฐ ํ–‰์€ ํƒญํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ํ–‰์„ ๋ฐ€์–ด์„œ ๋‹ซ์Šต๋‹ˆ๋‹ค.
  • ์•ก์…˜ ๋ฒ„ํŠผ์˜ ํฌ๊ธฐ๋Š” 48 ร— 48 ์ด๋ผ ์ „์—ญ ์ตœ์†Œ ํ„ฐ์น˜ ํƒ€๊ฒŸ(24 ร— 24)๋ณด๋‹ค ํฝ๋‹ˆ๋‹ค.
  • ์นธ์˜ ๋†’์ด๋Š” ๋ฒ„ํŠผ 48 ์— ๊ฐ„๊ฒฉ๊ณผ ๋ผ๋ฒจ ํ•œ ์ค„์„ ๋”ํ•ด ๋Œ€๋žต 66 ์ด๊ณ  ๊ธ€์ž ๋ฐฐ์œจ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๋ผ๋ฒจ์„ ๊ทธ๋ฆด ๋•Œ ํ–‰์ด ๊ทธ๋ณด๋‹ค ๋‚ฎ์œผ๋ฉด ์นธ์ด ์œ„์•„๋ž˜๋กœ ์ž˜๋ฆฝ๋‹ˆ๋‹ค(๋‘ ํ”Œ๋žซํผ ๋ชจ๋‘ ๊ฐ™์Šต๋‹ˆ๋‹ค). ๋‚ฎ์€ ํ–‰์—๋Š” showLabels: false ๋กœ ์•„์ด์ฝ˜๋งŒ ๊ทธ๋ฆฌ์„ธ์š”. ๊ทธ๋Ÿฌ๋ฉด ์นธ์ด 48 ์ด๋ผ ๊ทธ ์ด์ƒ์˜ ํ–‰์— ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.

์ „์—ญ ์ถ•(๋™์ž‘ ์ค„์ด๊ธฐ ยท ๊ณ ๋Œ€๋น„ ยท ์ƒ‰ ๊ฐ•์ œ ๋ชจ๋“œ ยท ์‹œ๋งจํ‹ฑ ์—ญํ•  ยท ์ตœ์†Œ ํ„ฐ์น˜ ํƒ€๊ฒŸ)์€ ์ ‘๊ทผ์„ฑ ํŽ˜์ด์ง€๊ฐ€ ํ•œ ๊ณณ์—์„œ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์ฐจ์ด์  (Platform Differences)#

๋‘ ํ”Œ๋žซํผ์˜ ์ƒ์„ฑ์ž๋Š” ์ด๋ฆ„ ยท ์ˆœ์„œ ยท ๊ธฐ๋ณธ๊ฐ’์ด ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ฐจ์ด๋Š” ๊ฐ™์€ ํŒ์ •์„ ๊ฐ ํ”Œ๋žซํผ์ด ์–ด๋–ป๊ฒŒ ์ž…๋ ฅ๋ฐ›๊ณ  ๊ทธ๋ฆฌ๋Š”๊ฐ€์— ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ญ๋ชฉFlutterWeb
๋“œ๋ž˜๊ทธ ์ธ์‹ (framework-infra) ์ œ์Šค์ฒ˜ ๊ฒฝ์Ÿ์ด ์ธ์‹ํ•˜๊ณ , ๊ธฐ๊ธฐ์˜ ํ„ฐ์น˜ ์Šฌ๋กญ(kTouchSlop, 18)์„ ์ฝ์Šต๋‹ˆ๋‹ค ํฌ์ธํ„ฐ ์ด๋ฒคํŠธ๋ฅผ Core ์˜ ์ œ์Šค์ฒ˜ ํŒ์ •์— ๋จน์ž…๋‹ˆ๋‹ค. 18 px ๋„˜๊ฒŒ ์›€์ง์ด๊ณ  ์„ธ๋กœ ์ด๋™์— ๋ฐ€๋ฆฌ์ง€ ์•Š์„ ๋•Œ ์‹œ์ž‘ํ•˜๋ฉฐ, touch-action: pan-y ๋กœ ์„ธ๋กœ ํŒฌ์„ ๋ธŒ๋ผ์šฐ์ €์— ๋งก๊น๋‹ˆ๋‹ค. ๋“œ๋ž˜๊ทธ๊ฐ€ ์‹œ์ž‘๋œ ๋’ค์—์•ผ ํฌ์ธํ„ฐ๋ฅผ ๋ถ™์žก์Šต๋‹ˆ๋‹ค
์ •์ฐฉ ๋ชจ์…˜ (framework-infra) ๋†“๋Š” ์†๋„๋ฅผ ์ด์–ด๋ฐ›๋Š” ์ž„๊ณ„ ๊ฐ์‡  ์Šคํ”„๋ง CoreEasing.enter ๊ณก์„ ์˜ CSS ์ „ํ™˜
์Šคํฌ๋ฆฐ ๋ฆฌ๋”์— ๋‹ฟ๋Š” ๋ฐฉ์‹ (framework-infra) ํ–‰์˜ ๋งž์ถค ์‹œ๋งจํ‹ฑ ์•ก์…˜. ๋ฐœํŒ์˜ ๋ฒ„ํŠผ์€ ์œ„์ ฏ ํŠธ๋ฆฌ์— ๋Š˜ ์žˆ์ง€๋งŒ ๊ทธ ์ชฝ์ด ์—ด๋ ค ๋“œ๋Ÿฌ๋‚  ๋•Œ๋งŒ ์‹œ๋งจํ‹ฑ ํŠธ๋ฆฌ์— ๋“ค์–ด์˜ด ๋ฐœํŒ์ด DOM ์— ๋Š˜ ์žˆ๋Š” ์‹ค์ œ <button>, aria-label
ํฌ์ปค์Šค ์ˆœ์„œ ยท Escape (framework-infra) FocusTraversalOrder ๊ฐ€ ์ˆœ์„œ๋ฅผ ์ •ํ•˜๊ณ , ํ–‰์˜ ํฌ์ปค์Šค ๋…ธ๋“œ๊ฐ€ Escape ๋ฅผ ๋ฐ›๊ณ  ํฌ์ปค์Šค๋ฅผ ๋Œ๋ ค๋ฐ›์Œ DOM ์ˆœ์„œ๊ฐ€ ๊ณง ์ˆœ์„œ. keydown ์„ ๋ฃจํŠธ์—์„œ ๋ฐ›๊ณ , ๋ฃจํŠธ์˜ tabindex="-1" ์ด ํฌ์ปค์Šค๋ฅผ ๋Œ๋ ค๋ฐ›์Œ. ๋ฐ€ ์ˆ˜ ์—†๋Š” ํ–‰์˜ ๋ฃจํŠธ์—๋„ ๋ถ™์Œ
๋‹ซํžŒ ํ–‰์˜ ํฌ์ปค์Šค ๋ฐ˜ํ™˜ (framework-infra) ํฌ์ปค์Šค๊ฐ€ ๋ฐœํŒ ์•ˆ์— ์žˆ์œผ๋ฉด, ๋‹ซ์œผ๋ผ๋Š” ์š”์ฒญ์„ ๋ฐ›๋Š” ์ˆœ๊ฐ„๊ณผ ๋‹ซํžŒ ์ฑ„ ์‰ฌ๋Š” ์ˆœ๊ฐ„์— ํ–‰์˜ ํฌ์ปค์Šค ๋…ธ๋“œ์— ํฌ์ปค์Šค๋ฅผ ์š”์ฒญํ•จ ํ–‰ ๋ชจ๋ธ์ด ์—ด๋ฆผ์—์„œ ๋‹ซํž˜์œผ๋กœ ๋ฐ”๋€Œ์—ˆ๋‹ค๊ณ  ์•Œ๋ฆด ๋•Œ(๋†“์€ ํŒ์ • ยท ํƒญ ยท ๋‹ค๋ฅธ ํ–‰์˜ ์ด๋™) ํฌ์ปค์Šค๊ฐ€ ๋ฐœํŒ ์•ˆ์— ์žˆ์—ˆ๋Š”์ง€ ๋ณด๊ณ , ์ง€๊ธˆ ํ•˜๋˜ ์ผ์ด ๋๋‚œ ๋’ค ํฌ์ปค์Šค๊ฐ€ ๋น„์—ˆ๊ฑฐ๋‚˜ ์•„์ง ๋ฐœํŒ์— ์žˆ์œผ๋ฉด ๋ฃจํŠธ๋กœ ์˜ฎ๊น€. ํŽ˜์ด์ง€๋ฅผ ์Šคํฌ๋กคํ•˜์ง€ ์•Š์Œ( preventScroll )
๋ฐœํŒ ๋„ˆ๋น„ (๋‚ด๋ถ€ ๊ตฌํ˜„) ํ•ด๊ฒฐ๋œ ์ˆซ์ž(๋ฐ€๋„ ยท ๋ฐฐ์œจ ์ ์šฉ)๋กœ ๊ณ„์‚ฐ ๋“œ๋ž˜๊ทธ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ DOM ์—์„œ ์žฐ๋‹ค. ๊ฐ„๊ฒฉ์ด CSS ๋ณ€์ˆ˜๋ผ ๋ฐ€๋„์— ๋”ฐ๋ผ ์›€์ง์ด๊ธฐ ๋•Œ๋ฌธ
์ฝ๊ธฐ ๋ฐฉํ–ฅ (๋‚ด๋ถ€ ๊ตฌํ˜„) Directionality. ๋ฐฉํ–ฅ์ด ๋ฐ”๋€Œ๋ฉด ํ–‰์ด ์ฆ‰์‹œ ๋‹ซํž˜ ๋ฃจํŠธ ์š”์†Œ์˜ ๊ณ„์‚ฐ๋œ direction ์„ ๋“œ๋ž˜๊ทธ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์ฝ์Œ
ํ…์ŠคํŠธ ์ž…๋ ฅ์—์„œ ์‹œ์ž‘ํ•œ ๋ˆŒ๋ฆผ (Web ์ „์šฉ HTML ํ‘œ๋ฉด) ํ•ด๋‹น ์—†์Œ โ€” ์ œ์Šค์ฒ˜ ๊ฒฝ์Ÿ์ด ๊ฐ€๋ฆ„ input ยท textarea ยท select ยท contenteditable ์—์„œ ์‹œ์ž‘ํ•œ ๋ˆŒ๋ฆผ์€ ์Šค์™€์ดํ”„๋กœ ๋ณด์ง€ ์•Š์Œ. ์บ๋Ÿฟ ์ด๋™์ด๊ธฐ ๋•Œ๋ฌธ
๋“œ๋ž˜๊ทธ ์งํ›„์˜ ํด๋ฆญ ยท ์•ก์…˜์˜ ์ด๋ฒคํŠธ (Web ์ „์šฉ HTML ํ‘œ๋ฉด) ํ•ด๋‹น ์—†์Œ ๋“œ๋ž˜๊ทธ๋ฅผ ๋†“์„ ๋•Œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ณด๋‚ด๋Š” ํด๋ฆญ์€ ์‚ผํ‚ด. child ์™€ ํ˜ธ์ถœ์ž์˜ onClick ์— ๋‹ฟ์ง€ ์•Š์Œ. ์•ก์…˜ ๋ฒ„ํŠผ์—์„œ ์˜ฌ๋ผ์˜จ ํด๋ฆญ ยท ํ‚ค ์ž…๋ ฅ๋„ ํ˜ธ์ถœ์ž์˜ onClick ยท onKeyDown ยท onKeyUp ์œผ๋กœ ๊ฐ€์ง€ ์•Š์Œ โ€” ๊ทธ ํ•ธ๋“ค๋Ÿฌ๋Š” ํ–‰ ๋ณธ๋ฌธ์„ ์œ„ํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ. ๋ณธ๋ฌธ ์•ˆ์— ๋†“์ธ ๋‹ค๋ฅธ SwipeActions ์˜ ์•ก์…˜๋„ ๊ฐ™๋‹ค(Flutter ์—์„œ ์•ก์…˜์€ ๋ˆŒ๋ฆผ์„ ๊ฐ€์ ธ๊ฐ€๋Š” ๋ฒ„ํŠผ์ด๋ผ ๋ฐ”๊นฅ ํ–‰์˜ onTap ์ด ์šธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค). ๋ฐ”๊นฅ ํ–‰ ๋ณธ๋ฌธ ์ž์ฒด์˜ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ณธ๋ฌธ ์•ˆ์˜ ์–ด๋А ๋ฒ„ํŠผ ํด๋ฆญ์ด๋“  ๋“ฃ๋“ฏ์ด ์ด๊ฒƒ๋„ ๋“ฃ๋Š”๋‹ค โ€” ์ด๋ฒคํŠธ๋ฅผ ๋ง‰์œผ๋ฉด ์กฐ์ƒ ์ „์ฒด(๋ฐ”๊นฅ ํด๋ฆญ์œผ๋กœ ๋‹ซ๋Š” ์˜ค๋ฒ„๋ ˆ์ด ๊ฐ™์€ ํŽ˜์ด์ง€ ๋ฆฌ์Šค๋„ˆ)์—๊ฒŒ์„œ ์ˆจ๊ธฐ๊ธฐ ๋•Œ๋ฌธ
์กฐ์ž‘ ํ‘œ๋ฉด (Web ์ „์šฉ HTML ํ‘œ๋ฉด) key id ยท classes ยท css ยท attributes โ€” ๋ฃจํŠธ <div> ๋กœ ์ „๋‹ฌ
  • Basic: ํ–‰ ๋ณธ๋ฌธ์œผ๋กœ ๊ฐ€์žฅ ํ”ํžˆ ๋„ฃ๋Š” ์Šฌ๋กฏ ๋ ˆ์ด์•„์›ƒ
  • Button: ์•ก์…˜์ด ํ•ฉ์„ฑํ•˜๋Š” ๋ฒ„ํŠผ. ์•ก์…˜์˜ ๋ชจ์–‘์€ ์ด ์ปดํฌ๋„ŒํŠธ์˜ ํ…Œ๋งˆ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค
  • Listing: ํ–‰์„ ์Œ“๋Š” ๋ชฉ๋ก ์ปจํ…Œ์ด๋„ˆ
  • Gap: ํ–‰ ์‚ฌ์ด์˜ ๊ฐ„๊ฒฉ

์กฐํ•ฉ ์˜ˆ์ œ#

SwipeActionScope(
  child: ListView.separated(
    itemCount: mails.length,
    separatorBuilder: (context, index) => const Divider(),
    itemBuilder: (context, index) {
      final mail = mails[index];
      return SwipeActions(
        trailingActions: [
          CoreSwipeAction(
            label: '๋ณด๊ด€',
            icon: const Icon(LucideIcons.archive),
            onPressed: () => handleArchive(mail),
          ),
        ],
        child: Basic(
          leading: const Icon(LucideIcons.mail),
          title: Text(mail.subject),
          subtitle: Text(mail.preview),
        ),
      );
    },
  ),
)