# Splitter 한 축을 나눠 쓰는 패널들과 그 사이를 끌어 크기를 바꾸는 구분자입니다. shadcn/ui Resizable 을 기준으로 삼았습니다. > 원본: `03_ASSET_GUIDE/catalog/splitter.js` · CSS: `03_ASSET_GUIDE/styles/elements/splitter.css` > kind `component` · category `display` · status `stable` · specSource `measured` > [!NOTE] > **기준은 shadcn/ui `Resizable` 입니다** > 이름·prop·단위 규칙을 [ui.shadcn.com/docs/components/resizable](https://ui.shadcn.com/docs/components/resizable) > 과 그것이 감싸는 [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels) > 의 문서에서 확인해 옮겼습니다. `orientation` · `defaultSize` · `minSize` · `maxSize` · > `collapsible` · `collapsedSize` · `withHandle` · `disableDoubleClick` 과 "숫자는 픽셀, > 단위 없는 문자열은 퍼센트" 규칙이 그쪽 계약 그대로입니다. > 키보드 세부 동작은 그 문서에 명시되어 있지 않아 **WAI-ARIA window splitter 패턴**을 > 근거로 적었습니다. 라이브러리가 이와 같이 구현하는지는 확인하지 않았습니다. > [!CAUTION] > **이 저장소에는 리사이즈 로직이 없습니다** > `splitter.css` 는 모양과 상태만 소유합니다. 실제로 끌어서 비율을 바꾸는 것은 구현하는 쪽이 > 붙이는 스크립트이고, 그 스크립트가 패널의 인라인 `flex-basis` 를 다시 씁니다. > Grid 의 컬럼 너비 조절과 같은 구조입니다. > (구현 `implement/src/components/splitter.tsx` 가 그 스크립트 — 포인터 드래그·키보드·접기 — 를 외부 라이브러리 없이 담당합니다.) --- ## 1. 구성 축을 정하는 **Group**, 그 축을 나눠 갖는 **Panel**, 사이에 놓여 끌 수 있는 **Separator** 셋입니다. Separator 는 패널 사이마다 하나씩 들어갑니다. ``` ┌─────────────────┬┬──────────────────────────┐ │ ┌─────────────┐ ││ ┌──────────────────────┐ │ │ │ Panel A │ ⣿│ │ Panel B │ │ │ └─────────────┘ ││ └──────────────────────┘ │ └─────────────────┴┴──────────────────────────┘ ① ② ③ ④⑤ ``` | # | 요소 | 필수 | 클래스 | 설명 | |---|---|---|---|---| | ① | Group | 필수 | `atom-splitter` | 축(`orientation`)을 정하고 남는 공간을 패널에 나눠 줍니다. | | ② | Panel | 필수 | `atom-splitter__panel` | 자기 몫을 차지합니다. 크기는 CSS 대신 인라인 `flex-basis` 로 지정합니다. | | ③ | Content | 선택 | `atom-splitter__content` | 패널 안에서 따로 스크롤되는 상자입니다. 선택 사항입니다. | | ④ | Separator | 필수 | `atom-splitter__separator` | 9px 히트 영역 안에 1px 선을 그립니다. 끄는 대상입니다. | | ⑤ | Grip | 선택 | `atom-splitter__grip` | 끌 수 있다는 것을 보여주는 손잡이입니다. shadcn 의 `withHandle` 에 해당하고 선택 사항입니다. | > [!WARNING] > **크기는 스타일시트가 정하지 않습니다** > 패널의 몫은 드래그할 때마다 스크립트가 인라인 `flex-basis` 로 다시 씁니다. `splitter.css` > 가 폭을 함께 선언하면 첫 드래그에서 두 값이 충돌합니다. --- ## 2. 종류 / 크기 ### Orientation `horizontal` 은 패널이 좌우로 늘어서고 Separator 가 좌우로 움직입니다. `vertical` 은 위아래입니다. 선의 방향이 아닌 **패널이 나뉘는 방향**을 가리킵니다. shadcn 의 `orientation` 과 같은 기준입니다. > [!NOTE] > **`aria-orientation` 은 반대로 붙습니다** > Separator 자체는 나뉘는 축과 직각으로 서 있습니다. 좌우로 나누는 Splitter 의 Separator 는 > 세로선이므로 `aria-orientation="vertical"` 입니다. WAI-ARIA `separator` 역할의 정의를 > 따른 것입니다. ### 패널 크기 shadcn 이 감싸는 react-resizable-panels 의 단위 규칙을 그대로 따릅니다. **숫자는 픽셀, 단위 없는 문자열은 퍼센트**이고 `px` · `%` · `em` · `rem` · `vh` · `vw` 를 명시할 수도 있습니다. | prop | 기본값 | 뜻 | | --- | --- | --- | | `defaultSize` | auto | 처음 차지할 몫 | | `minSize` | `0%` | 이보다 작아지지 않습니다 | | `maxSize` | `100%` | 이보다 커지지 않습니다 | | `collapsedSize` | `0%` | 접혔을 때의 몫 | `minSize` 는 스타일시트가 아닌 리사이즈 로직이 지킵니다. CSS 쪽에서는 패널에 `min-width: 0` · `min-height: 0` 을 걸어 **내용보다 작아질 수 있게** 둡니다. 그러지 않으면 가장 넓은 자식이 그룹 전체의 최소 크기를 정합니다. ### Grip Separator 가운데의 손잡이입니다. Grip 이 없으면 커서를 Separator 위에 올려야 위치를 알 수 있습니다. 두 패널의 경계가 분명하지 않으면 Grip 을 붙입니다. | Grip | 설명 | |---|---| | 없음 | 선만. 나눌 수 있다는 걸 이미 아는 자리에서만 | | 있음 | shadcn withHandle. 기본으로 이쪽을 쓴다 | ### State 끄는 동안의 `data-dragging`, 잠금 `data-disabled` 는 [3. 상태](#3-상태) 에 옮겼습니다. ### 접기 `collapsible` 패널은 `minSize` 아래로 끌리면 `collapsedSize` 까지 접힙니다. 접혀도 **Separator 는 남습니다.** 다시 펼칠 때 Separator 를 조작하기 때문입니다. | 상태 | 설명 | |---|---| | 펼침 | collapsible 패널이 minSize 이상 | | 접힘 | collapsedSize 로 줄고 Separator 는 남는다 (`data-collapsed`) | > [!WARNING] > **접힌 패널을 DOM 에서 빼지 않습니다** > `data-collapsed` 는 몫을 0으로 만들고 내용만 감춥니다. 패널을 실제로 제거하면 Separator 의 > 순서가 밀리고, 다시 펼칠 수단도 사라집니다. ### 중첩 패널 안에 다른 축의 Splitter 를 넣어 두 축 레이아웃을 만듭니다. 안쪽 Group 은 바깥의 표면(배경·테두리·모서리)을 버려서 경계선이 겹쳐 보이지 않게 합니다. ### 클래스 계약 `styles/elements/splitter.css` 헤더와 원본 `registry.json` `css.blocks` 기준입니다. 클래스가 없는 값(—)은 기본 스타일 그대로입니다. | Prop | 값 | 클래스 | |---|---|---| | `orientation` | `horizontal` · `vertical` | — · `--orientation_vertical` | | Part | 클래스 | |---|---| | Panel | `atom-splitter__panel` | | Separator | `atom-splitter__separator` (`role="separator"`) | | Grip | `atom-splitter__grip` | | Content | `atom-splitter__content` | 상태 선택자: `:hover` · `:focus-visible` · `[data-dragging]` · `[data-disabled]` · `[data-collapsed]` 중첩: `.atom-splitter__panel > .atom-splitter` 는 배경·테두리·모서리를 버립니다. --- ## 3. 상태 끄는 동안에는 Separator 에 `data-dragging` 이 붙어 선이 2px 로 굵어지고 브랜드 색이 됩니다. 같은 시점에 Group 에도 `data-dragging` 을 걸어 커서를 유지하고 글자 선택을 막습니다. 드래그 중에는 포인터가 9px 영역 밖으로 나가기 때문입니다. | 대상 | 속성 | 결과 | | --- | --- | --- | | Separator | `data-dragging` | 선 2px + 브랜드 색, Grip 강조 | | Group | `data-dragging` | 커서 유지, `user-select: none` | | Separator | `data-disabled` | 이 구분자만 잠급니다 | | Group | `data-disabled` | 전체를 잠급니다 — shadcn 의 `disabled` | | State | 표현 | 선택자 | |---|---|---| | hover | 선 `--atom-fg-brand`, Grip `--atom-bg-layer-raised` | `.atom-splitter__separator:hover` | | focus-visible | 2px 브랜드 링, offset -1px | `.atom-splitter__separator:focus-visible` | | dragging | 선 2px + 브랜드 색 · 그룹 커서 유지 | `[data-dragging]` (Separator · Group) | | disabled | 커서도 강조도 없다. Grip `opacity: 0.4` | `[data-disabled]` (Separator · Group) | | collapsed | `flex-basis: 0`, Content 숨김 | `.atom-splitter__panel[data-collapsed]` | --- ## 4. Props shadcn/ui `Resizable` 의 이름과 짝지으면 이렇습니다. | 이 문서 | shadcn/ui | 클래스 | | --- | --- | --- | | Group | `ResizablePanelGroup` | `atom-splitter` | | Panel | `ResizablePanel` | `atom-splitter__panel` | | Separator | `ResizableHandle` | `atom-splitter__separator` | | Grip | `withHandle` | `atom-splitter__grip` | | Prop | Type | Default | 필수 | 설명 | |---|---|---|---|---| | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | | 패널이 나뉘는 축입니다. Group 에 붙습니다. | | `disabled` | `boolean` | `false` | | Group 에 주면 전체를, Separator 에 주면 그 하나를 잠급니다. | | `defaultLayout` | `number[] \| string[]` | | | 처음 배치입니다. 저장해 둔 값을 넣으면 새로고침 뒤에도 유지됩니다. | | `defaultSize` | `number \| string` | | | Panel 의 처음 몫입니다. 숫자는 px, 단위 없는 문자열은 %. | | `minSize` | `number \| string` | `"0%"` | | Panel 의 최소 몫입니다. | | `maxSize` | `number \| string` | `"100%"` | | Panel 의 최대 몫입니다. | | `collapsible` | `boolean` | `false` | | `minSize` 아래로 끌리면 접히게 합니다. | | `collapsedSize` | `number \| string` | `"0%"` | | 접혔을 때의 몫입니다. | | `withHandle` | `boolean` | `false` | | Separator 가운데에 Grip 을 그립니다. | | `disableDoubleClick` | `boolean` | `false` | | 더블클릭으로 기본 비율에 돌아가는 동작을 끕니다. | | `onLayoutChange` | `(layout) => void` | | | 드래그하는 동안 포인터가 움직일 때마다 호출됩니다. | | `onLayoutChanged` | `(layout) => void` | | | 드래그가 끝난 뒤 한 번 호출됩니다. 저장은 이쪽에서 합니다. | | `onResize` | `(size) => void` | | | Panel 의 몫이 바뀔 때 호출됩니다. | ### Parts (compound) | Part | 요소 | 설명 | |---|---|---| | `Splitter` | `
` | Group (`ResizablePanelGroup`). `orientation` · `disabled` · `defaultLayout` · `onLayoutChange` · `onLayoutChanged` 를 받습니다. `layout` 은 패널 순서대로의 몫(%) 배열입니다. | | `Splitter.Panel` | `
` | Panel (`ResizablePanel`). `defaultSize` · `minSize` · `maxSize` · `collapsible` · `collapsedSize` · `onResize` 를 받습니다. 몫은 인라인 `flex-basis` + `flex-grow: 0` 으로 쓰고, 마지막 패널은 남는 공간을 채웁니다. | | `Splitter.Separator` | `