티스토리 뷰
배경
fe conf를 보다가 토스 라이브러리 공개를 보고 깜짝 놀랐다. 그리고 보다보니 유용한 것들이 많아서 종종 코드를 보고 있다. 사실 처음부터 모든 코드를 볼 수 는 없고 심심할 때 관심가는 것들을 하나씩 보고 있었는데 좀 구체적으로 살펴보기로 했다. 그리고 깃헙에 모노레포 환경으로 테트리스를 만들려고 하는데 이걸 하기 전에 스타일 관련해서 좀 생각도 하고 코드를 작성해보고 싶었다.(디자인 컴포넌트 제외) 개인적으로 tailwind같이 css가 직접 jsx에 들어가는 방법은 좋아하지 않는다.(관심사 분리가 되지 않기 때문) 장단점이 있다고 생각하지만 개인 프로젝트에서는 선택하지 않기로 했다. 그러면 일일히 스타일을 작성해야하는데 이게 생각보다 좀 귀찮고 시간도 걸린다. 이미 거대한 프로젝트에서 컴포넌트가 잘 관리된 상태라면 그렇진 않지만 나는 지금 무에서 시작하기 때문에 만들어진 컴포넌트를 적절히 사용하는게 아니라 계속해서 만들어야한다. 물론 기본 ui 라이브러리에서 제공하는 컴포넌트정도는 만들생각이다.
어쨋든... css 관련해서 좋은 방법이 없을까? 이런 자료는 어디서 찾지? 라고 생각하던 와중에 이게 공개되어서 이걸 가지고 공부하기로 했다.
들어가기 전에
나는 모든 코드를 까보진 않을 것이다. 내가 필요하다고 생각하는 것들만 정리하고 변경하고 싶은것들이 있으면 변경할 예정이다.
그리고 그 전에 color, media, typo, scroll, center, ellipsis는 이미 넣어놨다. media와 ellipsis가 중복되고 center는 없는데 이것만 미리 설명하고 넘어가겠다.
Center
export const center = css`
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: fixed;
left: 0;
top: 0;
`;
우리는 생각보다 위와같은 스타일을 자주 사용한다. 말 그대로 정가운데 넣어주는 스타일이다. zIndex도 인자로 넣어줄까? 생각했는데 일단 넘어가겟다.
코드 까보기
types.ts
import { AllHTMLAttributes, HTMLProps } from 'react';
export type ExtendHTMLProps<Elem extends HTMLElement, T> = Omit<HTMLProps<Elem>, keyof T> & T;
export type CSSPixelValue = string | number;
export type AxisDirection = 'vertical' | 'horizontal';
export type InferenceHTMLElement<K extends keyof JSX.IntrinsicElements> = NonNullable<
Extract<JSX.IntrinsicElements[K]['ref'], React.RefObject<any>>['current']
>;
export interface AsProps<T extends keyof JSX.IntrinsicElements> extends AllHTMLAttributes<T> {
as?: T;
}
나중에 설명
utils/position.ts
import type { CSSProperties } from 'react';
import { css, SerializedStyles } from '@emotion/react';
import { CSSPixelValue } from '../types';
import type { Property } from 'csstype';
interface Coordinates {
top?: CSSPixelValue;
right?: CSSPixelValue;
bottom?: CSSPixelValue;
left?: CSSPixelValue;
}
/**
* @name position
* @description
* CSS `position`과 `position`에 연관된 프로퍼티(`top`, `right`, `bottom`, `left`)를 쉽게 선언할 수 있도록 하는 shorthand 유틸리티입니다.
*
* ```ts
* type CSSPixelValue = string | number;
*
* function position(
* position: 'absolute' | 'fixed' | 'relative' | 'static' | 'sticky',
* top: CSSPixelValue,
* right: CSSPixelValue,
* bottom: CSSPixelValue,
* left: CSSPixelValue
* ): SerializedStyles;
*
* function position(
* top: CSSPixelValue,
* right: CSSPixelValue,
* bottom: CSSPixelValue,
* left: CSSPixelValue
* ): SerializedStyles;
* ```
*
* @example
* import { position } from '@toss/emotion-utils';
*
* const fullPageLayer = position('fixed', 0, 0, 0, 0);
* // 위 코드는 아래 코드와 동치입니다.
* const fullPageLayer = css`
* position: fixed;
* top: 0;
* right: 0;
* bottom: 0;
* left: 0;
* `;
*
* // 첫번째 인자를 생략하고 `top` 부터 넘길 수 있습니다.
* const allZero = position(0, 0, 0, 0);
*
* // 다음처럼도 사용 가능합니다
* position('absolute', {top: 0, left: 0});
*/
export function position(
position: CSSProperties['position'],
top: CSSPixelValue,
right: CSSPixelValue,
bottom: CSSPixelValue,
left: CSSPixelValue,
): SerializedStyles;
export function position(
top: CSSPixelValue,
right: CSSPixelValue,
bottom: CSSPixelValue,
left: CSSPixelValue,
): SerializedStyles;
export function position(position: CSSProperties['position'], coordinates?: Coordinates): SerializedStyles;
export function position(
positionOrTop: CSSProperties['position'] | CSSPixelValue,
topOrCoordinates: CSSPixelValue | Coordinates = {},
...values: CSSPixelValue[]
) {
const [top, right, bottom, left] = isPositionValue(positionOrTop)
? isCSSPixelValue(topOrCoordinates)
? [topOrCoordinates, ...values]
: [topOrCoordinates?.top, topOrCoordinates?.right, topOrCoordinates?.bottom, topOrCoordinates?.left]
: [positionOrTop, topOrCoordinates as CSSPixelValue, ...values];
return css([
css({ position: isPositionValue(positionOrTop) ? positionOrTop : undefined }),
css({ top, right, bottom, left }),
]);
}
function isPositionValue(value: unknown): value is Property.Position {
return ['static', 'relative', 'absolute', 'fixed', 'sticky', '-webkit-sticky'].includes(value as string);
}
function isCSSPixelValue(value: unknown): value is CSSPixelValue {
return typeof value === 'string' || typeof value === 'number';
}
코드를 읽기전에 사용법부터 보자. 여기서부터 배워야할점이 있다. 나는 이런 세부적인 주석을 적은적이 없다. 이유를 말해보자면 주석을 잘 적는데는 많은 시간이 걸리고 사용하는 사람들은 조금만 익숙해져도 주석없이 코드를 이해하기 때문이다. 하지만 이건 그렇게 좋은 생각이 아니다. 나만 이해하는, 팀만 이해하는 코드는 좋은 코드가 아니다. 물론 잘 작성된 코드는 주석이 필요하지 않다. 하지만 이런 함수는 익숙해지기 전까지는 쉽게 사용할 수 없다. 특히나 팀의 규모가 커질수록 이런 문제점들이 더욱 절실하게 느껴진다.
* @example
* import { position } from '@toss/emotion-utils';
*
* const fullPageLayer = position('fixed', 0, 0, 0, 0);
* // 위 코드는 아래 코드와 동치입니다.
* const fullPageLayer = css`
* position: fixed;
* top: 0;
* right: 0;
* bottom: 0;
* left: 0;
* `;
*
* // 첫번째 인자를 생략하고 `top` 부터 넘길 수 있습니다.
* const allZero = position(0, 0, 0, 0);
*
* // 다음처럼도 사용 가능합니다
* position('absolute', {top: 0, left: 0});
일단 사용법부터 보자. example이 있으니까 너무~~ 좋다. position을 넣을때 일일히 넣지 않고 모듈화시켜서 넣어주는 함수인것같다. 사용법이 직관적이고 여러가지 방법을 지원해서 편한대로 사용할 수 있다. 이런 마법같은 코드는 어떻게 만드는걸까?? 이제 코드를 읽어보자
export function position(
position: CSSProperties['position'],
top: CSSPixelValue,
right: CSSPixelValue,
bottom: CSSPixelValue,
left: CSSPixelValue,
): SerializedStyles;
export function position(
top: CSSPixelValue,
right: CSSPixelValue,
bottom: CSSPixelValue,
left: CSSPixelValue,
): SerializedStyles;
export function position(position: CSSProperties['position'], coordinates?: Coordinates): SerializedStyles;
export function position(
positionOrTop: CSSProperties['position'] | CSSPixelValue,
topOrCoordinates: CSSPixelValue | Coordinates = {},
...values: CSSPixelValue[]
)
똑같은 이름의 함수를 네개나 선언했다. 정확히는 위에 세개의 타입으로 접근할 수 있고 맨 아래 함수는 위 세개의 타입을 포함하고 있다. 아래와 같이 함수를 적고 커서를 올리면 세개의 함수가 나온다.
맨 위 세개의 함수는 타입이 직관적이기 때문에 설명을 생략한다.
마지막 함수의 타입만 살펴보자. positionOrTop은 첫번째 인자가 position이 올 수 도 있고 top이 올수도 있기 때문에 이렇게 정했다.
topOrCordinaters는 말 그대로 top 또는 Cordinaters가 오는 것 같다. 그런데... top, right, coordinates가 온다. 타입은 틀리지 않았지만 변수명이 틀렸다. 사소한 부분이긴 하지만 topOrRightOrCoordinates가 옳은 변수명이다. 너무 길어져서 이렇게 했나 싶기도 하다.
그리고 나머지는 무조건 CSSPixelValue가 오기 때문에 spread로 타입을 받았다.
function isPositionValue(value: unknown): value is Property.Position {
return ['static', 'relative', 'absolute', 'fixed', 'sticky', '-webkit-sticky'].includes(value as string);
}
function isCSSPixelValue(value: unknown): value is CSSPixelValue {
return typeof value === 'string' || typeof value === 'number';
}
함수는 간단하다. isPositionValue는 포지션인지 확인하는 함수이다. 이건 첫번째인자가 포지션인지 top인지 확인할 때 쓰일것같다. 그리고 value에는 여러타입이 올 수 있기 때문에 unknown으로 받았다. unknown으로 받았기 때문에 as나 is문법을 이용해 타입을 재정의해줘야 한다. 여기서는 is 문법을 사용했다. 이렇게 하게되면 return true인 경우 value를 Property.Position 타입으로 변경해준다. 유용하게 사용하자. 근데 isPositionValue에 배열에 타입을 지정하지 않았다. 이건 type safe하지 않은 방법 아닐까? 다음과 같이 작성하면 어떨
까? 참고로 includes는 타입이 아래와 같이 정의되어 있어서 as any 혹은 as Property.Position으로 지정해주지 않으면 ts에서 에러를 뱉는다. 망할 ts 이유가 있겠지? 타입스크립트에 pr을 날릴려다가 참았다.
function isPositionValue(value: unknown): value is Property.Position {
const positions: Property.Position[] = ['static', 'relative', 'absolute', 'fixed', 'sticky', '-webkit-sticky'];
return positions.includes(value as any);
}
불필요한 지역변수가 생기긴하는데... 그래도 강한 타입 체킹관점에서는 이게 더 옳은 방법 아닐까? 만약 함수가 실행될때 positions 배열이 계속 생성되는게 거슬린다면 positions는 최상위로 빼면 해결된다.(사실 원래 코드도 배열이 계속 생성된다. 그리고 이정도의 연산은 현대 웹 개발에서 전~~혀 중요하지 않은 부분이다. 성능 최적화는 이런데서 하는게 아니다.)
사실 정확한 타입을 지정하는게 그렇게 중요하지 않을 수도 있다. 타입스크립트에서 다시 자바스크립트로 돌아가는 사람도 있고 말이다. 그래도 나는 되도록이면 강한 타입 지정을 원한다. 테스트코드로 확인할 수도 있지만 타입으로 그 전 단계에서 확인할 수 있는 부분이다.
isCSSpixelValue는 is 문법만 알면 어렵지 않기 때문에 설명을 생략한다.
const [top, right, bottom, left] = isPositionValue(positionOrTop)
? isCSSPixelValue(topOrCoordinates)
? [topOrCoordinates, ...values]
: [topOrCoordinates?.top, topOrCoordinates?.right, topOrCoordinates?.bottom, topOrCoordinates?.left]
: [positionOrTop, topOrCoordinates as CSSPixelValue, ...values];
삼항이 두번나왔다... 사실 삼항을 사용하고 싶지 않으면 즉시실행함수를 사용하거나 getDetailPosition같은 함수를 만들어서 해결할 수도 있다. 나는 개인적으로 early return을 사용하는게 가독성이 좋다고 생각한다. 그런데 조금 의견이 갈리는 것같았다. 리팩터링 스터디를 하는 팀원 중 한명이 이와 관련된 글을 소개해줘서 첨부한다.
https://medium.com/javascript-scene/nested-ternaries-are-great-361bddd0f340
https://medium.com/@nimelrian/no-no-no-152f46558b48
if else문
const [top, right, bottom, left] = (() => {
if (isPositionValue(positionOrTop)) {
if (isCSSPixelValue(topOrCoordinates)) {
return [topOrCoordinates, ...values];
}
return [topOrCoordinates?.top, topOrCoordinates?.right, topOrCoordinates?.bottom, topOrCoordinates?.left];
}
return [positionOrTop, topOrCoordinates as CSSPixelValue, ...values];
})();
early return
const [top, right, bottom, left] = (() => {
if (!isPositionValue(positionOrTop)) {
return [positionOrTop, topOrCoordinates as CSSPixelValue, ...values];
}
if (!isCSSPixelValue(topOrCoordinates)) {
return [topOrCoordinates?.top, topOrCoordinates?.right, topOrCoordinates?.bottom, topOrCoordinates?.left];
}
return [topOrCoordinates, ...values];
})();
나는 아무리봐도 early return이 편하다. 그래서 이걸로 설명을 하겠다.
첫번째 return부분은 첫번째 인자가 position이 아닌 경우다. 즉 두번째함수가 되겠다. 앞에서부터 쭉 그대로 나열해주면 된다. topOrCoordinaters는 타입 지정이 안되어서 as를 사용했다. 사실 지금은 type safe한 경운데 타입스크립트에서 잡아주지 못한다. 이런경우는 타입스크립트 버전이 업데이트되면서 해결될까? 어쨋든 일단은 어쩔 수 없지만 as를 사용하자.
두번째 return 부분은 두번째 인자가 cssvlaue가 아닌 경우다. 즉 객체가 오는 세번째 함수다.(coordinates) 그렇기 때문에 객체의 값을 받아오면 된다. ?.으로 되어있는데 지금 같은 경우는 is문법을 사용했기 때문에 옵셔널체이닝 문법을 사용할 필요가 없다.
마지막 return 부분은 첫번째 인자가 position이고 두번째 인자가 cssvalue인 경우다. 그리고 당연하게도 첫번째 함수의 경우다. 그렇기 때문에 두번째 인자부터 쭈르륵 써주면 된다.
아 그리고 주석을 달아주면 더 좋다.(pr날렸는데 토스 개발자분이 주석을 달라고 하셨다.) 실제로 주석을 달았는데 주석을 반대로 달았다. 회사에서 퇴근직전에 급하기 커밋하다보니까 꼼꼼히 못 읽어서 실수한것이다. 이런것들을 사전에 방지하기 위해서 이런 주석은 좋아보이낟.
const [top, right, bottom, left] = (() => {
// position(top, right, bottom, left);
if (!isPositionValue(positionOrTop)) {
return [positionOrTop, topOrRightOrCoordinates as CSSPixelValue, ...values];
}
// position(position, coordinates);
if (!isCSSPixelValue(topOrRightOrCoordinates)) {
return [
topOrRightOrCoordinates.top,
topOrRightOrCoordinates.right,
topOrRightOrCoordinates.bottom,
topOrRightOrCoordinates.left,
];
}
// position(position, top, right, bottom, left);
return [topOrRightOrCoordinates, ...values];
})();
return css([
css({ position: isPositionValue(positionOrTop) ? positionOrTop : undefined }),
css({ top, right, bottom, left }),
]);
마지막 return 부분이다. 크게 볼건 없는데 isPositionValue를 위에서도 사용했는데 중복해서 사용했다. 어떤 관점에서는 인자가 변하지 않는 동일한 함수를 두 번 실행했기 때문에 하나의 변수에 할당해서 사용해야한다고 할 수 도 있다. 하지만 리팩터링 책에서는 이게 잘못된 방법이라고 하지 않는다. 오히려 불필요한 지역변수를 줄이는 방법이라고 한다. 성능에 약간의 문제가 있어도 말이다. 더군다나 지금은 성능에 문제가 없는 함수다. 그래서 그냥 두기로 했다. 만약 지역변수를 만든다면 변수명을 또 뭐로할지 고민이 된다.
다시 한번 코드를 읽어봤다. 고칠부분이 보였다.
position함수에서 position타입을 CSSProperties['position']로 사용하고 있다. 타입을 타고 들어가보면 알겠지만 undefined가 가능하다. 그래서 Property.Position로 바꿔줘야한다.
또한 즉시실행함수와 early return을 사용했는데 세 개의 타입중 어떤건지 알기가 힘들다. 그래서 다음과 같이 주석을 달아주면 좋다.(토스 개발자분 조언)
const [top, right, bottom, left] = (() => {
// position(top, right, bottom, left);
if (!isPositionValue(positionOrTop)) {
return [positionOrTop, topOrRightOrCoordinates as CSSPixelValue, ...values];
}
// position(position, coordinates);
if (!isCSSPixelValue(topOrRightOrCoordinates)) {
return [
topOrRightOrCoordinates.top,
topOrRightOrCoordinates.right,
topOrRightOrCoordinates.bottom,
topOrRightOrCoordinates.left,
];
}
// position(position, top, right, bottom, left);
return [topOrRightOrCoordinates, ...values];
})();
내가 수정한 최종 코드는 다음과 같다.
import { css, SerializedStyles } from '@emotion/react';
import { CSSPixelValue } from '../types';
import type { Property } from 'csstype';
interface Coordinates {
top?: CSSPixelValue;
right?: CSSPixelValue;
bottom?: CSSPixelValue;
left?: CSSPixelValue;
}
/**
* @name position
* @description
* CSS `position`과 `position`에 연관된 프로퍼티(`top`, `right`, `bottom`, `left`)를 쉽게 선언할 수 있도록 하는 shorthand 유틸리티입니다.
*
* ```ts
* type CSSPixelValue = string | number;
*
* function position(
* position: 'absolute' | 'fixed' | 'relative' | 'static' | 'sticky',
* top: CSSPixelValue,
* right: CSSPixelValue,
* bottom: CSSPixelValue,
* left: CSSPixelValue
* ): SerializedStyles;
*
* function position(
* top: CSSPixelValue,
* right: CSSPixelValue,
* bottom: CSSPixelValue,
* left: CSSPixelValue
* ): SerializedStyles;
* ```
*
* @example
* import { position } from '@toss/emotion-utils';
*
* const fullPageLayer = position('fixed', 0, 0, 0, 0);
* // 위 코드는 아래 코드와 동치입니다.
* const fullPageLayer = css`
* position: fixed;
* top: 0;
* right: 0;
* bottom: 0;
* left: 0;
* `;
*
* // 첫번째 인자를 생략하고 `top` 부터 넘길 수 있습니다.
* const allZero = position(0, 0, 0, 0);
*
* // 다음처럼도 사용 가능합니다
* position('absolute', {top: 0, left: 0});
*/
type PositionOrTop = Property.Position | CSSPixelValue;
type TopOrRightOrCoordinates = CSSPixelValue | Coordinates;
export function position(
position: Property.Position,
top: CSSPixelValue,
right: CSSPixelValue,
bottom: CSSPixelValue,
left: CSSPixelValue,
): SerializedStyles;
export function position(
top: CSSPixelValue,
right: CSSPixelValue,
bottom: CSSPixelValue,
left: CSSPixelValue,
): SerializedStyles;
export function position(position: Property.Position, coordinates?: Coordinates): SerializedStyles;
export function position(
positionOrTop: PositionOrTop,
topOrRightOrCoordinates: TopOrRightOrCoordinates = {},
...values: CSSPixelValue[]
) {
const [top, right, bottom, left] = (() => {
// position(top, right, bottom, left);
if (!isPositionValue(positionOrTop)) {
return [positionOrTop, topOrRightOrCoordinates as CSSPixelValue, ...values];
}
// position(position, coordinates);
if (!isCSSPixelValue(topOrRightOrCoordinates)) {
return [
topOrRightOrCoordinates.top,
topOrRightOrCoordinates.right,
topOrRightOrCoordinates.bottom,
topOrRightOrCoordinates.left,
];
}
// position(position, top, right, bottom, left);
return [topOrRightOrCoordinates, ...values];
})();
return css([
css({ position: isPositionValue(positionOrTop) ? positionOrTop : undefined }),
css({ top, right, bottom, left }),
]);
}
function isPositionValue(value: PositionOrTop): value is Property.Position {
const positions: Property.Position[] = ['static', 'relative', 'absolute', 'fixed', 'sticky', '-webkit-sticky'];
return positions.includes(value as any);
}
function isCSSPixelValue(value: TopOrRightOrCoordinates): value is CSSPixelValue {
return typeof value === 'string' || typeof value === 'number';
}
그리고 토스 레포에 pr을 날렸다. 조금 서로 의견이 안맞는 부분이 있어서 얘기를 몇번씩 주고받았다. 결과적으로는 pr을 받아주셨다. 감사합니다~~
https://github.com/toss/slash/pull/84
position.test.ts
/** @jsxImportSource @emotion/react */
import { matchers } from '@emotion/jest';
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import { position } from './position';
expect.extend(matchers);
describe('position 테스트', () => {
test(`position('absolute', 0, 0, 0, 0)`, () => {
const { getByTestId } = render(<div data-testid="test" css={position('absolute', 0, 0, 0, 0)} />);
const el = getByTestId('test');
expect(el).toHaveStyleRule('position', 'absolute');
expect(el).toHaveStyleRule('top', '0');
expect(el).toHaveStyleRule('right', '0');
expect(el).toHaveStyleRule('bottom', '0');
expect(el).toHaveStyleRule('left', '0');
});
test(`position(0, 0, 0, 0)`, () => {
const { getByTestId } = render(<div data-testid="test" css={position(0, 0, 0, 0)} />);
const el = getByTestId('test');
expect(el).toBeInTheDocument();
expect(el).not.toHaveStyleRule('position', 'absolute');
expect(el).toHaveStyleRule('top', '0');
expect(el).toHaveStyleRule('right', '0');
expect(el).toHaveStyleRule('bottom', '0');
expect(el).toHaveStyleRule('left', '0');
});
test(`position('absolute', {top: 0, left: 0})`, () => {
const { getByTestId } = render(<div data-testid="test" css={position('absolute', { top: 0, left: 0 })} />);
const el = getByTestId('test');
expect(el).toBeInTheDocument();
expect(el).toHaveStyleRule('position', 'absolute');
expect(el).toHaveStyleRule('top', '0');
expect(el).not.toHaveStyleRule('bottom', '0');
expect(el).not.toHaveStyleRule('right', '0');
expect(el).toHaveStyleRule('left', '0');
});
});
엄청 간단한 테스트 코드도 있었다. position을 absolute로만 넣어줬는데 굳이 다른걸로 테스트할 필요성은 없어보인다.
utils/size.ts
import { css } from '@emotion/react';
import { CSSProperties } from 'react';
interface SizeOptions {
width?: CSSProperties['width'];
height?: CSSProperties['height'];
}
/**
* @description width, height 스타일링을 위한 유틸리티입니다.
*
* ```ts
* function size(options: {
* width?: CSSProperties['width'];
* height?: CSSProperties['height'];
* }): SerializedStyles;
*
* size.full: SerializedStyles
* size.width100: SerializedStyles;
* size.height100: SerializedStyles;
* ```
*
* @example
* size({ width: '50%', height: '100px' });
* // =>
* // css`
* // width: 50%;
* // height: 100px;
* // `;
*/
export function size({ width, height }: SizeOptions) {
return css({ width, height });
}
size.full = size({ width: '100%', height: '100%' });
size.width100 = size({ width: '100%' });
size.height100 = size({ height: '100%' });
width와 height는 정말 많이 사용하는 스타일이다. 응집도를 높이기 위해 이렇게 했나? 생각이 든다. 나쁘지 않다고 생각해서 가져왔다. 그리고 추가적인 옵션으로 full, width100, height100을 줬다. 여기서는 딱히 손볼게 없어 보인다. 취향차이지만 나는 wFull, hFull이 더 편해보인다. 그거 말고는 그냥 그대로 쓰면 될 것 같다.
utils/visuallyHidden.ts
import { css } from '@emotion/react';
/**
* @description 시각적으로 보이지 않게 만드는 스타일링
*
* ```ts
* const visuallyHidden: SerializedStyles;
* ```
*/
export const visuallyHidden = css`
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
border: 0;
clip: rect(0, 0, 0, 0);
`;
컴포넌트를 가리는 방법에는 여러가지 방법이 있다.
1. 렌더링 안하기(&&연산자 사용)
2. display: none
3. opacity: 0
이정도고 opacity0을 줄 때 이벤트를 막을려면 cursor-events:none; 정도를 사용했다. 그런데 이건 조금 새로운 방법이라서 신기했다.
찾아보니 접근성 마크업을 하면서 css 스타일 또한 중요한데 텍스트를 숨김처리하는 동시에 스크린리더기에서만 읽게 하도록 하는 방법 중 clip속성을 이용하여 스타일링 한 방법이라고 한다.
출처: https://webclub.tistory.com/33 [Web Club:티스토리]
아무래도 큰 회사다보니 접근성 마크업도 중요하게 생각한다는 생각이든다. 접근성을 중요시해야하는데 맨날 생각만 하고 실천을 한적이 없는 것 같다. feconf에서 토스개발자분이 웹 접근성 관련한 세션을 열었는데 대충 본 기억이 있다. 좀 자세히 보고 공부해봐야지.
https://www.youtube.com/watch?v=tKj3xsXy9KM&t=442s&ab_channel=FEConfKorea
후기
요즘 오픈소스를 하나씩 까보는데 재밌다. 그리고 이번엔 pr도 날려봤다. 앞으로도 하나씩 코드보면서 개선할점이 있으면 pr을 날려봐야겠다.
https://github.com/toss/slash/pull/83
https://github.com/toss/slash/pull/84
링크
토스 레포
내 레포
https://github.com/yoonminsang/play-ground/blob/develop/packages/common-styles/src/theme/index.ts
'웹' 카테고리의 다른 글
프론트에서 모호한 것들 그리고 해결방법 1편(웹 스토리지) (2) | 2023.12.22 |
---|---|
추상화 나도 할 수 있을지도??(웹 프론트) 1편 (2) | 2023.09.24 |
usestore 라이브러리 까보기 2편(feat. feconf 상태관리) (0) | 2022.10.17 |
usestore 라이브러리 까보기 1편(feat. feconf 상태관리) (0) | 2022.10.17 |
최신 프론트엔드 개발환경 적용하기4(storybook with vite,emotion)) (0) | 2022.09.11 |