sh-ui-cli 0.113.0 → 0.115.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.
Files changed (23) hide show
  1. package/data/changelog/versions.json +27 -0
  2. package/data/registry/react/components/form/field.test.tsx +106 -1
  3. package/data/registry/react/components/form/field.tsx +179 -23
  4. package/data/registry/react/components/form/use-sh-ui-form.ts +14 -0
  5. package/data/registry/react/components/form-rhf/README.md +138 -8
  6. package/data/registry/react/components/form-rhf/index.tsx +75 -0
  7. package/data/registry/react/components/form-rhf/rhf.test.tsx +53 -1
  8. package/data/registry/react/components/label/index.tailwind.tsx +5 -1
  9. package/data/registry/react/components/label/styles.css +9 -5
  10. package/data/registry/react/components/label/styles.module.css +7 -5
  11. package/data/registry/react/components/rich-text-editor/index.module.tsx +523 -171
  12. package/data/registry/react/components/rich-text-editor/index.tailwind.tsx +596 -70
  13. package/data/registry/react/components/rich-text-editor/index.tsx +523 -171
  14. package/data/registry/react/components/rich-text-editor/styles.css +103 -5
  15. package/data/registry/react/components/rich-text-editor/styles.module.css +103 -5
  16. package/data/registry/react/components/sidebar/index.module.tsx +57 -0
  17. package/data/registry/react/components/sidebar/index.tailwind.tsx +68 -1
  18. package/data/registry/react/components/sidebar/index.tsx +57 -0
  19. package/data/registry/react/components/sidebar/styles.css +77 -0
  20. package/data/registry/react/components/sidebar/styles.module.css +77 -0
  21. package/data/registry/react/registry.json +319 -963
  22. package/data/registry/react/tokens-used.json +4 -1
  23. package/package.json +1 -1
@@ -6,7 +6,7 @@ import { useForm } from "react-hook-form";
6
6
  import { Form } from "../form";
7
7
  import { Field } from "../form/field";
8
8
  import { FormControl, FormError } from "../form/field";
9
- import { adaptReactHookForm } from "./index";
9
+ import { adaptReactHookForm, useReactHookFormAdapter } from "./index";
10
10
 
11
11
  function TestForm() {
12
12
  const rhf = useForm({ defaultValues: { email: "" }, mode: "onBlur" });
@@ -40,3 +40,55 @@ describe("adaptReactHookForm", () => {
40
40
  await screen.findByText("bad");
41
41
  });
42
42
  });
43
+
44
+ // ─────────────────────────────────────────────
45
+ // useReactHookFormAdapter — v0.114+ (안정화 hook)
46
+ // ─────────────────────────────────────────────
47
+ describe("useReactHookFormAdapter", () => {
48
+ it("returns same store instance across re-renders", () => {
49
+ const stores: any[] = [];
50
+ function Probe() {
51
+ const rhf = useForm({ defaultValues: { x: "" } });
52
+ const form = useReactHookFormAdapter(rhf);
53
+ stores.push(form);
54
+ const [, force] = React.useReducer((s: number) => s + 1, 0);
55
+ return <button onClick={force as any} data-testid="rerender">rerender</button>;
56
+ }
57
+ const { getByTestId } = render(<Probe />);
58
+ // 강제 re-render 3회
59
+ for (let i = 0; i < 3; i++) {
60
+ getByTestId("rerender").click();
61
+ }
62
+ // 최초 + re-render 들 모두 같은 인스턴스
63
+ expect(stores.length).toBeGreaterThanOrEqual(2);
64
+ for (const s of stores) {
65
+ expect(s).toBe(stores[0]);
66
+ }
67
+ });
68
+
69
+ it("works with render prop and propagates value through RHF", async () => {
70
+ const user = userEvent.setup();
71
+ function HookForm() {
72
+ const rhf = useForm({ defaultValues: { email: "" }, mode: "onBlur" });
73
+ const form = useReactHookFormAdapter(rhf);
74
+ return (
75
+ <Form form={form}>
76
+ <Field name="email">
77
+ {(field) => (
78
+ <input
79
+ data-testid="i"
80
+ value={(field.value as string) ?? ""}
81
+ onChange={(e) => field.handleChange(e.target.value)}
82
+ onBlur={field.handleBlur}
83
+ />
84
+ )}
85
+ </Field>
86
+ </Form>
87
+ );
88
+ }
89
+ render(<HookForm />);
90
+ const input = screen.getByTestId("i") as HTMLInputElement;
91
+ await user.type(input, "kim@studio");
92
+ expect(input.value).toBe("kim@studio");
93
+ });
94
+ });
@@ -17,7 +17,11 @@ export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
17
17
  <label
