Created
April 6, 2026 12:05
-
-
Save armand1m/5f8312d4418f1fa1f7ae6d5a0364d912 to your computer and use it in GitHub Desktop.
Smart quotes but using simple quotes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * typography.tsx | |
| * | |
| * Smart quotes wrapper for Next.js SSR. | |
| * Converts straight quotes to curly quotes at render time (server-side), | |
| * avoiding hydration mismatches and client-side flickering. | |
| * | |
| * Usage: | |
| * <Text>He said "hello" and it's fine.</Text> | |
| * <Text as="h1">The "Best" Title</Text> | |
| * | |
| * Safe to use with dangerouslySetInnerHTML because input is | |
| * always a hardcoded string — never user-generated content. | |
| */ | |
| import React from "react"; | |
| // --------------------------------------------------------------------------- | |
| // Smart quotes transformer | |
| // --------------------------------------------------------------------------- | |
| function smartQuotes(text: string): string { | |
| return ( | |
| text | |
| // Double quotes: "word" → "word" | |
| .replace(/"([^"]*)"/g, "\u201C$1\u201D") | |
| // Single quoted words: 'word' → 'word' | |
| .replace(/'([^']*)'/g, "\u2018$1\u2019") | |
| // Apostrophes in contractions and possessives: it's, don't, user's | |
| .replace(/(\w)'(\w)/g, "$1\u2019$2") | |
| ); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Component | |
| // --------------------------------------------------------------------------- | |
| type TextTag = "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "span" | "li" | "blockquote" | "label"; | |
| type TextProps<T extends TextTag = "p"> = { | |
| as?: T; | |
| children: string; | |
| className?: string; | |
| } & Omit<React.ComponentPropsWithoutRef<T>, "children" | "as" | "className" | "dangerouslySetInnerHTML">; | |
| export function Text<T extends TextTag = "p">({ | |
| as, | |
| children, | |
| className, | |
| ...rest | |
| }: TextProps<T>) { | |
| const Tag = (as ?? "p") as TextTag; | |
| return ( | |
| <Tag | |
| className={className} | |
| dangerouslySetInnerHTML={{ __html: smartQuotes(children) }} | |
| {...(rest as object)} | |
| /> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment