JunDS
웹 바닐라 Custom Elements 와 iOS 로 다시 짓는 디자인 시스템. 아래 목록과 상태는 라이브러리 원장(ledger)에서 빌드 타임에 파생됩니다 — 문서가 곧 라이브러리의 실상태입니다.
468 COMPONENTS445 WEB DONE142 iOS DONELEDGER 2026-07-27
Core13
토큰을 화면 골격으로 번역하는 프레임워크 코어.
Layout12
스택·그리드·페이지 — 배치를 담당하는 층.
Primitives51
버튼·입력·뱃지 — 더 쪼갤 수 없는 기본 단위.
Composites201
모달·탭·테이블 — 상태를 품은 조합 컴포넌트.
Patterns43
데이터테이블·칸반·폼위저드 — 화면 단위 패턴.
Finance86
시세·차트·호가 — 금융 도메인 전용 세트.
Hooks62
상태·이벤트·환경 감지를 캡슐화한 훅/Behavior.
기반 요소19
위 목록은 원장 468행에서 파생됩니다 — 웹 CE와 iOS 컴포넌트가 짝을 이루는 것들입니다. 토큰·배치 컨테이너·측정 규칙·모디파이어는 그 짝이 아니라서 원장에 행이 없고, 그래서 카탈로그에 나타나지 않습니다. 그런데 468종을 한꺼번에 바꾸는 것은 대개 이쪽입니다.
배치 (Layout)
UIKit에서 화면을 짜는 일 자체를 줄인 층. 웹은 CSS가 이미 하는 일이라 원장에 대응 행이 없고, 그래서 카탈로그에 안 나타난다 — 실제로는 이번에 가장 크게 바뀐 부분이다.
JdVStack · JdHStack · JdFlex
iOSDEC-042 · DEC-043자식을 블록으로 적으면 트리와 제약이 한 표현식으로 완성되는 선언형 생성자.
왜이전에는 뷰마다 생성 → addSubview → 제약 세 단계를 손으로 반복했고, 순서를 어기면 preconditionFailure로 앱이 죽었다. 빌더가 자식을 넣으므로 그 함정이 문법적으로 사라진다.
packages/ios/Sources/JunDSUIKit/Layout/JdViewBuilder.swiftJdColumnsView
iOSDEC-042 · DEC-043모든 행을 한 번에 측정해 열 폭을 공유하는 격자. fixed · fit(max:) · flex(weight:).
왜UIStackView의 행들은 서로를 몰라서 1행의 종목명 폭과 2행의 폭이 각자 정해진다 — 표가 어긋난다. 해결책이 여태 “UICollectionViewCompositionalLayout을 직접 세우기”였다.
packages/ios/Sources/JunDSUIKit/Layout/JdColumnsView.swiftJdWrapView
iOSDEC-041줄바꿈 흐름 컨테이너. 고유 폭 흐름(칩)과 균등 분할 격자(KPI 셀) 두 모드.
왜UIStackView는 줄바꿈을 못 한다. SwiftUI엔 JdFlowLayout이 있었지만 UIKit엔 대응물이 없어 웹에서 한 줄이던 배치가 iOS에서는 컬렉션 뷰 한 채였다.
packages/ios/Sources/JunDSUIKit/Layout/JdWrapView.swiftJdAdaptiveStackView
iOSDEC-042폭이 임계값 미만이면 축을 뒤집는 스택 (웹 미디어쿼리 대응).
왜layoutSubviews에서 axis를 잘못 쓰면 레이아웃 루프가 생긴다. 판정을 한 타입에 가두고 값이 실제로 바뀔 때만 쓴다. 폭 0(첫 프레임)은 좁다고 판정하지 않는다.
packages/ios/Sources/JunDSUIKit/Layout/JdViewBuilder.swiftJdMeasure.flowSize
iOSDEC-047흐름 배치 전용 측정 — 자연 폭으로 먼저 재고, 컨테이너보다 넓을 때만 폭을 강제한다.
왜격자는 열 폭을 강제하는 것이 맞지만 흐름에서 그러면 칩마다 컨테이너 폭을 요구해 한 줄에 하나씩 놓인다. 게다가 .greatestFiniteMagnitude 는 유한해서(isFinite==true) "무제한" 센티널로 쓰면 "폭 1.8e308으로 강제"가 된다 — 태그 칩 8개가 폭 900에서도 8줄로 깔렸다.
packages/ios/Sources/JunDSUIKit/Layout/JdMeasure.swiftJdMeasure
iOSDEC-046자식 측정의 단일 규칙 — systemLayoutSizeFitting → sizeThatFits → intrinsicContentSize 순.
왜UIView.sizeThatFits의 기본 구현은 bounds(=0)를 돌려주고, 내부 제약으로 크기가 정해지는 컨테이너는 고유 크기가 없다. 두 API만 묻던 코드가 KPI 카드를 0높이로 접어 화면이 비었다 — 테스트 737개가 통과하는 상태에서 시뮬레이터로 잡았다.
packages/ios/Sources/JunDSUIKit/Layout/JdMeasure.swiftJdAlign · JdJustify · JdEdge
iOSDEC-043정렬·분배·여백 어휘. 웹 align-items / justify-content 값 집합과 동일.
왜배치 표면이 UIStackView.Alignment를 그대로 노출하면 플랫폼 타입이 소비자 코드로 새고, 같은 개념을 웹은 stretch·iOS는 .fill로 불러 3플랫폼 문서가 갈라진다.
packages/ios/Sources/JunDSCore/JdAlign.swift시각·촉감 (Elevation · Motion)
“컴포넌트가 몇 개인가”로는 보이지 않는 층. 같은 컴포넌트가 눌리고 떠 보이게 만드는 공용 규칙이다.
JdElevation
iOSDEC-039토큰 그림자를 겹 단위로 적용하는 모디파이어 (다크는 헤어라인 링 + 주변광).
왜그림자 토큰이 2겹이 된 뒤로 각 컴포넌트가 첫 장만 꺼내 쓰고 있었다 — 라이트에서 주변광을 버리고 다크에서는 링을 그림자로 오해해 아무것도 그리지 않는다. 8곳을 이 렌더러로 이전했다.
packages/ios/Sources/JunDSSwiftUI/Bridge/JdElevation.swiftJdPressable · JdMotion 프리셋
iOSDEC-039프레스 스케일 + 스프링. Reduce Motion은 JdMotion 단일 진입점을 경유한다.
왜SwiftUI 53종 중 움직임을 가진 것이 14종뿐이었고 버튼조차 배경색 교체만 했다. 손가락이 픽셀을 가리는 터치에서 색 변화만으로는 “먹었다”가 전달되지 않는다.
packages/ios/Sources/JunDSSwiftUI/Bridge/JdPress.swiftJdShadowDominant
iOSDEC-039CALayer용 — blur가 가장 큰 겹(주변광)을 고른다.
왜CALayer는 그림자를 한 장만 가질 수 있다. “첫 장”은 라이트에서 접지 겹, 다크에서 헤어라인 링이라 두 모드 모두 틀린 장이다.
packages/ios/Sources/JunDSCore/JdShadowDominant.swiftJdFontBridge.scaledDigitFont
iOSDEC-040숫자만 등폭인 폰트 — 웹 font-variant-numeric: tabular-nums의 정확한 대응.
왜기존 scaledMonoFont는 글자까지 등폭이라 한글 라벨이 타자기처럼 보인다. 시세는 값이 갱신될 때 자리수가 바뀌어도 폭이 흔들리지 않는 것만 필요하다.
packages/ios/Sources/JunDSUIKit/Bridge/JdFontBridge.swiftfinance 공통 어휘
finance 86종이 공유하는 판정·색·포맷. 웹은 33개 CSS 파일이 같은 팔레트를 각자 재선언하고 있었다.
JdTrend · JdTrendPolicy
iOSDEC-040 · DEC-041추세 판정. 규칙이 세 개이고 그 차이가 타입이다 — live(|v|<0.005 보합) · exact(0만 보합) · gainOrEven(보합 없음).
왜웹에 세 규칙이 실제로 공존한다. 같은 -0.003이 실시간 배지에서는 보합, 확정 등락률 배지에서는 하락이다. 하나로 합치면 표면이 달라지므로 규칙 자체를 값으로 올렸다.
packages/ios/Sources/JunDSCore/Specs/JdFinanceSpecs.swiftJdFinanceTheme
iOSDEC-040상승·하락·보합·라이브 색의 단일 소스 + 앱 override.
왜기본값은 웹과 같은 초록 상승이다. 한국 시장 관례(적상승)를 기본으로 삼지 않은 이유는 웹이 이미 초록으로 출고돼 3플랫폼 표면이 갈라지기 때문 — 전환은 앱이 한 줄로 한다.
packages/ios/Sources/JunDSCore/Specs/JdFinanceSpecs.swiftJdFinanceTheme.categoryPalette · onTint
iOSDEC-047테마 카테고리 회전 팔레트 5종 + 틴트 위 글자색 규칙.
왜12~14% 틴트 배경에 원색 글자를 얹으면 대비가 무너진다(웹 실측 amber 약 1.9:1). 색상은 유지한 채 foreground 쪽으로 섞어 올리는 계산을 스펙이 소유한다 — 컴포넌트마다 다시 쓰면 반드시 어긋난다. 회전 순서도 웹과 같게 고정했다(다르면 같은 목록이 두 플랫폼에서 다른 색이 된다).
packages/ios/Sources/JunDSCore/Specs/JdFinanceChipSpecs.swiftJdFinanceFormat
iOSDEC-040부호·소수·퍼센트·가격 포맷. 로케일은 인자로만 받는다(Locale.current를 읽지 않는다).
왜기기 지역 설정이 결과에 새면 스냅샷·테스트·디자인 대조가 전부 흔들린다. 웹의 프리렌더 결정성 규칙과 같은 이유가 iOS에도 그대로 산다.
packages/ios/Sources/JunDSCore/Specs/JdFinanceSpecs.swift토큰 층 (양 플랫폼)
tokens/*.json 하나에서 CSS 변수·Swift·React TS 세 산출물이 나온다. 여기가 바뀌면 전 컴포넌트가 함께 바뀐다.
neutral 램프 11단
웹 · iOSDEC-039다크에서 반전되는 중성 램프 — “숫자가 클수록 대비가 높다”가 두 모드에서 같다.
왜웹 컴포넌트 32개 파일이 Tailwind 회색을 하드코딩해 다크에서 밝은 슬래브로 뒤집혔다. 램프가 모드를 알면 컴포넌트는 모드 분기 없이 토큰 하나만 쓰면 된다.
tokens/color.json엘리베이션 2겹
웹 · iOSDEC-039각 단이 접지 그림자 + 주변광 그림자. 다크는 첫 겹을 헤어라인 링으로 교체.
왜1겹 단일 그림자는 물체가 “떠 있다”가 아니라 “얼룩이 묻었다”로 읽힌다. 다크에서 융기를 읽게 하는 것은 그림자가 아니라 윗면의 빛이다.
tokens/shadow.json모션 5종 추가
웹 · iOSDEC-039duration press(90ms)·snap·emphasis, easing emphasized·overshoot.
왜overshoot는 스위치 썸·체크 표식처럼 자리를 잡는 움직임 전용이다 — 색·투명도에 쓰면 깜빡임으로 읽힌다.
tokens/motion.json포커스 링 단일 레시피
웹DEC-039--jd-focus-ring 하나로 168개 CSS의 링 조제를 통일.
왜box-shadow가 아니라 outline인 이유: border-radius를 따라가고, overflow:hidden 조상 안에서도 잘리지 않는다.
packages/web/src/styles/base.css