StackLayout | CoUI

StackLayout

자식을 z 축으로 겹쳐 쌓는 레이아웃 (Flutter `Stack` 매핑)

StackLayout#

CoStackLayout 은 자식 위젯을 z 축으로 겹쳐 그리는 레이아웃 primitive 입니다. Flutter 의 Stack 과 시맨틱이 1:1 동일하며 (fit / alignment / clipBehavior / textDirection), 자식 중 CoPositioned 인 항목은 top / right / bottom / left / width / height 로 절대 좌표 배치됩니다 — Flutter Positioned 와 동등.

카드 덱 같은 누적 겹침 효과는 자식들을 모두 CoPositioned 로 감싸 top: i * offset 형태로 누적 좌표를 직접 지정해서 만듭니다.

Live Preview#

Web
Layer 1
Layer 2
Layer 3
Flutter
Loading Flutter...
class StackLayoutDefaultExample extends StatelessComponent {
  const StackLayoutDefaultExample({super.key});

  static const _kLabels = ['Layer 1', 'Layer 2', 'Layer 3'];
  static const _kPerLayerOffset = CoreSpace.space8;

  @override
  Component build(BuildContext context) {
    final cs = context.theme.colorScheme;
    final ts = context.theme.typography;
    final layerClasses =
        'w-${CoreSpace.scale.space160} h-${CoreSpace.scale.space80} '
        'flex items-center justify-center '
        'bg-${cs.surfaceContainer} border border-${cs.outline} '
        'rounded-${CoreRadius.scale.radius16} '
        'text-${ts.bodyMedium} text-${cs.onSurface}';
    final totalHeight =
        CoreSpace.space80 + (_kLabels.length - 1) * _kPerLayerOffset;
    return div(
      [
        CoStacks(
          children: [
            for (var i = 0; i < _kLabels.length; i++)
              CoPosition(
                top: i * _kPerLayerOffset,
                child: div(
                  [Component.text(_kLabels[i])],
                  classes: layerClasses,
                ),
              ),
          ],
        ),
      ],
      styles: Styles(
        raw: {
          'width': '${CoreSpace.space160 / 16}rem',
          'height': '${totalHeight / 16}rem',
        },
      ),
    );
  }
}
class StackLayoutDefaultExample extends StatelessWidget {
  const StackLayoutDefaultExample({super.key});

  static const _kLabels = ['Layer 1', 'Layer 2', 'Layer 3'];
  static const _kPerLayerOffset = CoreSpace.space8;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final totalHeight =
        CoreSpace.space80 + (_kLabels.length - 1) * _kPerLayerOffset;
    return SizedBox(
      width: CoreSpace.space160,
      height: totalHeight,
      child: CoStacks(
        children: [
          for (var i = 0; i < _kLabels.length; i++)
            CoPosition(
              top: i * _kPerLayerOffset,
              child: Container(
                width: CoreSpace.space160,
                height: CoreSpace.space80,
                alignment: Alignment.center,
                decoration: BoxDecoration(
                  color: theme.colorScheme.surfaceContainer,
                  borderRadius: BorderRadius.circular(CoreRadius.radius16),
                  border: Border.all(color: theme.colorScheme.outline!),
                ),
                child: Text(
                  _kLabels[i],
                  style: theme.typography.bodyMedium.copyWith(
                    color: theme.colorScheme.onSurface,
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 자식을 z 축으로 겹쳐야 할 때 (배지, 오버레이, 카드 덱)
  • Flutter Stack / Web position: absolute양쪽 동일한 시맨틱 이 필요할 때
  • CoPositioned 로 자식의 절대 좌표를 명시하고 싶을 때

대신 다른 컴포넌트를 사용하세요:

  • Group: 자식을 행/열 방향으로 단순 정렬하고 싶을 때
  • Resizable: 분할 가능한 좌우/상하 영역이 필요할 때

기본 사용법 (Basic Usage)#

// 카드 덱 — 자식들을 모두 CoPositioned 로 감싸 누적 좌표 지정
CoStackLayout(
  children: [
    for (var i = 0; i < labels.length; i++)
      CoPositioned(
        top: i * 8.0,
        child: Card(child: Text(labels[i])),
      ),
  ],
)

// 일반 z 스택 — non-positioned 자식 + 우상단 배지
CoStackLayout(
  children: [
    background,
    CoPositioned(top: 8, right: 8, child: badge),
  ],
)

Web 에서도 동일한 생성자로 사용합니다 — children: List<Component> 만 다릅니다.

Props / Parameters#

CoStackLayout#

이름타입기본값설명
children List<W> required 자식 목록. 마지막 자식이 최상단
alignment CoreAlignment topStart non-positioned 자식의 정렬
fit CoreStackFit loose 부모 constraint 전달 정책 (loose / expand / passthrough)
clipBehavior CoreClipBehavior hardEdge overflow 클립 정책 ( none / hardEdge / antiAlias / antiAliasWithSaveLayer )
textDirection CoreTextDirection? null start/end 정렬을 ltr/rtl 로 해석할 방향

CoPositioned#

이름타입기본값설명
childW?null위치 지정할 자식
top / right / bottom / left double? null 스택 박스 가장자리에서의 거리 (logical px)
width / height double? null 자식의 명시 크기 (logical px)

CoPositioned.fill({ ... }) 편의 생성자는 top/right/bottom/left = 0 으로 자식이 스택 전체를 채우게 합니다 (Flutter Positioned.fill 매핑).

Theming#

CoreComponentTheme.stackLayout 에서 기본값 오버라이드:

CoreComponentTheme(
  stackLayout: CoreStackLayoutTheme(
    alignment: CoreAlignment.topStart,
    fit: CoreStackFit.loose,
    clipBehavior: CoreClipBehavior.hardEdge,
  ),
)

관련 컴포넌트#

  • Group — 절대 좌표 기반 자유 배치
  • Resizable — 분할 가능 영역