@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,118 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Figma風UIの左パネル上段: プレビュー付きのページ切替。
5
+ *
6
+ * レイヤーツリーだけの左パネルでは「今どのページを触っているのか」「隣に何があるのか」が
7
+ * 分からず、ページを移るのに一覧まで戻る必要があった。
8
+ * PowerPoint風UIのサムネイルレール(PptThumbnails)と同じ考え方で、
9
+ * デッキ(useDeck)の中身を DeckSlideRender で縮小描画する。
10
+ *
11
+ * 並べ替え・複製・削除まではここに載せない(PowerPoint風UIのレールが持つ)。
12
+ * ここが担うのは「今のページの把握」と「ページの切替」だけ。
13
+ */
14
+
15
+ import { memo, useEffect, useLayoutEffect, useRef, useState } from 'react';
16
+ import { useDeck } from '../../../components/viewer/useDeck';
17
+ import { DeckSlideRender } from '../../../components/DeckSlideRender';
18
+ import { flushAutoSave } from '../../autosave';
19
+
20
+ /**
21
+ * スライド描画はメモ化する。パネル幅の変更や他ページの選択で親が再レンダーしても、
22
+ * (page, template, edited) が同じReactツリーを組み直さないため
23
+ */
24
+ const MemoSlideRender = memo(DeckSlideRender);
25
+
26
+ /** 行の内訳: 左右padding(8+8) + 番号列(16) + 間隔(6)。残りがサムネイルの実効幅 */
27
+ const ROW_CHROME_PX = 8 + 8 + 16 + 6;
28
+
29
+ export function PagesPanel({ page, height = '33vh' }: { page: number; height?: string }) {
30
+ const deck = useDeck();
31
+ const rootRef = useRef<HTMLDivElement>(null);
32
+ const currentRef = useRef<HTMLButtonElement>(null);
33
+ /**
34
+ * サムネイルの実効幅。左パネルは幅を変えられるので、
35
+ * 固定値で縮小すると幅を変えるたびに右と下へ隙間が出る。実測してから縮小率を決める
36
+ */
37
+ const [thumbWidth, setThumbWidth] = useState(0);
38
+
39
+ useLayoutEffect(() => {
40
+ const el = rootRef.current;
41
+ if (!el) return;
42
+ // プレビューはパネル幅いっぱいだと大きすぎる。上限を設けて余白はページ番号側に
43
+ const measure = () => setThumbWidth(Math.min(128, Math.max(0, el.clientWidth - ROW_CHROME_PX)));
44
+ measure();
45
+ const observer = new ResizeObserver(measure);
46
+ observer.observe(el);
47
+ return () => observer.disconnect();
48
+ }, []);
49
+
50
+ // 編集中のページが視界の外にあると「今どこか」が分からないので追う
51
+ useEffect(() => {
52
+ currentRef.current?.scrollIntoView({ block: 'nearest' });
53
+ }, [page, deck.slides.length]);
54
+
55
+ /** ページ切替。未保存があれば黙って保存してから移る(失敗したときだけ確認する) */
56
+ const goto = async (n: number) => {
57
+ if (n === page) return;
58
+ if (!(await flushAutoSave())) {
59
+ if (!window.confirm('保存に失敗しました。変更を破棄して移動しますか?')) return;
60
+ }
61
+ window.location.hash = `#/edit/${n}`;
62
+ };
63
+
64
+ return (
65
+ <div
66
+ ref={rootRef}
67
+ data-pages-panel
68
+ className="flex shrink-0 flex-col overflow-y-auto border-b border-[#444444] bg-[#2c2c2c]"
69
+ style={{ height }}
70
+ >
71
+ <div className="sticky top-0 z-10 flex items-center justify-between bg-[#2c2c2c] px-2 py-1.5 text-[11px] font-medium text-gray-400">
72
+ ページ
73
+ <span className="tabular-nums text-gray-500">
74
+ {page} / {deck.slides.length || '—'}
75
+ </span>
76
+ </div>
77
+ {deck.slides.map((s, i) => {
78
+ const n = i + 1;
79
+ const current = n === page;
80
+ return (
81
+ <button
82
+ key={s.id}
83
+ ref={current ? currentRef : undefined}
84
+ data-page-thumb={n}
85
+ onClick={() => void goto(n)}
86
+ className="flex w-full shrink-0 items-start gap-1.5 px-2 py-1 text-left"
87
+ title={s.title ? `${n}. ${s.title}` : `${n}枚目`}
88
+ >
89
+ <span
90
+ className="mt-0.5 w-4 shrink-0 text-right text-[10px] tabular-nums"
91
+ style={{ color: current ? '#4fb8ff' : '#6b7280', fontWeight: current ? 700 : 400 }}
92
+ >
93
+ {n}
94
+ </span>
95
+ <span
96
+ className="relative block aspect-video overflow-hidden rounded-[3px] bg-white"
97
+ // w-full だとパネル幅に比例して巨大化する。実測幅(上限128px)で固定
98
+ style={{
99
+ width: thumbWidth || undefined,
100
+ boxShadow: current ? '0 0 0 2px #0d99ff' : '0 0 0 1px #444444',
101
+ opacity: s.hidden ? 0.45 : 1,
102
+ }}
103
+ >
104
+ {thumbWidth > 0 && (
105
+ <span
106
+ className="pointer-events-none absolute left-0 top-0 origin-top-left"
107
+ style={{ width: 1920, height: 1080, transform: `scale(${thumbWidth / 1920})` }}
108
+ >
109
+ <MemoSlideRender page={n} template={s.template} edited={s.edited} />
110
+ </span>
111
+ )}
112
+ </span>
113
+ </button>
114
+ );
115
+ })}
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,165 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * カラー値セル
5
+ * インラインでカラーピッカーを表示
6
+ */
7
+
8
+ import { useState, useCallback, useRef, useEffect } from 'react';
9
+ import {
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from '../../../components/ui/popover';
14
+ import { Input } from '../../../components/ui/input';
15
+ import { cn } from '../../../lib/utils';
16
+
17
+ interface ColorValueCellProps {
18
+ value: string;
19
+ isEditing: boolean;
20
+ isInherited?: boolean;
21
+ onStartEdit: () => void;
22
+ onFinishEdit: () => void;
23
+ onValueChange: (value: string) => void;
24
+ }
25
+
26
+ // シンプルなカラーピッカー(後でFigmaColorPickerに置き換え可能)
27
+ function SimpleColorPicker({
28
+ color,
29
+ onChange,
30
+ }: {
31
+ color: string;
32
+ onChange: (color: string) => void;
33
+ }) {
34
+ const [inputValue, setInputValue] = useState(color);
35
+
36
+ useEffect(() => {
37
+ setInputValue(color);
38
+ }, [color]);
39
+
40
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
41
+ const value = e.target.value;
42
+ setInputValue(value);
43
+ if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
44
+ onChange(value);
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div className="p-2 space-y-2">
50
+ {/* カラーパレット */}
51
+ <input
52
+ type="color"
53
+ value={color.startsWith('#') ? color : '#000000'}
54
+ onChange={(e) => {
55
+ setInputValue(e.target.value);
56
+ onChange(e.target.value);
57
+ }}
58
+ className="w-full h-24 rounded cursor-pointer border border-[#444444]"
59
+ />
60
+
61
+ {/* HEX入力 */}
62
+ <Input
63
+ type="text"
64
+ value={inputValue}
65
+ onChange={handleInputChange}
66
+ placeholder="#000000"
67
+ className="h-7 text-xs font-mono bg-[#383838] border-[#444444] text-white"
68
+ />
69
+
70
+ {/* プリセットカラー */}
71
+ <div className="grid grid-cols-8 gap-1">
72
+ {[
73
+ '#ef4444', '#f97316', '#eab308', '#22c55e',
74
+ '#14b8a6', '#3b82f6', '#8b5cf6', '#ec4899',
75
+ '#1f2937', '#374151', '#6b7280', '#9ca3af',
76
+ '#d1d5db', '#e5e7eb', '#f3f4f6', '#ffffff',
77
+ ].map((preset) => (
78
+ <button
79
+ key={preset}
80
+ onClick={() => {
81
+ setInputValue(preset);
82
+ onChange(preset);
83
+ }}
84
+ className={cn(
85
+ 'w-5 h-5 rounded border border-[#444444] cursor-pointer hover:ring-2 hover:ring-[#0d99ff]',
86
+ color === preset && 'ring-2 ring-[#0d99ff]'
87
+ )}
88
+ style={{ backgroundColor: preset }}
89
+ />
90
+ ))}
91
+ </div>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ export function ColorValueCell({
97
+ value,
98
+ isEditing,
99
+ isInherited = false,
100
+ onStartEdit,
101
+ onFinishEdit,
102
+ onValueChange,
103
+ }: ColorValueCellProps) {
104
+ const [isOpen, setIsOpen] = useState(false);
105
+
106
+ const handleOpenChange = useCallback((open: boolean) => {
107
+ setIsOpen(open);
108
+ if (open) {
109
+ onStartEdit();
110
+ } else {
111
+ onFinishEdit();
112
+ }
113
+ }, [onStartEdit, onFinishEdit]);
114
+
115
+ return (
116
+ <div
117
+ className={cn(
118
+ 'flex items-center gap-2 h-7 group cursor-pointer',
119
+ isInherited && 'opacity-50'
120
+ )}
121
+ >
122
+ {/* カラースウォッチ */}
123
+ <Popover open={isOpen} onOpenChange={handleOpenChange}>
124
+ <PopoverTrigger asChild>
125
+ <button
126
+ className={cn(
127
+ 'w-5 h-5 rounded border border-[#444444] flex-shrink-0',
128
+ 'hover:ring-2 hover:ring-[#0d99ff] transition-shadow',
129
+ isOpen && 'ring-2 ring-[#0d99ff]'
130
+ )}
131
+ style={{ backgroundColor: value }}
132
+ />
133
+ </PopoverTrigger>
134
+ <PopoverContent
135
+ className="w-48 p-0 bg-[#2c2c2c] border-[#444444]"
136
+ align="start"
137
+ side="bottom"
138
+ >
139
+ <SimpleColorPicker
140
+ color={value}
141
+ onChange={onValueChange}
142
+ />
143
+ </PopoverContent>
144
+ </Popover>
145
+
146
+ {/* HEX値 */}
147
+ <span
148
+ className={cn(
149
+ 'font-mono text-xs truncate',
150
+ isInherited ? 'text-gray-500 italic' : 'text-gray-300'
151
+ )}
152
+ onClick={() => handleOpenChange(true)}
153
+ >
154
+ {value}
155
+ </span>
156
+
157
+ {/* 継承インジケーター */}
158
+ {isInherited && (
159
+ <span className="text-[10px] text-gray-600 ml-auto">継承</span>
160
+ )}
161
+ </div>
162
+ );
163
+ }
164
+
165
+ export default ColorValueCell;
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * テキスト値セル
5
+ * インラインで編集可能なテキストセル
6
+ */
7
+
8
+ import { useState, useCallback, useRef, useEffect } from 'react';
9
+ import { Input } from '../../../components/ui/input';
10
+ import { cn } from '../../../lib/utils';
11
+
12
+ interface TextValueCellProps {
13
+ value: string;
14
+ isEditing: boolean;
15
+ isInherited?: boolean;
16
+ onStartEdit: () => void;
17
+ onFinishEdit: () => void;
18
+ onValueChange: (value: string) => void;
19
+ className?: string;
20
+ prefix?: React.ReactNode;
21
+ }
22
+
23
+ export function TextValueCell({
24
+ value,
25
+ isEditing,
26
+ isInherited = false,
27
+ onStartEdit,
28
+ onFinishEdit,
29
+ onValueChange,
30
+ className,
31
+ prefix,
32
+ }: TextValueCellProps) {
33
+ const [inputValue, setInputValue] = useState(value);
34
+ const inputRef = useRef<HTMLInputElement>(null);
35
+
36
+ // 外部の値が変わったら内部の値も更新
37
+ useEffect(() => {
38
+ if (!isEditing) {
39
+ setInputValue(value);
40
+ }
41
+ }, [value, isEditing]);
42
+
43
+ // 編集開始時にフォーカス
44
+ useEffect(() => {
45
+ if (isEditing && inputRef.current) {
46
+ inputRef.current.focus();
47
+ inputRef.current.select();
48
+ }
49
+ }, [isEditing]);
50
+
51
+ const handleKeyDown = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
52
+ if (e.key === 'Enter') {
53
+ onValueChange(inputValue);
54
+ onFinishEdit();
55
+ } else if (e.key === 'Escape') {
56
+ setInputValue(value);
57
+ onFinishEdit();
58
+ }
59
+ }, [inputValue, value, onValueChange, onFinishEdit]);
60
+
61
+ const handleBlur = useCallback(() => {
62
+ if (inputValue !== value) {
63
+ onValueChange(inputValue);
64
+ }
65
+ onFinishEdit();
66
+ }, [inputValue, value, onValueChange, onFinishEdit]);
67
+
68
+ if (isEditing) {
69
+ return (
70
+ <div className="flex items-center gap-2 h-7">
71
+ {prefix}
72
+ <Input
73
+ ref={inputRef}
74
+ type="text"
75
+ value={inputValue}
76
+ onChange={(e) => setInputValue(e.target.value)}
77
+ onKeyDown={handleKeyDown}
78
+ onBlur={handleBlur}
79
+ className={cn(
80
+ 'h-7 text-xs bg-[#383838] border-[#444444] text-white flex-1',
81
+ className
82
+ )}
83
+ />
84
+ </div>
85
+ );
86
+ }
87
+
88
+ return (
89
+ <div
90
+ className={cn(
91
+ 'flex items-center gap-2 h-7 cursor-pointer group',
92
+ 'hover:bg-[#444444]/50 rounded px-1 -mx-1',
93
+ isInherited && 'opacity-50'
94
+ )}
95
+ onClick={onStartEdit}
96
+ >
97
+ {prefix}
98
+ <span
99
+ className={cn(
100
+ 'truncate text-xs',
101
+ isInherited ? 'text-gray-500 italic' : 'text-gray-300',
102
+ className
103
+ )}
104
+ >
105
+ {value || <span className="text-gray-600">空</span>}
106
+ </span>
107
+
108
+ {/* 継承インジケーター */}
109
+ {isInherited && (
110
+ <span className="text-[10px] text-gray-600 ml-auto">継承</span>
111
+ )}
112
+ </div>
113
+ );
114
+ }
115
+
116
+ export default TextValueCell;