From bde6f3a988be76e6c5aa9fab06945b765fee4a33 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Thu, 24 Oct 2024 16:10:26 -0400 Subject: [PATCH] feat(apps): add bottom actions to app layout, configure and animate existing actions --- .changeset/khaki-poets-double.md | 5 + .changeset/selfish-parrots-applaud.md | 2 +- .changeset/spotty-readers-shake.md | 5 + apps/hostd/components/DockedControls.tsx | 11 + apps/hostd/components/HostdAuthedLayout.tsx | 6 +- apps/hostd/components/OnboardingBar.tsx | 344 ++++++++--------- apps/hostd/pages/_app.tsx | 1 + apps/renterd/components/DockedControls.tsx | 13 + .../components/Keys/KeysDockedControls.tsx | 5 + apps/renterd/components/Keys/Layout.tsx | 2 + apps/renterd/components/Keys/index.tsx | 2 - apps/renterd/components/OnboardingBar.tsx | 350 +++++++++--------- .../components/RenterdAuthedLayout.tsx | 8 +- apps/renterd/components/TransfersBar.tsx | 95 ++--- apps/renterd/config/providers.tsx | 4 - apps/renterd/pages/_app.tsx | 1 + apps/walletd/components/DockedControls.tsx | 5 + .../components/WalletdAuthedLayout.tsx | 4 +- apps/walletd/pages/_app.tsx | 1 + .../src/app/AppAuthedLayout/index.tsx | 5 + .../src/app/AppDockedControl.tsx | 21 ++ libs/design-system/src/index.ts | 1 + .../src/multi/MultiSelectionMenu.tsx | 84 ++--- 23 files changed, 531 insertions(+), 444 deletions(-) create mode 100644 .changeset/khaki-poets-double.md create mode 100644 .changeset/spotty-readers-shake.md create mode 100644 apps/hostd/components/DockedControls.tsx create mode 100644 apps/renterd/components/DockedControls.tsx create mode 100644 apps/renterd/components/Keys/KeysDockedControls.tsx create mode 100644 apps/walletd/components/DockedControls.tsx create mode 100644 libs/design-system/src/app/AppDockedControl.tsx diff --git a/.changeset/khaki-poets-double.md b/.changeset/khaki-poets-double.md new file mode 100644 index 000000000..b61c0b9fd --- /dev/null +++ b/.changeset/khaki-poets-double.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/design-system': minor +--- + +Added dockedControls to AppAuthedLayout. diff --git a/.changeset/selfish-parrots-applaud.md b/.changeset/selfish-parrots-applaud.md index dac1240e5..a55269424 100644 --- a/.changeset/selfish-parrots-applaud.md +++ b/.changeset/selfish-parrots-applaud.md @@ -3,4 +3,4 @@ 'renterd': minor --- -The onboarding wizard is now bottom right aligned. +The onboarding wizard now animates in and out. diff --git a/.changeset/spotty-readers-shake.md b/.changeset/spotty-readers-shake.md new file mode 100644 index 000000000..2f3f057ef --- /dev/null +++ b/.changeset/spotty-readers-shake.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The transfers bar now animates in and out. diff --git a/apps/hostd/components/DockedControls.tsx b/apps/hostd/components/DockedControls.tsx new file mode 100644 index 000000000..6f3bed20f --- /dev/null +++ b/apps/hostd/components/DockedControls.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import { OnboardingBar } from './OnboardingBar' + +export function DockedControls({ children }: { children?: React.ReactNode }) { + return ( +
+ {children} + +
+ ) +} diff --git a/apps/hostd/components/HostdAuthedLayout.tsx b/apps/hostd/components/HostdAuthedLayout.tsx index 7dd9f1bdd..42f928935 100644 --- a/apps/hostd/components/HostdAuthedLayout.tsx +++ b/apps/hostd/components/HostdAuthedLayout.tsx @@ -5,21 +5,22 @@ import { connectivityRoute } from '../config/routes' import { useSyncStatus } from '../hooks/useSyncStatus' import { HostdTestnetWarningBanner } from './HostdTestnetWarningBanner' import { Profile } from './Profile' +import { DockedControls } from './DockedControls' type Props = Omit< React.ComponentProps, 'appName' | 'connectivityRoute' | 'walletBalance' | 'profile' | 'isSynced' > -export function HostdAuthedLayout(props: Props) { +export function HostdAuthedLayout({ dockedControls, ...props }: Props) { const wallet = useWallet() const { isSynced } = useSyncStatus() return ( } profile={} + banner={} isSynced={isSynced} walletBalanceSc={ wallet.data && { @@ -29,6 +30,7 @@ export function HostdAuthedLayout(props: Props) { unconfirmed: new BigNumber(wallet.data.unconfirmed), } } + dockedControls={{dockedControls}} {...props} /> ) diff --git a/apps/hostd/components/OnboardingBar.tsx b/apps/hostd/components/OnboardingBar.tsx index 21b9a54f2..b877f9d31 100644 --- a/apps/hostd/components/OnboardingBar.tsx +++ b/apps/hostd/components/OnboardingBar.tsx @@ -6,6 +6,7 @@ import { ScrollArea, Text, Tooltip, + AppDockedControl, } from '@siafoundation/design-system' import { RadioButton16, @@ -62,188 +63,193 @@ export function OnboardingBar() { const completedSteps = steps.filter((step) => step).length if (totalSteps === completedSteps) { - return null + return } if (maximized) { return ( -
- - -
-
- - - Welcome to Sia - -
- -
-
- - Get set up by completing the following steps. Once they are - complete, your host is ready to store data. - -
-
openDialog('addressDetails')} - ellipsis - size="14" - underline="hover" - > - Step 1: Fund your wallet - - } - description={`Fund your wallet with siacoin to cover required contract collateral.${ - syncStatus.isWalletSynced - ? '' - : ' Balance will not be accurate until wallet is finished scanning.' - }`} - action={ - step1Funded ? ( - - + +
+ + +
+
+ + + Welcome to Sia - ) : ( - <> - {!syncStatus.isWalletSynced && ( - - {syncStatus.walletScanPercent}% - - )} - openDialog('addressDetails')} - > - - - - - - - ) - } - /> -
+ +
+
+ + Get set up by completing the following steps. Once they are + complete, your host is ready to store data. + +
+
openDialog('addressDetails')} + ellipsis + size="14" + underline="hover" + > + Step 1: Fund your wallet + + } + description={`Fund your wallet with siacoin to cover required contract collateral.${ + syncStatus.isWalletSynced + ? '' + : ' Balance will not be accurate until wallet is finished scanning.' + }`} + action={ + step1Funded ? ( + + - - ) - } - /> -
- Step 3: Configure pricing and settings - - } - description={`Configure your host's pricing and settings and start accepting contracts.`} - action={ - step3Configured ? ( - - - - ) : ( - <> - - - - - + ) : ( + <> + {!syncStatus.isWalletSynced && ( + + {syncStatus.walletScanPercent}% + + )} + openDialog('addressDetails')} + > + + + + + + + ) + } + /> +
+ Step 2: Add a volume + + } + description={ + 'Add a system volume that will be used to store data.' + } + action={ + step2Volumes ? ( + + - - ) - } - /> -
- Step 4: Wait for the blockchain to sync - - } - description={ - 'The blockchain will sync in the background, this takes some time. No user action required.' - } - action={ - step4Synced ? ( - - - - ) : ( - <> - - {syncStatus.syncPercent}% + ) : ( + <> + + + + + + + + ) + } + /> +
+ Step 3: Configure pricing and settings + + } + description={`Configure your host's pricing and settings and start accepting contracts.`} + action={ + step3Configured ? ( + + - - + ) : ( + <> + + + + + + + + ) + } + /> +
+ Step 4: Wait for the blockchain to sync + + } + description={ + 'The blockchain will sync in the background, this takes some time. No user action required.' + } + action={ + step4Synced ? ( + + - - ) - } - /> - - -
+ ) : ( + <> + + {syncStatus.syncPercent}% + + + + + + ) + } + /> +
+
+
+
) } + return ( -
- -
+ +
+ +
+
) } diff --git a/apps/hostd/pages/_app.tsx b/apps/hostd/pages/_app.tsx index 3bcc5f40d..5bd615516 100644 --- a/apps/hostd/pages/_app.tsx +++ b/apps/hostd/pages/_app.tsx @@ -33,6 +33,7 @@ export default function App(props: AppPropsWithLayout) { ) } + function AppCore({ Component, pageProps }: AppPropsWithLayout) { const Layout = Component.Layout const layoutProps = Component.useLayoutProps() diff --git a/apps/renterd/components/DockedControls.tsx b/apps/renterd/components/DockedControls.tsx new file mode 100644 index 000000000..81f381999 --- /dev/null +++ b/apps/renterd/components/DockedControls.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { OnboardingBar } from './OnboardingBar' +import { TransfersBar } from './TransfersBar' + +export function DockedControls({ children }: { children?: React.ReactNode }) { + return ( +
+ {children} + + +
+ ) +} diff --git a/apps/renterd/components/Keys/KeysDockedControls.tsx b/apps/renterd/components/Keys/KeysDockedControls.tsx new file mode 100644 index 000000000..f2b5adfaa --- /dev/null +++ b/apps/renterd/components/Keys/KeysDockedControls.tsx @@ -0,0 +1,5 @@ +import { KeysBatchMenu } from './KeysBatchMenu' + +export function KeysDockedControls() { + return +} diff --git a/apps/renterd/components/Keys/Layout.tsx b/apps/renterd/components/Keys/Layout.tsx index 83e3b1a5b..6f367c814 100644 --- a/apps/renterd/components/Keys/Layout.tsx +++ b/apps/renterd/components/Keys/Layout.tsx @@ -7,6 +7,7 @@ import { } from '../RenterdAuthedLayout' import { KeysActionsMenu } from './KeysActionsMenu' import { KeysStatsMenu } from './KeysStatsMenu' +import { KeysDockedControls } from './KeysDockedControls' export const Layout = RenterdAuthedLayout export function useLayoutProps(): RenterdAuthedPageLayoutProps { @@ -18,5 +19,6 @@ export function useLayoutProps(): RenterdAuthedPageLayoutProps { openSettings: () => openDialog('settings'), actions: , stats: , + dockedControls: , } } diff --git a/apps/renterd/components/Keys/index.tsx b/apps/renterd/components/Keys/index.tsx index 59b88784c..e4dcc0758 100644 --- a/apps/renterd/components/Keys/index.tsx +++ b/apps/renterd/components/Keys/index.tsx @@ -3,7 +3,6 @@ import { StateNoneMatching } from './StateNoneMatching' import { StateNoneYet } from './StateNoneYet' import { StateError } from './StateError' import { useKeys } from '../../contexts/keys' -import { KeysBatchMenu } from './KeysBatchMenu' export function Keys() { const { @@ -20,7 +19,6 @@ export function Keys() { return (
- step).length if (totalSteps === completedSteps) { - return null + return } if (maximized) { return ( -
- - -
-
- - - Welcome to Sia + +
+ + +
+
+ + + Welcome to Sia + +
+ +
+
+ + Get set up by completing the following steps. Once they are + complete, you can start uploading files.
- -
-
- - Get set up by completing the following steps. Once they are - complete, you can start uploading files. - -
-
- Step 1: Configure your storage settings - - } - description={ - 'Specify how much data you plan to store and your target price.' - } - action={ - step1Configured ? ( - - - - ) : ( - <> - - - - - +
+ Step 1: Configure your storage settings + + } + description={ + 'Specify how much data you plan to store and your target price.' + } + action={ + step1Configured ? ( + + - - ) - } - /> -
- Step 2: Wait for the blockchain to sync - - } - description={ - 'The blockchain will sync in the background, this takes some time. No user action required.' - } - action={ - step2Synced ? ( - - - - ) : ( - <> - - {syncStatus.syncPercent}% + ) : ( + <> + + + + + + + + ) + } + /> +
+ Step 2: Wait for the blockchain to sync + + } + description={ + 'The blockchain will sync in the background, this takes some time. No user action required.' + } + action={ + step2Synced ? ( + + - - + ) : ( + <> + + {syncStatus.syncPercent}% + + + + + + ) + } + /> +
openDialog('addressDetails')} + ellipsis + size="14" + underline="hover" + > + Step 3: Fund your wallet + + } + description={`Fund your wallet with at least ${humanSiacoin( + allowance + )} siacoin to cover the required allowance.${ + syncStatus.isWalletSynced + ? '' + : ' Balance will not be accurate until wallet is finished scanning.' + }`} + action={ + step3Funded ? ( + + - - ) - } - /> -
openDialog('addressDetails')} - ellipsis - size="14" - underline="hover" - > - Step 3: Fund your wallet - - } - description={`Fund your wallet with at least ${humanSiacoin( - allowance - )} siacoin to cover the required allowance.${ - syncStatus.isWalletSynced - ? '' - : ' Balance will not be accurate until wallet is finished scanning.' - }`} - action={ - step3Funded ? ( - - - - ) : ( - <> - {!syncStatus.isWalletSynced && ( - + {!syncStatus.isWalletSynced && ( + + {syncStatus.walletScanPercent}% + + )} + openDialog('addressDetails')} > - {syncStatus.walletScanPercent}% - - )} - openDialog('addressDetails')} - > - - - - + + + + + + + ) + } + /> +
+ Step 4: Wait for storage contracts to form + + } + description={ + 'Once all other steps are complete, contracts will automatically form. No user action required.' + } + action={ + step4Contracts ? ( + + - - ) - } - /> -
- Step 4: Wait for storage contracts to form - - } - description={ - 'Once all other steps are complete, contracts will automatically form. No user action required.' - } - action={ - step4Contracts ? ( - - - - ) : ( - <> - - {notEnoughContracts.count}/{notEnoughContracts.required} - - - - - - ) - } - /> - - -
+ ) : ( + <> + + {notEnoughContracts.count}/{notEnoughContracts.required} + + + + + + ) + } + /> + + +
+ ) } + return ( -
- -
+ +
+ +
+
) } diff --git a/apps/renterd/components/RenterdAuthedLayout.tsx b/apps/renterd/components/RenterdAuthedLayout.tsx index c9d6a21b1..735ccd777 100644 --- a/apps/renterd/components/RenterdAuthedLayout.tsx +++ b/apps/renterd/components/RenterdAuthedLayout.tsx @@ -5,30 +5,32 @@ import { connectivityRoute } from '../config/routes' import { useSyncStatus } from '../hooks/useSyncStatus' import { Profile } from './Profile' import { RenterdTestnetWarningBanner } from './RenterdTestnetWarningBanner' +import { DockedControls } from './DockedControls' type Props = Omit< React.ComponentProps, 'appName' | 'connectivityRoute' | 'walletBalance' | 'profile' | 'isSynced' > -export function RenterdAuthedLayout(props: Props) { +export function RenterdAuthedLayout({ dockedControls, ...props }: Props) { const wallet = useWallet() const { isSynced } = useSyncStatus() return ( } banner={} - connectivityRoute={connectivityRoute} isSynced={isSynced} walletBalanceSc={ wallet.data && { spendable: new BigNumber(wallet.data.spendable), confirmed: new BigNumber(wallet.data.confirmed), - unconfirmed: new BigNumber(wallet.data.unconfirmed), immature: new BigNumber(wallet.data.immature), + unconfirmed: new BigNumber(wallet.data.unconfirmed), } } + dockedControls={{dockedControls}} {...props} /> ) diff --git a/apps/renterd/components/TransfersBar.tsx b/apps/renterd/components/TransfersBar.tsx index 8ff48bfaa..3341458ce 100644 --- a/apps/renterd/components/TransfersBar.tsx +++ b/apps/renterd/components/TransfersBar.tsx @@ -1,4 +1,10 @@ -import { Button, Panel, ScrollArea, Text } from '@siafoundation/design-system' +import { + Button, + Panel, + ScrollArea, + Text, + AppDockedControl, +} from '@siafoundation/design-system' import { Download16, Subtract24, Upload16 } from '@siafoundation/react-icons' import { useState } from 'react' import { useFilesManager } from '../contexts/filesManager' @@ -18,20 +24,20 @@ export function TransfersBar() { const isActiveDownloads = !!downloadCount if (!isUnlockedAndAuthedRoute) { - return null + return } if (!isActiveUploads && !isActiveDownloads) { - return null + return } const controls = ( -
+
{isActiveUploads && !isViewingUploads ? ( -
- {downloadsList.map((download) => ( - downloadCancel(download)} - abortTip="Cancel download" - /> - ))} - - ) : null} - - - {controls} -
+ +
+ + + {isActiveDownloads ? ( + <> +
+ + Active downloads ({downloadCount}) + + +
+ {downloadsList.map((download) => ( + downloadCancel(download)} + abortTip="Cancel download" + /> + ))} + + ) : null} +
+
+ {controls} +
+
) } - return ( -
- {controls} -
- ) + + return {controls} } diff --git a/apps/renterd/config/providers.tsx b/apps/renterd/config/providers.tsx index 413a68929..87fd107f3 100644 --- a/apps/renterd/config/providers.tsx +++ b/apps/renterd/config/providers.tsx @@ -4,8 +4,6 @@ import { ContractsProvider } from '../contexts/contracts' import { HostsProvider } from '../contexts/hosts' import { AppProvider } from '../contexts/app' import { ConfigProvider } from '../contexts/config' -import { OnboardingBar } from '../components/OnboardingBar' -import { TransfersBar } from '../components/TransfersBar' import { TransactionsProvider } from '../contexts/transactions' import { KeysProvider } from '../contexts/keys' import { FilesFlatProvider } from '../contexts/filesFlat' @@ -34,8 +32,6 @@ export function Providers({ children }: Props) { {/* this is here so that dialogs can use all the other providers, and the other providers can trigger dialogs */} - - {children} diff --git a/apps/renterd/pages/_app.tsx b/apps/renterd/pages/_app.tsx index 57173c7dc..426600a61 100644 --- a/apps/renterd/pages/_app.tsx +++ b/apps/renterd/pages/_app.tsx @@ -33,6 +33,7 @@ export default function App(props: AppPropsWithLayout) { ) } + function AppCore({ Component, pageProps }: AppPropsWithLayout) { const Layout = Component.Layout const layoutProps = Component.useLayoutProps() diff --git a/apps/walletd/components/DockedControls.tsx b/apps/walletd/components/DockedControls.tsx new file mode 100644 index 000000000..275daebd7 --- /dev/null +++ b/apps/walletd/components/DockedControls.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +export function DockedControls({ children }: { children?: React.ReactNode }) { + return
{children}
+} diff --git a/apps/walletd/components/WalletdAuthedLayout.tsx b/apps/walletd/components/WalletdAuthedLayout.tsx index 128143fcb..b1f46a230 100644 --- a/apps/walletd/components/WalletdAuthedLayout.tsx +++ b/apps/walletd/components/WalletdAuthedLayout.tsx @@ -3,13 +3,14 @@ import { connectivityRoute } from '../config/routes' import { useSyncStatus } from '../hooks/useSyncStatus' import { Profile } from './Profile' import { WalletdTestnetWarningBanner } from './WalletdTestnetWarningBanner' +import { DockedControls } from './DockedControls' type Props = Omit< React.ComponentProps, 'appName' | 'connectivityRoute' | 'walletBalance' | 'profile' | 'isSynced' > -export function WalletdAuthedLayout(props: Props) { +export function WalletdAuthedLayout({ dockedControls, ...props }: Props) { const { isSynced } = useSyncStatus() return ( } showWallet={false} isSynced={isSynced} + dockedControls={{dockedControls}} {...props} /> ) diff --git a/apps/walletd/pages/_app.tsx b/apps/walletd/pages/_app.tsx index 02790f645..a937c00c7 100644 --- a/apps/walletd/pages/_app.tsx +++ b/apps/walletd/pages/_app.tsx @@ -31,6 +31,7 @@ export default function App(props: AppPropsWithLayout) { ) } + function AppCore({ Component, pageProps }: AppPropsWithLayout) { const Layout = Component.Layout const layoutProps = Component.useLayoutProps() diff --git a/libs/design-system/src/app/AppAuthedLayout/index.tsx b/libs/design-system/src/app/AppAuthedLayout/index.tsx index ac99ea8ae..e56e8d6e4 100644 --- a/libs/design-system/src/app/AppAuthedLayout/index.tsx +++ b/libs/design-system/src/app/AppAuthedLayout/index.tsx @@ -21,6 +21,7 @@ type Props = { nav?: React.ReactNode banner?: React.ReactNode actions?: React.ReactNode + dockedControls?: React.ReactNode sidenav?: React.ReactNode stats?: React.ReactNode after?: React.ReactNode @@ -59,6 +60,7 @@ export function AppAuthedLayout({ actions, stats, after, + dockedControls, children, sidenav, connectivityRoute, @@ -123,6 +125,9 @@ export function AppAuthedLayout({ {children} )} +
+ {dockedControls} +
diff --git a/libs/design-system/src/app/AppDockedControl.tsx b/libs/design-system/src/app/AppDockedControl.tsx new file mode 100644 index 000000000..f686e8b7c --- /dev/null +++ b/libs/design-system/src/app/AppDockedControl.tsx @@ -0,0 +1,21 @@ +'use client' + +import { AnimatePresence, motion } from 'framer-motion' + +export function AppDockedControl({ children }: { children?: React.ReactNode }) { + return ( + + {children && ( + + {children} + + )} + + ) +} diff --git a/libs/design-system/src/index.ts b/libs/design-system/src/index.ts index 741922b55..59d276395 100644 --- a/libs/design-system/src/index.ts +++ b/libs/design-system/src/index.ts @@ -85,6 +85,7 @@ export * from './app/AppAuthedLayout/SidenavItem' export * from './app/AppBackdrop' export * from './app/AlertsDialog' export * from './app/AppLogin' +export * from './app/AppDockedControl' export * from './app/WalletSendSiacoinDialog' export * from './app/SyncerConnectPeerDialog' export * from './app/TransactionDetailsDialog' diff --git a/libs/design-system/src/multi/MultiSelectionMenu.tsx b/libs/design-system/src/multi/MultiSelectionMenu.tsx index b5d855d06..f85859a19 100644 --- a/libs/design-system/src/multi/MultiSelectionMenu.tsx +++ b/libs/design-system/src/multi/MultiSelectionMenu.tsx @@ -1,12 +1,12 @@ 'use client' -import { motion, AnimatePresence } from 'framer-motion' import { Button } from '../core/Button' import { Panel } from '../core/Panel' import { Text } from '../core/Text' import { pluralize } from '@siafoundation/units' import { Close16 } from '@siafoundation/react-icons' import { MultiSelect, MultiSelectItem } from './useMultiSelect' +import { AppDockedControl } from '../app/AppDockedControl' export function MultiSelectionMenu({ multiSelect, @@ -20,51 +20,45 @@ export function MultiSelectionMenu({ entityWordPlural?: string }) { const isVisible = multiSelect.selectionCount > 0 + + if (!isVisible) { + return + } + return ( -
- - {isVisible && ( - - - {!!multiSelect.selectionCount && ( - - {`${pluralize(multiSelect.selectionCount, entityWord, { - plural: entityWordPlural, - })} selected${ - multiSelect.someSelectedItemsOutsideCurrentPage && - multiSelect.someSelectedOnCurrentPage - ? ' on this and other pages' - : !multiSelect.someSelectedItemsOutsideCurrentPage && - multiSelect.someSelectedOnCurrentPage - ? '' - : multiSelect.someSelectedItemsOutsideCurrentPage && - !multiSelect.someSelectedOnCurrentPage - ? ' on other pages' - : '' - }`} - - )} -
- {children} - - - + + + {!!multiSelect.selectionCount && ( + + {`${pluralize(multiSelect.selectionCount, entityWord, { + plural: entityWordPlural, + })} selected${ + multiSelect.someSelectedItemsOutsideCurrentPage && + multiSelect.someSelectedOnCurrentPage + ? ' on this and other pages' + : !multiSelect.someSelectedItemsOutsideCurrentPage && + multiSelect.someSelectedOnCurrentPage + ? '' + : multiSelect.someSelectedItemsOutsideCurrentPage && + !multiSelect.someSelectedOnCurrentPage + ? ' on other pages' + : '' + }`} + )} - -
+
+ {children} + + + ) }