diff --git a/ui/src/components/Button.tsx b/ui/src/components/Button.tsx index 7655415..9e4f191 100644 --- a/ui/src/components/Button.tsx +++ b/ui/src/components/Button.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx' -import { JSXElement, Show } from 'solid-js' +import { JSX, JSXElement, Show } from 'solid-js' type DaisyUIButtonColor = | 'neutral' @@ -15,14 +15,9 @@ type DaisyUIButtonVariant = 'outline' | 'dash' | 'soft' | 'ghost' | 'link' type DaisyUIButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' -/** - * Renders a button component with an optional loading state. - * If the loading state is passed the button will be disabled and show - * a loader ball while loading. - */ -export function Button(props: { - label: string - icon?: string +type DaisyUIButtonShape = 'square' | 'circle' + +interface ButtonBaseProps { onClick?: (event?: MouseEvent) => void isLoading?: boolean disabled?: boolean @@ -30,68 +25,99 @@ export function Button(props: { color?: DaisyUIButtonColor size?: DaisyUIButtonSize variant?: DaisyUIButtonVariant + shape?: DaisyUIButtonShape + block?: boolean class?: string + style?: JSX.CSSProperties dataCy?: string -}): JSXElement { + ref?: (el: HTMLButtonElement) => void +} + +/** + * Renders a button component with an optional loading state. + * If the loading state is passed the button will be disabled and show + * a loader ball while loading. + */ +export function Button( + props: ButtonBaseProps & { + label: JSXElement + icon?: string + trailingIcon?: string + } +): JSXElement { + const isDisabled = () => props.disabled || props.isLoading + return ( props.onClick?.(event)} data-cy={props.dataCy} > - - - - - {props.label} - - + + + + } + > + + {props.label} + + + + ) } -export function IconButton(props: { - icon: string - onClick?: () => void - isLoading?: boolean - disabled?: boolean - color?: DaisyUIButtonColor - size?: DaisyUIButtonSize - variant?: DaisyUIButtonVariant - class?: string - dataCy?: string -}): JSXElement { +export function IconButton( + props: ButtonBaseProps & { + icon: string + } +): JSXElement { + const isDisabled = () => props.disabled || props.isLoading + return ( props.onClick?.()} - disabled={props.disabled} + type={props.type ?? 'button'} + disabled={isDisabled()} + style={props.style} + onClick={(event) => props.onClick?.(event)} data-cy={props.dataCy} > } + fallback={ + + } > diff --git a/ui/src/components/FrozenWorkspaceModal.tsx b/ui/src/components/FrozenWorkspaceModal.tsx index d48a17e..ece9dac 100644 --- a/ui/src/components/FrozenWorkspaceModal.tsx +++ b/ui/src/components/FrozenWorkspaceModal.tsx @@ -2,6 +2,7 @@ import { JSXElement, Show } from 'solid-js' import { Portal } from 'solid-js/web' import clsx from 'clsx' +import { Button } from './Button' import { useLocale } from '../context/LocaleProvider' import type { FrozenReason } from '../models/Workspace' @@ -81,26 +82,31 @@ export function FrozenWorkspaceModal(props: Props): JSXElement { {/* Actions */} - props.onClose()} - > - - {t('later')} - - - + {t('later')} + + } + /> + - - {t('frozen_upgrade_button')} - - + label={ + + {t('frozen_upgrade_button')} + + } + /> {t('upgrade_available_soon')} diff --git a/ui/src/components/LanguageSelector.tsx b/ui/src/components/LanguageSelector.tsx index ad671fd..0118c46 100644 --- a/ui/src/components/LanguageSelector.tsx +++ b/ui/src/components/LanguageSelector.tsx @@ -1,5 +1,6 @@ import { createEffect, JSXElement, For } from 'solid-js' +import { Button } from './Button' import { useLocale, locales } from '../context/LocaleProvider' export function LanguageSelector(): JSXElement { @@ -23,19 +24,20 @@ export function LanguageSelector(): JSXElement { {(language) => ( - { setLocale(language) detailsRef?.removeAttribute('open') }} - > - - - {language} - - + label={ + + + {language} + + } + /> )} diff --git a/ui/src/components/Modal.tsx b/ui/src/components/Modal.tsx index abba0c4..3d0710b 100644 --- a/ui/src/components/Modal.tsx +++ b/ui/src/components/Modal.tsx @@ -2,6 +2,8 @@ import clsx from 'clsx' import { createSignal, JSXElement } from 'solid-js' import { Portal } from 'solid-js/web' +import { Button } from './Button' + /** * Creates a modal state that can be used to open and close modals * the keys passed in are used as identifiers for identifying differnent modals. @@ -43,12 +45,14 @@ export function Modal(props: { class={clsx('modal z-1000', isOpen() ? 'modal-open' : 'modal-close')} > - props.onClose()} - > - ✕ - + label="✕" + /> {props.title} {props.children} diff --git a/ui/src/components/ProfileMenu.tsx b/ui/src/components/ProfileMenu.tsx index 0c91952..7b9aca0 100644 --- a/ui/src/components/ProfileMenu.tsx +++ b/ui/src/components/ProfileMenu.tsx @@ -1,9 +1,9 @@ import { createSignal, JSXElement, Show } from 'solid-js' import { A, useNavigate } from '@solidjs/router' -import { clsx } from 'clsx' import { useUser } from '../context/UserProvider' import { useLocale } from '../context/LocaleProvider' +import { Button } from './Button' import { Toast } from './Toast' import { logout } from '../api' @@ -65,24 +65,17 @@ export function ProfileMenu(): JSXElement { - - } - > - - - {t('logout')} - + dataCy="logout" + label={t('logout')} + /> diff --git a/ui/src/components/TopBar.tsx b/ui/src/components/TopBar.tsx index 2675118..a45a8dc 100644 --- a/ui/src/components/TopBar.tsx +++ b/ui/src/components/TopBar.tsx @@ -5,6 +5,7 @@ import { clsx } from 'clsx' import { useUser } from '../context/UserProvider' import { useLocale } from '../context/LocaleProvider' +import { Button, IconButton } from '../components/Button' import { ThemeSwitcher } from '../components/ThemeSwitcher' import { LanguageSelector } from '../components/LanguageSelector' import { LoginModal } from '../components/LoginModal' @@ -22,12 +23,12 @@ export function TopBar(): JSXElement { <> - setSidebarOpen(!sidebarOpen())} - > - - + /> @@ -51,21 +52,19 @@ export function TopBar(): JSXElement { } > - setOpenLoginModal(true)} - data-cy="open-login-modal" - > - {t('login')} - + dataCy="open-login-modal" + label={t('login')} + /> - setOpenRegisterModal(true)} - data-cy="open-register-modal" - > - {t('register')} - + dataCy="open-register-modal" + label={t('register')} + /> @@ -82,12 +81,12 @@ export function TopBar(): JSXElement { My solid app - setSidebarOpen(false)} - > - - + /> @@ -104,29 +103,31 @@ export function TopBar(): JSXElement { } > - { setOpenLoginModal(true) setSidebarOpen(false) }} - data-cy="open-login-modal-mobile" - > - - {t('login')} - + dataCy="open-login-modal-mobile" + label={t('login')} + /> - { setOpenRegisterModal(true) setSidebarOpen(false) }} - data-cy="open-login-modal-mobile" - > - - {t('register')} - + dataCy="open-login-modal-mobile" + label={t('register')} + /> diff --git a/ui/src/index.css b/ui/src/index.css index ebb654c..3500304 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -473,6 +473,7 @@ .register-btn-submit { width: 100%; height: 46px; + min-height: 46px; background: var(--color-primary); color: white; font-family: inherit; @@ -485,6 +486,7 @@ align-items: center; justify-content: center; gap: 7px; + padding: 0; transition: background 0.2s, transform 0.15s, @@ -583,7 +585,10 @@ .login-back-btn { width: 100%; + min-height: 0; + height: auto; font-size: 12px; + font-weight: 400; color: #9ca3af; background: none; border: none; diff --git a/ui/src/pages/Base.tsx b/ui/src/pages/Base.tsx index 2ed4be6..f8695ef 100644 --- a/ui/src/pages/Base.tsx +++ b/ui/src/pages/Base.tsx @@ -2,6 +2,7 @@ import { JSXElement, createSignal, onCleanup, onMount } from 'solid-js' import { A } from '@solidjs/router' import { FrozenWorkspaceModal } from '../components/FrozenWorkspaceModal' +import { IconButton } from '../components/Button' import { ProfileMenu } from '../components/ProfileMenu' import { ThemeSwitcher } from '../components/ThemeSwitcher' import { LanguageSelector } from '../components/LanguageSelector' @@ -72,12 +73,13 @@ export function BasePage(props: BasePageProps): JSXElement { > {t('my_solid_app')} - setDrawerOpen(false)} - > - - + /> diff --git a/ui/src/pages/Login.tsx b/ui/src/pages/Login.tsx index dd133a6..a838c7f 100644 --- a/ui/src/pages/Login.tsx +++ b/ui/src/pages/Login.tsx @@ -12,6 +12,7 @@ import { User, UserAttributes } from '../models/User' import { useUser } from '../context/UserProvider' import { useLocale } from '../context/LocaleProvider' import { Alert } from '../components/Alert' +import { Button } from '../components/Button' import { createFormState } from '../form_helpers' export function LoginPage(): JSXElement { @@ -238,32 +239,30 @@ function LoginForm(): JSXElement { /> - - } - > - {t('login')} - - - - - - + isLoading={loginState.submitting} + label={ + + {t('login')} + + + + + + } + /> @@ -330,43 +329,39 @@ function LoginForm(): JSXElement { - - } - > - {t('login')} - - - - - - + isLoading={totpState.submitting} + label={ + + {t('login')} + + + + + + } + /> - { setAt2FAStep(false) reset(loginState) reset(totpState) }} - > - ← {t('back')} - + label={`← ${t('back')}`} + /> diff --git a/ui/src/pages/Register.tsx b/ui/src/pages/Register.tsx index 81ca2b5..adf9ac2 100644 --- a/ui/src/pages/Register.tsx +++ b/ui/src/pages/Register.tsx @@ -16,6 +16,7 @@ import { useUser } from '../context/UserProvider' import { useLocale } from '../context/LocaleProvider' import { useWorkspace } from '../context/WorkspaceProvider' import { Alert } from '../components/Alert' +import { Button } from '../components/Button' import { createFormState } from '../form_helpers' import { mustMatch } from '../validators' @@ -492,34 +493,32 @@ function RegisterForm(): JSXElement { /> - - } - > - {isInvite() - ? t('invite_join_workspace') - : t('register_create_free_account')} - - - - - - + isLoading={state.submitting} + label={ + + {isInvite() + ? t('invite_join_workspace') + : t('register_create_free_account')} + + + + + + } + /> {t('register_terms_prefix')}{' '} diff --git a/ui/src/pages/admin_page/UsersAdmin.tsx b/ui/src/pages/admin_page/UsersAdmin.tsx index d17ed02..867607f 100644 --- a/ui/src/pages/admin_page/UsersAdmin.tsx +++ b/ui/src/pages/admin_page/UsersAdmin.tsx @@ -20,7 +20,7 @@ import { useLocale } from '../../context/LocaleProvider' import { pattern, email, minLength, required } from '@modular-forms/solid' import { Table, TableRow } from '../../components/Table' import { Tooltip } from '../../components/Tooltip' -import { Button } from '../../components/Button' +import { Button, IconButton } from '../../components/Button' import { createFormState } from '../../form_helpers' export function UsersAdmin(): JSXElement { @@ -82,13 +82,13 @@ export function UsersAdmin(): JSXElement { const DeleteUserButton = (props: { user: UserAttributes }): JSXElement => { return ( - handleDelete(props.user)} - data-cy={`delete-user-${props.user.email}`} - > - - + dataCy={`delete-user-${props.user.email}`} + /> ) } @@ -108,14 +108,14 @@ export function UsersAdmin(): JSXElement { t('id'), t('email'), t('verified'), - openModal('createUser')} - data-cy="create-new-user" - > - - {t('create_new_user')} - , + dataCy="create-new-user" + label={{t('create_new_user')}} + />, ]} > diff --git a/ui/src/pages/workspace_modals/DeleteWorkspaceModal.tsx b/ui/src/pages/workspace_modals/DeleteWorkspaceModal.tsx index 53c4877..89738d9 100644 --- a/ui/src/pages/workspace_modals/DeleteWorkspaceModal.tsx +++ b/ui/src/pages/workspace_modals/DeleteWorkspaceModal.tsx @@ -61,13 +61,13 @@ export function DeleteWorkspaceModal(props: Props): JSXElement { {/* Footer */} - props.onCancel()} - > - {t('cancel')} - + label={t('cancel')} + />
{t('upgrade_available_soon')} diff --git a/ui/src/components/LanguageSelector.tsx b/ui/src/components/LanguageSelector.tsx index ad671fd..0118c46 100644 --- a/ui/src/components/LanguageSelector.tsx +++ b/ui/src/components/LanguageSelector.tsx @@ -1,5 +1,6 @@ import { createEffect, JSXElement, For } from 'solid-js' +import { Button } from './Button' import { useLocale, locales } from '../context/LocaleProvider' export function LanguageSelector(): JSXElement { @@ -23,19 +24,20 @@ export function LanguageSelector(): JSXElement { {(language) => ( - { setLocale(language) detailsRef?.removeAttribute('open') }} - > - - - {language} - - + label={ + + + {language} + + } + /> )} diff --git a/ui/src/components/Modal.tsx b/ui/src/components/Modal.tsx index abba0c4..3d0710b 100644 --- a/ui/src/components/Modal.tsx +++ b/ui/src/components/Modal.tsx @@ -2,6 +2,8 @@ import clsx from 'clsx' import { createSignal, JSXElement } from 'solid-js' import { Portal } from 'solid-js/web' +import { Button } from './Button' + /** * Creates a modal state that can be used to open and close modals * the keys passed in are used as identifiers for identifying differnent modals. @@ -43,12 +45,14 @@ export function Modal(props: { class={clsx('modal z-1000', isOpen() ? 'modal-open' : 'modal-close')} >
{t('register_terms_prefix')}{' '} diff --git a/ui/src/pages/admin_page/UsersAdmin.tsx b/ui/src/pages/admin_page/UsersAdmin.tsx index d17ed02..867607f 100644 --- a/ui/src/pages/admin_page/UsersAdmin.tsx +++ b/ui/src/pages/admin_page/UsersAdmin.tsx @@ -20,7 +20,7 @@ import { useLocale } from '../../context/LocaleProvider' import { pattern, email, minLength, required } from '@modular-forms/solid' import { Table, TableRow } from '../../components/Table' import { Tooltip } from '../../components/Tooltip' -import { Button } from '../../components/Button' +import { Button, IconButton } from '../../components/Button' import { createFormState } from '../../form_helpers' export function UsersAdmin(): JSXElement { @@ -82,13 +82,13 @@ export function UsersAdmin(): JSXElement { const DeleteUserButton = (props: { user: UserAttributes }): JSXElement => { return ( - handleDelete(props.user)} - data-cy={`delete-user-${props.user.email}`} - > - - + dataCy={`delete-user-${props.user.email}`} + /> ) } @@ -108,14 +108,14 @@ export function UsersAdmin(): JSXElement { t('id'), t('email'), t('verified'), - openModal('createUser')} - data-cy="create-new-user" - > - - {t('create_new_user')} - , + dataCy="create-new-user" + label={
{t('create_new_user')}