반응성이 높고 액세스 가능한 스위치 구성요소를 빌드하는 방법에 관한 기본 개요입니다.
이 게시물에서는 스위치 구성요소를 빌드하는 방법에 대한 생각을 공유하고자 합니다. 데모 사용해 보기
동영상을 선호하는 경우 이 게시물의 YouTube 버전을 확인하세요.
개요
스위치는 체크박스와 유사하게 작동하지만 불리언 켜기/끄기 상태를 명시적으로 나타냅니다.
이 데모는 대부분의 기능에 <input type="checkbox" role="switch">
를 사용하므로 CSS 또는 JavaScript가 없어도 완전히 작동하고 액세스할 수 있다는 이점이 있습니다. CSS를 로드하면 오른쪽에서 왼쪽으로 읽는 언어, 세로성, 애니메이션 등을 지원합니다. JavaScript를 로드하면 전환을 드래그하고 실질적으로 만들 수 있습니다.
맞춤 속성
다음 변수는 스위치의 다양한 부분과 해당 옵션을 나타냅니다. 최상위 클래스인 .gui-switch
에는 구성요소 하위 요소 전반에서 사용되는 맞춤 속성과 중앙 집중식 맞춤설정을 위한 진입점이 포함되어 있습니다.
추적
길이(--track-size
), 패딩, 두 가지 색상:
.gui-switch {
--track-size: calc(var(--thumb-size) * 2);
--track-padding: 2px;
--track-inactive: hsl(80 0% 80%);
--track-active: hsl(80 60% 45%);
--track-color-inactive: var(--track-inactive);
--track-color-active: var(--track-active);
@media (prefers-color-scheme: dark) {
--track-inactive: hsl(80 0% 35%);
--track-active: hsl(80 60% 60%);
}
}
미리보기
크기, 배경 색상, 상호작용 강조 표시 색상:
.gui-switch {
--thumb-size: 2rem;
--thumb: hsl(0 0% 100%);
--thumb-highlight: hsl(0 0% 0% / 25%);
--thumb-color: var(--thumb);
--thumb-color-highlight: var(--thumb-highlight);
@media (prefers-color-scheme: dark) {
--thumb: hsl(0 0% 5%);
--thumb-highlight: hsl(0 0% 100% / 25%);
}
}
모션 감소
명확한 별칭을 추가하고 반복을 줄이기 위해 이 미디어 쿼리 5의 초안 사양에 따라 PostCSS 플러그인을 사용하여 감소된 모션 환경설정 사용자 미디어 쿼리를 맞춤 속성에 배치할 수 있습니다.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
마크업
<input type="checkbox" role="switch">
요소를 <label>
로 래핑하여 체크박스와 라벨 연결의 모호성을 피하기 위해 관계를 번들로 묶고 사용자에게 라벨과 상호작용하여 입력을 전환할 수 있는 기능을 제공했습니다.
<label for="switch" class="gui-switch">
Label text
<input type="checkbox" role="switch" id="switch">
</label>
<input type="checkbox">
는 API 및 상태로 사전 빌드되어 제공됩니다. 브라우저는 checked
속성과 입력 이벤트(예: oninput
및 onchanged
)를 관리합니다.
레이아웃
Flexbox, 그리드, 맞춤 속성은 이 구성요소의 스타일을 유지하는 데 매우 중요합니다. 값을 중앙 집중화하고, 모호한 계산이나 영역에 이름을 지정하며, 간편한 구성요소 맞춤설정을 위해 작은 맞춤 속성 API를 사용 설정합니다.
.gui-switch
스위치의 최상위 레이아웃은 flexbox입니다. .gui-switch
클래스에는 하위 요소가 레이아웃을 계산하는 데 사용하는 비공개 및 공개 맞춤 속성이 포함되어 있습니다.
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
flexbox 레이아웃을 확장하고 수정하는 것은 다른 flexbox 레이아웃을 변경하는 것과 같습니다.
예를 들어 스위치 위 또는 아래에 라벨을 배치하거나 flex-direction
를 변경하는 방법은 다음과 같습니다.
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
추적
체크박스 입력은 일반 appearance: checkbox
를 삭제하고 대신 자체 크기를 제공하여 스위치 트랙으로 스타일이 지정됩니다.
.gui-switch > input {
appearance: none;
inline-size: var(--track-size);
block-size: var(--thumb-size);
padding: var(--track-padding);
flex-shrink: 0;
display: grid;
align-items: center;
grid: [track] 1fr / [track] 1fr;
}
또한 트랙은 썸네일이 소유권을 주장할 수 있는 1x1 단일 셀 그리드 트랙 영역을 만듭니다.
미리보기
스타일 appearance: none
는 브라우저에서 제공하는 시각적 체크표시도 삭제합니다. 이 구성요소는 입력에 의사 요소와 :checked
의사 클래스를 사용하여 이러한 시각적 표시기를 대체합니다.
썸네일은 input[type="checkbox"]
에 연결된 가상 요소 하위 요소이며 그리드 영역 track
을 주장하여 트랙 아래가 아닌 트랙 위에 스택됩니다.
.gui-switch > input::before {
content: "";
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
}
스타일
맞춤 속성을 사용하면 색상 구성표, 오른쪽에서 왼쪽으로 읽는 언어, 모션 환경설정에 맞게 조정되는 다목적 스위치 구성요소를 사용할 수 있습니다.
터치 상호작용 스타일
휴대기기에서는 브라우저가 라벨과 입력란에 탭 강조 표시 및 텍스트 선택 기능을 추가합니다. 이로 인해 전환에 필요한 스타일과 시각적 상호작용 피드백에 부정적인 영향을 미쳤습니다. CSS 몇 줄로 이러한 효과를 삭제하고 나만의 cursor: pointer
스타일을 추가할 수 있습니다.
.gui-switch {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
이러한 스타일은 유용한 시각적 상호작용 피드백이 될 수 있으므로 항상 삭제하는 것은 좋지 않습니다. 맞춤 대안을 삭제하는 경우 반드시 다른 대안을 제공하세요.
추적
이 요소의 스타일은 주로 모양과 색상과 관련이 있으며, 이는 캐스케이드를 통해 상위 .gui-switch
에서 액세스합니다.
.gui-switch > input {
appearance: none;
border: none;
outline-offset: 5px;
box-sizing: content-box;
padding: var(--track-padding);
background: var(--track-color-inactive);
inline-size: var(--track-size);
block-size: var(--thumb-size);
border-radius: var(--track-size);
}
스위치 트랙의 다양한 맞춤설정 옵션은 4가지 맞춤 속성에서 가져옵니다. appearance: none
가 일부 브라우저에서 체크박스의 테두리를 삭제하지 않으므로 border: none
가 추가되었습니다.
미리보기
썸네일 요소는 이미 오른쪽 track
에 있지만 원 스타일이 필요합니다.
.gui-switch > input::before {
background: var(--thumb-color);
border-radius: 50%;
}
상호작용
맞춤 속성을 사용하여 마우스 오버 강조 표시 및 썸네일 위치 변경사항을 표시하는 상호작용을 준비합니다. 모션 또는 마우스 오버 강조 표시 스타일을 전환하기 전에 사용자의 환경설정도 확인됩니다.
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
엄지 위치
맞춤 속성은 트랙에서 썸네일을 배치하기 위한 단일 소스 메커니즘을 제공합니다. 사용 가능한 트랙 및 썸 크기는 계산 시 썸을 적절하게 오프셋하고 트랙 내에 유지하는 데 사용됩니다. 0%
및 100%
input
요소는 위치 변수 --thumb-position
를 소유하고 있으며, thumb 가상 요소는 이를 translateX
위치로 사용합니다.
.gui-switch > input {
--thumb-position: 0%;
}
.gui-switch > input::before {
transform: translateX(var(--thumb-position));
}
이제 CSS와 체크박스 요소에 제공된 의사 클래스에서 --thumb-position
를 자유롭게 변경할 수 있습니다. 이 요소에서 앞서 조건부로 transition: transform
var(--thumb-transition-duration) ease
를 설정했으므로 변경 시 다음과 같은 변경사항이 애니메이션으로 처리될 수 있습니다.
/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
}
/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
}
이러한 분리된 조정이 잘 작동했다고 생각합니다. thumb 요소는 하나의 스타일, 즉 translateX
위치와만 관련이 있습니다. 입력은 모든 복잡성과 계산을 관리할 수 있습니다.
업종
input
요소에 CSS 변환을 사용하여 회전을 추가하는 수정자 클래스 -vertical
를 사용하여 지원이 이루어졌습니다.
하지만 3D로 회전된 요소는 구성요소의 전체 높이를 변경하지 않으며 이로 인해 블록 레이아웃이 벗어날 수 있습니다. --track-size
및 --track-padding
변수를 사용하여 이를 고려합니다. 예상대로 세로 버튼이 레이아웃에 흐르는 데 필요한 최소 공간을 계산합니다.
.gui-switch.-vertical {
min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));
& > input {
transform: rotate(-90deg);
}
}
(RTL) 오른쪽에서 왼쪽으로
CSS 친구인 엘라드 셰이커와 저는 단일 변수를 전환하여 오른쪽에서 왼쪽으로 언어를 처리하는 CSS 변환을 사용한 슬라이드 아웃 측면 메뉴의 프로토타입을 함께 제작했습니다. CSS에는 논리적 속성 변환이 없으며 앞으로도 없을 수 있기 때문에 이렇게 했습니다. 엘라드는 맞춤 속성 값을 사용하여 비율을 반전하여 논리 변환을 위한 자체 맞춤 로직을 단일 위치에서 관리할 수 있는 좋은 아이디어를 생각해 냈습니다. 이 스위치에서도 동일한 기법을 사용했으며 효과가 좋았습니다.
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
--isLTR
라는 맞춤 속성은 처음에 1
값을 보유합니다. 즉, 레이아웃이 기본적으로 왼쪽에서 오른쪽이므로 true
입니다. 그런 다음 CSS 가상 클래스 :dir()
를 사용하여 구성요소가 오른쪽에서 왼쪽 레이아웃 내에 있으면 값이 -1
로 설정됩니다.
변환 내의 calc()
내에서 --isLTR
를 사용하여 --isLTR
를 실행합니다.
.gui-switch.-vertical > input {
transform: rotate(-90deg);
transform: rotate(calc(90deg * var(--isLTR) * -1));
}
이제 세로 스위치의 회전이 오른쪽에서 왼쪽 레이아웃에 필요한 반대 측 위치를 고려합니다.
반대쪽 요구사항을 고려하도록 썸네일 가상 요소의 translateX
변환도 업데이트해야 합니다.
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
--thumb-position: calc(
((var(--track-size) / 2) - (var(--thumb-size) / 2))
* var(--isLTR)
);
}
이 접근 방식은 논리적 CSS 변환과 같은 개념과 관련된 모든 요구사항을 해결하는 데는 적합하지 않지만, 많은 사용 사례에 DRY 원칙을 제공합니다.
주
내장 input[type="checkbox"]
를 사용하려면 :checked
, :disabled
, :indeterminate
, :hover
과 같은 다양한 상태를 처리해야 합니다. :focus
는 의도적으로 그대로 두고 오프셋만 조정했습니다. 포커스 링은 Firefox 및 Safari에서 멋지게 표시되었습니다.
선택됨
<label for="switch-checked" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>
이 상태는 on
상태를 나타냅니다. 이 상태에서 입력 '트랙' 배경은 활성 색상으로 설정되고 썸 위치는 '끝'으로 설정됩니다.
.gui-switch > input:checked {
background: var(--track-color-active);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
사용 중지됨
<label for="switch-disabled" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>
:disabled
버튼은 시각적으로 다르게 보일 뿐만 아니라 요소를 변경 불가하게 만들어야 합니다. 상호작용 불변성은 브라우저에서 자유롭지만 시각적 상태에는 appearance: none
사용으로 인해 스타일이 필요합니다.
.gui-switch > input:disabled {
cursor: not-allowed;
--thumb-color: transparent;
&::before {
cursor: not-allowed;
box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);
@media (prefers-color-scheme: dark) { & {
box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
}}
}
}
이 상태는 사용 중지된 상태와 선택된 상태가 모두 있는 어두운 테마와 밝은 테마가 필요하므로 까다롭습니다. 스타일 조합의 유지보수 부담을 덜기 위해 이러한 상태에 스타일적으로 최소한의 스타일을 선택했습니다.
불확실
종종 잊어버리는 상태는 체크박스가 선택 또는 선택 해제되지 않은 :indeterminate
입니다. 재미있고 매력적이며 겸손한 상태입니다. 불리언 상태는 중간 상태가 은밀하게 있을 수 있다는 것을 상기시켜 주는 좋은 예입니다.
체크박스를 불확정으로 설정하는 것은 까다롭습니다. JavaScript만 설정할 수 있습니다.
<label for="switch-indeterminate" class="gui-switch">
Indeterminate
<input type="checkbox" role="switch" id="switch-indeterminate">
<script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>
이 상태는 소박하고 매력적이므로 스위치 엄지손가락 위치를 가운데에 배치하는 것이 적절하다고 생각했습니다.
.gui-switch > input:indeterminate {
--thumb-position: calc(
calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
* var(--isLTR)
);
}
마우스 오버
마우스 오버 상호작용은 연결된 UI를 시각적으로 지원하고 대화형 UI로의 방향을 제공해야 합니다. 이 스위치는 라벨이나 입력에 마우스를 가져가면 엄지손가락을 반투명 고리로 강조 표시합니다. 그러면 이 마우스 오버 애니메이션이 대화형 썸네일 요소를 향한 방향을 제공합니다.
'강조 표시' 효과는 box-shadow
로 실행됩니다. 사용 중지되지 않은 입력의 마우스 오버 시 --highlight-size
크기를 늘립니다. 사용자가 모션을 괜찮게 생각하면 box-shadow
를 전환하여 크기가 커지는 것을 확인할 수 있고, 모션을 괜찮게 생각하지 않으면 강조 표시가 즉시 표시됩니다.
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
.gui-switch > input:not(:disabled):hover::before {
--highlight-size: .5rem;
}
자바스크립트
스위치 인터페이스는 물리적 인터페이스를 에뮬레이션하려고 시도할 때, 특히 트랙 안에 원이 있는 이러한 종류의 스위치는 기이하게 느껴질 수 있습니다. iOS는 스위치를 통해 이 문제를 해결했습니다. 스위치를 좌우로 드래그할 수 있으며 이 옵션을 사용할 수 있어 매우 만족스럽습니다. 반대로 드래그 동작을 시도했는데 아무 일도 일어나지 않으면 UI 요소가 비활성 상태로 느껴질 수 있습니다.
드래그 가능한 엄지손가락
thumb 의사 요소는 .gui-switch > input
범위 var(--thumb-position)
에서 위치를 수신합니다. JavaScript는 입력에 인라인 스타일 값을 제공하여 포인터 동작을 따르는 것처럼 thumb 위치를 동적으로 업데이트할 수 있습니다. 포인터가 해제되면 인라인 스타일을 삭제하고 맞춤 속성 --thumb-position
를 사용하여 드래그가 꺼짐에 더 가까웠는지 또는 켜짐에 더 가까웠는지 확인합니다. 이는 솔루션의 핵심입니다. 포인터 이벤트는 조건부로 포인터 위치를 추적하여 CSS 맞춤 속성을 수정합니다.
이 스크립트가 표시되기 전에 구성요소는 이미 100% 작동하고 있었으므로 라벨을 클릭하여 입력을 전환하는 것과 같은 기존 동작을 유지하려면 상당한 작업이 필요합니다. JavaScript는 기존 기능을 희생하여 기능을 추가해서는 안 됩니다.
touch-action
드래그는 동작으로, 맞춤 동작이므로 touch-action
의 이점을 활용할 수 있습니다. 이 스위치의 경우 가로 동작은 스크립트에서 처리하거나 세로 스위치 변형에 대해 캡처된 세로 동작을 처리해야 합니다. touch-action
를 사용하면 브라우저에 이 요소에서 처리할 동작을 알려 스크립트가 경쟁 없이 동작을 처리할 수 있습니다.
다음 CSS는 포인터 동작이 이 스위치 트랙 내에서 시작되면 세로 동작을 처리하고 가로 동작은 아무것도 하지 않도록 브라우저에 지시합니다.
.gui-switch > input {
touch-action: pan-y;
}
원하는 결과는 페이지를 화면 이동하거나 스크롤하지 않는 가로 동작입니다. 포인터는 입력 내에서 시작을 세로로 스크롤하고 페이지를 스크롤할 수 있지만, 가로 포인터는 맞춤 처리됩니다.
픽셀 값 스타일 유틸리티
설정 시와 드래그 중에 요소에서 계산된 다양한 숫자 값을 가져와야 합니다. 다음 자바스크립트 함수는 CSS 속성을 고려하여 계산된 픽셀 값을 반환합니다. getStyle(checkbox, 'padding-left')
처럼 설정 스크립트에서 사용됩니다.
const getStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}
const getPseudoStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}
export {
getStyle,
getPseudoStyle,
}
window.getComputedStyle()
가 두 번째 인수인 타겟 의사 요소를 허용하는 방식을 확인합니다. JavaScript는 의사 요소에서도 요소에서 너무 많은 값을 읽을 수 있습니다.
dragging
이는 드래그 로직의 핵심 순간이며 함수 이벤트 핸들러에서 몇 가지 주목할 사항이 있습니다.
const dragging = event => {
if (!state.activethumb) return
let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
let directionality = getStyle(state.activethumb, '--isLTR')
let track = (directionality === -1)
? (state.activethumb.clientWidth * -1) + thumbsize + padding
: 0
let pos = Math.round(event.offsetX - thumbsize / 2)
if (pos < bounds.lower) pos = 0
if (pos > bounds.upper) pos = bounds.upper
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}
스크립트 주인공은 state.activethumb
입니다. 이 스크립트는 포인터와 함께 작은 원을 배치합니다. switches
객체는 키가 .gui-switch
이고 값이 스크립트의 효율성을 유지하는 캐시된 경계 및 크기인 Map()
입니다. 오른쪽에서 왼쪽은 CSS가 --isLTR
인 것과 동일한 맞춤 속성을 사용하여 처리되며 이를 사용하여 로직을 반전하고 RTL을 계속 지원할 수 있습니다. event.offsetX
도 유용합니다. 엄지손가락 배치에 유용한 델타 값이 포함되어 있기 때문입니다.
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
이 CSS의 마지막 줄은 썸네일 요소에서 사용되는 맞춤 속성을 설정합니다. 이 값 할당은 시간이 지남에 따라 전환되지만 이전 포인터 이벤트가 일시적으로 --thumb-transition-duration
를 0s
로 설정하여 느린 상호작용을 제거했습니다.
dragEnd
사용자가 스위치 밖으로 멀리 드래그한 다음 놓을 수 있도록 하려면 전역 창 이벤트를 등록해야 합니다.
window.addEventListener('pointerup', event => {
if (!state.activethumb) return
dragEnd(event)
})
사용자가 자유롭게 드래그할 수 있고 인터페이스가 이를 고려할 만큼 스마트해야 한다고 생각합니다. 이 스위치로 이를 처리하는 데는 많은 시간이 걸리지 않았지만 개발 과정에서는 신중하게 고려해야 했습니다.
const dragEnd = event => {
if (!state.activethumb) return
state.activethumb.checked = determineChecked()
if (state.activethumb.indeterminate)
state.activethumb.indeterminate = false
state.activethumb.style.removeProperty('--thumb-transition-duration')
state.activethumb.style.removeProperty('--thumb-position')
state.activethumb.removeEventListener('pointermove', dragging)
state.activethumb = null
padRelease()
}
요소와의 상호작용이 완료되었습니다. 이제 입력 checked 속성을 설정하고 모든 동작 이벤트를 삭제합니다. 체크박스는 state.activethumb.checked = determineChecked()
로 변경됩니다.
determineChecked()
dragEnd
에서 호출하는 이 함수는 트랙 경계 내에서 thumb 현재의 위치를 확인하고 트랙의 절반 이상이면 true를 반환합니다.
const determineChecked = () => {
let {bounds} = switches.get(state.activethumb.parentElement)
let curpos =
Math.abs(
parseInt(
state.activethumb.style.getPropertyValue('--thumb-position')))
if (!curpos) {
curpos = state.activethumb.checked
? bounds.lower
: bounds.upper
}
return curpos >= bounds.middle
}
추가 생각
드래그 동작은 선택한 초기 HTML 구조로 인해 약간의 코드 부채가 발생했습니다. 특히 입력을 라벨로 래핑하는 경우가 그렇습니다. 상위 요소인 라벨은 입력 후에 클릭 상호작용을 수신합니다. dragEnd
이벤트 끝에 padRelease()
가 이상한 함수로 표시되었을 수 있습니다.
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
이는 사용자가 실행한 상호작용을 선택 해제하거나 선택할 수 있으므로 라벨이 나중에 클릭을 받음을 고려하기 위함입니다.
이 작업을 다시 해야 한다면 라벨 클릭을 직접 처리하고 내장 동작과 충돌하지 않는 요소를 만들기 위해 UX 업그레이드 중에 JavaScript로 DOM을 조정하는 것이 좋을 수 있습니다.
이런 종류의 JavaScript는 작성하기가 가장 싫습니다. 조건부 이벤트 버블링을 관리하고 싶지 않습니다.
const preventBubbles = event => {
if (state.recentlyDragged)
event.preventDefault() && event.stopPropagation()
}
결론
이 작은 스위치 구성요소는 지금까지의 모든 GUI 챌린지 중 가장 많은 작업이 필요했습니다. 이제 제가 어떻게 했는지 알았으니 어떻게 하시겠어요? 🙂
접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. 데모를 만들고 트윗해 주세요 링크. 아래 커뮤니티 리믹스 섹션에 추가하겠습니다.
커뮤니티 리믹스
- 맞춤 요소(demo 및 code)를 사용한 @KonstantinRouda
- @jhvanderschee와 Codepen 버튼
리소스
.gui-switch
GitHub의 소스 코드를 찾습니다.