@hjmds/design-contracts 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action-session.d.ts +47 -0
- package/dist/action-session.d.ts.map +1 -0
- package/dist/action-session.js +60 -0
- package/dist/action-session.js.map +1 -0
- package/dist/activity-heatmap.d.ts +22 -0
- package/dist/activity-heatmap.d.ts.map +1 -0
- package/dist/activity-heatmap.js +29 -0
- package/dist/activity-heatmap.js.map +1 -0
- package/dist/auth-screen.d.ts +4 -0
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +2 -0
- package/dist/auth-screen.js.map +1 -1
- package/dist/avatar-fallback.d.ts +25 -0
- package/dist/avatar-fallback.d.ts.map +1 -0
- package/dist/avatar-fallback.js +17 -0
- package/dist/avatar-fallback.js.map +1 -0
- package/dist/behaviors.d.ts +2 -2
- package/dist/bottom-navigation-defaults.d.ts +1 -1
- package/dist/bottom-navigation-defaults.d.ts.map +1 -1
- package/dist/bottom-navigation-defaults.js.map +1 -1
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +5 -1
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/catalog.d.ts +29 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/code-block.d.ts +23 -0
- package/dist/code-block.d.ts.map +1 -0
- package/dist/code-block.js +13 -0
- package/dist/code-block.js.map +1 -0
- package/dist/component-definitions.d.ts +2 -3
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +1 -2
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +25 -1
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +12 -1
- package/dist/component-recipes.js.map +1 -1
- package/dist/component-references.d.ts +0 -6
- package/dist/component-references.d.ts.map +1 -1
- package/dist/component-references.js +0 -5
- package/dist/component-references.js.map +1 -1
- package/dist/content-transition.d.ts +17 -0
- package/dist/content-transition.d.ts.map +1 -0
- package/dist/content-transition.js +15 -0
- package/dist/content-transition.js.map +1 -0
- package/dist/duration-field.d.ts +22 -0
- package/dist/duration-field.d.ts.map +1 -0
- package/dist/duration-field.js +20 -0
- package/dist/duration-field.js.map +1 -0
- package/dist/effect-surface.d.ts +29 -0
- package/dist/effect-surface.d.ts.map +1 -0
- package/dist/effect-surface.js +26 -0
- package/dist/effect-surface.js.map +1 -0
- package/dist/gooey-navigation.d.ts +13 -0
- package/dist/gooey-navigation.d.ts.map +1 -0
- package/dist/gooey-navigation.js +11 -0
- package/dist/gooey-navigation.js.map +1 -0
- package/dist/gravity-letters.d.ts +12 -0
- package/dist/gravity-letters.d.ts.map +1 -0
- package/dist/gravity-letters.js +11 -0
- package/dist/gravity-letters.js.map +1 -0
- package/dist/grid-reveal.d.ts +8 -0
- package/dist/grid-reveal.d.ts.map +1 -0
- package/dist/grid-reveal.js +5 -0
- package/dist/grid-reveal.js.map +1 -0
- package/dist/internal/object-validation.d.ts +3 -0
- package/dist/internal/object-validation.d.ts.map +1 -0
- package/dist/internal/object-validation.js +12 -0
- package/dist/internal/object-validation.js.map +1 -0
- package/dist/internal/thinking-orb/braid.d.ts.map +1 -1
- package/dist/internal/thinking-orb/braid.js +2 -9
- package/dist/internal/thinking-orb/braid.js.map +1 -1
- package/dist/internal/thinking-orb/core.d.ts +1 -0
- package/dist/internal/thinking-orb/core.d.ts.map +1 -1
- package/dist/internal/thinking-orb/core.js +12 -0
- package/dist/internal/thinking-orb/core.js.map +1 -1
- package/dist/internal/thinking-orb/presentation.d.ts +4 -0
- package/dist/internal/thinking-orb/presentation.d.ts.map +1 -0
- package/dist/internal/thinking-orb/presentation.js +28 -0
- package/dist/internal/thinking-orb/presentation.js.map +1 -0
- package/dist/internal/thinking-orb/ribbon.d.ts.map +1 -1
- package/dist/internal/thinking-orb/ribbon.js +2 -9
- package/dist/internal/thinking-orb/ribbon.js.map +1 -1
- package/dist/layout.d.ts +0 -5
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +0 -7
- package/dist/layout.js.map +1 -1
- package/dist/otp-field.d.ts +2 -0
- package/dist/otp-field.d.ts.map +1 -1
- package/dist/otp-field.js.map +1 -1
- package/dist/popover.d.ts.map +1 -1
- package/dist/popover.js +3 -12
- package/dist/popover.js.map +1 -1
- package/dist/reactions.d.ts +10 -0
- package/dist/reactions.d.ts.map +1 -0
- package/dist/reactions.js +20 -0
- package/dist/reactions.js.map +1 -0
- package/dist/scroll-progress.d.ts +8 -0
- package/dist/scroll-progress.d.ts.map +1 -0
- package/dist/scroll-progress.js +10 -0
- package/dist/scroll-progress.js.map +1 -0
- package/dist/task-list.d.ts +7 -0
- package/dist/task-list.d.ts.map +1 -0
- package/dist/task-list.js +4 -0
- package/dist/task-list.js.map +1 -0
- package/dist/theme-studio.d.ts +14 -0
- package/dist/theme-studio.d.ts.map +1 -0
- package/dist/theme-studio.js +16 -0
- package/dist/theme-studio.js.map +1 -0
- package/dist/thinking-orb.d.ts +4 -2
- package/dist/thinking-orb.d.ts.map +1 -1
- package/dist/thinking-orb.js +8 -3
- package/dist/thinking-orb.js.map +1 -1
- package/dist/toast-liquid.d.ts +4 -4
- package/dist/toast-liquid.d.ts.map +1 -1
- package/dist/toast-liquid.js +11 -10
- package/dist/toast-liquid.js.map +1 -1
- package/dist/toast.d.ts +4 -4
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +4 -3
- package/dist/toast.js.map +1 -1
- package/dist/tooltip.d.ts.map +1 -1
- package/dist/tooltip.js +3 -12
- package/dist/tooltip.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/voice-note.d.ts +20 -0
- package/dist/voice-note.d.ts.map +1 -0
- package/dist/voice-note.js +16 -0
- package/dist/voice-note.js.map +1 -0
- package/docs/action-session.md +73 -0
- package/docs/activity-heatmap.md +32 -0
- package/docs/anchor.md +1 -1
- package/docs/auth-screen.md +44 -4
- package/docs/authoring-brief.md +5 -0
- package/docs/avatar-fallback.md +74 -0
- package/docs/bottom-navigation.md +16 -1
- package/docs/breadcrumb.md +1 -1
- package/docs/calendar.md +1 -1
- package/docs/cascader.md +1 -1
- package/docs/code-block.md +32 -0
- package/docs/command-palette.md +1 -1
- package/docs/compound-controls.md +83 -0
- package/docs/confirm-popover.md +1 -1
- package/docs/consumer-policy.md +30 -23
- package/docs/cross-platform-core-normalization.md +19 -21
- package/docs/data-table.md +8 -5
- package/docs/dialog.md +2 -0
- package/docs/effect-surface.md +50 -0
- package/docs/floating-action-button.md +1 -1
- package/docs/folder-preview.md +26 -0
- package/docs/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +3 -3
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +1 -1
- package/docs/gooey-navigation.md +36 -0
- package/docs/gravity-letters.md +26 -0
- package/docs/grid-reveal.md +11 -0
- package/docs/icon.md +26 -0
- package/docs/image.md +2 -2
- package/docs/layout.md +1 -2
- package/docs/migration-0.5.md +4 -1
- package/docs/migration-native-legacy-removal.md +59 -0
- package/docs/navigation-bar.md +28 -0
- package/docs/optional-adapters.md +24 -1
- package/docs/otp-field.md +15 -0
- package/docs/pagination.md +1 -1
- package/docs/popover.md +2 -2
- package/docs/product-adoption-1.4.md +7 -0
- package/docs/product-audit-2026-09-15.md +1 -1
- package/docs/provider-button.md +6 -3
- package/docs/rating.md +1 -1
- package/docs/react-native-completion.md +4 -4
- package/docs/screen-chrome.md +1 -1
- package/docs/scroll-progress.md +29 -0
- package/docs/sheet.md +2 -0
- package/docs/sidebar.md +6 -0
- package/docs/splitter.md +1 -1
- package/docs/step-player.md +16 -0
- package/docs/task-list.md +32 -0
- package/docs/theme-studio.md +13 -0
- package/docs/thinking-orb.md +9 -2
- package/docs/time-picker.md +1 -1
- package/docs/toast.md +8 -7
- package/docs/tour.md +1 -1
- package/docs/tree-select.md +2 -2
- package/docs/voice-note.md +19 -0
- package/package.json +91 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type VoiceNoteState = "paused" | "playing" | "loading" | "error";
|
|
2
|
+
export type VoiceNoteDescriptor = Readonly<{
|
|
3
|
+
title: string;
|
|
4
|
+
state: VoiceNoteState;
|
|
5
|
+
/** null means metadata has not loaded. Zero is a known empty recording. */
|
|
6
|
+
duration: number | null;
|
|
7
|
+
position: number;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
/** Normalize host media metadata without inventing playback or loading progress. */
|
|
11
|
+
export declare function resolveVoiceNote(input: VoiceNoteDescriptor): {
|
|
12
|
+
position: number;
|
|
13
|
+
canPlay: boolean;
|
|
14
|
+
canSeek: boolean;
|
|
15
|
+
title: string;
|
|
16
|
+
state: VoiceNoteState;
|
|
17
|
+
duration: number | null;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=voice-note.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-note.d.ts","sourceRoot":"","sources":["../src/voice-note.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AACH,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB;;;;WARlD,MAAM;WACN,cAAc;cAEX,MAAM,GAAG,IAAI;eAEZ,OAAO;EAYnB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Normalize host media metadata without inventing playback or loading progress. */
|
|
2
|
+
export function resolveVoiceNote(input) {
|
|
3
|
+
if (!input.title.trim())
|
|
4
|
+
throw new TypeError("VoiceNote requires a title");
|
|
5
|
+
if (!["paused", "playing", "loading", "error"].includes(input.state))
|
|
6
|
+
throw new TypeError("Unknown VoiceNote state");
|
|
7
|
+
if (input.duration !== null && (!Number.isFinite(input.duration) || input.duration < 0))
|
|
8
|
+
throw new RangeError("VoiceNote duration must be finite and nonnegative");
|
|
9
|
+
if (!Number.isFinite(input.position) || input.position < 0)
|
|
10
|
+
throw new RangeError("VoiceNote position must be finite and nonnegative");
|
|
11
|
+
const hasDuration = input.duration !== null && input.duration > 0;
|
|
12
|
+
return { ...input, position: hasDuration ? Math.min(input.position, input.duration) : 0,
|
|
13
|
+
canPlay: !input.disabled && input.state !== "loading" && input.state !== "error" && hasDuration,
|
|
14
|
+
canSeek: !input.disabled && input.state !== "loading" && input.state !== "error" && hasDuration };
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=voice-note.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-note.js","sourceRoot":"","sources":["../src/voice-note.ts"],"names":[],"mappings":"AASA,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,KAA0B;IACzD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;IAC3E,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,CAAC;IACrH,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,mDAAmD,CAAC,CAAC;IACnK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,mDAAmD,CAAC,CAAC;IACtI,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;IAClE,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACtF,OAAO,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO,IAAI,WAAW;QAC/F,OAAO,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO,IAAI,WAAW,EAAE,CAAC;AACtG,CAAC","sourcesContent":["export type VoiceNoteState = \"paused\" | \"playing\" | \"loading\" | \"error\";\nexport type VoiceNoteDescriptor = Readonly<{\n title: string;\n state: VoiceNoteState;\n /** null means metadata has not loaded. Zero is a known empty recording. */\n duration: number | null;\n position: number;\n disabled?: boolean;\n}>;\n/** Normalize host media metadata without inventing playback or loading progress. */\nexport function resolveVoiceNote(input: VoiceNoteDescriptor) {\n if (!input.title.trim()) throw new TypeError(\"VoiceNote requires a title\");\n if (![\"paused\", \"playing\", \"loading\", \"error\"].includes(input.state)) throw new TypeError(\"Unknown VoiceNote state\");\n if (input.duration !== null && (!Number.isFinite(input.duration) || input.duration < 0)) throw new RangeError(\"VoiceNote duration must be finite and nonnegative\");\n if (!Number.isFinite(input.position) || input.position < 0) throw new RangeError(\"VoiceNote position must be finite and nonnegative\");\n const hasDuration = input.duration !== null && input.duration > 0;\n return { ...input, position: hasDuration ? Math.min(input.position, input.duration!) : 0,\n canPlay: !input.disabled && input.state !== \"loading\" && input.state !== \"error\" && hasDuration,\n canSeek: !input.disabled && input.state !== \"loading\" && input.state !== \"error\" && hasDuration };\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 공통 실행과 실패 복구
|
|
2
|
+
|
|
3
|
+
검토일: 2026-10-02 · 상태: 선택적 행동 API, Storybook 실험 단계
|
|
4
|
+
|
|
5
|
+
## 문제와 기존 API의 관계
|
|
6
|
+
|
|
7
|
+
2026-10-02 Atlas 검토와 사용자 요청에서 일반 저장·낙관적 반영의 실패 복구가 앱별로
|
|
8
|
+
연결된다는 점을 확인했다. Button은 전달받은 loading 중 실행 차단, AlertDialogSession은
|
|
9
|
+
확인창의 비동기 확인·닫기를 이미 소유한다. 이들을 새 버튼·확인창으로 복제하지 않는다.
|
|
10
|
+
`createActionSession`은 확인창 밖 작업의 상태만 제공하며 기존 컴포넌트와 합성한다.
|
|
11
|
+
AlertDialog.onConfirm에 연결할 때에는 기존 확인창 세션을 우선하고 이중 상태를 만들지 않는다.
|
|
12
|
+
|
|
13
|
+
## 계약과 기본값
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createActionSession } from '@hjmds/design-contracts/action-session';
|
|
17
|
+
const save = createActionSession(''); // 제품의 화면 또는 작업 소유자가 한 번 생성
|
|
18
|
+
const submitted = draft; // 재시도 중 바뀐 입력을 읽지 않도록 제출 시점의 값을 캡처
|
|
19
|
+
await save.run(() => api.save(submitted), { retryable: true });
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- 상태는 idle → pending → success/error. 서버 어댑터가 resolve하기 전 성공을 표시하지 않는다.
|
|
23
|
+
- 같은 세션의 pending 중 새 run/retry는 blocked이며 작업 함수를 호출하지 않는다.
|
|
24
|
+
- 실패는 throw 대신 error 결과와 snapshot.error로 전달한다. 오류 문구와 재시도 가능성은 제품이
|
|
25
|
+
정한다. raw exception을 사용자에게 직접 노출하지 않는다.
|
|
26
|
+
- retryable 기본값은 false. 재시도가 안전하다고 제품이 명시한 경우에만 실패한 작업 함수를
|
|
27
|
+
다시 호출한다. 서버가 멱등 처리를 지원하면 **동일 제출의 재시도에는 동일 제품 요청 키**를
|
|
28
|
+
캡처해 사용한다. 이 helper의 operationId는 세션 안 응답 순서용 숫자이며 서버 멱등 키가 아니다.
|
|
29
|
+
- optimisticValue는 저위험 변경에만 사용한다. 즉시 pending 값으로 보이고, 성공 시 서버가
|
|
30
|
+
반환한 값을 확정한다. 실패하면 직전 확인 값으로 돌아간다. 객체 값은 불변 데이터로 전달한다.
|
|
31
|
+
- reset(value)는 세대를 바꿔 이전 응답과 재시도를 무효화한다. 실제 서버 작업을 취소하지 않는다.
|
|
32
|
+
제품은 계정·엔티티 변경 시 필요한 취소/재조회/캐시 무효화를 처리한다. 오래된 작업이 서버에
|
|
33
|
+
반영됐을 가능성이 있으면 이 helper만으로 정합성을 보장하지 않는다.
|
|
34
|
+
- 자동 재시도·네트워크·타이머·저장소·전역 싱글턴은 없다. 세션을 엔티티별로 유지한다.
|
|
35
|
+
task 함수를 버리려면 reset을 호출하며 렌더 중 세션을 새로 생성하지 않는다.
|
|
36
|
+
|
|
37
|
+
## 웹과 앱 연결
|
|
38
|
+
|
|
39
|
+
React와 React Native 모두 `useSyncExternalStore(session.subscribe, session.getSnapshot,
|
|
40
|
+
session.getSnapshot)`로 구독한다. SSR은 요청별 세션을 생성하고 hydration 초기값을 맞춘다.
|
|
41
|
+
Button.loading은 pending, 오류 안내는 error에서 제품이 지역화한 문자열로 표시한다.
|
|
42
|
+
웹은 status/alert, Native는 플랫폼 접근성 알림을 사용한다. HJM 모션·포커스·busy 처리는
|
|
43
|
+
기존 renderer가 계속 맡는다. store는 별도 React 훅·UI 패키지를 요구하지 않는다.
|
|
44
|
+
|
|
45
|
+
세션을 화면보다 오래 보존해야 하면 제품의 작업 소유자에 둔다. 화면 unmount 시 자동 reset은
|
|
46
|
+
진행 중 작업 결과를 버릴 수 있어 기본값으로 넣지 않았다. 서버 캐시나 TanStack Query를 이미
|
|
47
|
+
쓰는 앱은 기존 mutation 상태를 유지하고 중복 session을 추가하지 않는다. 이 계약의
|
|
48
|
+
중복 방지·오류·복구 원칙을 기존 계층에 연결한다.
|
|
49
|
+
|
|
50
|
+
## 초안과 실행 취소
|
|
51
|
+
|
|
52
|
+
초안은 입력 state/제품 저장소의 소유다. 실패 시 지우지 않고 재시도에서는 캡처한 제출 값을
|
|
53
|
+
사용한다. 현재보다 오래된 성공 응답으로 사용자가 새로 입력한 초안을 지우지 않는다.
|
|
54
|
+
프로세스 종료 복구·로그아웃 정리·암호화·오프라인 outbox는 제품 저장소가 결정한다.
|
|
55
|
+
|
|
56
|
+
실행 취소는 제품이 제공한 역연산을 run으로 실행해 성공 확인 후 복구한다. 토스트는 액션 진입점,
|
|
57
|
+
세션은 진행·실패 상태만 소유한다. 권한·만료·대상 버전·복구 데이터는 제품이 검증한다.
|
|
58
|
+
UI를 되돌렸다는 이유로 서버 취소 완료를 선언하거나 금융·영구 삭제에 가짜 Undo를 넣지 않는다.
|
|
59
|
+
|
|
60
|
+
## 검증 화면과 범위
|
|
61
|
+
|
|
62
|
+
Web/Native Storybook `실험/구성/공통 동작`의 저장과 재시도, 즉시 반영과 복구, 보관과 실행 취소.
|
|
63
|
+
각각 기본·어두운 테마·큰 글자 상태. 데모만 350ms 인위 지연과 실패 주입을 제공하며 실제 API나
|
|
64
|
+
영속 저장소는 연결하지 않는다. 보관 데모에 자동 만료는 없다. 실험의 스토리북 배포는 사용자 승인 후다.
|
|
65
|
+
|
|
66
|
+
회귀 검사는 중복 실행 차단, sync throw, 명시적인 재시도 허용, 낙관적 rollback, 새 세대 이후
|
|
67
|
+
역순 성공/실패 무시, 구독 해제, 실패한 역연산의 상태 보존을 검증한다. 실기기·소비 앱 채택과는 별개다.
|
|
68
|
+
|
|
69
|
+
## 패키지 경계와 크기
|
|
70
|
+
|
|
71
|
+
`./action-session` subpath만 추가하고 root/catalog에 새 컴포넌트를 만들지 않는다. manifest의
|
|
72
|
+
새 경로 이유는 위의 일반 작업/확인창 역할 분리다. 초기 ESM 2504 bytes / gzip 889 bytes,
|
|
73
|
+
1 module을 측정했고 신규 예산은 2900 / 1050 bytes로 설정했다. 기존 예산은 변경하지 않았다.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Activity heatmap
|
|
2
|
+
|
|
3
|
+
Reviewed: 2026-10-01. `/activity-heatmap` exports ActivityHeatmap on Web and Native.
|
|
4
|
+
Use it for a year-sized activity overview; Calendar remains the date-picker owner.
|
|
5
|
+
The showcase profile-activity fixture demonstrates its intended consumption. Values
|
|
6
|
+
are local demonstration data, not evidence of user engagement or GitHub activity.
|
|
7
|
+
|
|
8
|
+
The descriptor accepts inclusive ISO `startDate` and `endDate`, unique `days` entries
|
|
9
|
+
`{date, value}`, optional increasing positive `thresholds` (default 1, 3, 7), and
|
|
10
|
+
`weekStartsOn` (0 Sunday or 1 Monday, default Monday). Values must be finite and
|
|
11
|
+
nonnegative. Duplicate/out-of-range dates, invalid calendar dates and inverted or
|
|
12
|
+
longer-than-366-day ranges throw. Products paginate longer histories to keep the
|
|
13
|
+
visual overview and Native accessibility traversal bounded.
|
|
14
|
+
|
|
15
|
+
UTC calendar arithmetic avoids daylight-saving day shifts. Missing entries remain
|
|
16
|
+
`null`; an explicit zero stays zero. Missing cells have a dashed outline. Nonzero
|
|
17
|
+
values map to four intensities: up to the first threshold, then the second, then
|
|
18
|
+
the third, then above it. Zero and unknown use the neutral surface. The semantic
|
|
19
|
+
brand palette controls intensity; no provider-specific colors or fetching are built in.
|
|
20
|
+
|
|
21
|
+
Pass localized `label` and `formatDay(date, value)`; handle null explicitly.
|
|
22
|
+
Grid cells have those accessible labels and the Web also exposes hover titles.
|
|
23
|
+
`view="list"` provides the same information as visible text, so color is not the
|
|
24
|
+
only way to inspect values. The product supplies the view toggle using Button.
|
|
25
|
+
Native hosts put long lists in their own vertical ScrollView. Grid rendering scrolls
|
|
26
|
+
horizontally and is read-only; these small cells are not undersized tap targets.
|
|
27
|
+
|
|
28
|
+
Both showcases register `컴포넌트/데이터 표시/Activity Heatmap` with Default, Dark and
|
|
29
|
+
LargeText, including a functional list toggle. Contract and renderer tests verify
|
|
30
|
+
leap dates, missing/zero semantics, invalid data and accessible values. Native host tests are supplemented by the [LargeText simulator flow audit](../../../docs/evidence/component-flows-2026-10-01/README.md):
|
|
31
|
+
list/grid switching, unknown-data wording and readable date/value rows were verified.
|
|
32
|
+
Physical-device rendering and manually heard VoiceOver traversal remain unverified.
|
package/docs/anchor.md
CHANGED
|
@@ -54,7 +54,7 @@ listener·observer·예약 frame을 해제합니다. CSS transform만으로 위
|
|
|
54
54
|
Web 전용입니다. Native 라우팅·목록 위치 이동은 해당 플랫폼과 제품이 소유하며 빈 RN wrapper를
|
|
55
55
|
추가하지 않습니다. 중첩 트리 목차·자동 제목 수집·오버레이 anchor positioning은 포함하지 않습니다.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
`컴포넌트/탐색/Anchor`는 세 부분으로 나눈 읽기 가이드와 별도 스크롤 영역을 제공합니다.
|
|
58
58
|
브라우저 테스트는 위치 동기화·짧은 마지막 부분·offset·reduced motion·임시 focus 복원·
|
|
59
59
|
fragment 복구·나중에 삽입된 대상·좁은 목차의 긴 label 줄바꿈·Tab/Enter 활성화와 문서 스크롤을 검사합니다.
|
|
60
60
|
실제 제품 문서 채택·보조기기 검증은 남아 있습니다.
|
package/docs/auth-screen.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# AuthScreenLayout — 로그인 화면의 두 영역 골격
|
|
2
2
|
|
|
3
|
+
검토일: 2026-10-01
|
|
4
|
+
|
|
3
5
|
## 문제
|
|
4
6
|
|
|
5
7
|
계정이 있는 제품들이 같은 화면을 각자 만들었고, 같은 실수를 각자 했다.
|
|
@@ -26,6 +28,37 @@
|
|
|
26
28
|
`density`는 `compact`/`regular` 둘뿐이다. 제품이 화면 높이를 보고 고르며 HJM은 기기 종류를
|
|
27
29
|
추측하지 않는다 — 같은 폭이라도 키보드가 올라오면 좁아진다.
|
|
28
30
|
|
|
31
|
+
## 로그인 카드 진행 상태
|
|
32
|
+
|
|
33
|
+
2026-10-01 사용자 요청에 따라 로그인 화면의 제공자 버튼은 이름만 표시한다
|
|
34
|
+
(`카카오`, `네이버`, `Google`, `Apple`). HJM은 전달받은 label을 임의로 잘라내지 않으며
|
|
35
|
+
제품이 지역화 카탈로그에서 문구를 바꾼다.
|
|
36
|
+
|
|
37
|
+
Web·Native `AuthScreenLayout`의 `mainCard`는 주 행동을 공통 카드로 감싼다. 이미 제품이
|
|
38
|
+
카드를 공급하는 기존 화면과 중첩되지 않도록 기본값은 false이며, 새 조합은 mainCard를 켜고
|
|
39
|
+
main 슬롯에는 버튼 목록만 넣는다. 배경·padding·radius는 바깥 카드가 계속 소유한다.
|
|
40
|
+
|
|
41
|
+
인증 시작 시 지역화된 `pendingLabel`(예: `로그인 중`)을 전달하면 모든 주 행동을 숨기고
|
|
42
|
+
같은 카드의 정중앙에 하나의 로딩 표시를 띄운다. 내용은 크기와 폼 상태를 유지하기 위해
|
|
43
|
+
mount된 채 남지만 Web inert와 Native touch/accessibility 제외로 조작할 수 없다.
|
|
44
|
+
2026-10-01 추가 사용자 요청에 따라 pendingLabel은 스크린리더 안내로만 사용하며 로딩 아래에 문구를 표시하지 않는다.
|
|
45
|
+
pendingLabel은 비어 있으면 거부한다. 취소·실패 시 이 prop을 제거하면 버튼이 돌아온다.
|
|
46
|
+
hero와 footer는 진행 중에도 유지한다. 버튼별 busy spinner와 함께 쓰지 않는다.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<AuthScreenLayout
|
|
50
|
+
mainCard
|
|
51
|
+
{...(loginPending ? { pendingLabel: t("auth.pending") } : {})}
|
|
52
|
+
hero={hero}
|
|
53
|
+
main={providerButtons}
|
|
54
|
+
footer={policyLinks}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
제품은 pending 여부가 바뀌어도 같은 버튼 목록을 main에 전달해야 카드 크기가 유지된다.
|
|
59
|
+
진행 중 버튼 목록을 조건부 제거하거나 다른 내용으로 교체하지 않는다. 로딩은 기존 로그인
|
|
60
|
+
카드가 있는 상태에서 전환하는 계약이며 최초 제공자 목록 조회의 skeleton을 대신하지 않는다.
|
|
61
|
+
|
|
29
62
|
## HJM 기본값
|
|
30
63
|
|
|
31
64
|
| 값 | 기본 | 근거 |
|
|
@@ -53,13 +86,20 @@
|
|
|
53
86
|
|
|
54
87
|
| | Web | Native |
|
|
55
88
|
| --- | --- | --- |
|
|
56
|
-
| 뿌리 요소 | `<main
|
|
89
|
+
| 뿌리 요소 | 기본 `<main>`, 앱 셸이 main을 소유하면 `as="section"` + 인라인 custom property | `ScrollView` + `contentContainerStyle.flexGrow: 1` |
|
|
57
90
|
| 세로 중앙 | `flex: 1` 블록의 `justify-content: center` | 같은 구조를 `View`의 `flex: 1`로 |
|
|
58
91
|
| 스크롤 전환 | `min-block-size: 100dvh` + 자연 스크롤 | `ScrollView`가 내용이 길 때만 스크롤 |
|
|
59
92
|
| 수치 전달 | 해석된 값을 CSS 변수로 — 스타일시트와 계약이 어긋날 수 없다 | 인라인 스타일 |
|
|
60
93
|
|
|
61
94
|
## 검증 화면
|
|
62
95
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
현재 catalog와 생성 evidence에서 Web·Native 모두 stable이며, canonical preview와
|
|
97
|
+
default·dark·long copy·large text·RTL·reduced motion·accessibility 증거가 연결돼 있다.
|
|
98
|
+
2026-10-01 문서 점검에서 초기 구현 시점의 “아직 없음” 설명이 현재 projection과 어긋나
|
|
99
|
+
이를 정정했다. 근거는 [성숙도](generated/component-maturity.md)와
|
|
100
|
+
[renderer evidence](generated/renderer-evidence.md)의 AuthScreenLayout 항목이다.
|
|
101
|
+
|
|
102
|
+
이 증거는 HJM renderer 범위다. 소비 제품의 로그인 이관·실제 OAuth·기기 실행·게시 상태는
|
|
103
|
+
제품별 검증으로 확인한다. [제품 채택 가이드](product-adoption-1.4.md)의 슬롯·키보드·
|
|
104
|
+
중첩 main 처리 기준을 따른다. 포트폴리오 제품은 상위 로그인 화면 표준(LS)을 함께 적용하며,
|
|
105
|
+
`hasFooter: false` 옵션이 필수 정책 링크 생략을 허용하는 것은 아니다.
|
package/docs/authoring-brief.md
CHANGED
|
@@ -86,3 +86,8 @@ pnpm typecheck && pnpm test
|
|
|
86
86
|
- 판단이 갈렸던 자리: <대안과 택한 이유>
|
|
87
87
|
- 게이트: typecheck <결과> / test <N passed>
|
|
88
88
|
```
|
|
89
|
+
|
|
90
|
+
## 상호작용 저작 보완 · 2026-10-02
|
|
91
|
+
|
|
92
|
+
[제품 상호작용 기준](../../../docs/INTERACTION_QUALITY.md)에 따라 동작 소유권·중단·복구와
|
|
93
|
+
성능에 영향을 주는 변경의 측정 범위를 기록한다. 원본 영상만으로 동등한 성능을 주장하지 않는다.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Avatar fallback and Blobatar
|
|
2
|
+
|
|
3
|
+
Reviewed: 2026-10-01. Implemented locally; package publication and consumer adoption are separate.
|
|
4
|
+
|
|
5
|
+
`Avatar` remains the sole canonical component. Both renderers accept
|
|
6
|
+
`renderFallback({ size, decorative: true })`. The factory is called only while a
|
|
7
|
+
photo is absent or has failed. Returning null/undefined preserves the existing
|
|
8
|
+
Web `fallback` or Native initials. Existing consumers are unchanged.
|
|
9
|
+
|
|
10
|
+
The Avatar owns the accessible name; custom fallback content is decorative and
|
|
11
|
+
must not contain interactive controls. Native hides fallback descendants from
|
|
12
|
+
accessibility. Photo source changes retry loading rather than retaining an old
|
|
13
|
+
failure. Web continues forwarding the image error callback.
|
|
14
|
+
|
|
15
|
+
## Optional adapter
|
|
16
|
+
|
|
17
|
+
Import `createBlobatarFallback` from `@hjmds/react/avatar-blobatar` or
|
|
18
|
+
`@hjmds/react-native/avatar-blobatar`. Neither root imports the adapter.
|
|
19
|
+
Install `blobatar@2.7.0` and the matching `@blobatar/react@2.7.0` or
|
|
20
|
+
`@blobatar/react-native@2.7.0`; Native additionally uses the existing supported
|
|
21
|
+
`react-native-svg` peer. The static entry needs no Reanimated or Worklets import.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
const renderFallback = useMemo(
|
|
25
|
+
() => createBlobatarFallback({ seed: account.publicAvatarId, expression: 'idle' }),
|
|
26
|
+
[account.publicAvatarId],
|
|
27
|
+
);
|
|
28
|
+
// Web: <Avatar name={displayName} src={photo} renderFallback={renderFallback} />
|
|
29
|
+
// Native: <Avatar name={displayName} source={photo} accessibilityLabel={displayName}
|
|
30
|
+
// renderFallback={renderFallback} />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Only `idle` and `happy` static expressions are currently exposed. There is no
|
|
34
|
+
`motion` option in this first implementation. Use the same public product seed on
|
|
35
|
+
both platforms; names, emails, credentials and inferred emotions are not seeds.
|
|
36
|
+
Normalization is explicitly disabled to preserve case-sensitive account identity.
|
|
37
|
+
Generation is local (Web data URI, Native SVG paths). No avatar server is called.
|
|
38
|
+
Pinned upstream versions prevent an incidental generator upgrade from changing
|
|
39
|
+
existing faces. React adapters retain upstream generation rather than approximating
|
|
40
|
+
its artwork with a second generator. MIT notices remain in the upstream packages.
|
|
41
|
+
|
|
42
|
+
`패턴/프로필 편집` in both showcases demonstrates choosing a face,
|
|
43
|
+
editing a display name, toggling notifications, applying and reverting local
|
|
44
|
+
changes. Copy is keyed in the shared showcase fixture; it is not embedded in the
|
|
45
|
+
renderer. This is a composition example, not an account API or new canonical screen.
|
|
46
|
+
The original [integration plan](../../../docs/plans/visual-and-motion-integration-2026-10-01.md)
|
|
47
|
+
tracks later animation, theme and marketing work.
|
|
48
|
+
|
|
49
|
+
## Optional motion and expressions (2026-10-01)
|
|
50
|
+
|
|
51
|
+
`/avatar-blobatar-motion` on each renderer exports `createAnimatedBlobatarFallback`.
|
|
52
|
+
It accepts a stable public `seed`, `expression` (idle, happy, sad, surprised, wink,
|
|
53
|
+
sleepy or thinking), `active` (default false), and `visible` (default true). Pass its
|
|
54
|
+
result into the existing Avatar `renderFallback`. Avatar still owns naming and image
|
|
55
|
+
fallback; the animated artwork remains decorative.
|
|
56
|
+
|
|
57
|
+
The Web entry imports Blobatar's motion stylesheet and observes intersection/document
|
|
58
|
+
visibility. Native uses the upstream optional AnimatedBlobatar entry, requiring the
|
|
59
|
+
host's compatible Reanimated/Worklets, and observes AppState. Native hosts must pass
|
|
60
|
+
route/list visibility explicitly. Native switches to the static upstream renderer when
|
|
61
|
+
inactive, hidden, reduced or backgrounded: upstream 2.7.0 keeps its frame callback
|
|
62
|
+
running with `animate=false`, as confirmed by device-host CPU sampling. Seed and
|
|
63
|
+
expression are preserved, while the idle clock restarts when re-enabled.
|
|
64
|
+
Both honor HJM reduced motion and never enable
|
|
65
|
+
idle animation without `active`. Static `/avatar-blobatar` remains available without
|
|
66
|
+
motion imports. The pinned Blobatar 2.7.0 generation and original license remain.
|
|
67
|
+
|
|
68
|
+
컴포넌트/데이터 표시/Animated Blobatar provides Default, Dark and LargeText in both
|
|
69
|
+
showcases, with explicit pose and motion controls. Native gating tests mock the
|
|
70
|
+
upstream rendering boundary; the subsequent [Native integration audit](../../../docs/evidence/native-visual-integration-2026-10-01/README.md)
|
|
71
|
+
adds installed iPhone 17 / iOS 27 simulator captures and active/stopped CPU samples
|
|
72
|
+
from the real upstream runtime. The inactive frame-loop correction is backed by
|
|
73
|
+
those samples. This does not establish physical-device GPU timing, battery use,
|
|
74
|
+
or exhaustive geometry and screen-reader behavior.
|
|
@@ -35,7 +35,7 @@ const descriptor = {
|
|
|
35
35
|
|
|
36
36
|
시각·플랫폼 선택은 별도 `BottomNavigationConfiguration`으로 전달합니다.
|
|
37
37
|
|
|
38
|
-
- `presentation`: `bar | floating`
|
|
38
|
+
- `presentation`: `bar | floating | capsule`
|
|
39
39
|
- `distribution`: `equal | center-gap`
|
|
40
40
|
- `density`: `compact | regular`
|
|
41
41
|
- `direction`: `ltr | rtl`
|
|
@@ -126,3 +126,18 @@ distribution을 선택하고 outer Dock frame에서 기존 Button/IconButton 기
|
|
|
126
126
|
합성합니다. 이 action은 tab role, selected state, badge, `selectedKey`, destination count를
|
|
127
127
|
가질 수 없습니다. Web은 button, RN은 button role과 activate action을 사용하며 제품 router나
|
|
128
128
|
modal API를 직접 호출합니다.
|
|
129
|
+
|
|
130
|
+
## Capsule presentation (2026-10-01)
|
|
131
|
+
|
|
132
|
+
`configuration={{ presentation: "capsule" }}`는 둥근 route 표면과 선택된 항목의 가로형
|
|
133
|
+
icon/label을 제공합니다. `primaryAction`은 컬렉션 밖의 별도 원형 행동으로 배치됩니다.
|
|
134
|
+
`center-gap`과 함께 지정하면 오류입니다. 생성 행동이 route로 오인되거나 중앙에서 이름을
|
|
135
|
+
가리는 것을 방지하기 위한 선택입니다.
|
|
136
|
+
|
|
137
|
+
기본 크기에서 비선택 항목의 시각 label은 접히지만 전체 접근성 이름/배지/route intent는
|
|
138
|
+
그대로 유지됩니다. 큰 글씨(1.5 이상) 또는 5–6개 목적지는 모든 이름을 세로 배치해 표시합니다.
|
|
139
|
+
기존 bar/floating 및 router 소유 selectedKey는 바뀌지 않습니다.
|
|
140
|
+
[사용자 레퍼런스 두 번째 이미지](https://www.instagram.com/p/Ddb8lXmjnV9/?img_index=2)의
|
|
141
|
+
선택 캡슐과 분리된 추가 행동을 참고했으며, 원본 소스는 복제하지 않았습니다.
|
|
142
|
+
|
|
143
|
+
Storybook: **컴포넌트 → 탐색 → 캡슐 네비게이션**, Default/Dark/LargeText.
|
package/docs/breadcrumb.md
CHANGED
|
@@ -74,7 +74,7 @@ Web에서는:
|
|
|
74
74
|
경로도 유지합니다. 필수 props는 `label`, `items`이고 `ref`는 nav 요소를 가리킵니다.
|
|
75
75
|
|
|
76
76
|
2026-09-16 사용자의 React/RN 라이브러리 확장 요청으로, 제품 채택 대기와 라이브러리 beta
|
|
77
|
-
제공을 분리했습니다.
|
|
77
|
+
제공을 분리했습니다. `갤러리/웹 탐색`은 조상 링크로 보관함을 열고 다시 기록 목록으로
|
|
78
78
|
돌아오는 작동 예제입니다. 제품 채택이나 stable 증거로 계산하지 않습니다.
|
|
79
79
|
|
|
80
80
|
[WAI Breadcrumb](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/)의 landmark·조상 링크·
|
package/docs/calendar.md
CHANGED
package/docs/cascader.md
CHANGED
|
@@ -110,7 +110,7 @@ collection + tri-state 판정 모듈)으로 확정됐고, Tree renderer가 들
|
|
|
110
110
|
않으므로 막아 둔 자리를 여는 새 축이 필요 없다.
|
|
111
111
|
|
|
112
112
|
그래서 catalog의 Cascader 행은 `prerequisite`에서 `composed`(Popover·Tree)로 옮겼고,
|
|
113
|
-
작동 예제는 Showcase
|
|
113
|
+
작동 예제는 Showcase `컴포넌트/데이터 표시/Tree`의 Cascader 화면이다. "만들지 않는다"는 판정 자체는
|
|
114
114
|
그대로다 — 바뀐 것은 흡수하는 쪽이 컴포넌트가 아니라 조합이라는 점뿐이다.
|
|
115
115
|
|
|
116
116
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Code block
|
|
2
|
+
|
|
3
|
+
Reviewed: 2026-10-01. Optional `/code-block` entries in both renderers expose
|
|
4
|
+
`CodeBlock`. This is a source preview, not an editor or executable HTML renderer.
|
|
5
|
+
The visual integration request required syntax presentation without another input
|
|
6
|
+
or clipboard engine, so source validation belongs to contracts and actions are slots.
|
|
7
|
+
|
|
8
|
+
Pass `code`, a localized `label`, optional `language`, `wrap` and `tokens`.
|
|
9
|
+
Tokens have `{text, tone?}` where tone is plain, keyword, string, comment or number.
|
|
10
|
+
Their text must concatenate to the original code exactly, including whitespace;
|
|
11
|
+
invalid output throws. Use a product-selected highlighter to generate tokens.
|
|
12
|
+
No parser, remote highlighting service, raw HTML or execution is included. The
|
|
13
|
+
semantic palette supplies readable token colors; string/plain may share a color.
|
|
14
|
+
|
|
15
|
+
Long lines scroll horizontally by default. `wrap` opts into wrapping. On Web the
|
|
16
|
+
preformatted source is keyboard-focusable and selectable; on Native it uses a
|
|
17
|
+
selectable Text inside the existing horizontal ScrollView. The Native accessible
|
|
18
|
+
name includes the original source as well as its label, so naming does not hide code.
|
|
19
|
+
|
|
20
|
+
`copyAction` accepts an existing product control. Web can pass HJM ClipboardButton
|
|
21
|
+
with `value={code}`, localized idle/copied labels and a visible `onCopyError` response.
|
|
22
|
+
Native can use system text selection directly or supply the product's clipboard
|
|
23
|
+
button. No Expo clipboard peer is pulled into the generic renderer. The Native
|
|
24
|
+
showcase demonstrates actual system selection; it does not fake a copied state.
|
|
25
|
+
|
|
26
|
+
Both showcases register `컴포넌트/데이터 표시/Code Block`. Contract tests reject altered
|
|
27
|
+
highlight text; browser tests check literal script text, focus and wrapping; Native
|
|
28
|
+
host tests check selectable content and scroll/wrap composition. The
|
|
29
|
+
[Native flow audit](../../../docs/evidence/component-flows-2026-10-01/README.md) verifies
|
|
30
|
+
200% controlled text scaling and actual iOS simulator long-press Copy, with exact
|
|
31
|
+
source/clipboard equality. Header and source use the common text-scale helper;
|
|
32
|
+
token spans inherit once. Android clipboard and physical-device verification remain separate.
|
package/docs/command-palette.md
CHANGED
|
@@ -134,4 +134,4 @@ Native는 `unsupported`다. 전역 단축키는 제품 소유다.
|
|
|
134
134
|
- **전역 단축키는 제품 소유다.** 이 renderer는 여는 키를 정하지 않는다.
|
|
135
135
|
- 로컬 검증: `test/command-palette.browser.test.tsx` 5개(이름·초점·배경 inert, 활성 행과
|
|
136
136
|
disabled 건너뜀·재필터, 실행 시 강제 종료와 사유, 종료 후 후속 명령 순서, Escape·바깥
|
|
137
|
-
pointer 종료)와
|
|
137
|
+
pointer 종료)와 `컴포넌트/탐색/Command Palette`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Compound controls
|
|
2
|
+
|
|
3
|
+
Reviewed: 2026-10-01. Web and React Native expose identical optional entry names:
|
|
4
|
+
`/duration-field`, `/inline-confirm`, `/reaction-picker`, `/notification-bell`.
|
|
5
|
+
These are compositions of existing canonical controls, not replacements for them.
|
|
6
|
+
The 2026-10-01 visual integration request calls for reusable interactions as well as
|
|
7
|
+
page examples; keeping the existing controls preserves their disabled, focus and
|
|
8
|
+
loading behavior instead of introducing another control implementation.
|
|
9
|
+
|
|
10
|
+
## DurationField
|
|
11
|
+
|
|
12
|
+
Use for bounded elapsed time (integer seconds), not clock time or calendar dates.
|
|
13
|
+
Pass `value`, `onValueChange`, required `max`, optional `min` (default 0), and
|
|
14
|
+
`labels: {label, hours, minutes, seconds, increment(unit), decrement(unit)}`.
|
|
15
|
+
All labels are localized by the product. Three existing NumberFields edit the
|
|
16
|
+
hours/minutes/seconds representation. The shared `/duration-field` contract
|
|
17
|
+
validates safe integers and clamps the *combined* duration to the configured range.
|
|
18
|
+
Native field columns wrap according to the text scale so enlarged numeric values stay visible.
|
|
19
|
+
Hours are disabled when the maximum is below one hour. `disabled` disables all
|
|
20
|
+
inputs. Values outside the range throw rather than silently changing product state.
|
|
21
|
+
|
|
22
|
+
## InlineConfirm
|
|
23
|
+
|
|
24
|
+
Use for a local explicit confirmation when an overlay would interrupt the context.
|
|
25
|
+
Pass `label`, `prompt`, `confirmLabel`, `cancelLabel`, `pendingLabel`,
|
|
26
|
+
`successLabel`, `errorLabel`, and `onConfirm(): void | Promise<void>`.
|
|
27
|
+
The existing AlertDialog session owns async deduplication, pending and retry.
|
|
28
|
+
No action runs on the initial trigger. Pending disables both actions; rejection
|
|
29
|
+
shows the supplied safe error text and permits retry. Web initially focuses cancel,
|
|
30
|
+
Escape cancels, and cancellation restores trigger focus. Native exposes the
|
|
31
|
+
controls and live error text through its host accessibility APIs.
|
|
32
|
+
Native keeps the localized status text available to assistive technology. iOS
|
|
33
|
+
receives one explicit announcement per prompt/pending/error/success transition;
|
|
34
|
+
errors interrupt queued speech, other updates queue politely. Repeated rendering,
|
|
35
|
+
completion's internal closing phase and background updates do not repeat the prompt.
|
|
36
|
+
Android retains its live-region/alert semantics. Native bridge calls are lifecycle-tested;
|
|
37
|
+
this is not a claim that VoiceOver audio was manually heard.
|
|
38
|
+
|
|
39
|
+
Success remains visible. Remount with a new entity key to reset the interaction;
|
|
40
|
+
the session captures the action and labels when opened. The product owns operation
|
|
41
|
+
cancellation, authorization, persistence and server-side idempotency.
|
|
42
|
+
|
|
43
|
+
## ReactionPicker
|
|
44
|
+
|
|
45
|
+
Pass a nonempty list of `{id, emoji, label, count?, disabled?}`, controlled
|
|
46
|
+
`value: string | null`, `onValueChange`, and the group `label`.
|
|
47
|
+
The shared `/reactions` contract rejects duplicate IDs, invalid counts and unknown
|
|
48
|
+
selected IDs. Selecting the current reaction clears it; selecting another replaces
|
|
49
|
+
it. Existing Buttons own selection and disabled semantics. Counts are product data
|
|
50
|
+
and never increment implicitly. Include counts in localized option labels when
|
|
51
|
+
needed for screen readers; visible emoji/count artwork is decorative. Native passes
|
|
52
|
+
one composed text label to Button so emoji and count use its Text wrapper; sibling
|
|
53
|
+
raw strings bypass that wrapper and fail to render on a real device.
|
|
54
|
+
|
|
55
|
+
## NotificationBell
|
|
56
|
+
|
|
57
|
+
Pass `label`, nonnegative integer `count`, `icon`, `onPress`, and optionally
|
|
58
|
+
`disabled` or `active`. Supply the localized unread information in `label` and a
|
|
59
|
+
semantic Icon/Lucide glyph as `icon`. Existing IconButton and CounterBadge own the
|
|
60
|
+
control and count presentation. Artwork and badge are hidden from accessibility
|
|
61
|
+
so the button is announced once.
|
|
62
|
+
A count increase triggers one 400ms pulse; mounting or decreasing does not ring.
|
|
63
|
+
Reduced motion, inactive presentation and background document/AppState suppress
|
|
64
|
+
motion. Native hosts should set `active={false}` for offscreen items. Pressing does
|
|
65
|
+
not mark notifications read automatically; that operation belongs to the product.
|
|
66
|
+
|
|
67
|
+
## Evidence and examples
|
|
68
|
+
|
|
69
|
+
Both showcases register `컴포넌트/입력/Duration Field`,
|
|
70
|
+
`컴포넌트/동작/Inline Confirm`, `컴포넌트/동작/Reaction Picker` and
|
|
71
|
+
`컴포넌트/피드백/Notification Bell`, each with Default, Dark and LargeText.
|
|
72
|
+
`갤러리/복합 입력` provides the comparison examples. Web browser and Native
|
|
73
|
+
host-action tests exercise clamp, confirmation/retry/deduplication, controlled
|
|
74
|
+
reactions and motion suspension. Native host tests are not device-render evidence;
|
|
75
|
+
actual iPhone 17 / iOS 27 simulator LargeText cancel/error/retry/success/reset flows
|
|
76
|
+
are recorded in the [component flow audit](../../../docs/evidence/component-flows-2026-10-01/README.md).
|
|
77
|
+
Remaining device coverage stays tracked in the
|
|
78
|
+
[implementation ledger](../../../docs/plans/visual-integration-progress.md).
|
|
79
|
+
|
|
80
|
+
The same [Native flow audit](../../../docs/evidence/component-flows-2026-10-01/README.md)
|
|
81
|
+
also verifies LargeText duration unit increments/decrements and total seconds, reaction
|
|
82
|
+
selection/replacement/clearing, and local bell read/add state. These are actual simulator
|
|
83
|
+
actions, distinct from backend notification delivery and physical-device screen-reader QA.
|
package/docs/confirm-popover.md
CHANGED
|
@@ -99,7 +99,7 @@ Component Explorer에서 이 문서로 연결한다.
|
|
|
99
99
|
|
|
100
100
|
## 2026-09-16 작동 예제
|
|
101
101
|
|
|
102
|
-
React/RN 확장 요청에 따라
|
|
102
|
+
React/RN 확장 요청에 따라 `컴포넌트/오버레이/Popover/ReversibleConfirmation`을 제공한다.
|
|
103
103
|
`@hjmds/react/popover`의 children 함수에서 `close()`와 HJM Button을 조합한다.
|
|
104
104
|
취소 버튼을 초기 초점으로 지정하고, 보관 후 사라지는 트리거 대신 보관 취소 버튼에
|
|
105
105
|
초점을 보낸다. 보관 취소는 실제 로컬 상태를 복원한다. 외부 서버 요청이나 배포 증거는 아니다.
|
package/docs/consumer-policy.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# HJM 소비 앱 정책
|
|
2
2
|
|
|
3
3
|
상태: **Normative**
|
|
4
|
-
정책 버전: **
|
|
4
|
+
정책 버전: **2.0.0**
|
|
5
|
+
검토일: 2026-10-02 · 전체 소비 이관에 따른 deprecated API 제거
|
|
5
6
|
적용 대상: 신규 HJM Web·React Native 앱과 기존 앱의 새 화면
|
|
6
7
|
|
|
7
|
-
이
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
근거로 사용하고, 존재하지 않는 `v0.9.0` 정책 URL을 만들어 내면 안 됩니다.
|
|
8
|
+
이 원문은 `@hjmds/design-contracts/consumer-policy.md`에서 제공한다. 소비 앱은 설치한
|
|
9
|
+
패키지 버전의 정책과 HJM-APP-STANDARD release snapshot을 함께 확인한다. 이 checkout의
|
|
10
|
+
1.11 소스 갱신을 아직 설치하지 않은 앱에 자동 적용된 것으로 판단하지 않는다.
|
|
11
11
|
|
|
12
12
|
이 문서는 "HJM답게 보인다"를 특정 화면 복제로 정의하지 않습니다. 모든 제품이 공유해야
|
|
13
13
|
하는 것은 정보 위계, semantic role, 상태 피드백, 접근성 품질과 플랫폼별 행동입니다.
|
|
@@ -93,23 +93,15 @@ foundation이 모두 stable이 됐으므로, 중앙 app profile의 다음 개정
|
|
|
93
93
|
|
|
94
94
|
### 3.1 React Native legacy style compatibility boundary
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
color, typography, padding, gap, border, radius, height, opacity, transform과 interaction
|
|
106
|
-
state key를 타입에서 제외합니다. 허용된 margin 값도 HJM spacing token 또는 검토된 제품
|
|
107
|
-
adapter에서 와야 합니다(MUST).
|
|
108
|
-
- 기존 화면의 legacy prop은 migration 기간에만 유지할 수 있습니다(MAY). controlled visual
|
|
109
|
-
key가 남아 있으면 정책 항목, 소유자, 대체 semantic API와 제거 train을 ADR에 기록해야
|
|
110
|
-
하며, 신규 사용으로 복사해서는 안 됩니다(MUST NOT).
|
|
111
|
-
- 0.9 호환 train에서는 등록 style ID와 배열을 런타임에서 필터링하거나 조용히 삭제하지
|
|
112
|
-
않습니다. 그러한 즉시 변경은 기존 앱의 레이아웃을 예측 불가능하게 깨뜨리기 때문입니다.
|
|
96
|
+
2026-10-02 전체 소비 버전업 결정에 따라 deprecated 공개 스타일 통로는 1.11에서 제거합니다.
|
|
97
|
+
정확한 제거 속성과 대체 API는 [1.11 이관표](migration-native-legacy-removal.md)를 따릅니다.
|
|
98
|
+
1.x 설치본의 caller-last 호환 동작은 역사적 동작이며 현재 소스에 유지되지 않습니다.
|
|
99
|
+
|
|
100
|
+
- 배치에는 `layoutStyle`을 쓰고, 제공하지 않는 컴포넌트는 외부 composition wrapper를 씁니다.
|
|
101
|
+
- `HjmCompositionStyle` / `layoutStyle`은 margin, width, flex 계열과 `alignSelf`만 제공합니다. color,
|
|
102
|
+
typography, padding, gap, border, radius, height, opacity, transform은 recipe/semantic API 소유입니다.
|
|
103
|
+
- 내부 조합의 스타일은 비공개 renderer에 두며 앱에서 해당 내부 파일을 import하지 않습니다.
|
|
104
|
+
- 정식 플랫폼 host·제품 소유 슬롯·이미지 renderer 스타일을 deprecated 별칭과 혼동하지 않습니다.
|
|
113
105
|
|
|
114
106
|
소비 앱의 legacy prop이 실제로 무엇을 덮어쓰고 있었는지 확인한 뒤, 배치가 아닌 값은
|
|
115
107
|
semantic axis로 옮겼습니다. 아래는 그 축과 대체 대상입니다.
|
|
@@ -165,7 +157,7 @@ legacy raw style surface는 다음 조건이 모두 충족된 뒤 공지된 brea
|
|
|
165
157
|
“1.0.0 이전 제거” 기한은 실제 release/API와 어긋나므로 폐기합니다. 이미 게시한 1.x의 타입과
|
|
166
158
|
런타임을 minor에서 삭제하는 대신, 위 네 조건과 소비 앱별 이관 목록을 충족한 다음 major에서
|
|
167
159
|
제거합니다. 1.4는 semantic API와 이관 경로를 추가하는 train이며, 기존 prop을 새 화면에 쓰는
|
|
168
|
-
것은 계속 금지합니다.
|
|
160
|
+
것은 계속 금지합니다. 2026-10-02 사용자의 전체 이관 결정으로 제거 train을 1.11로 확정했습니다.
|
|
169
161
|
|
|
170
162
|
권장 구조는 다음과 같습니다. 실제 폴더명은 stack 관습에 맞춰도 역할은 유지합니다.
|
|
171
163
|
|
|
@@ -240,3 +232,18 @@ package로 승격하지 않습니다.
|
|
|
240
232
|
## Optional presentation adapters
|
|
241
233
|
|
|
242
234
|
The [optional adapter contract](optional-adapters.md) lists platform-specific entries and exact peer requirements. These extensions do not promote the canonical component or establish consumer/device compatibility. Native adapters require separately verified development clients; base imports retain their existing dependency contract.
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
## 상호작용 채택과 성능 기록
|
|
238
|
+
|
|
239
|
+
2026-10-02 사용자가 참고 인터랙션 수준의 제품 품질을 요청한 데 따른 구체 적용 기준이다.
|
|
240
|
+
기존 상태 피드백·접근성 책임을 제품의 입력 → 반응 → 확정 → 복구 흐름까지 연결한다.
|
|
241
|
+
|
|
242
|
+
- 제품은 설치된 HJM 버전의 API와 Storybook 배포/실험 상태를 확인하고 해당 범위에서 재사용한다.
|
|
243
|
+
새 동작이 필요한 경우 기존 Button·Sheet·Toast를 합성하며 같은 상태를 두 계층에서 관리하지 않는다.
|
|
244
|
+
- 성공은 서버 또는 제품의 실제 확정 뒤에 표시한다. 연속 입력·실패·역순 응답·중간 이탈에서도
|
|
245
|
+
초안과 복구 경로를 보존한다. 웹·앱 입력 방식이 달라도 같은 사용자 의도를 유지한다.
|
|
246
|
+
- 성능에 영향을 주는 제스처·전환·대량 렌더링 변경은 제품 조건의 변경 전후를 측정한다.
|
|
247
|
+
기기·OS·빌드·데이터·측정 방법과 미검증 범위를 기록하고 데모의 성공을 실기기 성능으로 쓰지 않는다.
|
|
248
|
+
- 구체 예제·목표·기록 양식은 [상호작용 적용·품질 기준](../../../docs/INTERACTION_QUALITY.md)을 따른다.
|
|
249
|
+
이 소스 문서의 변경이 아직 게시되지 않은 API를 소비 앱에 설치하거나 자동 적용하지는 않는다.
|