Join#
버튼·입력 필드 등 여러 요소를 동일한 외곽 반경으로 묶어 하나의 툴바/그룹처럼 보이게 하는 레이아웃 컴포넌트입니다.
Row / Column은 각 자식의 모서리를 그대로 두지만, CoJoin은 첫 번째 아이템만 시작 측 radius,
마지막 아이템만 끝 측 radius, 중간 아이템은 radius 0으로 clip 합니다. 결과적으로 버튼 그룹의 외곽만 둥글고 내부는 평평하게 이어진 시각적 단위가 됩니다.
보더 두께 자체를 겹치거나 제거하는 처리(
border-collapse동작)는 하지 않습니다. 각 자식은 자신의 보더를 그대로 가진 채 외곽 반경만 통일됩니다.
Live Preview#
default
Web
Flutter
Loading Flutter...
class JoinDefaultExample extends StatelessComponent {
const JoinDefaultExample({super.key});
@override
Component build(BuildContext context) {
return CoJoin(
children: [
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: Component.text('1'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: Component.text('2'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: Component.text('3'),
),
],
);
}
}
class JoinDefaultExample extends StatelessWidget {
const JoinDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return CoJoin(
children: [
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: const Text('1'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: const Text('2'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: const Text('3'),
),
],
);
}
}
vertical
Web
Flutter
Loading Flutter...
class JoinVerticalExample extends StatelessComponent {
const JoinVerticalExample({super.key});
@override
Component build(BuildContext context) {
return CoJoin(
vertical: true,
children: [
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: Component.text('A'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: Component.text('B'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: Component.text('C'),
),
],
);
}
}
class JoinVerticalExample extends StatelessWidget {
const JoinVerticalExample({super.key});
@override
Widget build(BuildContext context) {
return CoJoin(
vertical: true,
children: [
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: const Text('A'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: const Text('B'),
),
CoButton(
variant: CoreButtonVariant.outline,
onPressed: () {},
child: const Text('C'),
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 여러 버튼을 하나의 그룹처럼 붙여 툴바를 구성할 때
- 입력 필드와 버튼을 하나의 검색 바로 결합할 때
- 서로 연관된 버튼들이 독립적이지 않고 하나의 컨트롤로 인식되어야 할 때
대신 다른 컴포넌트를 사용하세요:
Gap: 요소 사이에 여백만 필요할 때Row/Column: 각 자식이 독립된 모서리를 유지해야 할 때. Join은 외곽 반경 clip만 다르다
기본 사용법 (Basic Usage)#
// 수평 버튼 그룹 (기본).
CoJoin(
children: [
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('1')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('2')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('3')),
],
)
// 수직 결합.
CoJoin(
vertical: true,
children: [
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('A')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('B')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('C')),
],
)
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
children |
List<Widget> / List<Component> |
필수 | 결합할 위젯 목록 |
vertical |
bool |
false |
true이면 수직 결합 |
borderRadius |
BorderRadiusGeometry? / double? |
CoreRadius.field |
외곽 모서리 반경 오버라이드 |
변형 (Variants)#
수평 (기본)#
CoJoin(
children: [
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('1')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('2')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('3')),
],
)
수직#
CoJoin(
vertical: true,
children: [
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('A')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('B')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('C')),
],
)
동작 스펙 (Behavior)#
인터랙션#
- Join 컨테이너 자체는 인터랙션 없음
- 각 자식 위젯이 독립적으로 인터랙션 처리
- 첫 번째/마지막 아이템만 외곽 radius 유지. 중간 아이템은 radius 0으로 이어 붙여진 느낌 제공
상태 전환#
- Join 컨테이너 자체 상태 없음
- 자식 버튼의 hover/pressed 상태는 각 버튼이 독립 처리
사용 가이드라인 (Usage Guidelines)#
✅ Do — 의미적으로 연관된 버튼들만 묶기#
CoJoin(
children: [
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('굵게')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('기울임')),
CoButton(variant: CoreButtonVariant.outline, onPressed: () {}, child: const Text('밑줄')),
],
)
❌ Don't — 관련 없는 버튼을 묶지 않기#
CoJoin(
children: [
CoButton(variant: CoreButtonVariant.primary, onPressed: () {}, child: const Text('저장')),
CoButton(variant: CoreButtonVariant.destructive, onPressed: () {}, child: const Text('삭제')),
],
)
관련 없는 버튼을 하나로 묶으면 사용자가 기능 관계를 잘못 이해할 수 있습니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | Join 내 다음 포커스 가능 요소로 이동 |
Enter / Space | 포커스된 버튼 활성화 |
터치 타겟#
- 각 자식 버튼 최소 터치 타겟: 48x48dp
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | CoJoin | CoJoin |
| 방향 | vertical: bool | vertical: bool |
| borderRadius 타입 | BorderRadiusGeometry? |
double? (px) |
| 렌더링 | Row / Column + ClipRRect |
<div> + inline border-radius + overflow: hidden |