feat : adding dialog to choose language

This commit is contained in:
2026-09-15 18:31:41 +07:00 Unverified
parent 0f48989e9c
commit d345b72b26
7 changed files with 182 additions and 6 deletions
@@ -0,0 +1,53 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
ExportLanguageOption,
type ExportLanguage,
} from "@/components/public/export-language";
export function ExportLanguageDialog({
open,
onOpenChange,
onSelect,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
onSelect: (language: ExportLanguage) => void;
}) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>เลือกภาษาในภาพ</DialogTitle>
<DialogDescription>What language do you want?</DialogDescription>
</DialogHeader>
<div className="flex gap-2">
<Button
type="button"
variant="secondary"
className="flex-1"
onClick={() => onSelect("th")}
>
<ExportLanguageOption language="th" />
</Button>
<Button
type="button"
variant="secondary"
className="flex-1"
onClick={() => onSelect("en")}
>
<ExportLanguageOption language="en" />
</Button>
</div>
</DialogContent>
</Dialog>
);
}
+76 -3
View File
@@ -6,6 +6,11 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export type ExportLanguage = "th" | "en";
const LANGUAGE_OPTIONS = {
th: { label: "ไทย" },
en: { label: "English" },
} satisfies Record<ExportLanguage, { label: string }>;
const STORAGE_KEY = "buzz-sheet:export-language";
const LANGUAGE_EVENT = "buzz-sheet:export-language-change";
let memoryLanguage: ExportLanguage = "th";
@@ -21,7 +26,7 @@ function storedLanguage(): ExportLanguage {
}
export function useExportLanguage() {
const language = useSyncExternalStore(
const language = useSyncExternalStore<ExportLanguage>(
(onStoreChange) => {
window.addEventListener(LANGUAGE_EVENT, onStoreChange);
window.addEventListener("storage", onStoreChange);
@@ -69,17 +74,85 @@ export function ExportLanguageToggle({
<ToggleGroupItem
value="th"
>
ไทย
<ExportLanguageOption language="th" />
</ToggleGroupItem>
<ToggleGroupItem
value="en"
>
English
<ExportLanguageOption language="en" />
</ToggleGroupItem>
</ToggleGroup>
);
}
export function ExportLanguageOption({
language,
}: {
language: ExportLanguage;
}) {
const option = LANGUAGE_OPTIONS[language];
return (
<>
<LanguageFlag language={language} />
{option.label}
</>
);
}
function LanguageFlag({ language }: { language: ExportLanguage }) {
if (language === "th") {
return (
<svg
data-icon="inline-start"
data-language-flag="th"
aria-hidden="true"
viewBox="0 0 9 6"
width="24"
height="16"
>
<rect width="9" height="6" fill="#a51931" />
<rect y="1" width="9" height="4" fill="#f4f5f8" />
<rect y="2" width="9" height="2" fill="#2d2a4a" />
</svg>
);
}
return (
<svg
data-icon="inline-start"
data-language-flag="en"
aria-hidden="true"
viewBox="0 0 19 10"
width="24"
height="16"
>
<rect width="19" height="10" fill="#fff" />
{Array.from({ length: 7 }, (_, index) => (
<rect
key={index}
y={index * 20 / 13}
width="19"
height={10 / 13}
fill="#b22234"
/>
))}
<rect width="7.6" height={70 / 13} fill="#3c3b6e" />
{Array.from({ length: 9 }, (_, row) =>
Array.from({ length: row % 2 === 0 ? 6 : 5 }, (_, column) => (
<circle
key={`${row}-${column}`}
cx={0.65 + column * 1.25 + (row % 2 === 0 ? 0 : 0.625)}
cy={0.35 + row * 0.58}
r="0.12"
fill="#fff"
/>
)),
)}
</svg>
);
}
export function ExportText({ th, en }: { th: string; en: string }) {
return (
<>
+24 -1
View File
@@ -6,6 +6,7 @@ import { DownloadIcon } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { ExportLanguageDialog } from "@/components/public/export-language-dialog";
import {
ExportLanguageToggle,
useExportLanguage,
@@ -53,6 +54,7 @@ export function GuideImageExport({
buttonLabel = "ส่งออกภาพ",
language: controlledLanguage,
showLanguageSelector = true,
promptForLanguage = false,
localizeFileName = true,
}: {
children: ReactNode;
@@ -66,6 +68,7 @@ export function GuideImageExport({
buttonLabel?: string;
language?: ExportLanguage;
showLanguageSelector?: boolean;
promptForLanguage?: boolean;
localizeFileName?: boolean;
}) {
const captureRef = useRef<HTMLDivElement>(null);
@@ -75,6 +78,16 @@ export function GuideImageExport({
const capturing = captureMode !== null;
const [storedLanguage, setStoredLanguage] = useExportLanguage();
const language = controlledLanguage ?? storedLanguage;
const [pendingCaptureMode, setPendingCaptureMode] = useState<
"with-notes" | "without-notes" | null
>(null);
function selectLanguage(nextLanguage: ExportLanguage) {
if (!pendingCaptureMode) return;
setStoredLanguage(nextLanguage);
setCaptureMode(pendingCaptureMode);
setPendingCaptureMode(null);
}
useEffect(() => {
if (!capturing) return;
@@ -153,7 +166,10 @@ export function GuideImageExport({
variant="outline"
size="sm"
disabled={capturing || disabled}
onClick={() => setCaptureMode(mode)}
onClick={() => {
if (promptForLanguage) setPendingCaptureMode(mode);
else setCaptureMode(mode);
}}
>
{captureMode === mode ? (
<Spinner data-icon="inline-start" />
@@ -168,6 +184,13 @@ export function GuideImageExport({
</Button>
))}
</div>
<ExportLanguageDialog
open={pendingCaptureMode !== null}
onOpenChange={(open) => {
if (!open) setPendingCaptureMode(null);
}}
onSelect={selectLanguage}
/>
{capturing
? createPortal(
<div
+2
View File
@@ -1174,6 +1174,8 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
width={1500}
height={1500}
captureClassName="p-0"
showLanguageSelector={false}
promptForLanguage
>
<ConstellationImage
guide={guide}
+8 -1
View File
@@ -469,7 +469,14 @@ export function TeamSearch({ groups }: { groups: PublicGuide["teamGroups"] }) {
key={team.id}
team={team}
action={
<GuideImageExport fileName={`${team.title}.png`} width={1080} captureClassName="p-0" allowWithoutNotes={false}>
<GuideImageExport
fileName={`${team.title}.png`}
width={1080}
captureClassName="p-0"
allowWithoutNotes={false}
showLanguageSelector={false}
promptForLanguage
>
<TeamExportImage team={team} />
</GuideImageExport>
}