diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a804fa1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Alexander J. Micharski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. 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-lock.json b/package-lock.json index 5b540e2..c7b110a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "carbon-type", - "version": "0.1.13", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "carbon-type", - "version": "0.1.13", + "version": "0.2.0", "dependencies": { - "@carbon/colors": "^11.30.0", - "@carbon/react": "^1.77.0", - "@carbon/styles": "^1.76.0", + "@carbon/colors": "^11.48.0", + "@carbon/react": "^1.103.0", + "@carbon/styles": "^1.102.0", "autoprefixer": "^10.4.21", "html-docx-js": "^0.3.1", - "next": "15.5.12", + "next": "^15.5.3", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -43,137 +43,145 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@carbon/colors": { - "version": "11.30.0", - "resolved": "https://registry.npmjs.org/@carbon/colors/-/colors-11.30.0.tgz", - "integrity": "sha512-DvTOv6yLpSnPVFJdNeW6dqo4KlpbmFBQvEM0HI/PP7WVcox8QEJKyw5SkEo/lK23pVg6xzBOQKIMLrouNAkWoQ==", + "version": "11.48.0", + "resolved": "https://registry.npmjs.org/@carbon/colors/-/colors-11.48.0.tgz", + "integrity": "sha512-fJfgGBlzKugSS32z9/yI+XrSgiZttrFPBcsqrfxMjWAIPCRke1l3nypfL0AenetVof95dE38KA09i08uK9Zl2Q==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@ibm/telemetry-js": "^1.5.0" } }, "node_modules/@carbon/feature-flags": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@carbon/feature-flags/-/feature-flags-0.24.0.tgz", - "integrity": "sha512-GQEeXnfmnAtGVfKHSwJoJUsZ8YXAMKgL1TkJf2cUVuHYFk2WScHvCx7SUTDFJKLalGB+QnZPNuFLZ5oapsuwPw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@carbon/feature-flags/-/feature-flags-1.1.0.tgz", + "integrity": "sha512-C9RigNQgO2WAKHjiZqJoumx3nBqs7n4fQETylHvrDeuVylztCCi7PCbr/yfpiUBfCervAsnYisBXynfH7Gcqcw==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@ibm/telemetry-js": "^1.5.0" } }, "node_modules/@carbon/grid": { - "version": "11.32.0", - "resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.32.0.tgz", - "integrity": "sha512-O8QkUoOglhmOgZ7cvxUSy9I+G+eY/k9t1BnqunHGs7xIswkXeFxOwvUPbcUbrVoNu4rMP8eXv91Eq/f3QpV/Jg==", + "version": "11.51.0", + "resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.51.0.tgz", + "integrity": "sha512-5x4ri8GCBjckxd7/QuJzWwdcoCdhukeOcZ3ASrYnMY8IYEo+7NzBjIIFA4M7B5jsT2zd29uIIZqcTHcPg+1L8w==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@carbon/layout": "^11.30.0", + "@carbon/layout": "^11.49.0", "@ibm/telemetry-js": "^1.5.0" } }, "node_modules/@carbon/icon-helpers": { - "version": "10.55.0", - "resolved": "https://registry.npmjs.org/@carbon/icon-helpers/-/icon-helpers-10.55.0.tgz", - "integrity": "sha512-CNpIbZ4sIoZtBufybrRaf6DoJrhJswNAPJoDZ8Yr8eTQG6tp9cVkEjh97ZXOGJuMVCkG+TyTPdIBAsUv9acFdA==", + "version": "10.73.0", + "resolved": "https://registry.npmjs.org/@carbon/icon-helpers/-/icon-helpers-10.73.0.tgz", + "integrity": "sha512-Grn/QxHZfSOdXp/WBadegVfq1LT96QwX9b977dqMU5qmxp6cxKqUGvV1oqs6GiBacKoQFrH2xgbKwlcecgSHfA==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@ibm/telemetry-js": "^1.5.0" } }, "node_modules/@carbon/icons-react": { - "version": "11.56.0", - "resolved": "https://registry.npmjs.org/@carbon/icons-react/-/icons-react-11.56.0.tgz", - "integrity": "sha512-nXNju4eTpDm2noD50eF8BtH9KgOGHO1vsvVwfCP94sY2hjB/w5hQy0pVDNtTz7nu4JGUQk2+wZlM50Wz33ZShw==", + "version": "11.76.0", + "resolved": "https://registry.npmjs.org/@carbon/icons-react/-/icons-react-11.76.0.tgz", + "integrity": "sha512-FsRlufW8lcdEX/Vj0NpdloBDPmXGRCIaD6ENhYt0f42zJncs6OqNQ58plqNteHg/JMQAdA+pP8f5tSkopNhRRg==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@carbon/icon-helpers": "^10.55.0", + "@carbon/icon-helpers": "^10.73.0", "@ibm/telemetry-js": "^1.5.0", - "prop-types": "^15.7.2" + "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=16" } }, "node_modules/@carbon/layout": { - "version": "11.30.0", - "resolved": "https://registry.npmjs.org/@carbon/layout/-/layout-11.30.0.tgz", - "integrity": "sha512-r/z1tvTkBfZu0Mk6r0QDYVvi67TT8NSIA4cJHTng4F+GiBu1VBpiHHYYMvdFzqYGhqKMVtER4hp9mldRBn00dQ==", + "version": "11.49.0", + "resolved": "https://registry.npmjs.org/@carbon/layout/-/layout-11.49.0.tgz", + "integrity": "sha512-eT/G2o7sF80r5r8NJSDuLTQmVMr1u7VjQEkANVeaLg366VF5q36tCKgbs6kzFgJPEZhifonajIp27cS+gfylvQ==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@ibm/telemetry-js": "^1.5.0" } }, "node_modules/@carbon/motion": { - "version": "11.25.0", - "resolved": "https://registry.npmjs.org/@carbon/motion/-/motion-11.25.0.tgz", - "integrity": "sha512-oqrXmWmlVuXmKhHG1dUm4zy2TR8CGYqcentDcMEAJqcvlF1qhDuF/1j0JQ0X7mOl3/suGBtBsxCoC5CN8KO1IQ==", + "version": "11.42.0", + "resolved": "https://registry.npmjs.org/@carbon/motion/-/motion-11.42.0.tgz", + "integrity": "sha512-FYBBMnYanddsxGilGSM8/8u0HXLY1hpfbtgpdWCsjUXtxZLvBHYKPbStJWBkvmDuwfAxeweS1xEFCYNIYIzNGA==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@ibm/telemetry-js": "^1.5.0" } }, "node_modules/@carbon/react": { - "version": "1.77.0", - "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.77.0.tgz", - "integrity": "sha512-yWR1I0GLVgYQbaGz2lWISympCoookc1wO99g2vcLSi9JTPw98swcjBm0lYfMVqh1ko9r/jGfb2U3xSG/HG7Omg==", + "version": "1.103.0", + "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.103.0.tgz", + "integrity": "sha512-HaG1PdvevVMEd+bF2Bq/4m/iCRHlUrtMv+rhOGyKn0sha/OjoJAF8QucITD8mHuHBresmSzIPwG8Spqo0zG0uw==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@babel/runtime": "^7.24.7", - "@carbon/feature-flags": "^0.24.0", - "@carbon/icons-react": "^11.56.0", - "@carbon/layout": "^11.30.0", - "@carbon/styles": "^1.76.0", + "@babel/runtime": "^7.27.3", + "@carbon/feature-flags": "^1.1.0", + "@carbon/icons-react": "^11.76.0", + "@carbon/layout": "^11.49.0", + "@carbon/styles": "^1.102.0", + "@carbon/utilities": "^0.17.0", "@floating-ui/react": "^0.27.4", "@ibm/telemetry-js": "^1.5.0", "classnames": "2.5.1", "copy-to-clipboard": "^3.3.1", - "downshift": "9.0.8", + "downshift": "9.0.10", "es-toolkit": "^1.27.0", "flatpickr": "4.6.13", "invariant": "^2.2.3", - "prop-types": "^15.7.2", + "prop-types": "^15.8.1", "react-fast-compare": "^3.2.2", - "tabbable": "^6.2.0", - "use-resize-observer": "^6.0.0", - "window-or-global": "^1.0.1" + "tabbable": "^6.2.0" }, "peerDependencies": { "react": "^16.8.6 || ^17.0.1 || ^18.2.0 || ^19.0.0", "react-dom": "^16.8.6 || ^17.0.1 || ^18.2.0 || ^19.0.0", - "react-is": "^18.3.1 || ^19.0.0", + "react-is": "^16.13.1 || ^17.0.2 || ^18.3.1 || ^19.0.0", "sass": "^1.33.0" } }, "node_modules/@carbon/styles": { - "version": "1.76.0", - "resolved": "https://registry.npmjs.org/@carbon/styles/-/styles-1.76.0.tgz", - "integrity": "sha512-NzffRGFo/PpAbpsX24GkNdFat6gRwmDpswZbQvu1GWXxMYHyIDM9ObVF0n+gjy5bcnkMjFyvN4eINJf1uDSf4w==", + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/@carbon/styles/-/styles-1.102.0.tgz", + "integrity": "sha512-Xi2esQ66FFlb8GUIL+Dj5qy9j0df4y5RfS/zMoNfAB5xgjDwqA6dWIwjk8NDywLeUiLfhG8CwovDP4d4QF2OsQ==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@carbon/colors": "^11.30.0", - "@carbon/feature-flags": "^0.24.0", - "@carbon/grid": "^11.32.0", - "@carbon/layout": "^11.30.0", - "@carbon/motion": "^11.25.0", - "@carbon/themes": "^11.47.0", - "@carbon/type": "^11.36.0", + "@carbon/colors": "^11.48.0", + "@carbon/feature-flags": "^1.1.0", + "@carbon/grid": "^11.51.0", + "@carbon/layout": "^11.49.0", + "@carbon/motion": "^11.42.0", + "@carbon/themes": "^11.69.0", + "@carbon/type": "^11.55.0", "@ibm/plex": "6.0.0-next.6", - "@ibm/plex-mono": "0.0.3-alpha.0", - "@ibm/plex-sans": "0.0.3-alpha.0", - "@ibm/plex-sans-arabic": "0.0.3-alpha.0", - "@ibm/plex-sans-devanagari": "0.0.3-alpha.0", - "@ibm/plex-sans-hebrew": "0.0.3-alpha.0", - "@ibm/plex-sans-thai": "0.0.3-alpha.0", - "@ibm/plex-sans-thai-looped": "0.0.3-alpha.0", - "@ibm/plex-serif": "0.0.3-alpha.0", + "@ibm/plex-mono": "1.1.0", + "@ibm/plex-sans": "1.1.0", + "@ibm/plex-sans-arabic": "1.1.0", + "@ibm/plex-sans-devanagari": "1.1.0", + "@ibm/plex-sans-hebrew": "1.1.0", + "@ibm/plex-sans-thai": "1.1.0", + "@ibm/plex-sans-thai-looped": "1.1.0", + "@ibm/plex-serif": "1.1.0", "@ibm/telemetry-js": "^1.5.0" }, "peerDependencies": { @@ -186,29 +194,42 @@ } }, "node_modules/@carbon/themes": { - "version": "11.47.0", - "resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.47.0.tgz", - "integrity": "sha512-I+nQs+RANvZRTCWcqRtK1crKmI/9bYkefQaOW4pJIetTmOt3sezM0MVDQn20nNXEJNY6zRVB1TZU3+mxYI41UQ==", + "version": "11.69.0", + "resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.69.0.tgz", + "integrity": "sha512-4Fh0N5vNVKwKLN/Mpmq/H48sfh8yJufCyjHX81CiC3G5wkzJJH1RJVbabp9vJLs0F2OEJhqpn3uvSsUBvNyDDg==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@carbon/colors": "^11.30.0", - "@carbon/layout": "^11.30.0", - "@carbon/type": "^11.36.0", + "@carbon/colors": "^11.48.0", + "@carbon/layout": "^11.49.0", + "@carbon/type": "^11.55.0", "@ibm/telemetry-js": "^1.5.0", "color": "^4.0.0" } }, "node_modules/@carbon/type": { - "version": "11.36.0", - "resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.36.0.tgz", - "integrity": "sha512-SL0LQTyRoPeifHZy9KFMNheas4qa6BvdvADkd0E4L2tG6JOijpqkicRQ/OnCe5Z/DfIdqe7Qj+0Qm5uGz6G69Q==", + "version": "11.55.0", + "resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.55.0.tgz", + "integrity": "sha512-VKSJ+TV/J2hdFkCqRuBqUJhtNIOG3BJCOEHv0L+bfrFk82mksnx59hsBtJKpnsgsZ9er+1e8hkZPqqo8dHRHvg==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@carbon/grid": "^11.32.0", - "@carbon/layout": "^11.30.0", + "@carbon/grid": "^11.51.0", + "@carbon/layout": "^11.49.0", "@ibm/telemetry-js": "^1.5.0" } }, + "node_modules/@carbon/utilities": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@carbon/utilities/-/utilities-0.17.0.tgz", + "integrity": "sha512-XNpZX7bbwwTzzjZhgufB9fHQH9i8Mec/0KXIULStzYMUlAHhstYMOCp+pLmI53ynyc+/PkCOTnAADXeZcy7Guw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1", + "@internationalized/number": "^3.6.1" + } + }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -474,49 +495,90 @@ "version": "6.0.0-next.6", "resolved": "https://registry.npmjs.org/@ibm/plex/-/plex-6.0.0-next.6.tgz", "integrity": "sha512-B3uGruTn2rS5gweynLmfSe7yCawSRsJguJJQHVQiqf4rh2RNgJFu8YLE2Zd/JHV0ZXoVMOslcXP2k3hMkxKEyA==", + "license": "OFL-1.1", "engines": { "node": ">=14" } }, "node_modules/@ibm/plex-mono": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-mono/-/plex-mono-0.0.3-alpha.0.tgz", - "integrity": "sha512-xSa/c1vrzGmMR5xQr/aWP/q62jUD41mKwm2w4kFsvIVyT9fxC3wq81UYMSGBxQZ6+P1AROMSefF22aLXkv6uqw==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-mono/-/plex-mono-1.1.0.tgz", + "integrity": "sha512-hpsdRxR3BRJkC6wGM4MZcUFD6C8M+mmK76RtAy/hlsfPro9FzpXVdIWC+G3jeQOXof109dxlUvmeKxpeKUG68A==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/plex-sans": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-sans/-/plex-sans-0.0.3-alpha.0.tgz", - "integrity": "sha512-JU3dmaJiTNL17MO2pTzUJUzYSLZjUmkFUOia9c/2mU4ehqyvw95yQ6G4XRRqEHQdUA7auO4I0GX8mcI8rQk/Tw==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans/-/plex-sans-1.1.0.tgz", + "integrity": "sha512-WPgvO6Yfj2w5YbhyAr1tv95RUz4LRJlqN+CmYvBglabXteufP1D1E9BABMde+ZIKdRbFJDoKF5eQzfhpnbgZcQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/plex-sans-arabic": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-sans-arabic/-/plex-sans-arabic-0.0.3-alpha.0.tgz", - "integrity": "sha512-tFi6soIKl/A2xWf5/N9kCkMhv+MOcEewWWFM9Bz9U0YO5I4KR0qdUTU7rN4jTjvCJGPExwPFukQKBNz7djuShg==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-arabic/-/plex-sans-arabic-1.1.0.tgz", + "integrity": "sha512-u8wIS6szLAOFvlBjCFZmtpKIqbhuIuniG2N0J+sio8vV6INH58hP0t0QNYrSl9SZtCv2Fwb4oQGuZJY3kJ4+QA==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/plex-sans-devanagari": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-sans-devanagari/-/plex-sans-devanagari-0.0.3-alpha.0.tgz", - "integrity": "sha512-jrhO6KOxwtpw3WaidCNSn+IWqxDyYGSUUP8i4WjmxkBREQNf4fSJwbjzgB79E/Mnhc3b2NZska+41k5owRlIoQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-devanagari/-/plex-sans-devanagari-1.1.0.tgz", + "integrity": "sha512-IVNV9NxXZDzcGZRao/xj+kiFwkdLkcw5vNiKwY8wEzzkpjApXJnPhJ0a7mIKNAh8oIadTIF68+iGtzRKK3nXAQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/plex-sans-hebrew": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-sans-hebrew/-/plex-sans-hebrew-0.0.3-alpha.0.tgz", - "integrity": "sha512-sMsn1jU8kyYfSlWMfjcbvpGXJIIXGOZD+sxtBcogZz4umnCq5ys+bmsqlzkfGR25DCB49WvseD2IHbejes0/aA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-hebrew/-/plex-sans-hebrew-1.1.0.tgz", + "integrity": "sha512-iix0rLpUD0E8dE8q+/t3B7u1or7h6gEzoy6TK9NwP41AN31WE55f2cFwQAXomBDwr0Ozc9sHYy97NutEukZXzQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/plex-sans-thai": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-sans-thai/-/plex-sans-thai-0.0.3-alpha.0.tgz", - "integrity": "sha512-3RteUFhshRTmP5Swq9LYravDXmVvjxtxsZ7qeSqjn31CUgeSuZKprDWb+RzSQrO+Jg7AI4g1lolzTr/jG/LnxA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-thai/-/plex-sans-thai-1.1.0.tgz", + "integrity": "sha512-vk7IrjdO69eEElJpFBppCha/wvU48DFyVuDewcfIf5L6Z11s0vbROANCvKipVPRUz1LE4ron8KoitWGcl3AlfA==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/plex-sans-thai-looped": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-sans-thai-looped/-/plex-sans-thai-looped-0.0.3-alpha.0.tgz", - "integrity": "sha512-mcddR5ZcAQx5TjmaxaXd6gTdtOgxlyVaKqjzQAjUbzNQy0cjTGhIJHB5VrFES7yJLRCtQNCNGP+bzupzHOQERw==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-sans-thai-looped/-/plex-sans-thai-looped-1.1.0.tgz", + "integrity": "sha512-9zbDGzmtscHgBRTF88y3/92zQx6lmKjz5ZxhgcljilwOpj08BAytDc3mzUl9XGUh+DmOMl0Ql1lk6ecsEYYg2w==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/plex-serif": { - "version": "0.0.3-alpha.0", - "resolved": "https://registry.npmjs.org/@ibm/plex-serif/-/plex-serif-0.0.3-alpha.0.tgz", - "integrity": "sha512-wuyglvk5dVTiOtRMlGhbRdu9zptl84CHLhjzuWPb2LwU3IiFlVWAirKaRKRv/AFwtAT9RoTtvT7spEyffdCzFw==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ibm/plex-serif/-/plex-serif-1.1.0.tgz", + "integrity": "sha512-ORIyWlK8t8mvpFI7AAfhVFH+sacink2l9AjLiKZscmAzLVSa2dqFckrPOXqx4dK/cax567gWwCpJNEYk7xWxBQ==", + "hasInstallScript": true, + "license": "OFL-1.1", + "dependencies": { + "@ibm/telemetry-js": "^1.6.1" + } }, "node_modules/@ibm/telemetry-js": { "version": "1.9.1", @@ -992,6 +1054,15 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1058,9 +1129,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz", - "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.13.tgz", + "integrity": "sha512-6h7Fm29+/u1WBPcPaQl0xBov7KXB6i0c8oFlSlehD+PuZJQjzXQBuYzfkM32G5iWOlKsXXyRtcMaaqwspRBujA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1073,9 +1144,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", - "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.13.tgz", + "integrity": "sha512-XrBbj2iY1mQSsJ8RoFClNpUB9uuZejP94v9pJuSAzdzwFVHeP+Vu2vzBCHwSObozgYNuTVwKhLukG1rGCgj8xA==", "cpu": [ "arm64" ], @@ -1089,9 +1160,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", - "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.13.tgz", + "integrity": "sha512-Ey3fuUeWDWtVdgiLHajk2aJ74Y8EWLeqvfwlkB5RvWsN7F1caQ6TjifsQzrAcOuNSnogGvFNYzjQlu7tu0kyWg==", "cpu": [ "x64" ], @@ -1105,9 +1176,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", - "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.13.tgz", + "integrity": "sha512-aLtu/WxDeL3188qx3zyB3+iw8nAB9F+2Mhyz9nNZpzsREc2t8jQTuiWY4+mtOgWp1d+/Q4eXuy9m3dwh3n1IyQ==", "cpu": [ "arm64" ], @@ -1121,9 +1192,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", - "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.13.tgz", + "integrity": "sha512-9VZ0OsVx9PEL72W50QD15iwSCF3GD/dwj42knfF5C4aiBPXr95etGIOGhb8rU7kpnzZuPNL81CY4vIyUKa2xvg==", "cpu": [ "arm64" ], @@ -1137,9 +1208,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz", - "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.13.tgz", + "integrity": "sha512-3knsu9H33e99ZfiWh0Bb04ymEO7YIiopOpXKX89ZZ/ER0iyfV1YLoJFxJJQNUD7OR8O7D7eiLI/TXPryPGv3+A==", "cpu": [ "x64" ], @@ -1153,9 +1224,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz", - "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.13.tgz", + "integrity": "sha512-AVPb6+QZ0pPanJFc1hpx81I5tTiBF4VITw5+PMaR1CrboAUUxtxn3IsV0h48xI7fzd6/zw9D9i6khRwME5NKUw==", "cpu": [ "x64" ], @@ -1169,9 +1240,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", - "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.13.tgz", + "integrity": "sha512-FZ/HXuTxn+e5Lp6oRZMvHaMJx22gAySveJdJE0//91Nb9rMuh2ftgKlEwBFJxhkw5kAF/yIXz3iBf0tvDXRmCA==", "cpu": [ "arm64" ], @@ -1185,9 +1256,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", - "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.13.tgz", + "integrity": "sha512-B5E82pX3VXu6Ib5mDuZEqGwT8asocZe3OMMnaM+Yfs0TRlmSQCBQUUXR9BkXQeGVboOWS1pTsRkS9wzFd8PABw==", "cpu": [ "x64" ], @@ -2208,10 +2279,11 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2389,6 +2461,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -2417,6 +2490,7 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -2434,7 +2508,8 @@ "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", - "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==" + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", @@ -2631,9 +2706,10 @@ } }, "node_modules/downshift": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/downshift/-/downshift-9.0.8.tgz", - "integrity": "sha512-59BWD7+hSUQIM1DeNPLirNNnZIO9qMdIK5GQ/Uo8q34gT4B78RBlb9dhzgnh0HfQTJj4T/JKYD8KoLAlMWnTsA==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-9.0.10.tgz", + "integrity": "sha512-TP/iqV6bBok6eGD5tZ8boM8Xt7/+DZvnVNr8cNIhbAm2oUBd79Tudiccs2hbcV9p7xAgS/ozE7Hxy3a9QqS6Mw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.5", "compute-scroll-into-view": "^3.1.0", @@ -2648,7 +2724,8 @@ "node_modules/downshift/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -3411,10 +3488,11 @@ "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==" }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/for-each": { "version": "0.3.5", @@ -3886,9 +3964,10 @@ } }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", @@ -4706,12 +4785,12 @@ "dev": true }, "node_modules/next": { - "version": "15.5.12", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", - "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.13.tgz", + "integrity": "sha512-n0AXf6vlTwGuM93Z++POtjMsRuQ9pT5v2URPciXKUQIl/EB2WjXF0YiIUxaa9AEMFaMpZlaG3KPK6i4UVnx9eQ==", "license": "MIT", "dependencies": { - "@next/env": "15.5.12", + "@next/env": "15.5.13", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -4724,14 +4803,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.12", - "@next/swc-darwin-x64": "15.5.12", - "@next/swc-linux-arm64-gnu": "15.5.12", - "@next/swc-linux-arm64-musl": "15.5.12", - "@next/swc-linux-x64-gnu": "15.5.12", - "@next/swc-linux-x64-musl": "15.5.12", - "@next/swc-win32-arm64-msvc": "15.5.12", - "@next/swc-win32-x64-msvc": "15.5.12", + "@next/swc-darwin-arm64": "15.5.13", + "@next/swc-darwin-x64": "15.5.13", + "@next/swc-linux-arm64-gnu": "15.5.13", + "@next/swc-linux-arm64-musl": "15.5.13", + "@next/swc-linux-x64-gnu": "15.5.13", + "@next/swc-linux-x64-musl": "15.5.13", + "@next/swc-win32-arm64-msvc": "15.5.13", + "@next/swc-win32-x64-msvc": "15.5.13", "sharp": "^0.34.3" }, "peerDependencies": { @@ -5393,11 +5472,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -5755,9 +5829,10 @@ } }, "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" } @@ -6480,18 +6555,6 @@ "punycode": "^2.1.0" } }, - "node_modules/use-resize-observer": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-6.1.0.tgz", - "integrity": "sha512-SiPcWHiIQ1CnHmb6PxbYtygqiZXR0U9dNkkbpX9VYnlstUwF8+QqpUTrzh13pjPwcjMVGR+QIC+nvF5ujfFNng==", - "dependencies": { - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6598,11 +6661,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/window-or-global": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/window-or-global/-/window-or-global-1.0.1.tgz", - "integrity": "sha512-tE12J/NenOv4xdVobD+AD3fT06T4KNqnzRhkv5nBIu7K+pvOH2oLCEgYP+i+5mF2jtI6FEADheOdZkA8YWET9w==" - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 2102b84..ade0140 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "carbon-type", - "version": "0.1.13", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", @@ -9,12 +9,12 @@ "lint": "next lint" }, "dependencies": { - "@carbon/colors": "^11.30.0", - "@carbon/react": "^1.77.0", - "@carbon/styles": "^1.76.0", + "@carbon/colors": "^11.48.0", + "@carbon/react": "^1.103.0", + "@carbon/styles": "^1.102.0", "autoprefixer": "^10.4.21", "html-docx-js": "^0.3.1", - "next": "15.5.12", + "next": "^15.5.3", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/src/app/globals.scss b/src/app/globals.scss index 5892860..a09065b 100644 --- a/src/app/globals.scss +++ b/src/app/globals.scss @@ -1,8 +1,6 @@ @use "@carbon/react"; @use "./styles/base"; @use "./styles/title-bar"; -@use "./styles/ribbon"; -@use "./styles/document"; @use "./styles/status"; @use "./styles/mobile"; diff --git a/src/app/page.tsx b/src/app/page.tsx index 9af8d55..fcdba35 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,11 +2,10 @@ import React, { useState, useRef, useCallback, useEffect } from 'react'; import { asBlob } from 'html-docx-js/dist/html-docx'; -import Ribbon from '@/components/Ribbon/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 Ribbon from '@/components/Ribbon'; +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); } } @@ -331,24 +347,46 @@ export default function WordProcessor() { (size: string) => { if (!size) return; setFontSize(size); + pendingFontSizeRef.current = null; const el = editorRef.current; if (!el) return; el.focus(); const sel = window.getSelection(); - if (!sel || !sel.rangeCount) return; + if (!sel) return; + + let range: Range; + if (!sel.rangeCount || !el.contains(sel.anchorNode)) { + range = document.createRange(); + range.selectNodeContents(el); + range.collapse(false); + sel.removeAllRanges(); + sel.addRange(range); + } else { + range = sel.getRangeAt(0); + } + + // Collapsed cursor: insert a styled empty span at the caret and place the + // caret inside it so subsequent typing inherits this exact size. + if (range.collapsed) { + const span = document.createElement('span'); + span.style.fontSize = `${size}pt`; + span.setAttribute('data-pending-font-size', '1'); + range.insertNode(span); + + const caretRange = document.createRange(); + caretRange.setStart(span, 0); + caretRange.collapse(true); + sel.removeAllRanges(); + sel.addRange(caretRange); - // Collapsed cursor: don't touch the DOM at all — that would inflate the line - // height and potentially move the cursor. Instead, store the size as pending; - // the beforeinput handler will wrap the first typed character in a span. - if (sel.isCollapsed) { - pendingFontSizeRef.current = size; - // Re-assert setFontSize so it wins over any selectionchange-triggered reset - // that fires synchronously inside el.focus() above (React 19 batches both). setFontSize(size); return; } + // Range formatting is applied immediately, so no pending caret override is needed. + pendingFontSizeRef.current = null; + // Non-collapsed selection: use execCommand('fontSize', '7') as a structural // marker — the browser handles all cross-element selection edge cases — // then replace every inserted with a proper inline span. @@ -541,21 +579,72 @@ export default function WordProcessor() { return values; }; - // Read the computed font at a single element (collapsed cursor). + const getDeepestLastNode = (node: Node | null): Node | null => { + if (!node) return null; + let current: Node = node; + while (current.lastChild) current = current.lastChild; + return current; + }; + + const getPreviousNode = (node: Node, root: Node): Node | null => { + let current: Node | null = node; + while (current && current !== root) { + if (current.previousSibling) return current.previousSibling; + current = current.parentNode; + } + return null; + }; + + const resolveCaretProbeElement = (sel: Selection, root: HTMLElement): Element | null => { + const anchor = sel.anchorNode; + if (!anchor) return null; + + if (anchor.nodeType === Node.TEXT_NODE) { + if (sel.anchorOffset > 0) { + return (anchor as Text).parentElement; + } + + const prev = getPreviousNode(anchor, root); + const probe = getDeepestLastNode(prev); + return probe + ? probe.nodeType === Node.TEXT_NODE + ? (probe as Text).parentElement + : (probe as Element) + : (anchor as Text).parentElement; + } + + const anchorEl = anchor as Element; + if (sel.anchorOffset > 0) { + const priorChild = anchor.childNodes[sel.anchorOffset - 1] ?? null; + const probe = getDeepestLastNode(priorChild); + if (probe) { + return probe.nodeType === Node.TEXT_NODE + ? (probe as Text).parentElement + : (probe as Element); + } + } + + const prev = getPreviousNode(anchor, root); + const probe = getDeepestLastNode(prev); + if (probe) { + return probe.nodeType === Node.TEXT_NODE + ? (probe as Text).parentElement + : (probe as Element); + } + + return anchorEl; + }; + + // Read the computed font at the caret position (collapsed cursor). const getComputedAtCaret = (prop: 'fontFamily' | 'fontSize'): string => { - const raw = document.queryCommandValue( - prop === 'fontFamily' ? 'fontName' : 'fontSize' - ); - // For fontName Chrome returns the actual name; for fontSize it returns 1-7 legacy values. - // Use computed style on the anchor node's parent instead for accuracy. const sel = window.getSelection(); - if (!sel || !sel.anchorNode) return ''; - const parent = - sel.anchorNode.nodeType === Node.TEXT_NODE - ? sel.anchorNode.parentElement - : (sel.anchorNode as Element); - if (!parent) return raw; - const computed = window.getComputedStyle(parent)[prop]; + const root = editorRef.current; + if (!sel || !root || !sel.anchorNode) return ''; + + const probeElement = resolveCaretProbeElement(sel, root); + if (!probeElement) return ''; + + const computed = window.getComputedStyle(probeElement)[prop]; return prop === 'fontFamily' ? computed.split(',')[0].trim().replace(/^["']|["']$/g, '') : pxToPt(computed); @@ -568,14 +657,13 @@ export default function WordProcessor() { if (!sel || !sel.rangeCount) return; if (!el.contains(sel.anchorNode)) return; - // Cursor moved — any pending font-size no longer applies to future typing. - pendingFontSizeRef.current = null; - const range = sel.getRangeAt(0); if (range.collapsed) { setFontFamily(getComputedAtCaret('fontFamily')); - setFontSize(getComputedAtCaret('fontSize')); + // If user picked a size for upcoming typing at a collapsed caret, + // keep that value visible instead of snapping back to surrounding text. + setFontSize(pendingFontSizeRef.current ?? getComputedAtCaret('fontSize')); setIsBold(document.queryCommandState('bold')); setIsItalic(document.queryCommandState('italic')); setIsUnderline(document.queryCommandState('underline')); @@ -591,7 +679,7 @@ export default function WordProcessor() { setFontFamily(fonts.size === 1 ? [...fonts][0] : ''); const sizes = getComputedValuesInRange(range, 'fontSize'); - setFontSize(sizes.size === 1 ? [...sizes][0] : ''); + setFontSize(pendingFontSizeRef.current ?? (sizes.size === 1 ? [...sizes][0] : '')); setIsBold(document.queryCommandState('bold')); setIsItalic(document.queryCommandState('italic')); setIsUnderline(document.queryCommandState('underline')); @@ -614,11 +702,39 @@ export default function WordProcessor() { if (e.inputType !== 'insertText' || !pendingFontSizeRef.current || !e.data) return; e.preventDefault(); const size = pendingFontSizeRef.current; - pendingFontSizeRef.current = null; document.execCommand('insertHTML', false, `${e.data}`); }; + + const clearPendingSize = () => { + pendingFontSizeRef.current = null; + }; + + const onKeyDown = (e: KeyboardEvent) => { + // Navigation keys indicate explicit caret move; stop forcing a pending size. + if ( + e.key === 'ArrowLeft' || + e.key === 'ArrowRight' || + e.key === 'ArrowUp' || + e.key === 'ArrowDown' || + e.key === 'Home' || + e.key === 'End' || + e.key === 'PageUp' || + e.key === 'PageDown' + ) { + clearPendingSize(); + } + }; + el.addEventListener('beforeinput', onBeforeInput); - return () => el.removeEventListener('beforeinput', onBeforeInput); + el.addEventListener('mouseup', clearPendingSize); + el.addEventListener('blur', clearPendingSize); + el.addEventListener('keydown', onKeyDown); + return () => { + el.removeEventListener('beforeinput', onBeforeInput); + el.removeEventListener('mouseup', clearPendingSize); + el.removeEventListener('blur', clearPendingSize); + el.removeEventListener('keydown', onKeyDown); + }; }, []); const handlePrint = useCallback(() => { @@ -642,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/_mobile.scss b/src/app/styles/_mobile.scss index dbd7c6f..83e0c2a 100644 --- a/src/app/styles/_mobile.scss +++ b/src/app/styles/_mobile.scss @@ -1,108 +1,3 @@ -.word-ribbon--mobile { - .ribbon-panel { - min-height: unset; - padding: 4px 6px; - gap: 4px; - } - - .ribbon-divider { - display: none; - } -} - -.ribbon-chunk-mobile-btn { - display: inline-flex; - align-items: center; - gap: 3px; - padding: 3px 8px; - height: 26px; - background: transparent; - border: 1px solid #c0c0c0; - border-radius: 3px; - font-size: 12px; - color: #222; - cursor: pointer; - white-space: nowrap; - line-height: 1; - - &:hover, - &--open { - background: #dde0e5; - } - - svg { - fill: #222; - flex-shrink: 0; - } -} - -.ribbon-chunk-mobile-dropdown { - position: fixed; - background: #f5f5f5; - border: 1px solid #c6c6c6; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); - z-index: 9999; - padding: 8px; - min-width: 180px; - max-width: calc(100vw - 16px); - overflow-y: auto; - overflow-x: hidden; - - // Re-apply ribbon button overrides - the portal lives outside .word-ribbon. - .cds--btn--ghost { - min-height: 22px; - padding: 2px 4px; - color: #222; - - &:hover { - background: #dde0e5; - color: #222; - } - - svg { - fill: #222; - } - } - - .cds--btn--primary { - min-height: 22px; - padding: 2px 4px; - } - - .ribbon-row { - flex-wrap: wrap; - gap: 3px; - margin-bottom: 4px; - - &:last-child { - margin-bottom: 0; - } - } - - .ribbon-select { - min-width: 100%; - height: 28px; - margin-bottom: 4px; - - &--size { - min-width: 60px; - width: 60px; - } - } - - .ribbon-styles { - gap: 4px; - } - - &__launcher { - display: flex; - justify-content: flex-end; - margin-top: 4px; - padding-top: 4px; - border-top: 1px solid #c8c8c8; - } -} - @media (max-width: 672px) { // Hide the verbose "Autosave" label and transient status text to free up // horizontal space; the toggle itself stays so the user can still control it. diff --git a/src/app/styles/_ribbon.scss b/src/app/styles/_ribbon.scss deleted file mode 100644 index fd93490..0000000 --- a/src/app/styles/_ribbon.scss +++ /dev/null @@ -1,547 +0,0 @@ -.word-ribbon { - flex-shrink: 0; - background: #f5f5f5; - border-bottom: 1px solid #b8b8b8; - - // Tab list - make it look like Word ribbon tabs - .cds--tabs__nav { - background: #d8d8d8; - border-bottom: 1px solid #b8b8b8; - } - - .cds--tabs__nav-link { - font-size: 11px; - padding: 4px 12px; - min-height: 26px; - color: #222; - background: transparent; - } - - .cds--tabs__nav-item--selected .cds--tabs__nav-link { - color: #222; - background: #f5f5f5; - border-bottom: 2px solid #f5f5f5; - } - - .cds--tabs__nav-item:hover .cds--tabs__nav-link { - background: #c8c8c8; - } - - // Tab content area - flush, no extra padding - .cds--tab-content { - padding: 0; - background: #f5f5f5; - } -} - -.ribbon-panel { - display: flex; - flex-direction: row; - align-items: stretch; - min-height: 72px; - padding: 4px 4px 0; - flex-wrap: nowrap; - overflow-x: auto; -} - -.ribbon-chunk { - display: flex; - flex-direction: column; - align-items: center; - padding: 0 6px 3px; - position: relative; - min-width: 40px; -} - -.ribbon-chunk__controls { - display: flex; - flex-direction: column; - gap: 2px; - flex: 1; - width: 100%; -} - -.ribbon-chunk__label { - font-size: 10px; - color: #555; - white-space: nowrap; - margin-top: 3px; - padding-top: 2px; - border-top: 1px solid #c8c8c8; - width: 100%; - user-select: none; - display: flex; - align-items: center; - justify-content: center; - gap: 3px; -} - -.ribbon-chunk__launcher { - display: inline-flex; - align-items: center; - flex-shrink: 0; -} - -.ribbon-divider { - width: 1px; - background: #c0c0c0; - align-self: stretch; - margin: 4px 4px 6px; - flex-shrink: 0; -} - -.ribbon-row { - display: flex; - flex-direction: row; - align-items: center; - gap: 1px; - flex-wrap: nowrap; -} - -.ribbon-clipboard { - display: flex; - flex-direction: row; - align-items: flex-start; - gap: 2px; -} - -.ribbon-clipboard__paste { - // Large paste button - .cds--btn { - flex-direction: column; - height: 52px; - width: 44px; - padding: 2px 4px; - font-size: 11px; - gap: 2px; - - svg { - width: 20px; - height: 20px; - } - } -} - -.ribbon-clipboard__small { - display: flex; - flex-direction: column; - gap: 1px; - padding-top: 2px; -} - -.ribbon-select { - height: 22px; - border: 1px solid #ababab; - border-radius: 2px; - background: #fff; - font-size: 12px; - padding: 0 4px; - cursor: pointer; - min-width: 130px; - color: #161616; - - &:focus { - outline: 2px solid #0f62fe; - outline-offset: -2px; - } - - &--size { - min-width: 44px; - width: 44px; - } -} - -.ribbon-styles { - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 2px; - - .ribbon-style-btn.cds--btn { - min-height: unset; - padding: 3px 8px; - line-height: 1.2; - } -} - -.format-btn { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 26px; - min-height: 22px; - padding: 2px 4px; - background: transparent; - border: 1px solid transparent; - cursor: pointer; - border-radius: 2px; - color: #222; - line-height: 1; - - &:hover { - background: #dde0e5; - } - - &--active { - background: #cce0ff; - border-color: #0f62fe; - } - - &:focus-visible { - outline: 2px solid #0f62fe; - outline-offset: 1px; - } -} - -.format-btn__label { - font-family: 'Times New Roman', Georgia, serif; - font-size: 14px; - color: inherit; - line-height: 1; - - &--bold { - font-weight: bold; - } - - &--italic { - font-style: italic; - } - - &--underline { - text-decoration: underline; - } - - &--strikethrough { - text-decoration: line-through; - } - - &--highlight { - background-color: #ffff00; - padding: 0 2px; - } -} - -.word-ribbon .cds--btn--ghost { - min-height: 22px; - padding: 2px 4px; - color: #222; - - &:hover { - background: #dde0e5; - color: #222; - } - - svg { - fill: #222; - } -} - -.word-ribbon .cds--btn--primary { - min-height: 22px; - padding: 2px 4px; -} - -.line-spacing-dropdown { - position: relative; - display: inline-flex; -} - -.line-spacing-btn { - display: inline-flex; - align-items: center; - gap: 2px; - min-height: 22px; - padding: 2px 4px; - background: transparent; - border: none; - cursor: pointer; - color: #222; - border-radius: 0; - - &:hover { - background: #dde0e5; - } - - svg { - fill: #222; - } -} - -.line-spacing-menu { - position: fixed; - background: #fff; - border: 1px solid #c6c6c6; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); - z-index: 9999; - min-width: 64px; - padding: 4px 0; - list-style: none; - margin: 0; -} - -.line-spacing-menu-item { - display: block; - width: 100%; - padding: 5px 16px; - background: none; - border: none; - cursor: pointer; - text-align: left; - font-size: 13px; - color: #161616; - - &:hover { - background: #e8e8e8; - } - - &--active { - background: #0f62fe; - color: #fff; - - &:hover { - background: #0353e9; - } - } -} - -.change-case-dropdown { - position: relative; - display: inline-flex; -} - -.change-case-btn { - display: inline-flex; - align-items: center; - gap: 2px; - min-height: 22px; - padding: 2px 6px; - background: transparent; - border: 1px solid transparent; - cursor: pointer; - color: #222; - border-radius: 2px; - - &:hover { - background: #dde0e5; - } - - &:focus-visible { - outline: 2px solid #0f62fe; - outline-offset: 1px; - } - - svg { - fill: #222; - } -} - -.change-case-btn__text { - font-family: 'Times New Roman', Georgia, serif; - font-size: 13px; - line-height: 1; -} - -.change-case-menu { - position: fixed; - background: #fff; - border: 1px solid #c6c6c6; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); - z-index: 9999; - min-width: 180px; - padding: 4px 0; - list-style: none; - margin: 0; -} - -.change-case-menu-item { - display: block; - width: 100%; - padding: 6px 12px; - background: none; - border: none; - cursor: pointer; - text-align: left; - font-size: 13px; - color: #161616; - - &:hover { - background: #e8e8e8; - } -} - -.text-effects-dropdown, -.font-color-dropdown, -.highlight-color-dropdown { - position: relative; - display: inline-flex; -} - -.text-effects-btn, -.font-color-btn, -.highlight-color-btn { - display: inline-flex; - align-items: center; - gap: 2px; - min-height: 22px; - padding: 2px 6px; - background: transparent; - border: 1px solid transparent; - cursor: pointer; - color: #222; - border-radius: 2px; - - &:hover { - background: #dde0e5; - } - - &:focus-visible { - outline: 2px solid #0f62fe; - outline-offset: 1px; - } - - svg { - fill: #222; - } -} - -.text-effects-btn__text, -.font-color-btn__text, -.highlight-color-btn__text { - font-family: 'Times New Roman', Georgia, serif; - font-size: 13px; - line-height: 1; -} - -.font-color-btn, -.highlight-color-btn { - justify-content: center; - gap: 4px; - min-width: 34px; - padding: 2px 4px; -} - -.font-color-btn__glyph, -.highlight-color-btn__glyph { - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 0; -} - -.font-color-btn__bar, -.highlight-color-btn__bar { - width: 12px; - height: 2px; - margin-top: 1px; -} - -.font-color-btn__bar { - background: #0f62fe; -} - -.highlight-color-btn__bar { - background: #ffff00; -} - -.text-effects-menu, -.font-color-menu, -.highlight-color-menu { - position: fixed; - background: #fff; - border: 1px solid #c6c6c6; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); - z-index: 9999; - min-width: 180px; - padding: 4px 0; - list-style: none; - margin: 0; -} - -.text-effects-menu-item, -.font-color-menu-item, -.highlight-color-menu-item { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - padding: 6px 12px; - background: none; - border: none; - cursor: pointer; - text-align: left; - font-size: 13px; - color: #161616; - - &:hover { - background: #e8e8e8; - } -} - -.color-option-swatch { - width: 12px; - height: 12px; - border: 1px solid #8d8d8d; - flex-shrink: 0; -} - -.citation-style-launcher { - display: inline-flex; - align-items: center; -} - -.citation-style-launcher-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - padding: 0; - background: transparent; - border: none; - cursor: pointer; - color: #555; - border-radius: 2px; - - &:hover { - background: #c8c8c8; - color: #161616; - } -} - -.citation-style-menu { - position: fixed; - background: #fff; - border: 1px solid #c6c6c6; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); - z-index: 9999; - min-width: 120px; - padding: 4px 0; - list-style: none; - margin: 0; -} - -.citation-style-menu-item { - display: block; - width: 100%; - padding: 6px 16px; - background: none; - border: none; - cursor: pointer; - text-align: left; - font-size: 13px; - color: #161616; - - &:hover { - background: #e8e8e8; - } - - &--active { - background: #0f62fe; - color: #fff; - - &:hover { - background: #0353e9; - } - } -} 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 370a04e..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; @@ -13,6 +14,55 @@ interface DocumentEditorProps { } const INITIAL_CONTENT = ''; +const BLOCK_LEVEL_TAGS = new Set([ + 'DIV', + 'P', + 'H1', + 'H2', + 'H3', + 'H4', + 'H5', + 'H6', + 'UL', + 'OL', + 'LI', + 'BLOCKQUOTE', + 'PRE', + 'TABLE', + 'HR', +]); + +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 the editable surface. + if (!el.firstChild) { + const line = document.createElement('div'); + line.appendChild(document.createElement('br')); + el.appendChild(line); + return; + } + + // If content is inline/text at the root level, keep it in one inline parent + // to avoid creating a new block line on each keystroke. + const hasBlockChild = Array.from(el.childNodes).some((child) => isBlockNode(child)); + if (!hasBlockChild) { + const isSingleInlineWrapper = + el.childNodes.length === 1 && + el.firstChild?.nodeType === Node.ELEMENT_NODE && + (el.firstChild as Element).tagName === 'SPAN'; + + if (!isSingleInlineWrapper) { + const wrapper = document.createElement('span'); + wrapper.setAttribute('data-root-inline', '1'); + while (el.firstChild) { + wrapper.appendChild(el.firstChild); + } + el.appendChild(wrapper); + } + } +}; const DocumentEditor = forwardRef( ({ onWordCountChange, onAutosave, onAutosaveStart, autosaveEnabled = true }, ref) => { @@ -26,11 +76,13 @@ const DocumentEditor = forwardRef( const saved = localStorage.getItem(AUTOSAVE_KEY); if (saved) { el.innerHTML = saved; + ensureRootContainer(el); const text = el.innerText; const words = text.trim() ? text.trim().split(/\s+/).length : 0; onWordCountChange?.(words); } else if (!el.innerHTML) { el.innerHTML = INITIAL_CONTENT; + ensureRootContainer(el); } // Flush any pending save and persist the current content on unmount. @@ -138,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/HelpTabPanel.tsx b/src/components/Ribbon/HelpTabPanel.tsx index 21578a0..fb14c3c 100644 --- a/src/components/Ribbon/HelpTabPanel.tsx +++ b/src/components/Ribbon/HelpTabPanel.tsx @@ -1,5 +1,5 @@ import { Button, TabPanel } from '@carbon/react'; -import { RibbonChunk } from './RibbonControls'; +import { RibbonChunk } from './RibbonChunk'; interface HelpTabPanelProps { onFeatureRequest: () => void; diff --git a/src/components/Ribbon/HomeTabPanel.tsx b/src/components/Ribbon/HomeTabPanel.tsx deleted file mode 100644 index 7d895bf..0000000 --- a/src/components/Ribbon/HomeTabPanel.tsx +++ /dev/null @@ -1,332 +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, - RibbonChunk, - RibbonDivider, - TextEffectsDropdown, -} from './RibbonControls'; -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" + /> + + + + + + + + + +
+ +
+