diff --git a/packages/component-library/package.json b/packages/component-library/package.json new file mode 100644 index 00000000000..97816f502a5 --- /dev/null +++ b/packages/component-library/package.json @@ -0,0 +1,24 @@ +{ + "name": "@actual-app/components", + "version": "0.0.1", + "license": "MIT", + "peerDependencies": { + "react": ">=18.2" + }, + "dependencies": { + "@emotion/css": "^11.13.4", + "react-aria-components": "^1.4.1" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "react": "18.2.0" + }, + "exports": { + "./icons/*": "./src/icons/*.tsx", + "./button": "./src/Button.tsx", + "./styles": "./src/styles.ts", + "./theme": "./src/theme.ts", + "./tokens": "./src/tokens.ts", + "./view": "./src/View.tsx" + } +} diff --git a/packages/desktop-client/src/components/common/Button2.tsx b/packages/component-library/src/Button.tsx similarity index 98% rename from packages/desktop-client/src/components/common/Button2.tsx rename to packages/component-library/src/Button.tsx index 7fda685aec2..a62469d5c73 100644 --- a/packages/desktop-client/src/components/common/Button2.tsx +++ b/packages/component-library/src/Button.tsx @@ -9,9 +9,9 @@ import { Button as ReactAriaButton } from 'react-aria-components'; import { css } from '@emotion/css'; -import { AnimatedLoading } from '../../icons/AnimatedLoading'; -import { styles, theme } from '../../style'; - +import { AnimatedLoading } from './icons/AnimatedLoading'; +import { styles } from './styles'; +import { theme } from './theme'; import { View } from './View'; const backgroundColor: { diff --git a/packages/desktop-client/src/components/common/View.tsx b/packages/component-library/src/View.tsx similarity index 94% rename from packages/desktop-client/src/components/common/View.tsx rename to packages/component-library/src/View.tsx index a74c3271219..5c976098003 100644 --- a/packages/desktop-client/src/components/common/View.tsx +++ b/packages/component-library/src/View.tsx @@ -2,7 +2,7 @@ import React, { forwardRef, type HTMLProps, type Ref } from 'react'; import { css, cx } from '@emotion/css'; -import { type CSSProperties } from '../../style'; +import { type CSSProperties } from './styles'; type ViewProps = HTMLProps & { className?: string; diff --git a/packages/component-library/src/icons/AnimatedLoading.tsx b/packages/component-library/src/icons/AnimatedLoading.tsx new file mode 100644 index 00000000000..e6272195cec --- /dev/null +++ b/packages/component-library/src/icons/AnimatedLoading.tsx @@ -0,0 +1,26 @@ +import React, { type SVGProps } from 'react'; + +import { css, keyframes } from '@emotion/css'; + +import { SvgLoading } from './Loading'; + +const rotation = keyframes({ + '0%': { transform: 'rotate(-90deg)' }, + '100%': { transform: 'rotate(666deg)' }, +}); + +export function AnimatedLoading(props: SVGProps) { + return ( + + + + ); +} diff --git a/packages/component-library/src/icons/Loading.tsx b/packages/component-library/src/icons/Loading.tsx new file mode 100644 index 00000000000..6012c53db38 --- /dev/null +++ b/packages/component-library/src/icons/Loading.tsx @@ -0,0 +1,33 @@ +import React, { type SVGProps, useState } from 'react'; + +export const SvgLoading = (props: SVGProps) => { + const { color = 'currentColor' } = props; + const [gradientId] = useState('gradient-' + Math.random()); + + return ( + + + + + + + + + + + + + + ); +}; diff --git a/packages/component-library/src/styles.ts b/packages/component-library/src/styles.ts new file mode 100644 index 00000000000..c5ac0e22b56 --- /dev/null +++ b/packages/component-library/src/styles.ts @@ -0,0 +1,151 @@ +import { keyframes } from '@emotion/css'; + +import { theme } from './theme'; +import { tokens } from './tokens'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type CSSProperties = Record; + +const MOBILE_MIN_HEIGHT = 40; + +const shadowLarge = { + boxShadow: '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)', +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const styles: Record = { + incomeHeaderHeight: 70, + cardShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)', + monthRightPadding: 5, + menuBorderRadius: 4, + mobileMinHeight: MOBILE_MIN_HEIGHT, + mobileMenuItem: { + fontSize: 17, + fontWeight: 400, + paddingTop: 8, + paddingBottom: 8, + height: MOBILE_MIN_HEIGHT, + minHeight: MOBILE_MIN_HEIGHT, + }, + mobileEditingPadding: 12, + altMenuMaxHeight: 250, + altMenuText: { + fontSize: 13, + }, + altMenuHeaderText: { + fontSize: 13, + fontWeight: 700, + }, + veryLargeText: { + fontSize: 30, + fontWeight: 600, + }, + largeText: { + fontSize: 20, + fontWeight: 700, + letterSpacing: 0.5, + }, + mediumText: { + fontSize: 15, + fontWeight: 500, + }, + smallText: { + fontSize: 13, + }, + verySmallText: { + fontSize: 12, + }, + tinyText: { + fontSize: 10, + }, + page: { + flex: 1, + '@media (max-height: 550px)': { + minHeight: 700, // ensure we can scroll on small screens + }, + paddingTop: 8, // height of the titlebar + [`@media (min-width: ${tokens.breakpoint_small})`]: { + paddingTop: 36, + }, + }, + pageContent: { + paddingLeft: 2, + paddingRight: 2, + [`@media (min-width: ${tokens.breakpoint_small})`]: { + paddingLeft: 20, + paddingRight: 20, + }, + }, + settingsPageContent: { + padding: 20, + [`@media (min-width: ${tokens.breakpoint_small})`]: { + padding: 'inherit', + }, + }, + staticText: { + cursor: 'default', + userSelect: 'none', + }, + shadow: { + boxShadow: '0 2px 4px 0 rgba(0,0,0,0.1)', + }, + shadowLarge, + tnum: { + // eslint-disable-next-line rulesdir/typography + fontFeatureSettings: '"tnum"', + }, + notFixed: { fontFeatureSettings: '' }, + text: { + fontSize: 16, + // lineHeight: 22.4 // TODO: This seems like trouble, but what's the right value? + }, + delayedFadeIn: { + animationName: keyframes({ + '0%': { opacity: 0 }, + '100%': { opacity: 1 }, + }), + animationDuration: '1s', + animationFillMode: 'both', + animationDelay: '0.5s', + }, + underlinedText: { + borderBottom: `2px solid`, + }, + noTapHighlight: { + WebkitTapHighlightColor: 'transparent', + ':focus': { + outline: 'none', + }, + }, + lineClamp: (lines: number) => { + return { + display: '-webkit-box', + WebkitLineClamp: lines, + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + textOverflow: 'ellipsis', + wordBreak: 'break-word', + }; + }, + tooltip: { + padding: 5, + ...shadowLarge, + borderWidth: 2, + borderRadius: 4, + borderStyle: 'solid', + borderColor: theme.tooltipBorder, + backgroundColor: theme.tooltipBackground, + color: theme.tooltipText, + overflow: 'auto', + }, + popover: { + border: 'none', + backgroundColor: theme.menuBackground, + color: theme.menuItemText, + }, + // Dynamically set + horizontalScrollbar: null as CSSProperties | null, + lightScrollbar: null as CSSProperties | null, + darkScrollbar: null as CSSProperties | null, + scrollbarWidth: null as number | null, +}; diff --git a/packages/component-library/src/theme.ts b/packages/component-library/src/theme.ts new file mode 100644 index 00000000000..b6662d8e6c4 --- /dev/null +++ b/packages/component-library/src/theme.ts @@ -0,0 +1,203 @@ +export const theme = { + pageBackground: 'var(--color-pageBackground)', + pageBackgroundModalActive: 'var(--color-pageBackgroundModalActive)', + pageBackgroundTopLeft: 'var(--color-pageBackgroundTopLeft)', + pageBackgroundBottomRight: 'var(--color-pageBackgroundBottomRight)', + pageBackgroundLineTop: 'var(--color-pageBackgroundLineTop)', + pageBackgroundLineMid: 'var(--color-pageBackgroundLineMid)', + pageBackgroundLineBottom: 'var(--color-pageBackgroundLineBottom)', + pageText: 'var(--color-pageText)', + pageTextLight: 'var(--color-pageTextLight)', + pageTextSubdued: 'var(--color-pageTextSubdued)', + pageTextDark: 'var(--color-pageTextDark)', + pageTextPositive: 'var(--color-pageTextPositive)', + pageTextLink: 'var(--color-pageTextLink)', + pageTextLinkLight: 'var(--color-pageTextLinkLight)', + cardBackground: 'var(--color-cardBackground)', + cardBorder: 'var(--color-cardBorder)', + cardShadow: 'var(--color-cardShadow)', + tableBackground: 'var(--color-tableBackground)', + tableRowBackgroundHover: 'var(--color-tableRowBackgroundHover)', + tableText: 'var(--color-tableText)', + tableTextLight: 'var(--color-tableTextLight)', + tableTextSubdued: 'var(--color-tableTextSubdued)', + tableTextSelected: 'var(--color-tableTextSelected)', + tableTextHover: 'var(--color-tableTextHover)', + tableTextInactive: 'var(--color-tableTextInactive)', + tableHeaderText: 'var(--color-tableHeaderText)', + tableHeaderBackground: 'var(--color-tableHeaderBackground)', + tableBorder: 'var(--color-tableBorder)', + tableBorderSelected: 'var(--color-tableBorderSelected)', + tableBorderHover: 'var(--color-tableBorderHover)', + tableBorderSeparator: 'var(--color-tableBorderSeparator)', + tableRowBackgroundHighlight: 'var(--color-tableRowBackgroundHighlight)', + tableRowBackgroundHighlightText: + 'var(--color-tableRowBackgroundHighlightText)', + tableRowHeaderBackground: 'var(--color-tableRowHeaderBackground)', + tableRowHeaderText: 'var(--color-tableRowHeaderText)', + sidebarBackground: 'var(--color-sidebarBackground)', + sidebarItemBackgroundPending: 'var(--color-sidebarItemBackgroundPending)', + sidebarItemBackgroundPositive: 'var(--color-sidebarItemBackgroundPositive)', + sidebarItemBackgroundFailed: 'var(--color-sidebarItemBackgroundFailed)', + sidebarItemAccentSelected: 'var(--color-sidebarItemAccentSelected)', + sidebarItemBackgroundHover: 'var(--color-sidebarItemBackgroundHover)', + sidebarItemText: 'var(--color-sidebarItemText)', + sidebarItemTextSelected: 'var(--color-sidebarItemTextSelected)', + menuBackground: 'var(--color-menuBackground)', + menuItemBackground: 'var(--color-menuItemBackground)', + menuItemBackgroundHover: 'var(--color-menuItemBackgroundHover)', + menuItemText: 'var(--color-menuItemText)', + menuItemTextHover: 'var(--color-menuItemTextHover)', + menuItemTextSelected: 'var(--color-menuItemTextSelected)', + menuItemTextHeader: 'var(--color-menuItemTextHeader)', + menuBorder: 'var(--color-menuBorder)', + menuBorderHover: 'var(--color-menuBorderHover)', + menuKeybindingText: 'var(--color-menuKeybindingText)', + menuAutoCompleteBackground: 'var(--color-menuAutoCompleteBackground)', + menuAutoCompleteBackgroundHover: + 'var(--color-menuAutoCompleteBackgroundHover)', + menuAutoCompleteText: 'var(--color-menuAutoCompleteText)', + menuAutoCompleteTextHover: 'var(--color-menuAutoCompleteTextHover)', + menuAutoCompleteTextHeader: 'var(--color-menuAutoCompleteTextHeader)', + menuAutoCompleteItemTextHover: 'var(--color-menuAutoCompleteItemTextHover)', + menuAutoCompleteItemText: 'var(--color-menuAutoCompleteItemText)', + modalBackground: 'var(--color-modalBackground)', + modalBorder: 'var(--color-modalBorder)', + mobileHeaderBackground: 'var(--color-mobileHeaderBackground)', + mobileHeaderText: 'var(--color-mobileHeaderText)', + mobileHeaderTextSubdued: 'var(--color-mobileHeaderTextSubdued)', + mobileHeaderTextHover: 'var(--color-mobileHeaderTextHover)', + mobilePageBackground: 'var(--color-mobilePageBackground)', + mobileNavBackground: 'var(--color-mobileNavBackground)', + mobileNavItem: 'var(--color-mobileNavItem)', + mobileNavItemSelected: 'var(--color-mobileNavItemSelected)', + mobileAccountShadow: 'var(--color-mobileAccountShadow)', + mobileAccountText: 'var(--color-mobileAccountText)', + mobileTransactionSelected: 'var(--color-mobileTransactionSelected)', + mobileViewTheme: 'var(--color-mobileViewTheme)', + mobileConfigServerViewTheme: 'var(--color-mobileConfigServerViewTheme)', + markdownNormal: 'var(--color-markdownNormal)', + markdownDark: 'var(--color-markdownDark)', + markdownLight: 'var(--color-markdownLight)', + buttonMenuText: 'var(--color-buttonMenuText)', + buttonMenuTextHover: 'var(--color-buttonMenuTextHover)', + buttonMenuBackground: 'var(--color-buttonMenuBackground)', + buttonMenuBackgroundHover: 'var(--color-buttonMenuBackgroundHover)', + buttonMenuBorder: 'var(--color-buttonMenuBorder)', + buttonMenuSelectedText: 'var(--color-buttonMenuSelectedText)', + buttonMenuSelectedTextHover: 'var(--color-buttonMenuSelectedTextHover)', + buttonMenuSelectedBackground: 'var(--color-buttonMenuSelectedBackground)', + buttonMenuSelectedBackgroundHover: + 'var(--color-buttonMenuSelectedBackgroundHover)', + buttonMenuSelectedBorder: 'var(--color-buttonMenuSelectedBorder)', + buttonPrimaryText: 'var(--color-buttonPrimaryText)', + buttonPrimaryTextHover: 'var(--color-buttonPrimaryTextHover)', + buttonPrimaryBackground: 'var(--color-buttonPrimaryBackground)', + buttonPrimaryBackgroundHover: 'var(--color-buttonPrimaryBackgroundHover)', + buttonPrimaryBorder: 'var(--color-buttonPrimaryBorder)', + buttonPrimaryShadow: 'var(--color-buttonPrimaryShadow)', + buttonPrimaryDisabledText: 'var(--color-buttonPrimaryDisabledText)', + buttonPrimaryDisabledBackground: + 'var(--color-buttonPrimaryDisabledBackground)', + buttonPrimaryDisabledBorder: 'var(--color-buttonPrimaryDisabledBorder)', + buttonNormalText: 'var(--color-buttonNormalText)', + buttonNormalTextHover: 'var(--color-buttonNormalTextHover)', + buttonNormalBackground: 'var(--color-buttonNormalBackground)', + buttonNormalBackgroundHover: 'var(--color-buttonNormalBackgroundHover)', + buttonNormalBorder: 'var(--color-buttonNormalBorder)', + buttonNormalShadow: 'var(--color-buttonNormalShadow)', + buttonNormalSelectedText: 'var(--color-buttonNormalSelectedText)', + buttonNormalSelectedBackground: 'var(--color-buttonNormalSelectedBackground)', + buttonNormalDisabledText: 'var(--color-buttonNormalDisabledText)', + buttonNormalDisabledBackground: 'var(--color-buttonNormalDisabledBackground)', + buttonNormalDisabledBorder: 'var(--color-buttonNormalDisabledBorder)', + buttonBareText: 'var(--color-buttonBareText)', + buttonBareTextHover: 'var(--color-buttonBareTextHover)', + buttonBareBackground: 'var(--color-buttonBareBackground)', + buttonBareBackgroundHover: 'var(--color-buttonBareBackgroundHover)', + buttonBareBackgroundActive: 'var(--color-buttonBareBackgroundActive)', + buttonBareDisabledText: 'var(--color-buttonBareDisabledText)', + buttonBareDisabledBackground: 'var(--color-buttonBareDisabledBackground)', + calendarText: 'var(--color-calendarText)', + calendarBackground: 'var(--color-calendarBackground)', + calendarItemText: 'var(--color-calendarItemText)', + calendarItemBackground: 'var(--color-calendarItemBackground)', + calendarSelectedBackground: 'var(--color-calendarSelectedBackground)', + noticeBackground: 'var(--color-noticeBackground)', + noticeBackgroundLight: 'var(--color-noticeBackgroundLight)', + noticeBackgroundDark: 'var(--color-noticeBackgroundDark)', + noticeText: 'var(--color-noticeText)', + noticeTextLight: 'var(--color-noticeTextLight)', + noticeTextDark: 'var(--color-noticeTextDark)', + noticeTextMenu: 'var(--color-noticeTextMenu)', + noticeTextMenuHover: 'var(--color-noticeTextMenuHover)', + noticeBorder: 'var(--color-noticeBorder)', + warningBackground: 'var(--color-warningBackground)', + warningText: 'var(--color-warningText)', + warningTextLight: 'var(--color-warningTextLight)', + warningTextDark: 'var(--color-warningTextDark)', + warningBorder: 'var(--color-warningBorder)', + errorBackground: 'var(--color-errorBackground)', + errorText: 'var(--color-errorText)', + errorTextDark: 'var(--color-errorTextDark)', + errorTextDarker: 'var(--color-errorTextDarker)', + errorTextMenu: 'var(--color-errorTextMenu)', + errorBorder: 'var(--color-errorBorder)', + upcomingBackground: 'var(--color-upcomingBackground)', + upcomingText: 'var(--color-upcomingText)', + upcomingBorder: 'var(--color-upcomingBorder)', + formLabelText: 'var(--color-formLabelText)', + formLabelBackground: 'var(--color-formLabelBackground)', + formInputBackground: 'var(--color-formInputBackground)', + formInputBackgroundSelected: 'var(--color-formInputBackgroundSelected)', + formInputBackgroundSelection: 'var(--color-formInputBackgroundSelection)', + formInputBorder: 'var(--color-formInputBorder)', + formInputTextReadOnlySelection: 'var(--color-formInputTextReadOnlySelection)', + formInputBorderSelected: 'var(--color-formInputBorderSelected)', + formInputText: 'var(--color-formInputText)', + formInputTextSelected: 'var(--color-formInputTextSelected)', + formInputTextPlaceholder: 'var(--color-formInputTextPlaceholder)', + formInputTextPlaceholderSelected: + 'var(--color-formInputTextPlaceholderSelected)', + formInputTextSelection: 'var(--color-formInputTextSelection)', + formInputShadowSelected: 'var(--color-formInputShadowSelected)', + formInputTextHighlight: 'var(--color-formInputTextHighlight)', + checkboxText: 'var(--color-checkboxText)', + checkboxBackgroundSelected: 'var(--color-checkboxBackgroundSelected)', + checkboxBorderSelected: 'var(--color-checkboxBorderSelected)', + checkboxShadowSelected: 'var(--color-checkboxShadowSelected)', + checkboxToggleBackground: 'var(--color-checkboxToggleBackground)', + checkboxToggleBackgroundSelected: + 'var(--color-checkboxToggleBackgroundSelected)', + checkboxToggleDisabled: 'var(--color-checkboxToggleDisabled)', + pillBackground: 'var(--color-pillBackground)', + pillBackgroundLight: 'var(--color-pillBackgroundLight)', + pillText: 'var(--color-pillText)', + pillTextHighlighted: 'var(--color-pillTextHighlighted)', + pillBorder: 'var(--color-pillBorder)', + pillBorderDark: 'var(--color-pillBorderDark)', + pillBackgroundSelected: 'var(--color-pillBackgroundSelected)', + pillTextSelected: 'var(--color-pillTextSelected)', + pillBorderSelected: 'var(--color-pillBorderSelected)', + pillTextSubdued: 'var(--color-pillTextSubdued)', + reportsRed: 'var(--color-reportsRed)', + reportsBlue: 'var(--color-reportsBlue)', + reportsGreen: 'var(--color-reportsGreen)', + reportsGray: 'var(--color-reportsGray)', + reportsLabel: 'var(--color-reportsLabel)', + reportsInnerLabel: 'var(--color-reportsInnerLabel)', + noteTagBackground: 'var(--color-noteTagBackground)', + noteTagBackgroundHover: 'var(--color-noteTagBackgroundHover)', + noteTagText: 'var(--color-noteTagText)', + budgetOtherMonth: 'var(--color-budgetOtherMonth)', + budgetCurrentMonth: 'var(--color-budgetCurrentMonth)', + budgetHeaderOtherMonth: 'var(--color-budgetHeaderOtherMonth)', + budgetHeaderCurrentMonth: 'var(--color-budgetHeaderCurrentMonth)', + floatingActionBarBackground: 'var(--color-floatingActionBarBackground)', + floatingActionBarBorder: 'var(--color-floatingActionBarBorder)', + floatingActionBarText: 'var(--color-floatingActionBarText)', + tooltipText: 'var(--color-tooltipText)', + tooltipBackground: 'var(--color-tooltipBackground)', + tooltipBorder: 'var(--color-tooltipBorder)', + calendarCellBackground: 'var(--color-calendarCellBackground)', +}; diff --git a/packages/component-library/src/tokens.ts b/packages/component-library/src/tokens.ts new file mode 100644 index 00000000000..9c5d1facabe --- /dev/null +++ b/packages/component-library/src/tokens.ts @@ -0,0 +1,35 @@ +enum BreakpointNames { + small = 'small', + medium = 'medium', + wide = 'wide', +} + +type NumericBreakpoints = { + [key in BreakpointNames]: number; +}; + +export const breakpoints: NumericBreakpoints = { + small: 512, + medium: 730, + wide: 1100, +}; + +type BreakpointsPx = { + [B in keyof NumericBreakpoints as `breakpoint_${B}`]: string; +}; + +// Provide the same breakpoints in a form usable by CSS media queries +// { +// breakpoint_small: '512px', +// breakpoint_medium: '740px', +// breakpoint_wide: '1100px', +// } +export const tokens: BreakpointsPx = Object.entries( + breakpoints, +).reduce( + (acc, [key, val]) => ({ + ...acc, + [`breakpoint_${key}`]: `${val}px`, + }), + {} as BreakpointsPx, +); diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 453de87d5ab..43336d5de03 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -6,6 +6,7 @@ "build" ], "devDependencies": { + "@actual-app/components": "workspace:*", "@emotion/css": "^11.13.4", "@fontsource/redacted-script": "^5.0.21", "@juggle/resize-observer": "^3.4.0", diff --git a/packages/desktop-client/src/components/FatalError.tsx b/packages/desktop-client/src/components/FatalError.tsx index 6ce18aecd46..7ef37440ed2 100644 --- a/packages/desktop-client/src/components/FatalError.tsx +++ b/packages/desktop-client/src/components/FatalError.tsx @@ -1,16 +1,17 @@ import React, { useState, type ReactNode } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import { Button } from '@actual-app/components/button'; +import { View } from '@actual-app/components/view'; + import { LazyLoadFailedError } from 'loot-core/src/shared/errors'; import { Block } from './common/Block'; -import { Button } from './common/Button2'; import { Link } from './common/Link'; import { Modal, ModalHeader } from './common/Modal'; import { Paragraph } from './common/Paragraph'; import { Stack } from './common/Stack'; import { Text } from './common/Text'; -import { View } from './common/View'; import { Checkbox } from './forms'; type AppError = Error & { diff --git a/packages/desktop-client/src/components/common/Button.tsx b/packages/desktop-client/src/components/common/Button.tsx index 0b169a56802..4ebfed37aa8 100644 --- a/packages/desktop-client/src/components/common/Button.tsx +++ b/packages/desktop-client/src/components/common/Button.tsx @@ -118,6 +118,7 @@ const _getActiveStyles = (type: ButtonType, bounce: boolean): CSSProperties => { } }; +/** @deprecated please use `import { Button } from '@actual-app/components/button';` */ export const Button = forwardRef( ( { diff --git a/packages/desktop-client/src/components/common/Button2.ts b/packages/desktop-client/src/components/common/Button2.ts new file mode 100644 index 00000000000..7871daccf4d --- /dev/null +++ b/packages/desktop-client/src/components/common/Button2.ts @@ -0,0 +1,10 @@ +import { + Button as ActualComponentButton, + ButtonWithLoading as ActualComponentButtonWithLoading, +} from '@actual-app/components/button'; + +/** @deprecated please import Button from '@actual-app/components/button' */ +export const Button = ActualComponentButton; + +/** @deprecated please import ButtonWithLoading from '@actual-app/components/button' */ +export const ButtonWithLoading = ActualComponentButtonWithLoading; diff --git a/packages/desktop-client/src/components/common/View.ts b/packages/desktop-client/src/components/common/View.ts new file mode 100644 index 00000000000..1de101a9a21 --- /dev/null +++ b/packages/desktop-client/src/components/common/View.ts @@ -0,0 +1,4 @@ +import { View as ActualComponentView } from '@actual-app/components/view'; + +/** @deprecated please import View from '@actual-app/components/view' */ +export const View = ActualComponentView; diff --git a/packages/desktop-client/src/icons/AnimatedLoading.tsx b/packages/desktop-client/src/icons/AnimatedLoading.tsx index e6272195cec..00b570495ce 100644 --- a/packages/desktop-client/src/icons/AnimatedLoading.tsx +++ b/packages/desktop-client/src/icons/AnimatedLoading.tsx @@ -1,26 +1 @@ -import React, { type SVGProps } from 'react'; - -import { css, keyframes } from '@emotion/css'; - -import { SvgLoading } from './Loading'; - -const rotation = keyframes({ - '0%': { transform: 'rotate(-90deg)' }, - '100%': { transform: 'rotate(666deg)' }, -}); - -export function AnimatedLoading(props: SVGProps) { - return ( - - - - ); -} +export * from '@actual-app/components/icons/AnimatedLoading'; diff --git a/packages/desktop-client/src/style/styles.ts b/packages/desktop-client/src/style/styles.ts index ae64ab7a715..36ec5e7a065 100644 --- a/packages/desktop-client/src/style/styles.ts +++ b/packages/desktop-client/src/style/styles.ts @@ -1,158 +1,12 @@ // @ts-strict-ignore -import { keyframes } from '@emotion/css'; +import { styles as baseStyles } from '@actual-app/components/styles'; import * as Platform from 'loot-core/src/client/platform'; -import { tokens } from '../tokens'; +export { type CSSProperties } from '@actual-app/components/styles'; -import { theme } from './theme'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type CSSProperties = Record; - -const MOBILE_MIN_HEIGHT = 40; - -const shadowLarge = { - boxShadow: '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)', -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const styles: Record = { - incomeHeaderHeight: 70, - cardShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)', - monthRightPadding: 5, - menuBorderRadius: 4, - mobileMinHeight: MOBILE_MIN_HEIGHT, - mobileMenuItem: { - fontSize: 17, - fontWeight: 400, - paddingTop: 8, - paddingBottom: 8, - height: MOBILE_MIN_HEIGHT, - minHeight: MOBILE_MIN_HEIGHT, - }, - mobileEditingPadding: 12, - altMenuMaxHeight: 250, - altMenuText: { - fontSize: 13, - }, - altMenuHeaderText: { - fontSize: 13, - fontWeight: 700, - }, - veryLargeText: { - fontSize: 30, - fontWeight: 600, - }, - largeText: { - fontSize: 20, - fontWeight: 700, - letterSpacing: 0.5, - }, - mediumText: { - fontSize: 15, - fontWeight: 500, - }, - smallText: { - fontSize: 13, - }, - verySmallText: { - fontSize: 12, - }, - tinyText: { - fontSize: 10, - }, - page: { - flex: 1, - '@media (max-height: 550px)': { - minHeight: 700, // ensure we can scroll on small screens - }, - paddingTop: 8, // height of the titlebar - [`@media (min-width: ${tokens.breakpoint_small})`]: { - paddingTop: 36, - }, - }, - pageContent: { - paddingLeft: 2, - paddingRight: 2, - [`@media (min-width: ${tokens.breakpoint_small})`]: { - paddingLeft: 20, - paddingRight: 20, - }, - }, - settingsPageContent: { - padding: 20, - [`@media (min-width: ${tokens.breakpoint_small})`]: { - padding: 'inherit', - }, - }, - staticText: { - cursor: 'default', - userSelect: 'none', - }, - shadow: { - boxShadow: '0 2px 4px 0 rgba(0,0,0,0.1)', - }, - shadowLarge, - tnum: { - // eslint-disable-next-line rulesdir/typography - fontFeatureSettings: '"tnum"', - }, - notFixed: { fontFeatureSettings: '' }, - text: { - fontSize: 16, - // lineHeight: 22.4 // TODO: This seems like trouble, but what's the right value? - }, - delayedFadeIn: { - animationName: keyframes({ - '0%': { opacity: 0 }, - '100%': { opacity: 1 }, - }), - animationDuration: '1s', - animationFillMode: 'both', - animationDelay: '0.5s', - }, - underlinedText: { - borderBottom: `2px solid`, - }, - noTapHighlight: { - WebkitTapHighlightColor: 'transparent', - ':focus': { - outline: 'none', - }, - }, - lineClamp: (lines: number) => { - return { - display: '-webkit-box', - WebkitLineClamp: lines, - WebkitBoxOrient: 'vertical', - overflow: 'hidden', - textOverflow: 'ellipsis', - wordBreak: 'break-word', - }; - }, - tooltip: { - padding: 5, - ...shadowLarge, - borderWidth: 2, - borderRadius: 4, - borderStyle: 'solid', - borderColor: theme.tooltipBorder, - backgroundColor: theme.tooltipBackground, - color: theme.tooltipText, - overflow: 'auto', - }, - popover: { - border: 'none', - backgroundColor: theme.menuBackground, - color: theme.menuItemText, - }, - // Dynamically set - horizontalScrollbar: null as CSSProperties | null, - lightScrollbar: null as CSSProperties | null, - darkScrollbar: null as CSSProperties | null, - scrollbarWidth: null as number | null, -}; +/** @deprecated please import styles from '@actual-app/components/styles' */ +export const styles = baseStyles; let hiddenScrollbars = false; diff --git a/packages/desktop-client/src/style/theme.tsx b/packages/desktop-client/src/style/theme.tsx index 1a8336c337e..095208adbc2 100644 --- a/packages/desktop-client/src/style/theme.tsx +++ b/packages/desktop-client/src/style/theme.tsx @@ -96,206 +96,4 @@ export function ThemeStyle() { return ; } -export const theme = { - pageBackground: 'var(--color-pageBackground)', - pageBackgroundModalActive: 'var(--color-pageBackgroundModalActive)', - pageBackgroundTopLeft: 'var(--color-pageBackgroundTopLeft)', - pageBackgroundBottomRight: 'var(--color-pageBackgroundBottomRight)', - pageBackgroundLineTop: 'var(--color-pageBackgroundLineTop)', - pageBackgroundLineMid: 'var(--color-pageBackgroundLineMid)', - pageBackgroundLineBottom: 'var(--color-pageBackgroundLineBottom)', - pageText: 'var(--color-pageText)', - pageTextLight: 'var(--color-pageTextLight)', - pageTextSubdued: 'var(--color-pageTextSubdued)', - pageTextDark: 'var(--color-pageTextDark)', - pageTextPositive: 'var(--color-pageTextPositive)', - pageTextLink: 'var(--color-pageTextLink)', - pageTextLinkLight: 'var(--color-pageTextLinkLight)', - cardBackground: 'var(--color-cardBackground)', - cardBorder: 'var(--color-cardBorder)', - cardShadow: 'var(--color-cardShadow)', - tableBackground: 'var(--color-tableBackground)', - tableRowBackgroundHover: 'var(--color-tableRowBackgroundHover)', - tableText: 'var(--color-tableText)', - tableTextLight: 'var(--color-tableTextLight)', - tableTextSubdued: 'var(--color-tableTextSubdued)', - tableTextSelected: 'var(--color-tableTextSelected)', - tableTextHover: 'var(--color-tableTextHover)', - tableTextInactive: 'var(--color-tableTextInactive)', - tableHeaderText: 'var(--color-tableHeaderText)', - tableHeaderBackground: 'var(--color-tableHeaderBackground)', - tableBorder: 'var(--color-tableBorder)', - tableBorderSelected: 'var(--color-tableBorderSelected)', - tableBorderHover: 'var(--color-tableBorderHover)', - tableBorderSeparator: 'var(--color-tableBorderSeparator)', - tableRowBackgroundHighlight: 'var(--color-tableRowBackgroundHighlight)', - tableRowBackgroundHighlightText: - 'var(--color-tableRowBackgroundHighlightText)', - tableRowHeaderBackground: 'var(--color-tableRowHeaderBackground)', - tableRowHeaderText: 'var(--color-tableRowHeaderText)', - sidebarBackground: 'var(--color-sidebarBackground)', - sidebarItemBackgroundPending: 'var(--color-sidebarItemBackgroundPending)', - sidebarItemBackgroundPositive: 'var(--color-sidebarItemBackgroundPositive)', - sidebarItemBackgroundFailed: 'var(--color-sidebarItemBackgroundFailed)', - sidebarItemAccentSelected: 'var(--color-sidebarItemAccentSelected)', - sidebarItemBackgroundHover: 'var(--color-sidebarItemBackgroundHover)', - sidebarItemText: 'var(--color-sidebarItemText)', - sidebarItemTextSelected: 'var(--color-sidebarItemTextSelected)', - menuBackground: 'var(--color-menuBackground)', - menuItemBackground: 'var(--color-menuItemBackground)', - menuItemBackgroundHover: 'var(--color-menuItemBackgroundHover)', - menuItemText: 'var(--color-menuItemText)', - menuItemTextHover: 'var(--color-menuItemTextHover)', - menuItemTextSelected: 'var(--color-menuItemTextSelected)', - menuItemTextHeader: 'var(--color-menuItemTextHeader)', - menuBorder: 'var(--color-menuBorder)', - menuBorderHover: 'var(--color-menuBorderHover)', - menuKeybindingText: 'var(--color-menuKeybindingText)', - menuAutoCompleteBackground: 'var(--color-menuAutoCompleteBackground)', - menuAutoCompleteBackgroundHover: - 'var(--color-menuAutoCompleteBackgroundHover)', - menuAutoCompleteText: 'var(--color-menuAutoCompleteText)', - menuAutoCompleteTextHover: 'var(--color-menuAutoCompleteTextHover)', - menuAutoCompleteTextHeader: 'var(--color-menuAutoCompleteTextHeader)', - menuAutoCompleteItemTextHover: 'var(--color-menuAutoCompleteItemTextHover)', - menuAutoCompleteItemText: 'var(--color-menuAutoCompleteItemText)', - modalBackground: 'var(--color-modalBackground)', - modalBorder: 'var(--color-modalBorder)', - mobileHeaderBackground: 'var(--color-mobileHeaderBackground)', - mobileHeaderText: 'var(--color-mobileHeaderText)', - mobileHeaderTextSubdued: 'var(--color-mobileHeaderTextSubdued)', - mobileHeaderTextHover: 'var(--color-mobileHeaderTextHover)', - mobilePageBackground: 'var(--color-mobilePageBackground)', - mobileNavBackground: 'var(--color-mobileNavBackground)', - mobileNavItem: 'var(--color-mobileNavItem)', - mobileNavItemSelected: 'var(--color-mobileNavItemSelected)', - mobileAccountShadow: 'var(--color-mobileAccountShadow)', - mobileAccountText: 'var(--color-mobileAccountText)', - mobileTransactionSelected: 'var(--color-mobileTransactionSelected)', - mobileViewTheme: 'var(--color-mobileViewTheme)', - mobileConfigServerViewTheme: 'var(--color-mobileConfigServerViewTheme)', - markdownNormal: 'var(--color-markdownNormal)', - markdownDark: 'var(--color-markdownDark)', - markdownLight: 'var(--color-markdownLight)', - buttonMenuText: 'var(--color-buttonMenuText)', - buttonMenuTextHover: 'var(--color-buttonMenuTextHover)', - buttonMenuBackground: 'var(--color-buttonMenuBackground)', - buttonMenuBackgroundHover: 'var(--color-buttonMenuBackgroundHover)', - buttonMenuBorder: 'var(--color-buttonMenuBorder)', - buttonMenuSelectedText: 'var(--color-buttonMenuSelectedText)', - buttonMenuSelectedTextHover: 'var(--color-buttonMenuSelectedTextHover)', - buttonMenuSelectedBackground: 'var(--color-buttonMenuSelectedBackground)', - buttonMenuSelectedBackgroundHover: - 'var(--color-buttonMenuSelectedBackgroundHover)', - buttonMenuSelectedBorder: 'var(--color-buttonMenuSelectedBorder)', - buttonPrimaryText: 'var(--color-buttonPrimaryText)', - buttonPrimaryTextHover: 'var(--color-buttonPrimaryTextHover)', - buttonPrimaryBackground: 'var(--color-buttonPrimaryBackground)', - buttonPrimaryBackgroundHover: 'var(--color-buttonPrimaryBackgroundHover)', - buttonPrimaryBorder: 'var(--color-buttonPrimaryBorder)', - buttonPrimaryShadow: 'var(--color-buttonPrimaryShadow)', - buttonPrimaryDisabledText: 'var(--color-buttonPrimaryDisabledText)', - buttonPrimaryDisabledBackground: - 'var(--color-buttonPrimaryDisabledBackground)', - buttonPrimaryDisabledBorder: 'var(--color-buttonPrimaryDisabledBorder)', - buttonNormalText: 'var(--color-buttonNormalText)', - buttonNormalTextHover: 'var(--color-buttonNormalTextHover)', - buttonNormalBackground: 'var(--color-buttonNormalBackground)', - buttonNormalBackgroundHover: 'var(--color-buttonNormalBackgroundHover)', - buttonNormalBorder: 'var(--color-buttonNormalBorder)', - buttonNormalShadow: 'var(--color-buttonNormalShadow)', - buttonNormalSelectedText: 'var(--color-buttonNormalSelectedText)', - buttonNormalSelectedBackground: 'var(--color-buttonNormalSelectedBackground)', - buttonNormalDisabledText: 'var(--color-buttonNormalDisabledText)', - buttonNormalDisabledBackground: 'var(--color-buttonNormalDisabledBackground)', - buttonNormalDisabledBorder: 'var(--color-buttonNormalDisabledBorder)', - buttonBareText: 'var(--color-buttonBareText)', - buttonBareTextHover: 'var(--color-buttonBareTextHover)', - buttonBareBackground: 'var(--color-buttonBareBackground)', - buttonBareBackgroundHover: 'var(--color-buttonBareBackgroundHover)', - buttonBareBackgroundActive: 'var(--color-buttonBareBackgroundActive)', - buttonBareDisabledText: 'var(--color-buttonBareDisabledText)', - buttonBareDisabledBackground: 'var(--color-buttonBareDisabledBackground)', - calendarText: 'var(--color-calendarText)', - calendarBackground: 'var(--color-calendarBackground)', - calendarItemText: 'var(--color-calendarItemText)', - calendarItemBackground: 'var(--color-calendarItemBackground)', - calendarSelectedBackground: 'var(--color-calendarSelectedBackground)', - noticeBackground: 'var(--color-noticeBackground)', - noticeBackgroundLight: 'var(--color-noticeBackgroundLight)', - noticeBackgroundDark: 'var(--color-noticeBackgroundDark)', - noticeText: 'var(--color-noticeText)', - noticeTextLight: 'var(--color-noticeTextLight)', - noticeTextDark: 'var(--color-noticeTextDark)', - noticeTextMenu: 'var(--color-noticeTextMenu)', - noticeTextMenuHover: 'var(--color-noticeTextMenuHover)', - noticeBorder: 'var(--color-noticeBorder)', - warningBackground: 'var(--color-warningBackground)', - warningText: 'var(--color-warningText)', - warningTextLight: 'var(--color-warningTextLight)', - warningTextDark: 'var(--color-warningTextDark)', - warningBorder: 'var(--color-warningBorder)', - errorBackground: 'var(--color-errorBackground)', - errorText: 'var(--color-errorText)', - errorTextDark: 'var(--color-errorTextDark)', - errorTextDarker: 'var(--color-errorTextDarker)', - errorTextMenu: 'var(--color-errorTextMenu)', - errorBorder: 'var(--color-errorBorder)', - upcomingBackground: 'var(--color-upcomingBackground)', - upcomingText: 'var(--color-upcomingText)', - upcomingBorder: 'var(--color-upcomingBorder)', - formLabelText: 'var(--color-formLabelText)', - formLabelBackground: 'var(--color-formLabelBackground)', - formInputBackground: 'var(--color-formInputBackground)', - formInputBackgroundSelected: 'var(--color-formInputBackgroundSelected)', - formInputBackgroundSelection: 'var(--color-formInputBackgroundSelection)', - formInputBorder: 'var(--color-formInputBorder)', - formInputTextReadOnlySelection: 'var(--color-formInputTextReadOnlySelection)', - formInputBorderSelected: 'var(--color-formInputBorderSelected)', - formInputText: 'var(--color-formInputText)', - formInputTextSelected: 'var(--color-formInputTextSelected)', - formInputTextPlaceholder: 'var(--color-formInputTextPlaceholder)', - formInputTextPlaceholderSelected: - 'var(--color-formInputTextPlaceholderSelected)', - formInputTextSelection: 'var(--color-formInputTextSelection)', - formInputShadowSelected: 'var(--color-formInputShadowSelected)', - formInputTextHighlight: 'var(--color-formInputTextHighlight)', - checkboxText: 'var(--color-checkboxText)', - checkboxBackgroundSelected: 'var(--color-checkboxBackgroundSelected)', - checkboxBorderSelected: 'var(--color-checkboxBorderSelected)', - checkboxShadowSelected: 'var(--color-checkboxShadowSelected)', - checkboxToggleBackground: 'var(--color-checkboxToggleBackground)', - checkboxToggleBackgroundSelected: - 'var(--color-checkboxToggleBackgroundSelected)', - checkboxToggleDisabled: 'var(--color-checkboxToggleDisabled)', - pillBackground: 'var(--color-pillBackground)', - pillBackgroundLight: 'var(--color-pillBackgroundLight)', - pillText: 'var(--color-pillText)', - pillTextHighlighted: 'var(--color-pillTextHighlighted)', - pillBorder: 'var(--color-pillBorder)', - pillBorderDark: 'var(--color-pillBorderDark)', - pillBackgroundSelected: 'var(--color-pillBackgroundSelected)', - pillTextSelected: 'var(--color-pillTextSelected)', - pillBorderSelected: 'var(--color-pillBorderSelected)', - pillTextSubdued: 'var(--color-pillTextSubdued)', - reportsRed: 'var(--color-reportsRed)', - reportsBlue: 'var(--color-reportsBlue)', - reportsGreen: 'var(--color-reportsGreen)', - reportsGray: 'var(--color-reportsGray)', - reportsLabel: 'var(--color-reportsLabel)', - reportsInnerLabel: 'var(--color-reportsInnerLabel)', - noteTagBackground: 'var(--color-noteTagBackground)', - noteTagBackgroundHover: 'var(--color-noteTagBackgroundHover)', - noteTagText: 'var(--color-noteTagText)', - budgetOtherMonth: 'var(--color-budgetOtherMonth)', - budgetCurrentMonth: 'var(--color-budgetCurrentMonth)', - budgetHeaderOtherMonth: 'var(--color-budgetHeaderOtherMonth)', - budgetHeaderCurrentMonth: 'var(--color-budgetHeaderCurrentMonth)', - floatingActionBarBackground: 'var(--color-floatingActionBarBackground)', - floatingActionBarBorder: 'var(--color-floatingActionBarBorder)', - floatingActionBarText: 'var(--color-floatingActionBarText)', - tooltipText: 'var(--color-tooltipText)', - tooltipBackground: 'var(--color-tooltipBackground)', - tooltipBorder: 'var(--color-tooltipBorder)', - calendarCellBackground: 'var(--color-calendarCellBackground)', -}; +export * from '@actual-app/components/theme'; diff --git a/packages/desktop-client/src/tokens.ts b/packages/desktop-client/src/tokens.ts index 9c5d1facabe..44a243f4b03 100644 --- a/packages/desktop-client/src/tokens.ts +++ b/packages/desktop-client/src/tokens.ts @@ -1,35 +1 @@ -enum BreakpointNames { - small = 'small', - medium = 'medium', - wide = 'wide', -} - -type NumericBreakpoints = { - [key in BreakpointNames]: number; -}; - -export const breakpoints: NumericBreakpoints = { - small: 512, - medium: 730, - wide: 1100, -}; - -type BreakpointsPx = { - [B in keyof NumericBreakpoints as `breakpoint_${B}`]: string; -}; - -// Provide the same breakpoints in a form usable by CSS media queries -// { -// breakpoint_small: '512px', -// breakpoint_medium: '740px', -// breakpoint_wide: '1100px', -// } -export const tokens: BreakpointsPx = Object.entries( - breakpoints, -).reduce( - (acc, [key, val]) => ({ - ...acc, - [`breakpoint_${key}`]: `${val}px`, - }), - {} as BreakpointsPx, -); +export * from '@actual-app/components/tokens'; diff --git a/upcoming-release-notes/4169.md b/upcoming-release-notes/4169.md new file mode 100644 index 00000000000..161c495a441 --- /dev/null +++ b/upcoming-release-notes/4169.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Introduce `@actual-app/components` - package with reusable components. diff --git a/yarn.lock b/yarn.lock index e215d43dbf1..7ef7e3db0c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,19 @@ __metadata: languageName: unknown linkType: soft +"@actual-app/components@workspace:*, @actual-app/components@workspace:packages/component-library": + version: 0.0.0-use.local + resolution: "@actual-app/components@workspace:packages/component-library" + dependencies: + "@emotion/css": "npm:^11.13.4" + "@types/react": "npm:^18.2.0" + react: "npm:18.2.0" + react-aria-components: "npm:^1.4.1" + peerDependencies: + react: ">=18.2" + languageName: unknown + linkType: soft + "@actual-app/crdt@workspace:^, @actual-app/crdt@workspace:packages/crdt": version: 0.0.0-use.local resolution: "@actual-app/crdt@workspace:packages/crdt" @@ -59,6 +72,7 @@ __metadata: version: 0.0.0-use.local resolution: "@actual-app/web@workspace:packages/desktop-client" dependencies: + "@actual-app/components": "workspace:*" "@emotion/css": "npm:^11.13.4" "@fontsource/redacted-script": "npm:^5.0.21" "@juggle/resize-observer": "npm:^3.4.0"