import React, { useState } from 'react'; import { Slot } from 'radix-ui'; import { Dialog, Text, Badge } from '@preply/ds-web-lib'; import styled from 'styled-components'; import { getTokenVar as t } from '@preply/ds-web-core'; import { spacing, color, radius } from '@preply/ds-core'; type FontFamily = 'Platform' | 'Figtree' | 'Noto Sans'; const FONT_FAMILY_FULL: Record = { Platform: "'Platform', 'Platform-fallback', 'Platform-fallback-android', 'Noto Sans', 'NotoSans-fallback', 'NotoSans-fallback-android', sans-serif", Figtree: "'Figtree', 'Figtree-fallback', 'Figtree-fallback-android', 'Noto Sans', 'NotoSans-fallback', 'NotoSans-fallback-android', sans-serif", 'Noto Sans': "'Noto Sans', 'NotoSans-fallback', 'NotoSans-fallback-android', sans-serif", }; type UnicodeRange = { name: string; start: number; end: number; }; const UNICODE_RANGES: UnicodeRange[] = [ { name: 'Basic Latin', start: 0x0020, end: 0x007f }, { name: 'Latin-1 Supplement', start: 0x00a0, end: 0x00ff }, { name: 'Latin Extended-A', start: 0x0100, end: 0x017f }, { name: 'Latin Extended-B', start: 0x0180, end: 0x024f }, { name: 'Spacing Modifier Letters', start: 0x02b0, end: 0x02ff }, { name: 'Cyrillic', start: 0x0400, end: 0x04ff }, { name: 'Arabic', start: 0x0600, end: 0x06ff }, { name: 'Thai', start: 0x0e00, end: 0x0e7f }, { name: 'Latin Extended Additional', start: 0x1e00, end: 0x1eff }, { name: 'General Punctuation', start: 0x2000, end: 0x206f }, { name: 'Currency Symbols', start: 0x20a0, end: 0x20cf }, ]; function generateCharacters(range: UnicodeRange): { char: string; codePoint: number }[] { const characters: { char: string; codePoint: number }[] = []; for (let codePoint = range.start; codePoint <= range.end; codePoint++) { const char = String.fromCodePoint(codePoint); // Skip control characters and non-printable characters if (codePoint < 0x0020 || (codePoint >= 0x007f && codePoint < 0x00a0)) { continue; } characters.push({ char, codePoint }); } return characters; } function formatCodePoint(codePoint: number): string { return `U+${codePoint.toString(16).toUpperCase().padStart(4, '0')}`; } const ranges = UNICODE_RANGES.map(range => ({ ...range, characters: generateCharacters(range), })); const Container = styled.div` display: flex; flex-direction: column; gap: ${t(spacing[24])}; max-height: 60vh; overflow-y: auto; `; const RangeSection = styled.div` display: flex; flex-direction: column; gap: ${t(spacing[8])}; `; const RangeHeader = styled.div` display: flex; align-items: center; gap: ${t(spacing[8])}; position: sticky; top: 0; background: ${t(color.background.primary)}; padding: ${t(spacing[8])} 0; `; const CharacterGrid = styled.div` display: grid; grid-template-columns: repeat(auto-fill, minmax(48px, 1fr)); gap: ${t(spacing[4])}; `; const CharacterCell = styled.div<{ $fontFamily: string }>` display: flex; flex-direction: column; align-items: center; justify-content: center; gap: ${t(spacing[4])}; padding: ${t(spacing[8])}; border: 1px solid ${t(color.border.tertiary)}; border-radius: ${t(radius[4])}; `; const Character = styled.span<{ $fontFamily: string }>` font-family: ${props => FONT_FAMILY_FULL[props.$fontFamily]} !important; font-size: 20px; min-height: 24px; `; const CodePoint = styled.span` font-size: 9px; color: ${t(color.text.tertiary)}; font-family: monospace; `; type Props = { fontFamily: FontFamily; children: React.ReactNode; }; export function UnicodeTableDialog({ fontFamily, children }: Props) { const [open, setOpen] = useState(false); return ( <> setOpen(true)}>{children} setOpen(false)} title={`Unicode Table - ${fontFamily}`} description="" size="lg" > {ranges.map(range => ( {range.name} {formatCodePoint(range.start)} – {formatCodePoint(range.end)} {range.characters.map(({ char, codePoint }) => ( {char} {formatCodePoint(codePoint)} ))} ))} ); }