18
18
  ref={ref}
19
19
  className={cn(
20
- "flex flex-col gap-0.5 text-[length:var(--text-sm)] font-medium leading-snug text-foreground cursor-pointer select-none not-has-[[data-sh-ui-label-part]]:block",
20
+ // 기본 typography + 인터랙션만. display label 의 native(inline) 유지.
21
+ // sub-part(LabelTitle/Subtitle/Description/Caption) 가 자식에 있을 때만
22
+ // 자동으로 flex-col stack — 사용자가 가로 정렬(inline-flex items-center)
23
+ // 을 명시할 수 있도록 기본 display 를 강제하지 않는다.
24
+ "text-[length:var(--text-sm)] font-medium leading-snug text-foreground cursor-pointer select-none has-[[data-sh-ui-label-part]]:flex has-[[data-sh-ui-label-part]]:flex-col has-[[data-sh-ui-label-part]]:gap-0.5",
21
25
  // 필수 표시 — title 이 있으면 title 뒤, 없으면 label 뒤에 * 부착
22
26
  isRequired &&
23
27
  "has-[[data-sh-ui-label-part='title']]:[&>[data-sh-ui-label-part='title']]:after:content-['_*'] has-[[data-sh-ui-label-part='title']]:[&>[data-sh-ui-label-part='title']]:after:text-danger has-[[data-sh-ui-label-part='title']]:[&>[data-sh-ui-label-part='title']]:after:font-semibold not-has-[[data-sh-ui-label-part='title']]:after:content-['_*'] not-has-[[data-sh-ui-label-part='title']]:after:text-danger not-has-[[data-sh-ui-label-part='title']]:after:font-semibold",
@@ -1,7 +1,6 @@
1
+ /* 기본 — typography + 인터랙션만. display 는 label 의 native (inline) 유지.
2
+ 사용자가 `inline-flex items-center gap-2` 같이 가로 정렬을 명시할 수 있다. */
1
3
  .sh-ui-label {
2
- display: flex;
3
- flex-direction: column;
4
- gap: 0.125rem;
5
4
  font-size: var(--text-sm);
6
5
  font-weight: var(--weight-medium);
7
6
  line-height: 1.4;
@@ -10,8 +9,13 @@
10
9
  user-select: none;
11
10
  }
12
11
 
13
- .sh-ui-label:not(:has(.sh-ui-label__title, .sh-ui-label__subtitle, .sh-ui-label__description, .sh-ui-label__caption)) {
14
- display: block;
12
+ /* sub-part (Title/Subtitle/Description/Caption) 자식에 있을 때만 세로 stack.
13
+ 사용자가 `<Label><LabelTitle .../><LabelDescription .../></Label>` 형태로
14
+ 쓸 때 자동으로 정렬. */
15
+ .sh-ui-label:has(.sh-ui-label__title, .sh-ui-label__subtitle, .sh-ui-label__description, .sh-ui-label__caption) {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: 0.125rem;
15
19
  }
16
20
 
17
21
  /* ───── 텍스트 계층 ───── */
@@ -1,7 +1,6 @@
1
+ /* 기본 — typography + 인터랙션만. display 는 label 의 native (inline) 유지.
2
+ 사용자가 `inline-flex items-center gap-2` 같이 가로 정렬을 명시할 수 있다. */
1
3
  .label {
2
- display: flex;
3
- flex-direction: column;
4
- gap: 0.125rem;
5
4
  font-size: var(--text-sm);
6
5
  font-weight: var(--weight-medium);
7
6
  line-height: 1.4;
@@ -10,8 +9,11 @@
10
9
  user-select: none;
11
10
  }
12
11
 
13
- .label:not(:has(.label__title, .label__subtitle, .label__description, .label__caption)) {
14
- display: block;
12
+ /* sub-part (Title/Subtitle/Description/Caption) 자식에 있을 때만 세로 stack. */
13
+ .label:has(.label__title, .label__subtitle, .label__description, .label__caption) {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 0.125rem;
15
17
  }
16
18
 
17
19
  /* ───── 텍스트 계층 ───── */