원문: https://github.com/facebook/astryx/wiki/Architecture-Cheat-Sheet · 번역 기준: 2026-09-03
감사(audit) rubric이 아니라 운영용 라우팅 가이드입니다.
origin/main의baa01b1c25355a85f26c7363f5c98b4d6e806c6b(2026-08-25) 기준으로 검증되었습니다. 링크된 owner API를 사용하고, 상세 점검은 기존 문서로 라우팅하세요.
NewComponent와 new-component의 이름을 바꾸고, 컴포넌트의 시맨틱에 맞는 intrinsic root를 선택하세요.
// Copyright (c) Meta Platforms, Inc. and affiliates.
/**
* @file NewComponent.tsx
* @input React content, BaseProps DOM passthrough, StyleX overrides
* @output Exports NewComponent and NewComponentProps
* @position Core leaf component; consumed by index.ts
*
* SYNC: When modified, update these files to stay in sync:
* - /packages/core/src/NewComponent/NewComponent.doc.mjs
* - /packages/core/src/NewComponent/NewComponent.test.tsx
* - /packages/core/src/NewComponent/index.ts
* - /apps/storybook/stories/NewComponent.stories.tsx
*/
import type {ReactNode} from 'react';
import * as stylex from '@stylexjs/stylex';
import type {BaseProps} from '../BaseProps';
import {colorVars, spacingVars} from '../theme/tokens.stylex';
import {mergeProps, themeProps} from '../utils';
const styles = stylex.create({
root: {
alignItems: 'center',
color: colorVars['--color-text-primary'],
display: 'inline-flex',
gap: spacingVars['--spacing-1'],
},
});
export interface NewComponentProps extends BaseProps<HTMLSpanElement> {
children: ReactNode;
ref?: React.Ref<HTMLSpanElement>;
}
export function NewComponent({
children,
xstyle,
className,
style,
ref,
...rest
}: NewComponentProps) {
return (
<span
ref={ref}
{...mergeProps(
themeProps('new-component'),
stylex.props(styles.root, xstyle),
className,
style,
)}
{...rest}>
{children}
</span>
);
}
NewComponent.displayName = 'NewComponent';
이 순서는 현재의 Badge 패턴입니다: themeProps → 하나의 stylex.props(base, xstyle) 호출 → consumer className → consumer style → 중립적인 ...rest. 컴포넌트가 role, ARIA 값, 또는 handler를 소유한다면 아래 Public DOM/style/event/ref composition의 충돌 규칙을 적용하세요.
| 트리거 | 이 owner API를 조합 |
|---|---|
| 상태 전환을 announce | useAnnounce • useTranslator; A6/A7/A16 감사 |
| focus를 trap/복원 | useFocusTrap; layer 참여자는 dismissal에도 함께 참여 |
| 인터랙티브 표면을 확장 | useClickableContainer; input chrome은 useInputContainer 사용 |
| hover/focus/touch 시 컨트롤을 표시 | useContainerReveal |
| 내비게이션을 렌더링 | useLinkComponent • Link / Item이 사용하는 공유 target/rel 처리 |
| 컬렉션을 탐색 | Item • useListFocus / useGridFocus / useTreeFocus; 필요하면 useTypeahead 추가 |
| floating/modal UI를 열기 | 기존 Dialog/Popover/Tooltip 계열 우선; 그렇지 않으면 useLayer • dismissal/depth owner |
| 크기를 받거나 제공 | useSize; 컨테이너 owner는 SizeProvider 사용 |
| 폼 데이터를 수집 | Field anatomy + getInputARIA • useInputContainer |
| 컨테이너 geometry를 게시하거나 소비 | container()와 4개의 방향별 padding 변수; edge owner는 edgeCompSlot 사용 |
기존 layer 계열을 우선하세요: 이미 portal 배치, dismissal, 중첩, focus, 라이프사이클을 소유하고 있습니다. useLayer는 저수준의 컴포넌트 저자용 인프라이지, consumer의 출발점이 아닙니다.
import {Button, Popover} from '@astryxdesign/core';
<Popover label="Settings" content={<div>Settings</div>}>
<Button label="Settings" />
</Popover>
라우팅: packages/core/src/Layer/; Component Audit Rubric §1 and Q10.
Consumer는 완성된 input과 그 표준 controlled API로 시작합니다. isRequired 또는 isOptional 중 하나를 선택하세요; TextInput은 isDisabled, isReadOnly, isLoading도 지원합니다—busy prop은 없습니다.
import {TextInput, type TextInputStatus} from '@astryxdesign/core/TextInput';
const status: TextInputStatus | undefined = isInvalid
? {type: 'error', message: validationMessage}
: undefined;
<TextInput
label="Email"
description="Used for account notifications"
value={email}
onChange={setEmail}
changeAction={saveEmail}
status={status}
isRequired
isDisabled={isDisabled}
disabledMessage={disabledMessage}
isReadOnly={isReadOnly}
isLoading={isSaving}
hasClear
/>
시스템 input을 만들 때는 anatomy를 다시 만들지 말고 공개된 author primitive를 조합하세요:
import {Field} from '@astryxdesign/core/Field';
import {getInputARIA} from '@astryxdesign/core/utils';
const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(labelID, [
description ? descriptionID : null,
]);
<Field label={label} inputID={inputID} labelID={labelID}
description={description}
descriptionID={description ? descriptionID : undefined}>
<input id={inputID} aria-labelledby={ariaLabelledBy}
aria-describedby={ariaDescribedBy} />
</Field>
라우팅: API Conventions — Input Component Props 및 감사 §§1, 3, 4.