본문으로 건너뛰기
cccskills
무료GitHub에 공개

component-architecture

Use when designing or changing a component's public API, its composition, or its state model in libs/ui-react or libs/ui-rnative — layering (core vs internal vs primitives), BaseProps/Props splits, converting a component to compound / changing its composition with createSafeContext, controlled/uncontrolled state, prop-naming conventions, and cross-platform API parity. Load this before shaping a new component, changing its props, or refactoring its composition.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md8.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Component architecture & API design

How Lumen components are shaped, layered and composed. This is the authoring counterpart to component-anatomy (which covers where files live) — this skill covers what the component's API and internals look like. The styling mechanics live in component-styling.

Layering: core/ vs internal/ vs primitives/

Components sit in one of a few layers; put new code in the right one.

  • core/ — the public components that ship from the package root (Button, Card, Switch, …).
  • internal/ — shared building blocks that are not public API. Reach for a Base* wrapper when several public components share the same chrome, variants or behaviour: BaseButton backs Button, IconButton, CardButton, MediaButton, TileButton; BaseInput backs TextInput, SearchInput, AddressInput; BaseTag backs the tag family. Add one only when a second consumer appears — don't pre-abstract.
  • primitives/ (React Native only) — token-constrained styled wrappers (Box, Text, Pressable) that core RN components build on. Web has no primitive layer; it composes semantic HTML + Tailwind + Radix/Base UI Slot.
  • symbols/ — the icon registry and generated icon components.

BaseProps vs Props

When a Base* wrapper exists, split the types: the Base*Props carries the shared surface, and each public component narrows it.

// internal/BaseButton/types.ts — the shared surface
export type BaseButtonProps = {
  appearance?: 'base' | 'gray' | 'accent' | 'transparent' | 'no-background' | 'red';
  size?: 'xs' | 'sm' | 'md' | 'lg';
  isFull?: boolean;
  loading?: boolean;
  asChild?: boolean;
  icon?: ComponentType<{ size?: IconSize; className?: string }>;
} & ComponentPropsWithRef<'button'>;

// core/Button/types.ts — narrows and constrains it
export type ButtonProps = {
  /** @required */
  children: ReactNode;
  /** @default md */
  size?: 'sm' | 'md' | 'lg'; // Button drops the 'xs' size
} & Omit<BaseButtonProps, 'children' | 'size'>;

Both types live in the component's types.ts (see component-anatomy). Prefer Omit/Pick over redefining shared props.

Compound components + createSafeContext

A component with distinct parts (Card + CardHeader + CardContent + CardFooter, Dialog + DialogTrigger + DialogContent, ListItem, BottomSheet) exports each sub-part from its folder barrel and shares state through a context built with createSafeContext from @ledgerhq/lumen-utils-shared — not a hand-rolled createContext.

// createSafeContext<CtxValue>(rootComponentName, defaultContext?)
//   → [Provider, useSafeContext]
const [CardProvider, useCardContext] = createSafeContext<CardContextValue>('Card');

// In a sub-part: throws `${consumerName} must be used within Card` when the
// provider is missing and the context is required.
const ctx = useCardContext({ consumerName: 'CardHeader', contextRequired: true });
  • Set contextRequired: false for a sub-part that may render standalone (returns a partial/default context instead of throwing).
  • If the component owns a disabled state, provide it through the shared disabled context rather than a second provider — see disabled-context. Card is the reference (CardProvider + DisabledProvider).

Controlled / uncontrolled state

For any stateful, user-driven value (selection, open/checked, text), support both controlled and uncontrolled use through useControllableState rather than hand-rolling the fallback:

