diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 9b944f712..faf91b29d 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -20,7 +20,7 @@ import { } from '@sd/interface'; import { getSpacedropState } from '@sd/interface/hooks/useSpacedropState'; -import '@sd/ui/style'; +import '@sd/ui/style/style.scss'; import * as commands from './commands'; import { createUpdater } from './updater'; diff --git a/apps/storybook/.storybook/preview.ts b/apps/storybook/.storybook/preview.ts index 1384b87e1..eb1ab4236 100644 --- a/apps/storybook/.storybook/preview.ts +++ b/apps/storybook/.storybook/preview.ts @@ -1,6 +1,6 @@ import type { Preview } from '@storybook/react'; -import '@sd/ui/style'; +import '@sd/ui/style.scss'; const preview: Preview = { parameters: { diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index fd460a22c..d4c554b75 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -2,7 +2,7 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom/client'; -import '@sd/ui/style'; +import '@sd/ui/style/style.scss'; import '~/patches'; import App from './App'; diff --git a/interface/components/PasswordMeter.tsx b/interface/components/PasswordMeter.tsx index 5f7c6aa72..0ff466d6c 100644 --- a/interface/components/PasswordMeter.tsx +++ b/interface/components/PasswordMeter.tsx @@ -1,12 +1,29 @@ import clsx from 'clsx'; -import { getPasswordStrength } from '@sd/client'; +import { useEffect, useState } from 'react'; +import { type getPasswordStrength } from '@sd/client'; export interface PasswordMeterProps { password: string; } export const PasswordMeter = (props: PasswordMeterProps) => { - const { score, scoreText } = getPasswordStrength(props.password); + const [getStrength, setGetStrength] = useState(); + const { score, scoreText } = getStrength + ? getStrength(props.password) + : { score: 0, scoreText: 'Loading...' }; + + useEffect(() => { + let cancelled = false; + + import('@sd/client').then(({ getPasswordStrength }) => { + if (cancelled) return; + setGetStrength(() => getPasswordStrength); + }); + + return () => { + cancelled = true; + }; + }, []); return (
diff --git a/interface/package.json b/interface/package.json index f59d715e6..6da3afa45 100644 --- a/interface/package.json +++ b/interface/package.json @@ -3,6 +3,7 @@ "private": true, "main": "index.tsx", "types": "index.tsx", + "sideEffects": false, "scripts": { "lint": "eslint . --cache", "typecheck": "tsc -b" @@ -10,7 +11,7 @@ "dependencies": { "@fontsource/inter": "^4.5.13", "@headlessui/react": "^1.7.3", - "@icons-pack/react-simple-icons": "^7.2.0", + "@icons-pack/react-simple-icons": "^9.1.0", "@phosphor-icons/react": "^2.0.10", "@radix-ui/react-progress": "^1.0.1", "@radix-ui/react-slider": "^1.1.0", diff --git a/packages/assets/package.json b/packages/assets/package.json index 1ace082cf..09330f2b6 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "license": "GPL-3.0-only", "private": true, + "sideEffects": false, "scripts": { "gen": "node ./scripts/generate.mjs" } diff --git a/packages/assets/svgs/brands/index.ts b/packages/assets/svgs/brands/index.ts index 7c774c76c..ae8850504 100644 --- a/packages/assets/svgs/brands/index.ts +++ b/packages/assets/svgs/brands/index.ts @@ -3,6 +3,8 @@ * To regenerate this file, run: pnpm assets gen */ +'use client'; + import { ReactComponent as Academia } from './Academia.svg'; import { ReactComponent as Apple } from './apple.svg'; import { ReactComponent as Discord } from './Discord.svg'; diff --git a/packages/client/package.json b/packages/client/package.json index ad49e7662..13ddb430a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,7 @@ { "name": "@sd/client", "private": true, + "sideEffects": false, "main": "./src/index.ts", "types": "./src/index.ts", "scripts": { diff --git a/packages/ui/package.json b/packages/ui/package.json index 643162b20..10df02533 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -4,6 +4,7 @@ "license": "GPL-3.0-only", "main": "src/index.ts", "types": "src/index.ts", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./src/forms": "./src/forms/index.ts", diff --git a/packages/ui/src/Button.tsx b/packages/ui/src/Button.tsx index 53016bc80..87712393b 100644 --- a/packages/ui/src/Button.tsx +++ b/packages/ui/src/Button.tsx @@ -1,9 +1,11 @@ +'use client'; + import { cva, cx, VariantProps } from 'class-variance-authority'; import clsx from 'clsx'; import { ComponentProps, forwardRef } from 'react'; import { Link } from 'react-router-dom'; -export interface ButtonBaseProps extends VariantProps {} +export interface ButtonBaseProps extends VariantProps {} export type ButtonProps = ButtonBaseProps & React.ButtonHTMLAttributes & { @@ -22,7 +24,7 @@ type Button = { const hasHref = (props: ButtonProps | LinkButtonProps): props is LinkButtonProps => 'href' in props; -export const styles = cva( +export const buttonStyles = cva( [ 'cursor-default items-center rounded-md border outline-none transition-colors duration-100', 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-70', @@ -73,7 +75,7 @@ export const Button = forwardRef< HTMLButtonElement | HTMLAnchorElement, ButtonProps | LinkButtonProps >(({ className, ...props }, ref) => { - className = cx(styles(props), className); + className = cx(buttonStyles(props), className); return hasHref(props) ? ( ) : ( @@ -88,7 +90,7 @@ export const ButtonLink = forwardRef< return ( { + const [zxcvbn, setZxcvbn] = useState(); + const [strength, setStrength] = useState<{ label: string; score: number }>(); - const updateStrength = useDebouncedCallback( - () => setStrength(props.password ? getPasswordStrength(props.password) : undefined), - 100 - ); + const updateStrength = useDebouncedCallback(() => { + if (!zxcvbn) return; + + setStrength(props.password ? getPasswordStrength(props.password, zxcvbn) : undefined); + }, 100); // TODO: Remove duplicate in @sd/client - function getPasswordStrength(password: string): { label: string; score: number } { + function getPasswordStrength( + password: string, + zxcvbn: typeof import('./zxcvbn') + ): { label: string; score: number } { const ratings = ['Poor', 'Weak', 'Good', 'Strong', 'Perfect']; - zxcvbnOptions.setOptions({ + zxcvbn.zxcvbnOptions.setOptions({ dictionary: { - ...zxcvbnCommonPackage.dictionary, - ...zxcvbnEnPackage.dictionary + ...zxcvbn.languageCommon.dictionary, + ...zxcvbn.languageEn.dictionary }, - graphs: zxcvbnCommonPackage.adjacencyGraphs, - translations: zxcvbnEnPackage.translations + graphs: zxcvbn.languageCommon.adjacencyGraphs, + translations: zxcvbn.languageEn.translations }); - const result = zxcvbn(password); + const result = zxcvbn.zxcvbn(password); return { label: ratings[result.score]!, score: result.score }; } + useEffect(() => { + let cancelled = false; + + import('./zxcvbn').then((zxcvbn) => { + if (cancelled) return; + setZxcvbn(zxcvbn); + }); + + return () => { + cancelled = true; + }; + }, []); + useEffect(() => updateStrength(), [props.password, updateStrength]); return ( diff --git a/packages/ui/src/forms/index.ts b/packages/ui/src/forms/index.ts index f1b0715b0..4c93d3e33 100644 --- a/packages/ui/src/forms/index.ts +++ b/packages/ui/src/forms/index.ts @@ -1,3 +1,5 @@ +'use client'; + export * from './Form'; export * from './FormField'; export * from './CheckBoxField'; diff --git a/packages/ui/src/forms/zxcvbn.ts b/packages/ui/src/forms/zxcvbn.ts new file mode 100644 index 000000000..c382a2f3d --- /dev/null +++ b/packages/ui/src/forms/zxcvbn.ts @@ -0,0 +1,3 @@ +export { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core'; +export { default as languageCommon } from '@zxcvbn-ts/language-common'; +export { default as languageEn } from '@zxcvbn-ts/language-en'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 792d05f9a..ba4b3a411 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