返回 oh-my-ppt
TextInspector.tsx
1 import { AlignCenter, AlignJustify, AlignLeft, AlignRight, Type } from 'lucide-react'
2 import { RichTextBox } from '../../ui/RichTextBox'
3 import { ToggleGroup, ToggleGroupItem } from '../../ui/ToggleGroup'
4 import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/Tooltip'
5 import { InspectorSection } from './InspectorSection'
6 import type { ElementEditorProps } from './types'
7 import { useT } from '@renderer/i18n'
8
9 const TEXT_ALIGN_OPTIONS = [
10 { value: 'left', icon: AlignLeft },
11 { value: 'center', icon: AlignCenter },
12 { value: 'right', icon: AlignRight },
13 { value: 'justify', icon: AlignJustify }
14 ] as const
15
16 function getPreviewScale(fontSize: string): number | undefined {
17 const parsed = Number(String(fontSize || '').replace(/px$/i, ''))
18 if (!Number.isFinite(parsed) || parsed <= 36) return undefined
19 return 1 / 3
20 }
21
22 export function TextInspector({ draft, onDraftChange }: ElementEditorProps): React.JSX.Element {
23 const t = useT()
24 const textAlign = draft.textAlign || 'left'
25 const previewScale = getPreviewScale(draft.fontSize)
26 const getAlignLabel = (value: (typeof TEXT_ALIGN_OPTIONS)[number]['value']): string => {
27 switch (value) {
28 case 'center':
29 return t('sessionDetail.alignCenter')
30 case 'right':
31 return t('sessionDetail.alignRight')
32 case 'justify':
33 return t('sessionDetail.alignJustify')
34 default:
35 return t('sessionDetail.alignLeft')
36 }
37 }
38
39 return (
40 <>
41 <InspectorSection
42 title={t('sessionDetail.textContent')}
43 icon={<Type className="h-3.5 w-3.5 text-[#7a875f]" />}
44 >
45 <RichTextBox
46 value={draft.html}
47 fallbackText={draft.text}
48 defaultColor={draft.color}
49 defaultFontSize={draft.fontSize}
50 previewScale={previewScale}
51 onChange={(value) => onDraftChange({ ...draft, html: value.html, text: value.text })}
52 onCommit={(value) =>
53 onDraftChange(
54 { ...draft, html: value.html, text: value.text },
55 { commit: true, fields: ['html'] }
56 )
57 }
58 />
59 <div className="mt-3 space-y-1.5">
60 <div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-[#8a806b]">
61 {t('sessionDetail.textAlign')}
62 </div>
63 <ToggleGroup
64 type="single"
65 value={textAlign}
66 onValueChange={(value) => {
67 if (!value) return
68 onDraftChange({ ...draft, textAlign: value }, { commit: true, fields: ['textAlign'] })
69 }}
70 aria-label={t('sessionDetail.textAlign')}
71 className="inline-flex overflow-hidden rounded-[9px] border border-[#d9cfbd]/72 bg-[#fffaf1]/90 p-0.5 shadow-[inset_0_1px_2px_rgba(77,63,46,0.06)]"
72 >
73 {TEXT_ALIGN_OPTIONS.map(({ value, icon: Icon }) => {
74 const label = getAlignLabel(value)
75 return (
76 <Tooltip key={value}>
77 <TooltipTrigger asChild>
78 <ToggleGroupItem
79 value={value}
80 aria-label={label}
81 title={label}
82 className="rounded-[7px]"
83 >
84 <Icon className="h-3.5 w-3.5" />
85 </ToggleGroupItem>
86 </TooltipTrigger>
87 <TooltipContent>{label}</TooltipContent>
88 </Tooltip>
89 )
90 })}
91 </ToggleGroup>
92 </div>
93 </InspectorSection>
94 </>
95 )
96 }
97
97 lines Plain Text