const [value, setValue] = useControllableState({
  prop: valueProp,        // controlled value (undefined ⇒ uncontrolled)
  defaultProp,            // initial value when uncontrolled
  onChange,               // fired on change (prop-wins by default)
});
  • Expose the trio on the public API: value + defaultValue + onChange (named per the component's domain, e.g. checked/defaultChecked/onCheckedChange).
  • Known debt: useControllableState currently lives per-lib (libs/ui-react/src/utils/…, libs/ui-rnative/src/lib/utils/…) rather than in utils-shared. Use the one in your lib; don't add a third copy.

API design rules

  • Prop names follow established vocabulary: onOpenChange not onToggle, appearance not variant. Match the name an existing component already uses for the same concept.
  • Booleans are positive: isFull, overlay, loading — never noOverlay.
  • Keep the public surface minimal and self-explanatory. Every public prop carries JSDoc with intent and an @default (or @required) tag.
  • JSDoc describes behaviour, not the type. Do not enumerate union/literal values already on the TypeScript type ('auto' / 'none', 'plain', …). That makes JSDoc a second API that can drift. Keep the behavioural description; let the type (and JSDoc parsers) surface the literals.
  • Shared logic goes in utils-shared, not copied across components.
  • A hook that manages state and derives data and subscribes to events is doing too much — split it.
  • A wrapper that spreads ComponentPropsWithRef<'el'> must actually forward those props and ref (and testID/...props on RN) — don't silently drop onClick.

Cross-platform API parity

The same component ships on web and native and should keep matching prop names, defaults and variant vocabulary — an app author moving between platforms should not relearn the API. Button is the reference: appearance / size / isFull / loading / icon are identical on both.

Divergence is allowed only when a platform idiom demands it, and it should be deliberate. The tolerated example is Switch: web uses selected / defaultSelected / onChange; native uses checked / defaultChecked / onCheckedChange (aligning with the native primitive). When you change a component on one platform, check the other's types.ts and keep them in step unless there's such a reason.

Memoization

Lumen does not blanket-memoize. React.memo is reserved for components with a measured re-render cost (essentially only AmountDisplay); useMemo/useCallback are used where there's real work (layout math, throttled scroll, expensive derived styles), not by default. Add memoization when you can point to the reason, not reflexively.

Reference components to imitate

  • Button / BaseButton — internal/ layering, Base*Props → public props, variant vocabulary.
  • Card — compound parts + createSafeContext + disabled context.
  • Select (web) — controlled/uncontrolled + compound architecture.
  • Switch — controlled state and the tolerated cross-platform divergence.

Review checks

Rules verifiable from a diff.

CheckApplies toDetectSkip
Prop name off the established vocabularybothonToggle/variant-style names for existing conceptsgenuinely new concept
Negative boolean propbothno*/disable*-phrased boolean props—
Shared component family not split into Base*Props → public propsbothduplicated prop surface across sibling componentssingle-use component
Compound sub-part uses hand-rolled createContext instead of createSafeContextbothcreateContext( in a component foldergenuinely global/app context
Controlled component hand-rolls the controlled/uncontrolled fallbackbothvalue ?? internalState logic instead of useControllableStateRadix/Base UI-delegated state (web)
Cross-platform prop divergence without a stated reasonbothprop names/defaults differ from the other platform's types.tsdocumented platform-idiom divergence (e.g. Switch)
Reflexive memoization with no measured needbothmemo/useMemo/useCallback around trivial workmeasured hot paths
Public prop missing JSDoc intent / @defaultbothundocumented prop in types.tsinternal Base*Props
JSDoc enumerates union/literal values already on the typebothJSDoc lists `'foo' \'bar'` or bullet-lists the same literals as the type
Logic duplicated across components that belongs in utils-sharedbothcopy-pasted helper in two component folders—

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Creates and maintains Figma Code Connect files (`*.figma.tsx`) that map Figma components to code via the parser-based `figma.connect()` API. Use when the user mentions Code Connect, Figma component mapping, design-to-code translation, or asks to create/update .figma.tsx files.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LedgerHQ/lumen232026년 10월 17일 업데이트

Use when creating, naming, or placing a file or folder in libs/*, or when modifying or adding a file to an existing component (even when the barrel isn't touched) — component vs utility naming, the one-responsibility-per-file layout, when a folder needs an `index.ts` barrel (public API only), and the required set of files a component needs per lib. Load this before scaffolding or restructuring a component so the layout matches the codebase.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LedgerHQ/lumen232026년 10월 17일 업데이트

Use when writing or editing Storybook MDX docs (*.mdx) — the two-tab Overview/Implementation structure, story-backed `<Source>` examples, and doc table guidelines.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LedgerHQ/lumen232026년 10월 17일 업데이트

Use when creating or editing Storybook stories (*.stories.tsx, React or React Native) — story layout, controls, and export naming conventions.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LedgerHQ/lumen232026년 10월 17일 업데이트

Use when building or styling a component in libs/ui-react or libs/ui-rnative — the cross-platform styling principles, plus routing to the platform mechanics: Tailwind + cva + cn on web, useStyleSheet + themeJS + lx on React Native. Load this before writing component styles.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LedgerHQ/lumen232026년 10월 17일 업데이트

Use when writing or editing component tests in libs/ui-react or libs/ui-rnative — shared structure and coverage conventions, plus the per-platform runner: Vitest + React Testing Library on web, Jest + React Native Testing Library on RN. Load this before writing tests.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

LedgerHQ/lumen232026년 10월 17일 업데이트

LedgerHQ의 스킬 모두 보기

이 스킬의 문제 신고