@growgroup/visual-editor 0.1.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +52 -0
  3. package/README.md +143 -0
  4. package/dist/editor.css +2 -0
  5. package/package.json +94 -0
  6. package/src/components/DeckSlideRender.tsx +21 -0
  7. package/src/components/SaveNote.tsx +21 -0
  8. package/src/components/auth/AuthProvider.tsx +44 -0
  9. package/src/components/ui/accordion.tsx +57 -0
  10. package/src/components/ui/alert-dialog.tsx +141 -0
  11. package/src/components/ui/badge.tsx +36 -0
  12. package/src/components/ui/button.tsx +56 -0
  13. package/src/components/ui/card.tsx +76 -0
  14. package/src/components/ui/checkbox.tsx +30 -0
  15. package/src/components/ui/collapsible.tsx +11 -0
  16. package/src/components/ui/context-menu.tsx +200 -0
  17. package/src/components/ui/dialog.tsx +122 -0
  18. package/src/components/ui/dropdown-menu.tsx +201 -0
  19. package/src/components/ui/input.tsx +22 -0
  20. package/src/components/ui/label.tsx +26 -0
  21. package/src/components/ui/popover.tsx +33 -0
  22. package/src/components/ui/scroll-area.tsx +48 -0
  23. package/src/components/ui/scrubbable-label.tsx +95 -0
  24. package/src/components/ui/select.tsx +159 -0
  25. package/src/components/ui/separator.tsx +31 -0
  26. package/src/components/ui/slider.tsx +28 -0
  27. package/src/components/ui/switch.tsx +29 -0
  28. package/src/components/ui/tabs.tsx +55 -0
  29. package/src/components/ui/textarea.tsx +22 -0
  30. package/src/components/ui/tooltip.tsx +32 -0
  31. package/src/components/viewer/useDeck.ts +58 -0
  32. package/src/editor/EditorContext.tsx +632 -0
  33. package/src/editor/EditorToolbar.tsx +446 -0
  34. package/src/editor/FrontendVisualEditor.tsx +2595 -0
  35. package/src/editor/autosave.ts +34 -0
  36. package/src/editor/components/AiPromptPopover.tsx +378 -0
  37. package/src/editor/components/BreakpointGuides.tsx +323 -0
  38. package/src/editor/components/BreakpointSelector.tsx +180 -0
  39. package/src/editor/components/ComponentPropertyEditor.tsx +426 -0
  40. package/src/editor/components/CssEditorDialog.tsx +168 -0
  41. package/src/editor/components/EditorCanvas.tsx +987 -0
  42. package/src/editor/components/EditorContextMenu.tsx +515 -0
  43. package/src/editor/components/EditorFooter.tsx +81 -0
  44. package/src/editor/components/EditorHeader.tsx +524 -0
  45. package/src/editor/components/EditorLayerPanel.tsx +1197 -0
  46. package/src/editor/components/EditorPropertyPanel.tsx +2996 -0
  47. package/src/editor/components/EditorSidebar.tsx +226 -0
  48. package/src/editor/components/FigmaColorPicker.tsx +1577 -0
  49. package/src/editor/components/HtmlEditorDialog.tsx +71 -0
  50. package/src/editor/components/HtmlImportDialog.tsx +346 -0
  51. package/src/editor/components/JsEditorDialog.tsx +175 -0
  52. package/src/editor/components/LayerTreeItem.tsx +494 -0
  53. package/src/editor/components/MasterComponentEditor.tsx +846 -0
  54. package/src/editor/components/MediaLibraryDialog.tsx +447 -0
  55. package/src/editor/components/PageSettingsDialog.tsx +776 -0
  56. package/src/editor/components/VariablePicker.tsx +355 -0
  57. package/src/editor/components/VariablesDialog.tsx +564 -0
  58. package/src/editor/components/VariantEditor.tsx +687 -0
  59. package/src/editor/components/VariantMatrix.tsx +507 -0
  60. package/src/editor/components/component-panel/CategoryAccordion.tsx +139 -0
  61. package/src/editor/components/component-panel/ComponentGrid.tsx +51 -0
  62. package/src/editor/components/component-panel/ComponentItem.tsx +368 -0
  63. package/src/editor/components/component-panel/ComponentPanel.tsx +275 -0
  64. package/src/editor/components/component-panel/ComponentSearch.tsx +82 -0
  65. package/src/editor/components/component-panel/VariantList.tsx +260 -0
  66. package/src/editor/components/component-panel/index.ts +12 -0
  67. package/src/editor/components/index.ts +48 -0
  68. package/src/editor/components/multi-page/CanvasZoomControls.tsx +71 -0
  69. package/src/editor/components/multi-page/InfiniteCanvas.tsx +95 -0
  70. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +1404 -0
  71. package/src/editor/components/multi-page/PageFrameOverlay.tsx +76 -0
  72. package/src/editor/components/multi-page/PageLabel.tsx +34 -0
  73. package/src/editor/components/multi-page/PageLivePreview.tsx +242 -0
  74. package/src/editor/components/multi-page/PageThumbnail.tsx +59 -0
  75. package/src/editor/components/multi-page/index.ts +7 -0
  76. package/src/editor/components/ppt/PptChrome.tsx +2508 -0
  77. package/src/editor/components/ppt/PptComments.tsx +589 -0
  78. package/src/editor/components/ppt/PptDesignProposals.tsx +275 -0
  79. package/src/editor/components/ppt/PptFormatPane.tsx +320 -0
  80. package/src/editor/components/ppt/PptNotes.tsx +246 -0
  81. package/src/editor/components/property-panel/AlignmentPanel.tsx +110 -0
  82. package/src/editor/components/property-panel/AutoLayoutPanel.tsx +615 -0
  83. package/src/editor/components/property-panel/BorderSection.tsx +104 -0
  84. package/src/editor/components/property-panel/CompactNumberInput.tsx +237 -0
  85. package/src/editor/components/property-panel/CompactSizeInput.tsx +148 -0
  86. package/src/editor/components/property-panel/EffectSection.tsx +157 -0
  87. package/src/editor/components/property-panel/FillSection.tsx +69 -0
  88. package/src/editor/components/property-panel/GoogleFontPicker.tsx +192 -0
  89. package/src/editor/components/property-panel/ImageSection.tsx +206 -0
  90. package/src/editor/components/property-panel/ImgSrcSection.tsx +125 -0
  91. package/src/editor/components/property-panel/InstanceOverrideSection.tsx +413 -0
  92. package/src/editor/components/property-panel/LayoutSection.tsx +105 -0
  93. package/src/editor/components/property-panel/LinkSection.tsx +103 -0
  94. package/src/editor/components/property-panel/PropertyVariablePicker.tsx +311 -0
  95. package/src/editor/components/property-panel/ScalePanel.tsx +212 -0
  96. package/src/editor/components/property-panel/TypographySection.tsx +230 -0
  97. package/src/editor/components/property-panel/UnitAwareNumberInput.tsx +238 -0
  98. package/src/editor/components/property-panel/VariableAwareColorInput.tsx +477 -0
  99. package/src/editor/components/property-panel/VariableAwareInput.tsx +606 -0
  100. package/src/editor/components/property-panel/VariableAwareSizeInput.tsx +588 -0
  101. package/src/editor/components/property-panel/VariableAwareUnitInput.tsx +546 -0
  102. package/src/editor/components/property-panel/index.ts +24 -0
  103. package/src/editor/components/property-panel/unit-utils.ts +261 -0
  104. package/src/editor/components/shell/EditorTopBar.tsx +392 -0
  105. package/src/editor/components/shell/LeftPanel.tsx +47 -0
  106. package/src/editor/components/shell/PagesPanel.tsx +118 -0
  107. package/src/editor/components/variables-panel/ColorValueCell.tsx +165 -0
  108. package/src/editor/components/variables-panel/TextValueCell.tsx +116 -0
  109. package/src/editor/components/variables-panel/VariablesPanel.tsx +518 -0
  110. package/src/editor/components/variables-panel/index.ts +7 -0
  111. package/src/editor/constants.ts +468 -0
  112. package/src/editor/contexts/EditorArtboardContext.tsx +229 -0
  113. package/src/editor/contexts/EditorComponentsContext.tsx +1320 -0
  114. package/src/editor/contexts/EditorDocumentContext.tsx +113 -0
  115. package/src/editor/contexts/EditorHistoryContext.tsx +516 -0
  116. package/src/editor/contexts/EditorRefsContext.tsx +204 -0
  117. package/src/editor/contexts/EditorSelectionContext.tsx +54 -0
  118. package/src/editor/contexts/EditorToolContext.tsx +58 -0
  119. package/src/editor/contexts/EditorUIStateContext.tsx +122 -0
  120. package/src/editor/contexts/EditorVariablesContext.tsx +397 -0
  121. package/src/editor/contexts/EditorViewContext.tsx +101 -0
  122. package/src/editor/contexts/MultiPageCanvasContext.tsx +498 -0
  123. package/src/editor/contexts/index.ts +17 -0
  124. package/src/editor/editor-skin.css +122 -0
  125. package/src/editor/hooks/index.ts +38 -0
  126. package/src/editor/hooks/useAiReplace.ts +397 -0
  127. package/src/editor/hooks/useAltMeasure.ts +67 -0
  128. package/src/editor/hooks/useBrowserZoomPrevention.ts +121 -0
  129. package/src/editor/hooks/useCanvasControls.ts +1222 -0
  130. package/src/editor/hooks/useComponentEditMode.ts +658 -0
  131. package/src/editor/hooks/useComponentInstances.ts +515 -0
  132. package/src/editor/hooks/useContextMenuHandler.ts +157 -0
  133. package/src/editor/hooks/useCoordinateTransform.ts +207 -0
  134. package/src/editor/hooks/useDragResize.ts +1317 -0
  135. package/src/editor/hooks/useDrawingMode.ts +472 -0
  136. package/src/editor/hooks/useEditorColors.ts +144 -0
  137. package/src/editor/hooks/useEditorMessages.ts +108 -0
  138. package/src/editor/hooks/useElementActions.ts +1754 -0
  139. package/src/editor/hooks/useElementSelection.ts +1573 -0
  140. package/src/editor/hooks/useFocusManagement.ts +249 -0
  141. package/src/editor/hooks/useGoogleFonts.ts +305 -0
  142. package/src/editor/hooks/useIframeInitializer.ts +584 -0
  143. package/src/editor/hooks/useIframeSetup.ts +360 -0
  144. package/src/editor/hooks/useImageUpload.ts +270 -0
  145. package/src/editor/hooks/useInfiniteCanvas.ts +301 -0
  146. package/src/editor/hooks/useKeyboardShortcuts.ts +205 -0
  147. package/src/editor/hooks/useMarqueeSelection.ts +388 -0
  148. package/src/editor/hooks/useMediaLibrary.ts +129 -0
  149. package/src/editor/hooks/usePageSettingsManager.ts +739 -0
  150. package/src/editor/hooks/useResizablePanel.ts +163 -0
  151. package/src/editor/hooks/useRichPaste.ts +255 -0
  152. package/src/editor/hooks/useTouchGestures.ts +197 -0
  153. package/src/editor/index.ts +27 -0
  154. package/src/editor/types/page-settings.ts +513 -0
  155. package/src/editor/types.ts +382 -0
  156. package/src/editor/utils/align-elements.ts +169 -0
  157. package/src/editor/utils/api-json.ts +21 -0
  158. package/src/editor/utils/aspect-lock.ts +50 -0
  159. package/src/editor/utils/component-renderer.ts +702 -0
  160. package/src/editor/utils/component-sync.ts +845 -0
  161. package/src/editor/utils/crop-mode.ts +265 -0
  162. package/src/editor/utils/dom-utils.ts +2238 -0
  163. package/src/editor/utils/element-effects.ts +218 -0
  164. package/src/editor/utils/eyedropper.ts +35 -0
  165. package/src/editor/utils/figma-export.ts +560 -0
  166. package/src/editor/utils/figma-kiwi-decoder.ts +722 -0
  167. package/src/editor/utils/figma-kiwi-encoder.ts +583 -0
  168. package/src/editor/utils/figma-paste.ts +2262 -0
  169. package/src/editor/utils/flex-reorder.ts +231 -0
  170. package/src/editor/utils/flex-utils.ts +1230 -0
  171. package/src/editor/utils/geometry.ts +232 -0
  172. package/src/editor/utils/grid-layout.ts +210 -0
  173. package/src/editor/utils/html-utils.ts +470 -0
  174. package/src/editor/utils/index.ts +11 -0
  175. package/src/editor/utils/ink-style.ts +57 -0
  176. package/src/editor/utils/inline-format.ts +236 -0
  177. package/src/editor/utils/measure-distance.ts +120 -0
  178. package/src/editor/utils/override-apply.ts +526 -0
  179. package/src/editor/utils/override-detection.ts +613 -0
  180. package/src/editor/utils/paste-processors.ts +609 -0
  181. package/src/editor/utils/paste-sanitizer.ts +244 -0
  182. package/src/editor/utils/restore-flow.ts +188 -0
  183. package/src/editor/utils/shape-library.ts +171 -0
  184. package/src/editor/utils/slide-root.ts +29 -0
  185. package/src/editor/utils/smart-guides.ts +218 -0
  186. package/src/editor/utils/style-utils.ts +685 -0
  187. package/src/editor/utils/table-edit.ts +151 -0
  188. package/src/editor/utils/tailwind-mappings.ts +778 -0
  189. package/src/editor/utils/tailwind-utils.ts +804 -0
  190. package/src/editor/utils/text-highlight.ts +216 -0
  191. package/src/editor/utils/theme-to-variables.ts +341 -0
  192. package/src/editor/utils/variant-resolver.ts +456 -0
  193. package/src/editor/utils/viewport-utils.ts +295 -0
  194. package/src/hooks/useEditorHistory.ts +195 -0
  195. package/src/hooks/useEditorShortcuts.ts +260 -0
  196. package/src/index.ts +40 -0
  197. package/src/io.ts +134 -0
  198. package/src/lib/agent/slide-agent/types.ts +547 -0
  199. package/src/lib/agent/website-agent/types.ts +805 -0
  200. package/src/lib/api/auth-fetch.ts +58 -0
  201. package/src/lib/deck.ts +69 -0
  202. package/src/lib/export.ts +47 -0
  203. package/src/lib/firebase/config.ts +29 -0
  204. package/src/lib/firebase/css-variables.ts +378 -0
  205. package/src/lib/firebase/editor-components.ts +677 -0
  206. package/src/lib/firebase/storage.ts +469 -0
  207. package/src/lib/utils.ts +6 -0
  208. package/src/styles/editor.css +52 -0
  209. package/src/styles/skin.css +122 -0
  210. package/src/types/css-variables.ts +645 -0
  211. package/src/types/editor-components.ts +944 -0
  212. package/src/types/editor.ts +420 -0
  213. package/src/types/page-master.ts +197 -0
  214. package/src/types/page.ts +419 -0
  215. package/src/types/slide.ts +402 -0
  216. package/src/types/website-theme.ts +788 -0
  217. package/src/types/website.ts +374 -0
  218. package/src/vendor/firebase-firestore.ts +13 -0
  219. package/src/vendor/firebase-functions.ts +17 -0
