From 3e5ec58758676c637345d02190c1de2f5b7e2470 Mon Sep 17 00:00:00 2001 From: Alex Micharski Date: Wed, 18 Mar 2026 22:37:00 -0400 Subject: [PATCH] added custom styles --- next.config.ts | 19 + package.json | 2 +- src/app/globals.scss | 1 - src/app/page.tsx | 228 ++++---- src/app/styles/_base.scss | 1 + src/app/styles/_status.scss | 4 +- src/app/styles/_title-bar.scss | 11 +- .../DocumentEditor.module.scss} | 48 +- .../DocumentEditor/DocumentEditor.tsx | 5 +- .../DocumentEditor/DocumentSurface.tsx | 31 + src/components/DocumentEditor/index.ts | 2 + src/components/Ribbon/FileTabPanel.tsx | 90 +++ src/components/Ribbon/FontToolbar.tsx | 264 +++++++++ src/components/Ribbon/HomeTabPanel.tsx | 331 ----------- src/components/Ribbon/NewStyleTabPanel.tsx | 103 ++++ src/components/Ribbon/ParagraphToolbar.tsx | 136 +++++ src/components/Ribbon/Ribbon.module.scss | 498 ++++++++++++++-- src/components/Ribbon/Ribbon.tsx | 531 ++++++++++++++++-- src/components/Ribbon/RibbonChunk.tsx | 24 +- src/components/Ribbon/RibbonControls.tsx | 103 +--- .../Ribbon/RibbonSecondaryPanels.tsx | 8 +- src/components/Ribbon/StyleTabPanel.tsx | 77 +++ src/components/Ribbon/ribbonConfig.ts | 2 +- src/components/Ribbon/types.ts | 4 + src/components/TitleBar/TitleBar.tsx | 42 ++ .../WordStatusBar/WordStatusBar.tsx | 18 - src/components/WordTitleBar/WordTitleBar.tsx | 90 --- 27 files changed, 1925 insertions(+), 748 deletions(-) rename src/{app/styles/_document.scss => components/DocumentEditor/DocumentEditor.module.scss} (69%) create mode 100644 src/components/DocumentEditor/DocumentSurface.tsx create mode 100644 src/components/DocumentEditor/index.ts create mode 100644 src/components/Ribbon/FileTabPanel.tsx create mode 100644 src/components/Ribbon/FontToolbar.tsx delete mode 100644 src/components/Ribbon/HomeTabPanel.tsx create mode 100644 src/components/Ribbon/NewStyleTabPanel.tsx create mode 100644 src/components/Ribbon/ParagraphToolbar.tsx create mode 100644 src/components/Ribbon/StyleTabPanel.tsx create mode 100644 src/components/TitleBar/TitleBar.tsx delete mode 100644 src/components/WordStatusBar/WordStatusBar.tsx delete mode 100644 src/components/WordTitleBar/WordTitleBar.tsx diff --git a/next.config.ts b/next.config.ts index 6fc7e73..a8e2433 100644 --- a/next.config.ts +++ b/next.config.ts @@ -7,6 +7,25 @@ const nextConfig: NextConfig = { }, trailingSlash: true, sassOptions: { + // Silence Sass deprecation warnings emitted by dependencies in node_modules. + quietDeps: true, + // Suppress known upstream/toolchain deprecations until Carbon/Next update internals. + silenceDeprecations: ['mixed-decls', 'legacy-js-api'], + }, + webpack: (config) => { + config.ignoreWarnings = [ + ...(config.ignoreWarnings ?? []), + { + // Suppress Dart Sass deprecation noise from current Next/Sass integration. + message: /Deprecation.*legacy JS API is deprecated/i, + }, + { + // Suppress upstream Carbon mixed declarations deprecation output. + message: /Deprecation Warning.*mixed-decls/i, + }, + ]; + + return config; }, } diff --git a/package.json b/package.json index 0389a17..ade0140 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-type", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/globals.scss b/src/app/globals.scss index 67ad3ee..a09065b 100644 --- a/src/app/globals.scss +++ b/src/app/globals.scss @@ -1,7 +1,6 @@ @use "@carbon/react"; @use "./styles/base"; @use "./styles/title-bar"; -@use "./styles/document"; @use "./styles/status"; @use "./styles/mobile"; diff --git a/src/app/page.tsx b/src/app/page.tsx index ee10d7b..fcdba35 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,10 +3,9 @@ import React, { useState, useRef, useCallback, useEffect } from 'react'; import { asBlob } from 'html-docx-js/dist/html-docx'; import Ribbon from '@/components/Ribbon'; -import DocumentEditor from '@/components/DocumentEditor/DocumentEditor'; -import WordTitleBar from '@/components/WordTitleBar/WordTitleBar'; -import WordStatusBar from '@/components/WordStatusBar/WordStatusBar'; -import { DocumentTextStyle, getStylePresets } from '@/components/Ribbon/ribbonConfig'; +import DocumentEditor, { DocumentSurface } from '@/components/DocumentEditor'; +import TitleBar from '@/components/TitleBar/TitleBar'; +import { DocumentTextStyle, getStylePresets, StylePreset } from '@/components/Ribbon/ribbonConfig'; type TextAlignment = 'left' | 'center' | 'right' | 'justify'; type CaseMode = 'sentence' | 'lowercase' | 'uppercase' | 'capitalize' | 'toggle'; @@ -204,6 +203,74 @@ export default function WordProcessor() { if (!el) return; el.focus(); + const applyStylePresetToSelection = (preset: StylePreset) => { + const applyTextStyle = (block: HTMLElement, textStyle: DocumentTextStyle) => { + block.style.fontFamily = textStyle.fontFamily; + block.style.fontSize = `${textStyle.fontSizePt}pt`; + block.style.fontWeight = textStyle.fontWeight; + block.style.fontStyle = textStyle.fontStyle ?? 'normal'; + block.style.color = textStyle.color; + }; + + const getBlockAncestor = (node: Node): HTMLElement | null => { + let current: Node | null = node; + while (current && current !== el) { + if ( + current.nodeType === Node.ELEMENT_NODE && + ['P', 'H1', 'H2', 'H3'].includes((current as Element).tagName) + ) { + return current as HTMLElement; + } + current = current.parentNode; + } + return null; + }; + + const selection = window.getSelection(); + if (!selection?.rangeCount) { + return; + } + + const range = selection.getRangeAt(0); + const blocks = new Set(); + const startBlock = getBlockAncestor(range.startContainer); + const endBlock = getBlockAncestor(range.endContainer); + if (startBlock) blocks.add(startBlock); + if (endBlock) blocks.add(endBlock); + + const ancestor = range.commonAncestorContainer; + const walkerRoot = + ancestor.nodeType === Node.ELEMENT_NODE + ? (ancestor as Element) + : (ancestor.parentElement ?? el); + const walker = document.createTreeWalker(walkerRoot, NodeFilter.SHOW_ELEMENT, { + acceptNode(node) { + const tag = (node as Element).tagName; + return ['P', 'H1', 'H2', 'H3'].includes(tag) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_SKIP; + }, + }); + + while (walker.nextNode()) { + const node = walker.currentNode as HTMLElement; + if (range.intersectsNode(node)) blocks.add(node); + } + + blocks.forEach((block) => applyTextStyle(block, preset.textStyle)); + }; + + if (command === 'applyStylePreset' && value) { + try { + const preset = JSON.parse(value) as StylePreset; + document.execCommand('formatBlock', false, preset.val); + applyStylePresetToSelection(preset); + } catch { + // Ignore malformed style payloads and keep editor stable. + } + return; + } + if (command === 'changeCase') { const mode = value as CaseMode | undefined; if (!mode) return; @@ -249,58 +316,7 @@ export default function WordProcessor() { if (command === 'formatBlock' && value) { const selectedStyle = getStylePresets(citationStyle).find((style) => style.val === value); if (selectedStyle) { - const applyTextStyle = (block: HTMLElement, textStyle: DocumentTextStyle) => { - block.style.fontFamily = textStyle.fontFamily; - block.style.fontSize = `${textStyle.fontSizePt}pt`; - block.style.fontWeight = textStyle.fontWeight; - block.style.fontStyle = textStyle.fontStyle ?? 'normal'; - block.style.color = textStyle.color; - }; - - const getBlockAncestor = (node: Node): HTMLElement | null => { - let current: Node | null = node; - while (current && current !== el) { - if ( - current.nodeType === Node.ELEMENT_NODE && - ['P', 'H1', 'H2', 'H3'].includes((current as Element).tagName) - ) { - return current as HTMLElement; - } - current = current.parentNode; - } - return null; - }; - - const selection = window.getSelection(); - if (selection?.rangeCount) { - const range = selection.getRangeAt(0); - const blocks = new Set(); - const startBlock = getBlockAncestor(range.startContainer); - const endBlock = getBlockAncestor(range.endContainer); - if (startBlock) blocks.add(startBlock); - if (endBlock) blocks.add(endBlock); - - const ancestor = range.commonAncestorContainer; - const walkerRoot = - ancestor.nodeType === Node.ELEMENT_NODE - ? (ancestor as Element) - : (ancestor.parentElement ?? el); - const walker = document.createTreeWalker(walkerRoot, NodeFilter.SHOW_ELEMENT, { - acceptNode(node) { - const tag = (node as Element).tagName; - return ['P', 'H1', 'H2', 'H3'].includes(tag) - ? NodeFilter.FILTER_ACCEPT - : NodeFilter.FILTER_SKIP; - }, - }); - - while (walker.nextNode()) { - const node = walker.currentNode as HTMLElement; - if (range.intersectsNode(node)) blocks.add(node); - } - - blocks.forEach((block) => applyTextStyle(block, selectedStyle.textStyle)); - } + applyStylePresetToSelection(selectedStyle); } } @@ -742,7 +758,7 @@ export default function WordProcessor() { return (
- handleFormat('undo')} - onRedo={() => handleFormat('redo')} - onPrint={handlePrint} /> - {/* Ribbon */} - - - {/* Document Canvas — grey background, scrollable */} -
- {/* Document Page — white paper, scaled for zoom */} -
- { + const el = editorRef.current; + if (!el) return; + el.innerHTML = '

'; + el.focus(); + setWordCount(0); + }} + onOpen={() => { + window.alert('Open is not implemented yet.'); + }} + onDownload={handleSave} + onPageSetup={() => { + window.alert('Page Setup is not implemented yet.'); + }} + fontSize={fontSize} + fontFamily={fontFamily} + onFontSizeChange={handleFontSizeChange} + onFontFamilyChange={handleFontFamilyChange} + isBold={isBold} + isItalic={isItalic} + isUnderline={isUnderline} + isStrikethrough={isStrikethrough} + isSubscript={isSubscript} + isSuperscript={isSuperscript} + isUnorderedList={isUnorderedList} + isOrderedList={isOrderedList} + alignment={alignment} + onPrint={handlePrint} + onZoom={handleZoom} + lineSpacing={lineSpacing} + onLineSpacingChange={handleLineSpacingChange} + citationStyle={citationStyle} + onCitationStyleChange={handleCitationStyleChange} /> -
-
- - + )} + > + +
); } diff --git a/src/app/styles/_base.scss b/src/app/styles/_base.scss index f0fc9dd..f9b3ba7 100644 --- a/src/app/styles/_base.scss +++ b/src/app/styles/_base.scss @@ -10,6 +10,7 @@ html { } .word-processor { + background-color: transparent; display: flex; flex-direction: column; height: 100dvh; diff --git a/src/app/styles/_status.scss b/src/app/styles/_status.scss index b6f39a1..e434c84 100644 --- a/src/app/styles/_status.scss +++ b/src/app/styles/_status.scss @@ -28,8 +28,8 @@ font-size: 10px; font-weight: 700; letter-spacing: 0.12em; - color: rgba(255, 60, 60, 0.55); - border: 1px solid rgba(255, 60, 60, 0.35); + color: rgba(255, 0, 0, 1); + border: 1px solid rgba(255, 0, 0, 0.75); border-radius: 3px; padding: 1px 5px; pointer-events: none; diff --git a/src/app/styles/_title-bar.scss b/src/app/styles/_title-bar.scss index 2b7ab8b..c0b5d48 100644 --- a/src/app/styles/_title-bar.scss +++ b/src/app/styles/_title-bar.scss @@ -1,9 +1,8 @@ .word-title-bar { display: flex; align-items: center; - background: #2b5797; - color: #fff; - height: 34px; + background: #525252; + height: 40px; padding: 0 4px; flex-shrink: 0; gap: 4px; @@ -34,7 +33,7 @@ text-align: center; font-size: 13px; font-weight: 400; - color: #fff; + color: #edf5ff; pointer-events: none; user-select: none; } @@ -70,6 +69,10 @@ margin: 0; } + .cds--toggle__switch--checked { + background-color: #0f62fe; + } + .cds--toggle__label { margin: 0; } diff --git a/src/app/styles/_document.scss b/src/components/DocumentEditor/DocumentEditor.module.scss similarity index 69% rename from src/app/styles/_document.scss rename to src/components/DocumentEditor/DocumentEditor.module.scss index add87a8..76d5cdc 100644 --- a/src/app/styles/_document.scss +++ b/src/components/DocumentEditor/DocumentEditor.module.scss @@ -1,9 +1,12 @@ -.document-canvas { +.canvas { flex: 1; overflow-y: auto; - background: #808080; + overflow-x: hidden; + background: #6f6f6f; display: flex; - justify-content: center; + flex-direction: column; + align-items: center; + justify-content: flex-start; padding: 24px 24px; @media (max-width: 815px) { @@ -11,13 +14,36 @@ } } -.document-page { +.canvasStack { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 24px; + + @media (max-width: 815px) { + padding-top: 0; + } +} + +.toolbarSlot { + width: 100%; + display: flex; + justify-content: center; + position: sticky; + top: 0; + z-index: 6; +} + +.page { background: #fff; width: min(816px, 100%); min-height: 1056px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.55); padding: clamp(16px, 6vw, 96px); flex-shrink: 0; + position: relative; + z-index: 1; @media (max-width: 815px) { width: 100%; @@ -26,7 +52,7 @@ } } -.document-content { +.content { min-height: 864px; outline: none; line-height: 1.5; @@ -90,15 +116,15 @@ li { margin: 0 0 4px; } -} -.document-content:focus, -.document-content:focus-visible { - outline: none !important; - box-shadow: none; + &:focus, + &:focus-visible { + outline: none !important; + box-shadow: none; + } } -.document-page:focus-within { +.page:focus-within { outline: none; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.55); } diff --git a/src/components/DocumentEditor/DocumentEditor.tsx b/src/components/DocumentEditor/DocumentEditor.tsx index 23e0530..480e46e 100644 --- a/src/components/DocumentEditor/DocumentEditor.tsx +++ b/src/components/DocumentEditor/DocumentEditor.tsx @@ -1,6 +1,7 @@ 'use client'; import React, { useEffect, useRef, forwardRef } from 'react'; +import styles from './DocumentEditor.module.scss'; const AUTOSAVE_KEY = 'carbon-type-autosave'; const AUTOSAVE_DEBOUNCE_MS = 1000; @@ -35,7 +36,7 @@ const isBlockNode = (node: Node): boolean => node.nodeType === Node.ELEMENT_NODE && BLOCK_LEVEL_TAGS.has((node as Element).tagName); const ensureRootContainer = (el: HTMLDivElement) => { - // Guarantee at least one child container under .document-content. + // Guarantee at least one child container under the editable surface. if (!el.firstChild) { const line = document.createElement('div'); line.appendChild(document.createElement('br')); @@ -189,7 +190,7 @@ const DocumentEditor = forwardRef( return (
( +
+ {toolbar &&
{toolbar}
} +
+
+ {children} +
+
+
+); + +export default DocumentSurface; diff --git a/src/components/DocumentEditor/index.ts b/src/components/DocumentEditor/index.ts new file mode 100644 index 0000000..33c53e8 --- /dev/null +++ b/src/components/DocumentEditor/index.ts @@ -0,0 +1,2 @@ +export { default } from './DocumentEditor'; +export { default as DocumentSurface } from './DocumentSurface'; diff --git a/src/components/Ribbon/FileTabPanel.tsx b/src/components/Ribbon/FileTabPanel.tsx new file mode 100644 index 0000000..774a012 --- /dev/null +++ b/src/components/Ribbon/FileTabPanel.tsx @@ -0,0 +1,90 @@ +import { Button, TabPanel } from '@carbon/react'; +import { DocumentAdd, FolderOpen, DocumentDownload, SettingsAdjust, Printer } from '@carbon/icons-react'; +import { RibbonChunk, RibbonDivider } from './RibbonChunk'; + +interface FileTabPanelProps { + onNew?: () => void; + onOpen?: () => void; + onDownload?: () => void; + onPageSetup?: () => void; + onPrint: () => void; +} + +const FileTabPanel = ({ + onNew, + onOpen, + onDownload, + onPageSetup, + onPrint, +}: FileTabPanelProps) => ( + +
+ +
+ + + +
+
+ + +
+ + +
+
+
+
+); + +export default FileTabPanel; diff --git a/src/components/Ribbon/FontToolbar.tsx b/src/components/Ribbon/FontToolbar.tsx new file mode 100644 index 0000000..6c7443d --- /dev/null +++ b/src/components/Ribbon/FontToolbar.tsx @@ -0,0 +1,264 @@ +import { Button } from '@carbon/react'; +import { + Cut, + Copy, + Paste, + TextClearFormat, + Undo, + Redo, + DocumentAdd, + FolderOpen, + DocumentDownload, +} from '@carbon/icons-react'; +import { FONTS, SIZES } from './ribbonConfig'; +import { + ChangeCaseDropdown, + FontColorDropdown, + FormatButton, + HighlightColorDropdown, + TextEffectsDropdown, +} from './RibbonControls'; +import { RibbonProps } from './types'; + +type FontToolbarProps = Pick< + RibbonProps, + | 'onFormat' + | 'fontSize' + | 'fontFamily' + | 'onFontSizeChange' + | 'onFontFamilyChange' + | 'onNew' + | 'onOpen' + | 'onDownload' + | 'isBold' + | 'isItalic' + | 'isUnderline' + | 'isStrikethrough' + | 'isSubscript' + | 'isSuperscript' +> & { + newStyleMode?: boolean; + isMobile?: boolean; +}; + +const FontToolbar = ({ + onFormat, + fontSize, + fontFamily, + onFontSizeChange, + onFontFamilyChange, + onNew, + onOpen, + onDownload, + isBold, + isItalic, + isUnderline, + isStrikethrough, + isSubscript, + isSuperscript, + newStyleMode = false, + isMobile = false, +}: FontToolbarProps) => { + const fmt = (cmd: string, val?: string) => () => onFormat(cmd, val); + const parsedFontSize = Number.parseInt(fontSize, 10); + + const getClosestFontSizeIndex = (targetSize: number): number => { + let bestIndex = 0; + let bestDistance = Number.POSITIVE_INFINITY; + + SIZES.forEach((size, index) => { + const current = Number.parseInt(size, 10); + const distance = Math.abs(current - targetSize); + if (distance < bestDistance) { + bestDistance = distance; + bestIndex = index; + } + }); + + return bestIndex; + }; + + const stepFontSize = (delta: -1 | 1) => { + const currentIndex = Number.isNaN(parsedFontSize) + ? 0 + : getClosestFontSizeIndex(parsedFontSize); + const nextIndex = Math.max(0, Math.min(SIZES.length - 1, currentIndex + delta)); + onFontSizeChange(SIZES[nextIndex]); + }; + + return ( +
+
+
+ {isMobile ? ( + <> +
+ +
+
+
+ ); +}; + +export default FontToolbar; diff --git a/src/components/Ribbon/HomeTabPanel.tsx b/src/components/Ribbon/HomeTabPanel.tsx deleted file mode 100644 index 56ddd42..0000000 --- a/src/components/Ribbon/HomeTabPanel.tsx +++ /dev/null @@ -1,331 +0,0 @@ -import { Button, TabPanel } from '@carbon/react'; -import { - TextAlignLeft, - TextAlignCenter, - TextAlignRight, - TextAlignJustify, - ListBulleted, - ListNumbered, - Cut, - Copy, - Paste, - TextIndentMore, - TextIndentLess, - TextClearFormat, -} from '@carbon/icons-react'; -import { FONTS, SIZES, getStylePresets, toPreviewStyle } from './ribbonConfig'; -import { - ChangeCaseDropdown, - CitationStyleDropdown, - FontColorDropdown, - FormatButton, - HighlightColorDropdown, - LineSpacingDropdown, - TextEffectsDropdown, -} from './RibbonControls'; -import { RibbonChunk, RibbonDivider } from './RibbonChunk'; -import { RibbonProps } from './types'; - -type HomeTabPanelProps = Pick< - RibbonProps, - | 'onFormat' - | 'fontSize' - | 'fontFamily' - | 'onFontSizeChange' - | 'onFontFamilyChange' - | 'isBold' - | 'isItalic' - | 'isUnderline' - | 'isStrikethrough' - | 'isSubscript' - | 'isSuperscript' - | 'isUnorderedList' - | 'isOrderedList' - | 'alignment' - | 'lineSpacing' - | 'onLineSpacingChange' - | 'citationStyle' - | 'onCitationStyleChange' ->; - -const HomeTabPanel = ({ - onFormat, - fontSize, - fontFamily, - onFontSizeChange, - onFontFamilyChange, - isBold, - isItalic, - isUnderline, - isStrikethrough, - isSubscript, - isSuperscript, - isUnorderedList, - isOrderedList, - alignment, - lineSpacing, - onLineSpacingChange, - citationStyle, - onCitationStyleChange, -}: HomeTabPanelProps) => { - const fmt = (cmd: string, val?: string) => () => onFormat(cmd, val); - const styles = getStylePresets(citationStyle); - const parsedFontSize = Number.parseInt(fontSize, 10); - - const getClosestFontSizeIndex = (targetSize: number): number => { - let bestIndex = 0; - let bestDistance = Number.POSITIVE_INFINITY; - - SIZES.forEach((size, index) => { - const current = Number.parseInt(size, 10); - const distance = Math.abs(current - targetSize); - if (distance < bestDistance) { - bestDistance = distance; - bestIndex = index; - } - }); - - return bestIndex; - }; - - const stepFontSize = (delta: -1 | 1) => { - const currentIndex = Number.isNaN(parsedFontSize) - ? 0 - : getClosestFontSizeIndex(parsedFontSize); - const nextIndex = Math.max(0, Math.min(SIZES.length - 1, currentIndex + delta)); - onFontSizeChange(SIZES[nextIndex]); - }; - - return ( - -
- -
-
-
-
-
-
-
- - - -
- - - stepFontSize(1)} title="Increase Font Size"> - A+ - - stepFontSize(-1)} title="Decrease Font Size"> - A- - - onFormat('changeCase', caseType)} /> -
-
- - B - - - I - - - U - - - S - - - X2 - - - X2 - - onFormat('textEffect', effect)} /> - onFormat('foreColor', color)} /> - onFormat('hiliteColor', color)} /> -
-
- - - -
-
-
-
-
- - - - } - > -
- {styles.map((style) => ( - - ))} -
-
-
-
- ); -}; - -export default HomeTabPanel; diff --git a/src/components/Ribbon/NewStyleTabPanel.tsx b/src/components/Ribbon/NewStyleTabPanel.tsx new file mode 100644 index 0000000..6d6b406 --- /dev/null +++ b/src/components/Ribbon/NewStyleTabPanel.tsx @@ -0,0 +1,103 @@ +import { Button, TabPanel } from '@carbon/react'; +import { FormEvent, useState } from 'react'; + +export interface NewStyleFormValues { + styleName: string; + styleType: string; + baseStyle: 'Normal' | 'Heading 1' | 'Heading 2' | 'Heading 3'; + followingParagraphStyle: string; +} + +interface NewStyleTabPanelProps { + onSaveStyle: (values: NewStyleFormValues) => void; +} + +const NewStyleTabPanel = ({ onSaveStyle }: NewStyleTabPanelProps) => { + const [styleName, setStyleName] = useState(''); + const [styleType, setStyleType] = useState('Linked (paragraph and character)'); + const [baseStyle, setBaseStyle] = useState('Normal'); + const [followingParagraphStyle, setFollowingParagraphStyle] = useState('Normal'); + const [previewText, setPreviewText] = useState('The quick brown fox jumped over the lazy dog.'); + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + onSaveStyle({ + styleName: styleName.trim(), + styleType, + baseStyle: baseStyle as NewStyleFormValues['baseStyle'], + followingParagraphStyle, + }); + }; + + return ( + +
+
+
+ + setStyleName(event.target.value)} + placeholder="My Custom Style" + /> + + + + + + + + + +
+ +
+