@@ -0,0 +1,246 @@
1
+ "use client";
2
+
3
+ /**
4
+ * PowerPointのノート欄相当。スライドごとのトークスクリプト(発表原稿)を編集する。
5
+ *
6
+ * 保存先は public/presenter-script.json の segments[{page, text, note}]。
7
+ * これは発表者コンソール(presenter.html)がそのまま読む原稿ファイルなので、
8
+ * ここで書いた内容が本番のカンペになる。案件データ扱いで同期に消されない。
9
+ */
10
+
11
+ import { useCallback, useEffect, useRef, useState } from 'react';
12
+ import { ChevronDown, ChevronUp, Loader2, Sparkles, StickyNote } from 'lucide-react';
13
+ import { PPT_PALETTES, type PptTheme } from './PptChrome';
14
+ import { readApiJson } from '../../utils/api-json';
15
+
16
+ type Segment = { page: number; text?: string; note?: string };
17
+
18
+ export function PptNotes({ page, theme }: { page: number; theme: PptTheme }) {
19
+ const pal = PPT_PALETTES[theme];
20
+ const [open, setOpen] = useState(false);
21
+ const [segments, setSegments] = useState<Map<number, Segment>>(new Map());
22
+ const [loaded, setLoaded] = useState(false);
23
+ const [text, setText] = useState('');
24
+ const [note, setNote] = useState('');
25
+ const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
26
+ const dirtyRef = useRef<{ page: number; text: string; note: string } | null>(null);
27
+
28
+ // 原稿ファイルを読み込む(1回)
29
+ useEffect(() => {
30
+ let alive = true;
31
+ fetch('/__presenter-script')
32
+ .then((r) => r.json())
33
+ .then((data) => {
34
+ if (!alive) return;
35
+ const map = new Map<number, Segment>();
36
+ for (const seg of data.segments ?? []) map.set(seg.page, seg);
37
+ setSegments(map);
38
+ setLoaded(true);
39
+ })
40
+ .catch(() => setLoaded(true));
41
+ return () => {
42
+ alive = false;
43
+ };
44
+ }, []);
45
+
46
+ const flush = useCallback(async () => {
47
+ const d = dirtyRef.current;
48
+ if (!d) return;
49
+ dirtyRef.current = null;
50
+ try {
51
+ await fetch('/__presenter-script/segment', {
52
+ method: 'POST',
53
+ body: JSON.stringify(d),
54
+ });
55
+ } catch {
56
+ // devサーバー再起動中など。次の編集で再保存される
57
+ }
58
+ }, []);
59
+
60
+ // ページ切替: 編集中の内容を保存してから、切替先の原稿を出す
61
+ useEffect(() => {
62
+ void flush();
63
+ const seg = segments.get(page);
64
+ setText(seg?.text ?? '');
65
+ setNote(seg?.note ?? '');
66
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
+ }, [page, loaded]);
68
+
69
+ // アンマウント時も取りこぼさない
70
+ useEffect(() => () => { void flush(); }, [flush]);
71
+
72
+ const queueSave = (nextText: string, nextNote: string) => {
73
+ setSegments((m) => {
74
+ const copy = new Map(m);
75
+ copy.set(page, { page, text: nextText, note: nextNote });
76
+ return copy;
77
+ });
78
+ dirtyRef.current = { page, text: nextText, note: nextNote };
79
+ if (saveTimer.current) clearTimeout(saveTimer.current);
80
+ saveTimer.current = setTimeout(() => void flush(), 800);
81
+ };
82
+
83
+ const hasContent = Boolean(text.trim() || note.trim());
84
+
85
+ // ================= AIで原稿を生成 =================
86
+ const [genState, setGenState] = useState<{ running: boolean; message: string }>({
87
+ running: false,
88
+ message: '',
89
+ });
90
+ const [askMinutes, setAskMinutes] = useState(false);
91
+ const [minutes, setMinutes] = useState('30');
92
+
93
+ /** 生成完了後にファイルを読み直し、表示中ページの原稿を差し替える */
94
+ const reloadSegments = useCallback(async () => {
95
+ try {
96
+ const data = await (await fetch('/__presenter-script')).json();
97
+ const map = new Map<number, Segment>();
98
+ for (const seg of data.segments ?? []) map.set(seg.page, seg);
99
+ setSegments(map);
100
+ const seg = map.get(page);
101
+ // 入力途中の手元の内容を潰さないよう、フォーカスが無いときだけ反映
102
+ if (!dirtyRef.current) {
103
+ setText(seg?.text ?? '');
104
+ setNote(seg?.note ?? '');
105
+ }
106
+ } catch {
107
+ // devサーバー再起動中など。次の操作で読み直される
108
+ }
109
+ }, [page]);
110
+
111
+ const startGen = useCallback(
112
+ async (payload: { pages?: number[]; all?: boolean; minutes?: number }) => {
113
+ setGenState({ running: true, message: '開始中…' });
114
+ setOpen(true);
115
+ try {
116
+ const res = await fetch('/__script-gen', { method: 'POST', body: JSON.stringify(payload) });
117
+ const { jobId, error } = await readApiJson<{ jobId?: string; error?: string }>(res);
118
+ if (!jobId) throw new Error(error || '開始できませんでした');
119
+ // 完了までポーリング(生成は1バッチ30秒〜)
120
+ for (;;) {
121
+ await new Promise((r) => setTimeout(r, 1500));
122
+ const st = await readApiJson<{ state: string; message?: string; error?: string }>(
123
+ await fetch(`/__script-gen/status/${jobId}`),
124
+ );
125
+ setGenState({ running: st.state === 'running', message: st.message ?? '' });
126
+ if (st.state === 'done') {
127
+ await reloadSegments();
128
+ return;
129
+ }
130
+ if (st.state === 'error') throw new Error(st.error || '生成に失敗しました');
131
+ }
132
+ } catch (e) {
133
+ setGenState({ running: false, message: `失敗: ${String(e).slice(0, 120)}` });
134
+ }
135
+ },
136
+ [reloadSegments],
137
+ );
138
+
139
+ return (
140
+ <div className="shrink-0 border-t" style={{ borderColor: pal.border, backgroundColor: pal.rail }}>
141
+ {/* 折りたたみバー(実機の「ノートを入力」) */}
142
+ <div className="flex h-6 w-full items-center gap-1.5 px-3 text-[11px]">
143
+ <button
144
+ onClick={() => setOpen((v) => !v)}
145
+ className="flex min-w-0 flex-1 items-center gap-1.5 text-left"
146
+ style={{ color: hasContent ? pal.text : pal.sub }}
147
+ title="トークスクリプト(発表者コンソールの原稿)を編集"
148
+ >
149
+ <StickyNote className="h-3 w-3 shrink-0" style={{ color: hasContent ? '#498205' : pal.sub }} />
150
+ <span className="truncate">
151
+ {genState.message
152
+ ? genState.message
153
+ : hasContent
154
+ ? 'ノートあり — トークスクリプトを編集'
155
+ : 'ノートを入力(トークスクリプト)'}
156
+ </span>
157
+ </button>
158
+ {/* AI生成: このスライドだけ / 発表時間を決めて全スライド */}
159
+ {genState.running ? (
160
+ <span className="flex shrink-0 items-center gap-1" style={{ color: pal.sub }}>
161
+ <Loader2 className="h-3 w-3 animate-spin" />
162
+ 生成中…
163
+ </span>
164
+ ) : askMinutes ? (
165
+ <span className="flex shrink-0 items-center gap-1" style={{ color: pal.sub }}>
166
+ 発表時間
167
+ <input
168
+ type="number"
169
+ min={1}
170
+ max={600}
171
+ value={minutes}
172
+ autoFocus
173
+ onChange={(e) => setMinutes(e.target.value)}
174
+ onKeyDown={(e) => {
175
+ if (e.key === 'Enter') {
176
+ setAskMinutes(false);
177
+ void startGen({ all: true, minutes: Math.max(1, Number(minutes) || 30) });
178
+ }
179
+ if (e.key === 'Escape') setAskMinutes(false);
180
+ }}
181
+ className="h-5 w-[52px] rounded border px-1 text-right text-[11px] outline-none"
182
+ style={{ backgroundColor: pal.control, borderColor: pal.border, color: pal.text }}
183
+ />
184
+
185
+ <button
186
+ onClick={() => {
187
+ setAskMinutes(false);
188
+ void startGen({ all: true, minutes: Math.max(1, Number(minutes) || 30) });
189
+ }}
190
+ className="rounded border px-1.5 py-0.5"
191
+ style={{ borderColor: pal.border, color: pal.text }}
192
+ >
193
+ 生成
194
+ </button>
195
+ <button onClick={() => setAskMinutes(false)} className="px-1" style={{ color: pal.sub }}>
196
+
197
+ </button>
198
+ </span>
199
+ ) : (
200
+ <span className="flex shrink-0 items-center gap-1">
201
+ <button
202
+ onClick={() => void startGen({ pages: [page] })}
203
+ title="このスライドの原稿をAIで生成(既存の原稿は置き換わる。段取りメモは残る)"
204
+ className="flex items-center gap-1 rounded border px-1.5 py-0.5"
205
+ style={{ borderColor: pal.border, color: pal.text }}
206
+ >
207
+ <Sparkles className="h-3 w-3" />
208
+ このスライドを生成
209
+ </button>
210
+ <button
211
+ onClick={() => setAskMinutes(true)}
212
+ title="発表時間を決めて、全スライドの原稿をAIで一括生成(時間から文字数を配分)"
213
+ className="flex items-center gap-1 rounded border px-1.5 py-0.5"
214
+ style={{ borderColor: pal.border, color: pal.text }}
215
+ >
216
+ <Sparkles className="h-3 w-3" />
217
+ 全スライドを生成…
218
+ </button>
219
+ </span>
220
+ )}
221
+ <button onClick={() => setOpen((v) => !v)} className="shrink-0" style={{ color: pal.sub }}>
222
+ {open ? <ChevronDown className="h-3 w-3" /> : <ChevronUp className="h-3 w-3" />}
223
+ </button>
224
+ </div>
225
+
226
+ {open && (
227
+ <div className="flex gap-2 px-3 pb-2">
228
+ <textarea
229
+ value={text}
230
+ onChange={(e) => { setText(e.target.value); queueSave(e.target.value, note); }}
231
+ placeholder={`スライド ${page} で話すこと…(発表者コンソールにそのまま出ます)`}
232
+ className="h-[104px] flex-1 resize-none rounded border p-2 text-[12.5px] leading-relaxed outline-none"
233
+ style={{ backgroundColor: pal.control, borderColor: pal.border, color: pal.text }}
234
+ />
235
+ <textarea
236
+ value={note}
237
+ onChange={(e) => { setNote(e.target.value); queueSave(text, e.target.value); }}
238
+ placeholder="段取りメモ(任意)…"
239
+ className="h-[104px] w-[220px] resize-none rounded border p-2 text-[11.5px] leading-relaxed outline-none"
240
+ style={{ backgroundColor: pal.control, borderColor: pal.border, color: pal.sub }}
241
+ />
242
+ </div>
243
+ )}
244
+ </div>
245
+ );
246
+ }
@@ -0,0 +1,110 @@
1
+ import { Button } from '../../../components/ui/button';
2
+ import {
3
+ AlignHorizontalJustifyStart,
4
+ AlignHorizontalJustifyCenter,
5
+ AlignHorizontalJustifyEnd,
6
+ AlignVerticalJustifyStart,
7
+ AlignVerticalJustifyCenter,
8
+ AlignVerticalJustifyEnd,
9
+ AlignHorizontalSpaceAround,
10
+ AlignVerticalSpaceAround,
11
+ } from 'lucide-react';
12
+ import {
13
+ Tooltip,
14
+ TooltipContent,
15
+ TooltipProvider,
16
+ TooltipTrigger,
17
+ } from '../../../components/ui/tooltip';
18
+
19
+ export type AlignmentType =
20
+ | 'left'
21
+ | 'center-h'
22
+ | 'right'
23
+ | 'top'
24
+ | 'center-v'
25
+ | 'bottom'
26
+ | 'distribute-h'
27
+ | 'distribute-v';
28
+
29
+ interface AlignmentPanelProps {
30
+ onAlign: (type: AlignmentType) => void;
31
+ canDistribute: boolean;
32
+ }
33
+
34
+ export function AlignmentPanel({ onAlign, canDistribute }: AlignmentPanelProps) {
35
+ return (
36
+ <div className="flex items-center gap-0.5 mb-2">
37
+ <TooltipButton
38
+ icon={<AlignHorizontalJustifyStart className="h-3.5 w-3.5" />}
39
+ label="左揃え"
40
+ onClick={() => onAlign('left')}
41
+ />
42
+ <TooltipButton
43
+ icon={<AlignHorizontalJustifyCenter className="h-3.5 w-3.5" />}
44
+ label="水平方向中央揃え"
45
+ onClick={() => onAlign('center-h')}
46
+ />
47
+ <TooltipButton
48
+ icon={<AlignHorizontalJustifyEnd className="h-3.5 w-3.5" />}
49
+ label="右揃え"
50
+ onClick={() => onAlign('right')}
51
+ />
52
+
53
+ <div className="w-px h-3 bg-[#4a4a4a] mx-0.5" />
54
+
55
+ <TooltipButton
56
+ icon={<AlignVerticalJustifyStart className="h-3.5 w-3.5" />}
57
+ label="上揃え"
58
+ onClick={() => onAlign('top')}
59
+ />
60
+ <TooltipButton
61
+ icon={<AlignVerticalJustifyCenter className="h-3.5 w-3.5" />}
62
+ label="垂直方向中央揃え"
63
+ onClick={() => onAlign('center-v')}
64
+ />
65
+ <TooltipButton
66
+ icon={<AlignVerticalJustifyEnd className="h-3.5 w-3.5" />}
67
+ label="下揃え"
68
+ onClick={() => onAlign('bottom')}
69
+ />
70
+
71
+ <div className="w-px h-3 bg-[#4a4a4a] mx-0.5" />
72
+
73
+ <TooltipButton
74
+ icon={<AlignHorizontalSpaceAround className="h-3.5 w-3.5" />}
75
+ label="水平方向等間隔"
76
+ onClick={() => onAlign('distribute-h')}
77
+ disabled={!canDistribute}
78
+ />
79
+ <TooltipButton
80
+ icon={<AlignVerticalSpaceAround className="h-3.5 w-3.5" />}
81
+ label="垂直方向等間隔"
82
+ onClick={() => onAlign('distribute-v')}
83
+ disabled={!canDistribute}
84
+ />
85
+ </div>
86
+ );
87
+ }
88
+
89
+ function TooltipButton({ icon, label, onClick, disabled }: { icon: React.ReactNode, label: string, onClick: () => void, disabled?: boolean }) {
90
+ return (
91
+ <TooltipProvider delayDuration={300}>
92
+ <Tooltip>
93
+ <TooltipTrigger asChild>
94
+ <Button
95
+ variant="ghost"
96
+ size="icon"
97
+ className="h-6 w-6 rounded-sm text-gray-400 hover:text-white hover:bg-[#4a4a4a] disabled:opacity-30"
98
+ onClick={onClick}
99
+ disabled={disabled}
100
+ >
101
+ {icon}
102
+ </Button>
103
+ </TooltipTrigger>
104
+ <TooltipContent>
105
+ <p className="text-xs">{label}</p>
106
+ </TooltipContent>
107
+ </Tooltip>
108
+ </TooltipProvider>
109
+ );
110
+ }