From 571a81865c44308ce657e51ff553f60c5332a2b4 Mon Sep 17 00:00:00 2001 From: Max Korsunov Date: Fri, 8 Nov 2024 15:14:41 +0400 Subject: [PATCH] feat: create Tailwind-based UI package (#1891) --- .../account/use-staking-tokens-and-filter.ts | 4 +- apps/minifront/tsconfig.json | 3 +- package.json | 4 +- packages/ui-tailwind/.storybook/main.ts | 35 + .../ui-tailwind/.storybook/penumbra-theme.ts | 20 + packages/ui-tailwind/.storybook/preview.tsx | 63 + .../ui-tailwind/.storybook/public/logo.svg | 3 + packages/ui-tailwind/.storybook/tailwind.css | 3 + packages/ui-tailwind/.storybook/typings.d.ts | 14 + packages/ui-tailwind/eslint.config.js | 20 + packages/ui-tailwind/package.json | 80 + packages/ui-tailwind/postcss.config.js | 6 + .../src/AddressView/AddressIcon.tsx | 15 + .../src/AddressView/index.stories.tsx | 35 + .../ui-tailwind/src/AddressView/index.tsx | 58 + .../src/AssetIcon/DelegationTokenIcon.tsx | 104 + .../src/AssetIcon/UnbondingTokenIcon.tsx | 104 + .../src/AssetIcon/index.stories.tsx | 37 + packages/ui-tailwind/src/AssetIcon/index.tsx | 50 + .../ui-tailwind/src/AssetSelector/Custom.tsx | 126 + .../src/AssetSelector/SearchFilter.tsx | 21 + .../src/AssetSelector/SelectItem.tsx | 84 + .../ui-tailwind/src/AssetSelector/Trigger.tsx | 74 + .../src/AssetSelector/index.stories.tsx | 42 + .../ui-tailwind/src/AssetSelector/index.tsx | 148 + .../src/AssetSelector/shared/Context.tsx | 19 + .../filterMetadataOrBalancesResponseByText.ts | 22 + .../src/AssetSelector/shared/groupAndSort.ts | 92 + .../src/AssetSelector/shared/helpers.ts | 26 + .../src/AssetSelector/shared/types.ts | 29 + .../ui-tailwind/src/Button/index.stories.tsx | 35 + packages/ui-tailwind/src/Button/index.tsx | 155 + .../ui-tailwind/src/Card/index.stories.tsx | 85 + packages/ui-tailwind/src/Card/index.tsx | 72 + .../ui-tailwind/src/ConditionalWrap/index.tsx | 61 + .../CopyToClipboardButton/index.stories.tsx | 18 + .../src/CopyToClipboardButton/index.tsx | 43 + packages/ui-tailwind/src/Density/index.tsx | 82 + packages/ui-tailwind/src/Dialog/Content.tsx | 80 + packages/ui-tailwind/src/Dialog/Context.tsx | 6 + .../ui-tailwind/src/Dialog/EmptyContent.tsx | 26 + packages/ui-tailwind/src/Dialog/RadioItem.tsx | 100 + .../ui-tailwind/src/Dialog/RadioItemGroup.tsx | 12 + packages/ui-tailwind/src/Dialog/Trigger.tsx | 19 + .../ui-tailwind/src/Dialog/index.stories.tsx | 111 + packages/ui-tailwind/src/Dialog/index.tsx | 151 + .../ui-tailwind/src/Display/index.stories.tsx | 46 + packages/ui-tailwind/src/Display/index.tsx | 27 + .../src/DropdownMenu/CheckboxItem.tsx | 39 + .../ui-tailwind/src/DropdownMenu/Content.tsx | 29 + .../ui-tailwind/src/DropdownMenu/Item.tsx | 27 + .../src/DropdownMenu/RadioGroup.tsx | 16 + .../src/DropdownMenu/RadioItem.tsx | 32 + .../ui-tailwind/src/DropdownMenu/Root.tsx | 90 + .../ui-tailwind/src/DropdownMenu/Trigger.tsx | 10 + .../src/DropdownMenu/index.stories.tsx | 117 + .../ui-tailwind/src/DropdownMenu/index.tsx | 9 + .../ui-tailwind/src/Grid/index.stories.tsx | 65 + packages/ui-tailwind/src/Grid/index.tsx | 169 + .../ui-tailwind/src/Icon/index.stories.ts | 25 + packages/ui-tailwind/src/Icon/index.tsx | 66 + .../ui-tailwind/src/Identicon/generate.ts | 43 + .../src/Identicon/index.stories.tsx | 19 + packages/ui-tailwind/src/Identicon/index.tsx | 94 + .../src/MenuItem/index.stories.tsx | 31 + packages/ui-tailwind/src/MenuItem/index.tsx | 32 + .../ui-tailwind/src/Pill/index.stories.tsx | 18 + packages/ui-tailwind/src/Pill/index.tsx | 86 + .../ui-tailwind/src/Popover/index.stories.tsx | 59 + packages/ui-tailwind/src/Popover/index.tsx | 127 + .../src/Progress/index.stories.tsx | 19 + packages/ui-tailwind/src/Progress/index.tsx | 57 + .../ui-tailwind/src/Table/index.stories.tsx | 58 + packages/ui-tailwind/src/Table/index.tsx | 161 + .../ui-tailwind/src/Tabs/index.stories.tsx | 38 + packages/ui-tailwind/src/Tabs/index.tsx | 113 + .../ui-tailwind/src/Text/index.stories.tsx | 151 + packages/ui-tailwind/src/Text/index.tsx | 199 + packages/ui-tailwind/src/Text/types.ts | 132 + .../src/TextInput/index.stories.tsx | 64 + packages/ui-tailwind/src/TextInput/index.tsx | 90 + .../ui-tailwind/src/Toast/index.stories.tsx | 95 + packages/ui-tailwind/src/Toast/index.tsx | 3 + packages/ui-tailwind/src/Toast/open.ts | 107 + packages/ui-tailwind/src/Toast/provider.tsx | 41 + .../ui-tailwind/src/Tooltip/index.stories.tsx | 29 + packages/ui-tailwind/src/Tooltip/index.tsx | 5 + packages/ui-tailwind/src/Tooltip/tooltip.tsx | 70 + .../src/ValueView/index.stories.tsx | 43 + packages/ui-tailwind/src/ValueView/index.tsx | 95 + .../src/WalletBalance/index.stories.tsx | 27 + .../ui-tailwind/src/WalletBalance/index.tsx | 83 + packages/ui-tailwind/src/theme/fonts.css | 120 + .../src/theme/fonts/IosevkaTerm-Regular.woff2 | Bin 0 -> 108736 bytes .../theme/fonts/Poppins-Italic-LatinExt.woff2 | Bin 0 -> 5900 bytes .../src/theme/fonts/Poppins-Italic.woff2 | Bin 0 -> 8676 bytes .../theme/fonts/Poppins-Medium-LatinExt.woff2 | Bin 0 -> 5472 bytes .../src/theme/fonts/Poppins-Medium.woff2 | Bin 0 -> 7740 bytes .../fonts/Poppins-MediumItalic-LatinExt.woff2 | Bin 0 -> 5824 bytes .../theme/fonts/Poppins-MediumItalic.woff2 | Bin 0 -> 8516 bytes .../fonts/Poppins-Regular-LatinExt.woff2 | Bin 0 -> 5532 bytes .../src/theme/fonts/Poppins-Regular.woff2 | Bin 0 -> 7900 bytes .../fonts/WorkSans-Medium-LatinExt.woff2 | Bin 0 -> 15552 bytes .../fonts/WorkSans-Medium-Vietnamese.woff2 | Bin 0 -> 5296 bytes .../src/theme/fonts/WorkSans-Medium.woff2 | Bin 0 -> 20864 bytes packages/ui-tailwind/src/theme/globals.css | 18 + packages/ui-tailwind/src/theme/index.tsx | 6 + .../ui-tailwind/src/theme/tailwind-config.ts | 67 + packages/ui-tailwind/src/theme/theme.ts | 295 + packages/ui-tailwind/src/utils/action-type.ts | 98 + .../src/utils/bufs/address-view.ts | 54 + .../src/utils/bufs/balances-responses.ts | 18 + packages/ui-tailwind/src/utils/bufs/index.ts | 9 + .../ui-tailwind/src/utils/bufs/metadata.ts | 78 + .../ui-tailwind/src/utils/bufs/value-view.ts | 68 + packages/ui-tailwind/src/utils/button.ts | 76 + packages/ui-tailwind/src/utils/color.ts | 116 + packages/ui-tailwind/src/utils/density.ts | 38 + .../ui-tailwind/src/utils/disabled-context.ts | 44 + packages/ui-tailwind/src/utils/hexOpacity.ts | 12 + packages/ui-tailwind/src/utils/menu-item.ts | 25 + packages/ui-tailwind/src/utils/popover.ts | 24 + packages/ui-tailwind/src/utils/typography.ts | 48 + packages/ui-tailwind/tailwind.config.ts | 7 + packages/ui-tailwind/tsconfig.json | 11 + packages/ui-tailwind/vite.config.ts | 66 + packages/ui/package.json | 12 +- packages/ui/tsconfig.json | 3 +- pnpm-lock.yaml | 8779 ++++++----------- 129 files changed, 9554 insertions(+), 5891 deletions(-) create mode 100644 packages/ui-tailwind/.storybook/main.ts create mode 100644 packages/ui-tailwind/.storybook/penumbra-theme.ts create mode 100644 packages/ui-tailwind/.storybook/preview.tsx create mode 100644 packages/ui-tailwind/.storybook/public/logo.svg create mode 100644 packages/ui-tailwind/.storybook/tailwind.css create mode 100644 packages/ui-tailwind/.storybook/typings.d.ts create mode 100644 packages/ui-tailwind/eslint.config.js create mode 100644 packages/ui-tailwind/package.json create mode 100644 packages/ui-tailwind/postcss.config.js create mode 100644 packages/ui-tailwind/src/AddressView/AddressIcon.tsx create mode 100644 packages/ui-tailwind/src/AddressView/index.stories.tsx create mode 100644 packages/ui-tailwind/src/AddressView/index.tsx create mode 100644 packages/ui-tailwind/src/AssetIcon/DelegationTokenIcon.tsx create mode 100644 packages/ui-tailwind/src/AssetIcon/UnbondingTokenIcon.tsx create mode 100644 packages/ui-tailwind/src/AssetIcon/index.stories.tsx create mode 100644 packages/ui-tailwind/src/AssetIcon/index.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/Custom.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/SearchFilter.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/SelectItem.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/Trigger.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/index.stories.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/index.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/shared/Context.tsx create mode 100644 packages/ui-tailwind/src/AssetSelector/shared/filterMetadataOrBalancesResponseByText.ts create mode 100644 packages/ui-tailwind/src/AssetSelector/shared/groupAndSort.ts create mode 100644 packages/ui-tailwind/src/AssetSelector/shared/helpers.ts create mode 100644 packages/ui-tailwind/src/AssetSelector/shared/types.ts create mode 100644 packages/ui-tailwind/src/Button/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Button/index.tsx create mode 100644 packages/ui-tailwind/src/Card/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Card/index.tsx create mode 100644 packages/ui-tailwind/src/ConditionalWrap/index.tsx create mode 100644 packages/ui-tailwind/src/CopyToClipboardButton/index.stories.tsx create mode 100644 packages/ui-tailwind/src/CopyToClipboardButton/index.tsx create mode 100644 packages/ui-tailwind/src/Density/index.tsx create mode 100644 packages/ui-tailwind/src/Dialog/Content.tsx create mode 100644 packages/ui-tailwind/src/Dialog/Context.tsx create mode 100644 packages/ui-tailwind/src/Dialog/EmptyContent.tsx create mode 100644 packages/ui-tailwind/src/Dialog/RadioItem.tsx create mode 100644 packages/ui-tailwind/src/Dialog/RadioItemGroup.tsx create mode 100644 packages/ui-tailwind/src/Dialog/Trigger.tsx create mode 100644 packages/ui-tailwind/src/Dialog/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Dialog/index.tsx create mode 100644 packages/ui-tailwind/src/Display/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Display/index.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/CheckboxItem.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/Content.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/Item.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/RadioGroup.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/RadioItem.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/Root.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/Trigger.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/index.stories.tsx create mode 100644 packages/ui-tailwind/src/DropdownMenu/index.tsx create mode 100644 packages/ui-tailwind/src/Grid/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Grid/index.tsx create mode 100644 packages/ui-tailwind/src/Icon/index.stories.ts create mode 100644 packages/ui-tailwind/src/Icon/index.tsx create mode 100644 packages/ui-tailwind/src/Identicon/generate.ts create mode 100644 packages/ui-tailwind/src/Identicon/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Identicon/index.tsx create mode 100644 packages/ui-tailwind/src/MenuItem/index.stories.tsx create mode 100644 packages/ui-tailwind/src/MenuItem/index.tsx create mode 100644 packages/ui-tailwind/src/Pill/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Pill/index.tsx create mode 100644 packages/ui-tailwind/src/Popover/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Popover/index.tsx create mode 100644 packages/ui-tailwind/src/Progress/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Progress/index.tsx create mode 100644 packages/ui-tailwind/src/Table/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Table/index.tsx create mode 100644 packages/ui-tailwind/src/Tabs/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Tabs/index.tsx create mode 100644 packages/ui-tailwind/src/Text/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Text/index.tsx create mode 100644 packages/ui-tailwind/src/Text/types.ts create mode 100644 packages/ui-tailwind/src/TextInput/index.stories.tsx create mode 100644 packages/ui-tailwind/src/TextInput/index.tsx create mode 100644 packages/ui-tailwind/src/Toast/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Toast/index.tsx create mode 100644 packages/ui-tailwind/src/Toast/open.ts create mode 100644 packages/ui-tailwind/src/Toast/provider.tsx create mode 100644 packages/ui-tailwind/src/Tooltip/index.stories.tsx create mode 100644 packages/ui-tailwind/src/Tooltip/index.tsx create mode 100644 packages/ui-tailwind/src/Tooltip/tooltip.tsx create mode 100644 packages/ui-tailwind/src/ValueView/index.stories.tsx create mode 100644 packages/ui-tailwind/src/ValueView/index.tsx create mode 100644 packages/ui-tailwind/src/WalletBalance/index.stories.tsx create mode 100644 packages/ui-tailwind/src/WalletBalance/index.tsx create mode 100644 packages/ui-tailwind/src/theme/fonts.css create mode 100644 packages/ui-tailwind/src/theme/fonts/IosevkaTerm-Regular.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-Italic-LatinExt.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-Italic.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-Medium-LatinExt.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-Medium.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-MediumItalic-LatinExt.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-MediumItalic.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-Regular-LatinExt.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/Poppins-Regular.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/WorkSans-Medium-LatinExt.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/WorkSans-Medium-Vietnamese.woff2 create mode 100644 packages/ui-tailwind/src/theme/fonts/WorkSans-Medium.woff2 create mode 100644 packages/ui-tailwind/src/theme/globals.css create mode 100644 packages/ui-tailwind/src/theme/index.tsx create mode 100644 packages/ui-tailwind/src/theme/tailwind-config.ts create mode 100644 packages/ui-tailwind/src/theme/theme.ts create mode 100644 packages/ui-tailwind/src/utils/action-type.ts create mode 100644 packages/ui-tailwind/src/utils/bufs/address-view.ts create mode 100644 packages/ui-tailwind/src/utils/bufs/balances-responses.ts create mode 100644 packages/ui-tailwind/src/utils/bufs/index.ts create mode 100644 packages/ui-tailwind/src/utils/bufs/metadata.ts create mode 100644 packages/ui-tailwind/src/utils/bufs/value-view.ts create mode 100644 packages/ui-tailwind/src/utils/button.ts create mode 100644 packages/ui-tailwind/src/utils/color.ts create mode 100644 packages/ui-tailwind/src/utils/density.ts create mode 100644 packages/ui-tailwind/src/utils/disabled-context.ts create mode 100644 packages/ui-tailwind/src/utils/hexOpacity.ts create mode 100644 packages/ui-tailwind/src/utils/menu-item.ts create mode 100644 packages/ui-tailwind/src/utils/popover.ts create mode 100644 packages/ui-tailwind/src/utils/typography.ts create mode 100644 packages/ui-tailwind/tailwind.config.ts create mode 100644 packages/ui-tailwind/tsconfig.json create mode 100644 packages/ui-tailwind/vite.config.ts diff --git a/apps/minifront/src/components/staking/account/use-staking-tokens-and-filter.ts b/apps/minifront/src/components/staking/account/use-staking-tokens-and-filter.ts index f691b5cd96..3376ba36e7 100644 --- a/apps/minifront/src/components/staking/account/use-staking-tokens-and-filter.ts +++ b/apps/minifront/src/components/staking/account/use-staking-tokens-and-filter.ts @@ -63,7 +63,7 @@ export const useStakingTokensAndFilter = ( shouldReselect: (before, after) => before?.data !== after.data, }); - const stakingTokensByAccount = useMemo(() => { + const stakingTokensByAccount = useMemo>(() => { if (!stakingTokenMetadata || !balancesByAccount) { return new Map(); } @@ -71,7 +71,7 @@ export const useStakingTokensAndFilter = ( return balancesByAccount.reduce( (acc: Map, cur: BalancesByAccount) => toStakingTokensByAccount(acc, cur, stakingTokenMetadata), - new Map(), + new Map(), ); }, [stakingTokenMetadata, balancesByAccount]); diff --git a/apps/minifront/tsconfig.json b/apps/minifront/tsconfig.json index f52bee8c2d..f70cb69ec0 100644 --- a/apps/minifront/tsconfig.json +++ b/apps/minifront/tsconfig.json @@ -4,7 +4,8 @@ "exactOptionalPropertyTypes": false, "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], "noEmit": true, - "target": "ESNext" + "target": "ESNext", + "types": ["vitest/globals", "@testing-library/jest-dom"] }, "extends": ["@tsconfig/strictest/tsconfig.json", "@tsconfig/vite-react/tsconfig.json"], "include": ["src", "tests-setup.ts", "__mocks__", "vite.config.ts", "vitest.config.ts"] diff --git a/package.json b/package.json index b5a2c82268..ffc962f9ac 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@eslint/js": "^9.6.0", "@microsoft/api-extractor": "^7.47.0", "@repo/tailwind-config": "workspace:*", - "@storybook/react-vite": "8.1.1", + "@storybook/react-vite": "^8.4.2", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", "@tsconfig/strictest": "^2.0.5", @@ -55,7 +55,7 @@ "@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react-swc": "^3.6.0", "@vitest/browser": "^1.6.0", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.20", "eslint": "^9.6.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", diff --git a/packages/ui-tailwind/.storybook/main.ts b/packages/ui-tailwind/.storybook/main.ts new file mode 100644 index 0000000000..2a6ad3473e --- /dev/null +++ b/packages/ui-tailwind/.storybook/main.ts @@ -0,0 +1,35 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +import { join, dirname } from 'path'; + +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +function getAbsolutePath(value: string): any { + return dirname(require.resolve(join(value, 'package.json'))); +} +const config: StorybookConfig = { + stories: [ + { + directory: '../src', + files: '**/@(*.stories.@(js|jsx|mjs|ts|tsx)|*.mdx)', + titlePrefix: 'UI library', + }, + ], + addons: [ + getAbsolutePath('@storybook/addon-links'), + getAbsolutePath('@storybook/addon-essentials'), + getAbsolutePath('@chromatic-com/storybook'), + getAbsolutePath('@storybook/addon-interactions'), + getAbsolutePath('@storybook/preview-api'), + ], + framework: { + name: getAbsolutePath('@storybook/react-vite'), + options: {}, + }, + typescript: { + reactDocgen: 'react-docgen-typescript', + }, +}; +export default config; diff --git a/packages/ui-tailwind/.storybook/penumbra-theme.ts b/packages/ui-tailwind/.storybook/penumbra-theme.ts new file mode 100644 index 0000000000..70bf9ffcfc --- /dev/null +++ b/packages/ui-tailwind/.storybook/penumbra-theme.ts @@ -0,0 +1,20 @@ +import { create } from '@storybook/theming/create'; +import logo from './public/logo.svg'; + +const penumbraTheme = create({ + appBg: 'black', + appContentBg: 'black', + appPreviewBg: 'black', + barBg: 'black', + base: 'dark', + brandImage: logo, + brandTitle: 'Penumbra UI library', + colorPrimary: '#8d5728', + colorSecondary: '#629994', + fontBase: 'Poppins', + fontCode: '"Iosevka Term",monospace', + textColor: 'white', + textMutedColor: '#e3e3e3', +}); + +export default penumbraTheme; diff --git a/packages/ui-tailwind/.storybook/preview.tsx b/packages/ui-tailwind/.storybook/preview.tsx new file mode 100644 index 0000000000..77b9f12fa0 --- /dev/null +++ b/packages/ui-tailwind/.storybook/preview.tsx @@ -0,0 +1,63 @@ +import type { Preview } from '@storybook/react'; +import penumbraTheme from './penumbra-theme'; +import { useState } from 'react'; +import { ConditionalWrap } from '../src/ConditionalWrap'; +import { Density } from '../src/Density'; +import { Tabs } from '../src/Tabs'; + +import './tailwind.css'; +import '../src/theme/fonts.css'; +import '../src/theme/globals.css'; + +/** + * Utility component to let users control the density, for components whose + * stories include the `density` tag. + */ +const DensityWrapper = ({ children, showDensityControl }) => { + const [density, setDensity] = useState('sparse'); + + return ( + {children}} + else={children => {children}} + > +
+ {showDensityControl && ( + + + + )} + + {children} +
+
+ ); +}; + +const preview: Preview = { + tags: ['autodocs'], + parameters: { + docs: { + theme: penumbraTheme, + }, + }, + decorators: [ + (Story, { title, tags }) => { + return ( + + + + ); + }, + ], +}; + +export default preview; diff --git a/packages/ui-tailwind/.storybook/public/logo.svg b/packages/ui-tailwind/.storybook/public/logo.svg new file mode 100644 index 0000000000..0293e288a9 --- /dev/null +++ b/packages/ui-tailwind/.storybook/public/logo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/ui-tailwind/.storybook/tailwind.css b/packages/ui-tailwind/.storybook/tailwind.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/packages/ui-tailwind/.storybook/tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/ui-tailwind/.storybook/typings.d.ts b/packages/ui-tailwind/.storybook/typings.d.ts new file mode 100644 index 0000000000..68f8b2215d --- /dev/null +++ b/packages/ui-tailwind/.storybook/typings.d.ts @@ -0,0 +1,14 @@ +declare module '*.jpg' { + const value: string; + export default value; +} + +declare module '*.png' { + const value: string; + export default value; +} + +declare module '*.svg' { + const value: string; + export default value; +} diff --git a/packages/ui-tailwind/eslint.config.js b/packages/ui-tailwind/eslint.config.js new file mode 100644 index 0000000000..c04ada5a3d --- /dev/null +++ b/packages/ui-tailwind/eslint.config.js @@ -0,0 +1,20 @@ +// eslint-disable-next-line -- ignore +import config from '../../eslint.config.js'; +import tailwindcss from 'eslint-plugin-tailwindcss'; +import { resolve } from 'node:path'; + +config.push({ + name: 'custom:tailwindcss-config', + plugins: { tailwindcss }, + settings: { + tailwindcss: { + config: resolve('tailwind.config.ts'), + }, + }, + rules: { + ...tailwindcss.configs.recommended.rules, + 'tailwindcss/no-custom-classname': ['error', { callees: ['cn', 'cva', 'clsx'] }], + }, +}); + +export default config; diff --git a/packages/ui-tailwind/package.json b/packages/ui-tailwind/package.json new file mode 100644 index 0000000000..f0b4100531 --- /dev/null +++ b/packages/ui-tailwind/package.json @@ -0,0 +1,80 @@ +{ + "name": "@penumbra-zone/ui-tailwind", + "version": "0.1.0", + "license": "(MIT OR Apache-2.0)", + "description": "UI components for Penumbra", + "type": "module", + "engine": { + "node": ">=22" + }, + "scripts": { + "build": "vite build", + "build-storybook": "storybook build", + "dev:pack": "VITE_WATCH=true vite build --watch", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "lint:strict": "tsc --noEmit && eslint src --max-warnings 0", + "storybook": "storybook dev -p 6006" + }, + "files": [ + "dist" + ], + "exports": { + "./*": "./src/*/index.tsx" + }, + "publishConfig": { + "exports": { + "./style.css": { + "default": "./dist/style.css" + }, + "./*": { + "types": "./dist/src/*/index.d.ts", + "default": "./dist/src/*/index.js" + } + } + }, + "dependencies": { + "@penumbra-zone/bech32m": "workspace:*", + "@penumbra-zone/getters": "workspace:*", + "@penumbra-zone/protobuf": "workspace:*", + "@penumbra-zone/types": "workspace:*", + "@radix-ui/react-dialog": "1.0.5", + "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-progress": "^1.0.3", + "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-tooltip": "^1.0.7", + "clsx": "^2.1.1", + "lucide-react": "^0.378.0", + "murmurhash3js": "^3.0.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "sonner": "1.4.3", + "tinycolor2": "^1.6.0" + }, + "devDependencies": { + "@chromatic-com/storybook": "^3.2.2", + "@storybook/addon-essentials": "^8.4.2", + "@storybook/addon-interactions": "^8.4.2", + "@storybook/addon-links": "^8.1.1", + "@storybook/blocks": "^8.4.2", + "@storybook/manager-api": "^8.1.11", + "@storybook/preview-api": "^8.1.1", + "@storybook/react": "^8.4.2", + "@storybook/react-vite": "^8.4.2", + "@storybook/test": "^8.4.2", + "@storybook/theming": "^8.1.11", + "@types/murmurhash3js": "^3.0.7", + "@types/react": "^18.3.2", + "@types/react-dom": "^18.3.0", + "@types/tinycolor2": "^1.4.6", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.38", + "storybook": "^8.4.2", + "tailwindcss": "^3.4.3", + "typescript": "5.5.3", + "vite": "^5.2.11", + "vite-plugin-dts": "^4.0.3" + } +} diff --git a/packages/ui-tailwind/postcss.config.js b/packages/ui-tailwind/postcss.config.js new file mode 100644 index 0000000000..2aa7205d4b --- /dev/null +++ b/packages/ui-tailwind/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/ui-tailwind/src/AddressView/AddressIcon.tsx b/packages/ui-tailwind/src/AddressView/AddressIcon.tsx new file mode 100644 index 0000000000..04937b9bb7 --- /dev/null +++ b/packages/ui-tailwind/src/AddressView/AddressIcon.tsx @@ -0,0 +1,15 @@ +import { Address } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; +import { bech32mAddress } from '@penumbra-zone/bech32m/penumbra'; +import { Identicon } from '../Identicon'; + +export interface AddressIconProps { + address: Address; + size: number; +} + +/** + * A simple component to display a consistently styled icon for a given address. + */ +export const AddressIcon = ({ address, size }: AddressIconProps) => ( + +); diff --git a/packages/ui-tailwind/src/AddressView/index.stories.tsx b/packages/ui-tailwind/src/AddressView/index.stories.tsx new file mode 100644 index 0000000000..cafe0b3a25 --- /dev/null +++ b/packages/ui-tailwind/src/AddressView/index.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { AddressViewComponent } from '.'; +import { ADDRESS_VIEW_DECODED, ADDRESS_VIEW_OPAQUE } from '../utils/bufs'; + +const meta: Meta = { + component: AddressViewComponent, + tags: ['autodocs', '!dev'], + argTypes: { + addressView: { + options: ['Sample decoded address view', 'Sample opaque address view'], + mapping: { + 'Sample decoded address view': ADDRESS_VIEW_DECODED, + 'Sample opaque address view': ADDRESS_VIEW_OPAQUE, + }, + }, + }, + decorators: [ + Story => ( +
+ +
+ ), + ], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + addressView: ADDRESS_VIEW_DECODED, + copyable: true, + }, +}; diff --git a/packages/ui-tailwind/src/AddressView/index.tsx b/packages/ui-tailwind/src/AddressView/index.tsx new file mode 100644 index 0000000000..a93e95605d --- /dev/null +++ b/packages/ui-tailwind/src/AddressView/index.tsx @@ -0,0 +1,58 @@ +import { AddressView } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; +import { getAddressIndex } from '@penumbra-zone/getters/address-view'; +import { bech32mAddress } from '@penumbra-zone/bech32m/penumbra'; +import { CopyToClipboardButton } from '../CopyToClipboardButton'; +import { AddressIcon } from './AddressIcon'; +import { Text } from '../Text'; + +export interface AddressViewProps { + addressView: AddressView | undefined; + copyable?: boolean; + hideIcon?: boolean; +} + +// Renders an address or an address view. +// If the view is given and is "visible", the account information will be displayed instead. +export const AddressViewComponent = ({ + addressView, + copyable = true, + hideIcon, +}: AddressViewProps) => { + if (!addressView?.addressView.value?.address) { + return null; + } + + const addressIndex = getAddressIndex.optional(addressView); + + // a randomized index has nonzero randomizer bytes + const isRandomized = addressIndex?.randomizer.some(v => v); + + const encodedAddress = bech32mAddress(addressView.addressView.value.address); + + return ( +
+ {!hideIcon && ( +
+ +
+ )} + + {addressIndex ? ( + + {isRandomized && 'IBC Deposit Address for '} + {`Sub-Account #${addressIndex.account}`} + + ) : ( + + {encodedAddress} + + )} + + {copyable && !isRandomized && ( +
+ +
+ )} +
+ ); +}; diff --git a/packages/ui-tailwind/src/AssetIcon/DelegationTokenIcon.tsx b/packages/ui-tailwind/src/AssetIcon/DelegationTokenIcon.tsx new file mode 100644 index 0000000000..f81f006418 --- /dev/null +++ b/packages/ui-tailwind/src/AssetIcon/DelegationTokenIcon.tsx @@ -0,0 +1,104 @@ +import { assetPatterns } from '@penumbra-zone/types/assets'; + +const getFirstEightCharactersOfValidatorId = (displayDenom = ''): [string, string] => { + const id = (assetPatterns.delegationToken.capture(displayDenom)?.id ?? '').substring(0, 8); + + const firstFour = id.substring(0, 4); + const lastFour = id.substring(4); + + return [firstFour, lastFour]; +}; + +export interface DelegationTokenIconProps { + displayDenom?: string; +} + +export const DelegationTokenIcon = ({ displayDenom }: DelegationTokenIconProps) => { + const [firstFour, lastFour] = getFirstEightCharactersOfValidatorId(displayDenom); + + return ( + + + + + + + + + + + + + + {firstFour} + + + {lastFour} + + + + + + + + + ); +}; diff --git a/packages/ui-tailwind/src/AssetIcon/UnbondingTokenIcon.tsx b/packages/ui-tailwind/src/AssetIcon/UnbondingTokenIcon.tsx new file mode 100644 index 0000000000..5ebd251ef1 --- /dev/null +++ b/packages/ui-tailwind/src/AssetIcon/UnbondingTokenIcon.tsx @@ -0,0 +1,104 @@ +import { assetPatterns } from '@penumbra-zone/types/assets'; + +const getFirstEightCharactersOfValidatorId = (displayDenom = ''): [string, string] => { + const id = (assetPatterns.unbondingToken.capture(displayDenom)?.id ?? '').substring(0, 8); + + const firstFour = id.substring(0, 4); + const lastFour = id.substring(4); + + return [firstFour, lastFour]; +}; + +export interface UnbondingTokenIconProps { + displayDenom?: string; +} + +export const UnbondingTokenIcon = ({ displayDenom }: UnbondingTokenIconProps) => { + const [firstFour, lastFour] = getFirstEightCharactersOfValidatorId(displayDenom); + + return ( + + + + + + + + + + + + + + {firstFour} + + + {lastFour} + + + + + + + + + ); +}; diff --git a/packages/ui-tailwind/src/AssetIcon/index.stories.tsx b/packages/ui-tailwind/src/AssetIcon/index.stories.tsx new file mode 100644 index 0000000000..aa0a66e713 --- /dev/null +++ b/packages/ui-tailwind/src/AssetIcon/index.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { AssetIcon } from '.'; +import { + PENUMBRA_METADATA, + DELEGATION_TOKEN_METADATA, + UNBONDING_TOKEN_METADATA, + UNKNOWN_TOKEN_METADATA, + PIZZA_METADATA, +} from '../utils/bufs'; + +const meta: Meta = { + component: AssetIcon, + tags: ['autodocs', '!dev'], + argTypes: { + metadata: { + options: ['Penumbra', 'Pizza', 'Delegation token', 'Unbonding token', 'Unknown asset'], + mapping: { + Penumbra: PENUMBRA_METADATA, + Pizza: PIZZA_METADATA, + 'Delegation token': DELEGATION_TOKEN_METADATA, + 'Unbonding token': UNBONDING_TOKEN_METADATA, + 'Unknown asset': UNKNOWN_TOKEN_METADATA, + }, + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + size: 'md', + metadata: PENUMBRA_METADATA, + }, +}; diff --git a/packages/ui-tailwind/src/AssetIcon/index.tsx b/packages/ui-tailwind/src/AssetIcon/index.tsx new file mode 100644 index 0000000000..cf7a1cf222 --- /dev/null +++ b/packages/ui-tailwind/src/AssetIcon/index.tsx @@ -0,0 +1,50 @@ +import { ReactNode } from 'react'; +import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { getDisplay } from '@penumbra-zone/getters/metadata'; +import { assetPatterns } from '@penumbra-zone/types/assets'; +import { Identicon } from '../Identicon'; +import { DelegationTokenIcon } from './DelegationTokenIcon'; +import { UnbondingTokenIcon } from './UnbondingTokenIcon'; +import cn from 'clsx'; + +type Size = 'lg' | 'md' | 'sm'; + +const sizeMap: Record = { + lg: cn('w-8 h-8'), + md: cn('w-6 h-6'), + sm: cn('w-4 h-4'), +}; + +export interface AssetIconProps { + size?: Size; + metadata?: Metadata; +} + +export const AssetIcon = ({ metadata, size = 'md' }: AssetIconProps) => { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- possibly empty string + const icon = metadata?.images[0]?.png || metadata?.images[0]?.svg; + const display = getDisplay.optional(metadata); + const isDelegationToken = display ? assetPatterns.delegationToken.matches(display) : false; + const isUnbondingToken = display ? assetPatterns.unbondingToken.matches(display) : false; + + let assetIcon: ReactNode; + if (icon) { + assetIcon = Asset icon; + } else if (isDelegationToken) { + assetIcon = ; + } else if (isUnbondingToken) { + /** + * @todo: Render a custom unbonding token for validators that have a + * logo -- e.g., with the validator ID superimposed over the validator logo. + */ + assetIcon = ; + } else { + assetIcon = ; + } + + return ( +
*]:w-full [&>*]:h-full')}> + {assetIcon} +
+ ); +}; diff --git a/packages/ui-tailwind/src/AssetSelector/Custom.tsx b/packages/ui-tailwind/src/AssetSelector/Custom.tsx new file mode 100644 index 0000000000..f9df9516b8 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/Custom.tsx @@ -0,0 +1,126 @@ +import { ReactNode, useId, useState } from 'react'; +import { Dialog } from '../Dialog'; +import { getHash } from './shared/helpers'; +import { AssetSelectorContext } from './shared/Context'; +import { AssetSelectorSearchFilter } from './SearchFilter'; +import { AssetSelectorTrigger } from './Trigger'; +import { AssetSelectorBaseProps } from './shared/types'; + +interface ChildrenArguments { + onClose: VoidFunction; + /** + * Takes the `Metadata` or `BalancesResponse` and returns + * a unique key string to be used within map in React + */ + getKeyHash: typeof getHash; +} + +export interface AssetSelectorCustomProps extends AssetSelectorBaseProps { + /** A value of the search filter inside the selector dialog */ + search?: string; + + /** Fires when user inputs the value into the search filter inside the selector dialog */ + onSearchChange?: (newValue: string) => void; + + /** + * Use children as a function to get assistance with keying + * the `ListItem`s and implement you own closing logic. + * + * Example: + * ```tsx + * + * {({ getKeyHash, onClose }) => ( + * <> + * {options.map(option => ( + * + * ))} + * + * + * )} + * + * ``` + * */ + children?: ReactNode | ((args: ChildrenArguments) => ReactNode); +} + +/** + * A custom version of the `AssetSelector` that lets you customize the contents of the selector dialog. + * + * Use `AssetSelector.ListItem` inside the `AssetSelector.Custom` to render the options + * of the selector. It is up for you to sort or group the options however you want. + * + * Example usage: + * + * ```tsx + * const [value, setValue] = useState(); + * const [search, setSearch] = useState(''); + * + * const filteredOptions = useMemo( + * () => mixedOptions.filter(filterMetadataOrBalancesResponseByText(search)), + * [search], + * ); + * + * return ( + * + * {({ getKeyHash }) => + * filteredOptions.map(option => ( + * + * )) + * } + * + * ); + * ``` + */ +export const AssetSelectorCustom = ({ + value, + onChange, + dialogTitle = 'Select Asset', + actionType, + disabled, + children, + search, + onSearchChange, +}: AssetSelectorCustomProps) => { + const layoutId = useId(); + + const [isOpen, setIsOpen] = useState(false); + + const onClose = () => setIsOpen(false); + + return ( + setIsOpen(false)}> + + setIsOpen(true)} + /> + + + ) + } + > + +
+ {typeof children === 'function' + ? children({ onClose, getKeyHash: getHash }) + : children} +
+
+
+
+
+ ); +}; diff --git a/packages/ui-tailwind/src/AssetSelector/SearchFilter.tsx b/packages/ui-tailwind/src/AssetSelector/SearchFilter.tsx new file mode 100644 index 0000000000..7b179303a7 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/SearchFilter.tsx @@ -0,0 +1,21 @@ +import { Search } from 'lucide-react'; +import { Icon } from '../Icon'; +import { TextInput } from '../TextInput'; + +export interface AssetSelectorSearchFilterProps { + value?: string; + onChange?: (newValue: string) => void; +} + +export const AssetSelectorSearchFilter = ({ value, onChange }: AssetSelectorSearchFilterProps) => { + const handleSearch = (newValue: string) => onChange?.(newValue); + + return ( + } + value={value ?? ''} + onChange={handleSearch} + placeholder='Search...' + /> + ); +}; diff --git a/packages/ui-tailwind/src/AssetSelector/SelectItem.tsx b/packages/ui-tailwind/src/AssetSelector/SelectItem.tsx new file mode 100644 index 0000000000..8f17139c83 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/SelectItem.tsx @@ -0,0 +1,84 @@ +import { AssetIcon } from '../AssetIcon'; +import { Text } from '../Text'; +import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view'; +import { + getAddressIndex, + getBalanceView, + getMetadataFromBalancesResponse, +} from '@penumbra-zone/getters/balances-response'; +import { ActionType } from '../utils/action-type'; +import { AssetSelectorValue } from './shared/types'; +import { getHash, isBalancesResponse } from './shared/helpers'; +import { RadioItem } from '../Dialog/RadioItem'; +import { useAssetsSelector } from './shared/Context'; + +export interface AssetSelectorItemProps { + /** + * A `BalancesResponse` or `Metadata` protobuf message type. Renders the asset + * icon name and, depending on the type, the value of the asset in the account. + * */ + value: AssetSelectorValue; + disabled?: boolean; + actionType?: ActionType; +} + +/** A radio button that selects an asset or a balance from the `AssetSelector` */ +export const Item = ({ value, disabled, actionType = 'default' }: AssetSelectorItemProps) => { + const { onClose, onChange } = useAssetsSelector(); + + const hash = getHash(value); + + const metadata = isBalancesResponse(value) + ? getMetadataFromBalancesResponse.optional(value) + : value; + + const balance = isBalancesResponse(value) + ? { + addressIndexAccount: getAddressIndex.optional(value)?.account, + valueView: getBalanceView.optional(value), + } + : undefined; + + // click is triggered by radix-ui on focus, click, arrow selection, etc. – basically always + const onSelect = () => { + onChange?.(value); + }; + + return ( + } + title={ + <> + {balance?.valueView && ( + + {getFormattedAmtFromValueView(balance.valueView, true)}{' '} + + )} + + + {metadata?.symbol ?? 'Unknown'} + + + + } + endAdornment={ + balance?.addressIndexAccount !== undefined && ( +
+ + #{balance.addressIndexAccount} + + + Account + +
+ ) + } + /> + ); +}; diff --git a/packages/ui-tailwind/src/AssetSelector/Trigger.tsx b/packages/ui-tailwind/src/AssetSelector/Trigger.tsx new file mode 100644 index 0000000000..396806ce44 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/Trigger.tsx @@ -0,0 +1,74 @@ +import { forwardRef, MouseEventHandler } from 'react'; +import { ChevronsUpDownIcon } from 'lucide-react'; +import cn from 'clsx'; +import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { ActionType, getFocusOutlineColorByActionType } from '../utils/action-type'; +import { useDensity } from '../utils/density'; +import { Icon } from '../Icon'; +import { Text } from '../Text'; +import { AssetIcon } from '../AssetIcon'; +import { isMetadata } from './shared/helpers.ts'; +import { Dialog } from '../Dialog'; +import { AssetSelectorValue } from './shared/types'; + +export interface AssetSelectorTriggerProps { + value?: AssetSelectorValue; + actionType?: ActionType; + disabled?: boolean; + onClick?: MouseEventHandler; + layoutId?: string; +} + +export const AssetSelectorTrigger = forwardRef( + ({ value, actionType = 'default', disabled, onClick }, ref) => { + const density = useDensity(); + + const metadata = isMetadata(value) ? value : getMetadataFromBalancesResponse.optional(value); + + return ( + + + + ); + }, +); +AssetSelectorTrigger.displayName = 'AssetSelectorTrigger'; diff --git a/packages/ui-tailwind/src/AssetSelector/index.stories.tsx b/packages/ui-tailwind/src/AssetSelector/index.stories.tsx new file mode 100644 index 0000000000..f11608b0e2 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/index.stories.tsx @@ -0,0 +1,42 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { AssetSelector, AssetSelectorValue } from '.'; +import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { useState } from 'react'; +import { + OSMO_BALANCE, + OSMO_METADATA, + PENUMBRA2_BALANCE, + PENUMBRA_BALANCE, + PENUMBRA_METADATA, + PIZZA_METADATA, +} from '../utils/bufs'; + +const balanceOptions: BalancesResponse[] = [PENUMBRA_BALANCE, PENUMBRA2_BALANCE, OSMO_BALANCE]; +const assetOptions: Metadata[] = [PIZZA_METADATA, PENUMBRA_METADATA, OSMO_METADATA]; + +const meta: Meta = { + component: AssetSelector, + tags: ['autodocs', '!dev', 'density'], + argTypes: { + value: { control: false }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const MixedBalancesResponsesAndMetadata: Story = { + args: { + dialogTitle: 'Transfer Assets', + assets: assetOptions, + balances: balanceOptions, + }, + + render: function Render(props) { + const [value, setValue] = useState(); + + return ; + }, +}; diff --git a/packages/ui-tailwind/src/AssetSelector/index.tsx b/packages/ui-tailwind/src/AssetSelector/index.tsx new file mode 100644 index 0000000000..c249155751 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/index.tsx @@ -0,0 +1,148 @@ +import { useMemo, useState } from 'react'; +import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; + +import { isBalancesResponse, isMetadata } from './shared/helpers'; +import { filterMetadataOrBalancesResponseByText } from './shared/filterMetadataOrBalancesResponseByText'; +import { AssetSelectorBaseProps, AssetSelectorValue } from './shared/types'; +import { AssetSelectorCustom, AssetSelectorCustomProps } from './Custom'; +import { Item, AssetSelectorItemProps } from './SelectItem'; +import { Text } from '../Text'; +import { filterAssets, groupAndSortBalances } from './shared/groupAndSort'; + +export interface AssetSelectorProps extends AssetSelectorBaseProps { + /** + * An array of `Metadata` – protobuf message types describing the asset: + * its name, symbol, id, icons, and more + */ + assets?: Metadata[]; + /** + * An array of `BalancesResponse` – protobuf message types describing the balance of an asset: + * the account containing the asset, the value of this asset and its description (has `Metadata` inside it) + */ + balances?: BalancesResponse[]; +} +/** + * Allows users to choose an asset for e.g., the swap and send forms. Note that + * it can render an array of just `Metadata`s, or a mixed array of + * both `Metadata`s and `BalancesResponse`s. The latter is useful for e.g., + * letting the user estimate a swap of an asset they don't hold. + * + * The component has two ways of using it: + * + * ### 1. + * + * A default way with pre-defined grouping, sorting, searching and rendering algorithms. Renders the list of balances on top of the dialog with account index grouping and priority sorting within each group. When searching, it filters the assets by name, symbol, display name and base name. + * + * Example: + * + * ```tsx + * const [value, setValue] = useState(); + * + * + * ``` + * + * ### 2. + * + * A custom way. You can use the `AssetSelector.Custom` with `AssetSelector.ListItem` to render the options of the selector. It is up to the consumer to sort or group the options however they want. + * + * Example: + * + * ```tsx + * const [value, setValue] = useState(); + * const [search, setSearch] = useState(''); + * + * const filteredOptions = useMemo( + * () => mixedOptions.filter(filterMetadataOrBalancesResponseByText(search)), + * [search], + * ); + * + * return ( + * + * {({ getKeyHash }) => + * filteredOptions.map(option => ( + * + * )) + * } + * + * ); + * ``` + */ +export const AssetSelector = ({ + assets = [], + balances = [], + value, + onChange, + dialogTitle = 'Select Asset', + actionType, + disabled, +}: AssetSelectorProps) => { + const [search, setSearch] = useState(''); + + const { filteredAssets, filteredBalances } = useMemo( + () => ({ + filteredAssets: filterAssets(assets).filter(filterMetadataOrBalancesResponseByText(search)), + filteredBalances: groupAndSortBalances( + balances.filter(filterMetadataOrBalancesResponseByText(search)), + ), + }), + [assets, balances, search], + ); + + return ( + + {({ getKeyHash }) => ( +
+ {!!filteredBalances.length && ( + + Your Tokens + + )} + + {filteredBalances.map(([account, balances]) => ( +
+ {balances.map(balance => ( + + ))} +
+ ))} + + {!!filteredAssets.length && ( + + All Tokens + + )} + + {filteredAssets.map(asset => ( + + ))} +
+ )} +
+ ); +}; + +AssetSelector.Custom = AssetSelectorCustom; +AssetSelector.Item = Item; + +export { isBalancesResponse, isMetadata, groupAndSortBalances, filterAssets }; + +export type { AssetSelectorValue, AssetSelectorCustomProps, AssetSelectorItemProps }; diff --git a/packages/ui-tailwind/src/AssetSelector/shared/Context.tsx b/packages/ui-tailwind/src/AssetSelector/shared/Context.tsx new file mode 100644 index 0000000000..43255a1d22 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/shared/Context.tsx @@ -0,0 +1,19 @@ +import { createContext, useContext } from 'react'; +import { AssetSelectorValue } from './types.ts'; + +export interface AssetSelectorContextValue { + onClose: VoidFunction; + onChange?: (value: AssetSelectorValue) => void; + value: AssetSelectorValue | undefined; +} + +/** + * Provides helper functions to be consumed from `ListItem` component, only for inner usage. + * These components must be rendered by the user to provide custom sorting or grouping but + * the selection logic is standardized by this context. + */ +export const AssetSelectorContext = createContext( + {} as AssetSelectorContextValue, +); + +export const useAssetsSelector = () => useContext(AssetSelectorContext); diff --git a/packages/ui-tailwind/src/AssetSelector/shared/filterMetadataOrBalancesResponseByText.ts b/packages/ui-tailwind/src/AssetSelector/shared/filterMetadataOrBalancesResponseByText.ts new file mode 100644 index 0000000000..7e5b9599dc --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/shared/filterMetadataOrBalancesResponseByText.ts @@ -0,0 +1,22 @@ +import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { isMetadata } from './helpers.ts'; +import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; + +export const filterMetadataOrBalancesResponseByText = + (textSearch: string) => + (value: Metadata | BalancesResponse): boolean => { + if (!textSearch.trim()) { + return true; + } + + const lowerCaseTextSearch = textSearch.toLocaleLowerCase(); + const metadata = isMetadata(value) ? value : getMetadataFromBalancesResponse(value); + + return ( + metadata.name.toLocaleLowerCase().includes(lowerCaseTextSearch) || + metadata.display.toLocaleLowerCase().includes(lowerCaseTextSearch) || + metadata.base.toLocaleLowerCase().includes(lowerCaseTextSearch) || + metadata.symbol.toLocaleLowerCase().includes(lowerCaseTextSearch) + ); + }; diff --git a/packages/ui-tailwind/src/AssetSelector/shared/groupAndSort.ts b/packages/ui-tailwind/src/AssetSelector/shared/groupAndSort.ts new file mode 100644 index 0000000000..4791965684 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/shared/groupAndSort.ts @@ -0,0 +1,92 @@ +import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { + getAmount, + getMetadataFromBalancesResponse, + getAddressIndex, + getValueViewCaseFromBalancesResponse, +} from '@penumbra-zone/getters/balances-response'; +import { joinLoHiAmount, multiplyAmountByNumber } from '@penumbra-zone/types/amount'; +import { assetPatterns } from '@penumbra-zone/types/assets'; +import { getDisplay } from '@penumbra-zone/getters/metadata'; + +const nonSwappableAssetPatterns = [ + assetPatterns.lpNft, + assetPatterns.proposalNft, + assetPatterns.votingReceipt, + assetPatterns.auctionNft, + assetPatterns.lpNft, + + // In theory, these asset types are swappable, but we have removed them for now to get a better UX + assetPatterns.delegationToken, + assetPatterns.unbondingToken, +]; + +const isSwappableMetadata = (metadata: Metadata): boolean => { + return !nonSwappableAssetPatterns.some(pattern => pattern.matches(getDisplay(metadata))); +}; + +const isUnswappableBalance = (balance: BalancesResponse): boolean => { + const metadata = getMetadataFromBalancesResponse.optional(balance); + if (!metadata) { + return true; + } + return nonSwappableAssetPatterns.some(pattern => pattern.matches(getDisplay(metadata))); +}; + +const isUnknownBalance = (balance: BalancesResponse): boolean => { + return getValueViewCaseFromBalancesResponse.optional(balance) !== 'knownAssetId'; +}; + +const groupByAccount = ( + acc: Record, + curr: BalancesResponse, +): Record => { + const index = getAddressIndex.optional(curr)?.account; + + if (index === undefined || isUnknownBalance(curr) || isUnswappableBalance(curr)) { + return acc; + } + + if (acc[index]) { + acc[index].push(curr); + } else { + acc[index] = [curr]; + } + + return acc; +}; + +const sortByAccountIndex = (a: [string, BalancesResponse[]], b: [string, BalancesResponse[]]) => { + return Number(a[0]) - Number(b[0]); +}; + +const sortbyPriorityScore = (a: BalancesResponse, b: BalancesResponse) => { + const aScore = getMetadataFromBalancesResponse.optional(a)?.priorityScore ?? 1n; + const bScore = getMetadataFromBalancesResponse.optional(b)?.priorityScore ?? 1n; + + const aAmount = getAmount.optional(a); + const bAmount = getAmount.optional(b); + + const aPriority = aAmount + ? joinLoHiAmount(multiplyAmountByNumber(aAmount, Number(aScore))) + : aScore; + const bPriority = bAmount + ? joinLoHiAmount(multiplyAmountByNumber(bAmount, Number(bScore))) + : bScore; + + return Number(bPriority - aPriority); +}; + +export const groupAndSortBalances = ( + balances: BalancesResponse[], +): [string, BalancesResponse[]][] => { + const grouped = balances.reduce(groupByAccount, {}); + return Object.entries(grouped) + .sort(sortByAccountIndex) + .map(([index, balances]) => [index, balances.sort(sortbyPriorityScore)]); +}; + +export const filterAssets = (assets: Metadata[]): Metadata[] => { + return assets.filter(isSwappableMetadata); +}; diff --git a/packages/ui-tailwind/src/AssetSelector/shared/helpers.ts b/packages/ui-tailwind/src/AssetSelector/shared/helpers.ts new file mode 100644 index 0000000000..2da039b413 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/shared/helpers.ts @@ -0,0 +1,26 @@ +import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { uint8ArrayToHex } from '@penumbra-zone/types/hex'; +import { AssetSelectorValue } from './types.ts'; + +/** Type predicate to check if a value is a `Metadata`. */ +export const isMetadata = (value?: AssetSelectorValue): value is Metadata => + value?.getType().typeName === Metadata.typeName; + +/** Type predicate to check if a value is a `BalancesResponse`. */ +export const isBalancesResponse = (value?: AssetSelectorValue): value is BalancesResponse => + value?.getType().typeName === BalancesResponse.typeName; + +/** returns a unique id of a specific Metadata or BalancesResponse */ +export const getHash = (value: AssetSelectorValue) => { + return uint8ArrayToHex(value.toBinary()); +}; + +/** compares Metadata or BalancesResponse with another option */ +export const isEqual = (value1: AssetSelectorValue, value2: AssetSelectorValue | undefined) => { + if (isMetadata(value1)) { + return isMetadata(value2) && value1.equals(value2); + } + + return isBalancesResponse(value2) && value1.equals(value2); +}; diff --git a/packages/ui-tailwind/src/AssetSelector/shared/types.ts b/packages/ui-tailwind/src/AssetSelector/shared/types.ts new file mode 100644 index 0000000000..cd328b6f64 --- /dev/null +++ b/packages/ui-tailwind/src/AssetSelector/shared/types.ts @@ -0,0 +1,29 @@ +import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { ActionType } from '../../utils/action-type'; + +export type AssetSelectorValue = BalancesResponse | Metadata; + +export interface AssetSelectorBaseProps { + /** The value of the selected asset or balance */ + value?: AssetSelectorValue; + + /** Callback when the selected asset or balance changes */ + onChange?: (value: AssetSelectorValue) => void; + + /** The title of the dialog */ + dialogTitle?: string; + + /** + * What type of action is this component related to? Leave as `default` for most + * buttons, set to `accent` for the single most important action on a given + * page, set to `unshield` for actions that will unshield the user's funds, + * and set to `destructive` for destructive actions. + * + * Default: `default` + */ + actionType?: ActionType; + + /** Whether the asset selector is disabled */ + disabled?: boolean; +} diff --git a/packages/ui-tailwind/src/Button/index.stories.tsx b/packages/ui-tailwind/src/Button/index.stories.tsx new file mode 100644 index 0000000000..112b4e2baa --- /dev/null +++ b/packages/ui-tailwind/src/Button/index.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Button } from '.'; +import { ArrowLeftRight, Check, Copy } from 'lucide-react'; + +const meta: Meta = { + component: Button, + tags: ['autodocs', '!dev', 'density'], + argTypes: { + icon: { + control: 'select', + options: ['None', 'Copy', 'Check', 'ArrowLeftRight'], + mapping: { None: undefined, Copy, Check, ArrowLeftRight }, + }, + iconOnly: { + options: ['true', 'false', 'adornment'], + mapping: { true: true, false: false, adornment: 'adornment' }, + }, + onClick: { control: false }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + children: 'Save', + actionType: 'default', + disabled: false, + icon: Copy, + iconOnly: false, + type: 'button', + }, +}; diff --git a/packages/ui-tailwind/src/Button/index.tsx b/packages/ui-tailwind/src/Button/index.tsx new file mode 100644 index 0000000000..1963a51c18 --- /dev/null +++ b/packages/ui-tailwind/src/Button/index.tsx @@ -0,0 +1,155 @@ +import { FC, forwardRef, MouseEventHandler, ReactNode } from 'react'; +import { LucideIcon } from 'lucide-react'; +import cn from 'clsx'; +import { getOutlineColorByActionType, ActionType } from '../utils/action-type'; +import { Priority, buttonBase, getBackground, getFocusOutline, getOverlays } from '../utils/button'; +import { button } from '../utils/typography'; +import { useDensity } from '../utils/density'; + +const iconOnlyAdornment = cn('rounded-full p-1 w-max'); +const sparse = (iconOnly?: boolean | 'adornment') => + cn('rounded-sm h-12', iconOnly ? 'w-12 min-w-12 pl-0 pr-0' : 'w-full pl-4 pr-4'); + +const compact = (iconOnly?: boolean | 'adornment') => + cn('rounded-full h-8 min-w-8 w-max', iconOnly ? 'pl-2 pr-2' : 'pl-4 pr-4'); + +interface BaseButtonProps { + type?: HTMLButtonElement['type']; + /** + * The button label. If `iconOnly` is `true` or `adornment`, this will be used + * as the `aria-label` attribute. + */ + children: ReactNode; + /** + * What type of action is this button related to? Leave as `default` for most + * buttons, set to `accent` for the single most important action on a given + * page, set to `unshield` for actions that will unshield the user's funds, + * and set to `destructive` for destructive actions. + * + * Default: `default` + */ + actionType?: ActionType; + disabled?: boolean; + onClick?: MouseEventHandler; + priority?: Priority; +} + +interface IconOnlyProps { + /** + * When set to `true`, will render just an icon button. When set to + * `adornment`, will render an icon button without the fill or outline of a + * normal button. This latter case is useful when the button is an adornment + * to another component (e.g., when it's a copy icon attached to an + * `AddressViewComponent`). + * + * In both of these cases, the label text passed via `children` will be used + * as the `aria-label`. + * + * Note that, when `iconOnly` is `adornment`, density has no impact on the + * button: it will render at the same size in either a `compact` or `sparse` + * context. + */ + iconOnly: true | 'adornment'; + /** + * The icon import from `lucide-react` to render. If `iconOnly` is `true`, no + * label will be rendered -- just the icon. Otherwise, the icon will be + * rendered to the left of the label. + * + * ```tsx + * import { ChevronRight } from 'lucide-react'; + * + * + * ``` + */ + icon: LucideIcon | FC; +} + +interface RegularProps { + iconOnly?: false; + /** + * The icon import from `lucide-react` to render. If `iconOnly` is `true`, no + * label will be rendered -- just the icon. Otherwise, the icon will be + * rendered to the left of the label. + * + * ```tsx + * import { ChevronRight } from 'lucide-react'; + * + * + * ``` + */ + icon?: LucideIcon | FC; +} + +export type ButtonProps = BaseButtonProps & (IconOnlyProps | RegularProps); + +/** + * A component for all your button needs! + * + * See individual props for how to use ` + ); + }, +); +Button.displayName = 'Button'; diff --git a/packages/ui-tailwind/src/Card/index.stories.tsx b/packages/ui-tailwind/src/Card/index.stories.tsx new file mode 100644 index 0000000000..cbefd33980 --- /dev/null +++ b/packages/ui-tailwind/src/Card/index.stories.tsx @@ -0,0 +1,85 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Card } from '.'; + +// import storiesBg from './storiesBg.jpg'; +import { Text } from '../Text'; +import { useState } from 'react'; +import { Button } from '../Button'; +import { Tabs } from '../Tabs'; +import { Send } from 'lucide-react'; + +const meta: Meta = { + component: Card, + tags: ['autodocs', '!dev'], + decorators: [ + Story => ( +
+ +
+ ), + ], + argTypes: { + as: { + options: ['section', 'div', 'main'], + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + as: 'section', + title: 'Card title', + }, + + render: function Render({ as, title }) { + const [tab, setTab] = useState('one'); + // const [textInput, setTextInput] = useState(''); + + return ( + + + +
+ + This is the card content. Note that each top-level item inside the card is spaced apart + with a spacing of 4. Hence the distance between the tabs and this + paragraph, and the distance between this paragraph and the stack below. + +
+ + + + + This is a <Card.Stack /> comprised of several{' '} + <Card.Section />s. Note that the top and bottom of the + entire stack have rounded corners. + + + + + Card sections in a stack are useful for forms: each field of the form can be wrapped + in a <Card.Section />. + + + + +
+ ); + }, +}; diff --git a/packages/ui-tailwind/src/Card/index.tsx b/packages/ui-tailwind/src/Card/index.tsx new file mode 100644 index 0000000000..0683e9381e --- /dev/null +++ b/packages/ui-tailwind/src/Card/index.tsx @@ -0,0 +1,72 @@ +import { ReactNode, ElementType } from 'react'; +import cn from 'clsx'; +import { large } from '../utils/typography'; + +export interface CardProps { + children?: ReactNode; + /** + * Which component or HTML element to render this card as. + * + * @example + * ```tsx + * This is a main element with card styling + * ``` + */ + as?: ElementType; + title?: ReactNode; +} + +/** + * ``s are rectangular sections of a page set off from the rest of the + * page by a background and an optional title. They're useful for presenting + * data, or for wrapping a form. + * + * A `` wraps its children in a flex column with a spacing of `4` + * between each top-level HTML element. This results in a standard card layout + * no matter what its contents are. + * + * If you wish to pass children to `` that should not be spaced apart in + * that way, simply pass a single HTML element as the root of the ``'s + * children. That way, the built-in flex column will have no effect: + * + * ```tsx + * + *
+ * These two elements... + * ...will not appear in a flex column, but rather inline beside each + * other. + *
+ *
+ * ``` + * + * You can also use `` and `` to create a stack of + * sections, which are useful for wrapping individual form fields. + * + * ```tsx + * + * + * Section one + * Section two + * + * + * ``` + */ +export const Card = ({ children, as: Wrapper = 'section', title }: CardProps) => { + return ( + + {title &&

{title}

} + +
{children}
+
+ ); +}; + +const Stack = ({ children }: { children?: ReactNode }) => { + return
{children}
; +}; +Card.Stack = Stack; + +const Section = ({ children }: { children?: ReactNode }) => ( +
{children}
+); +Card.Section = Section; diff --git a/packages/ui-tailwind/src/ConditionalWrap/index.tsx b/packages/ui-tailwind/src/ConditionalWrap/index.tsx new file mode 100644 index 0000000000..878b2af924 --- /dev/null +++ b/packages/ui-tailwind/src/ConditionalWrap/index.tsx @@ -0,0 +1,61 @@ +import { ReactNode } from 'react'; + +export interface ConditionalWrapProps { + if: boolean; + then: (children: ReactNode) => ReactNode; + else?: (children: ReactNode) => ReactNode; + children: ReactNode; +} + +/** + * Utility component to optionally wrap a React component with another React + * component, depending on a condition. + * + * @example + * ```tsx + * ( + * + * {children} + * Here is the tooltip text. + * + * )} + * > + * Here is the content that may or may not need a tooltip. + * + * ``` + * + * You can also pass an `else` prop to wrap the `children` if the condition is + * _not_ met. + * + * @example + * ```tsx + * ( + * + * {children} + * Here is the tooltip text. + * + * )} + * else={(children) => ( + * {children} + * )} + * > + * Here is the content that may or may not need a tooltip. + * + * ``` + * + * @see https://stackoverflow.com/a/56870316/974981 + */ +export const ConditionalWrap = ({ + children, + + // Rename these to avoid using reserved words + if: condition, + then: thenWrapper, + else: elseWrapper, +}: ConditionalWrapProps) => + // eslint-disable-next-line no-nested-ternary -- simple nested ternary + condition ? thenWrapper(children) : elseWrapper ? elseWrapper(children) : children; diff --git a/packages/ui-tailwind/src/CopyToClipboardButton/index.stories.tsx b/packages/ui-tailwind/src/CopyToClipboardButton/index.stories.tsx new file mode 100644 index 0000000000..64edef298f --- /dev/null +++ b/packages/ui-tailwind/src/CopyToClipboardButton/index.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { CopyToClipboardButton } from '.'; + +const meta: Meta = { + component: CopyToClipboardButton, + tags: ['autodocs', '!dev'], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + text: 'This is sample text copied by the PenumbraUI component.', + disabled: false, + }, +}; diff --git a/packages/ui-tailwind/src/CopyToClipboardButton/index.tsx b/packages/ui-tailwind/src/CopyToClipboardButton/index.tsx new file mode 100644 index 0000000000..4c6b27f127 --- /dev/null +++ b/packages/ui-tailwind/src/CopyToClipboardButton/index.tsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; +import { Copy, Check, LucideIcon } from 'lucide-react'; +import { Button } from '../Button'; + +const useClipboardButton = (text: string) => { + const [icon, setIcon] = useState(Copy); + const [label, setLabel] = useState('Copy'); + + const onClick = () => { + setIcon(Check); + setLabel('Copied'); + setTimeout(() => { + setIcon(Copy); + setLabel('Copy'); + }, 2000); + void navigator.clipboard.writeText(text); + }; + + return { onClick, icon, label }; +}; + +export interface CopyToClipboardButtonProps { + /** + * The text that should be copied to the clipboard when the user presses this + * button. + */ + text: string; + disabled?: boolean; +} + +/** + * A simple icon button for copying some text to the clipboard. Use it alongside + * text that the user may want to copy. + */ +export const CopyToClipboardButton = ({ text, disabled = false }: CopyToClipboardButtonProps) => { + const { onClick, icon, label } = useClipboardButton(text); + + return ( + + ); +}; diff --git a/packages/ui-tailwind/src/Density/index.tsx b/packages/ui-tailwind/src/Density/index.tsx new file mode 100644 index 0000000000..452d389bb5 --- /dev/null +++ b/packages/ui-tailwind/src/Density/index.tsx @@ -0,0 +1,82 @@ +import { ReactNode } from 'react'; +import { Density as TDensity, DensityContext } from '../utils/density'; + +export type DensityProps = { + children?: ReactNode; +} & (SelectedDensity extends 'sparse' + ? { sparse: true; compact?: never } + : { compact: true; sparse?: never }); + +/** + * Use the `` component to set the density for all descendants in the + * component tree that support density variants. + * + * In Penumbra UI, density is never set as a prop directly on a component. + * Instead, it's set indirectly via a React context, so that entire regions of a + * UI can have a matching density. + * + * For example, imagine you have a `` (which supports density + * variants), which contain a bunch of ``s (which also + * support density variants). You may also have other components in the table + * which contain nested components with density variants. If we used a `density` + * prop, you'd need to set that prop on every single component in that tree. + * + * Instead, you can simply wrap the entire `
` with `` + * or ``, and it will set a density context value for all + * descendant components: + * + * ```tsx + * + *
+ * + * + * This will be rendered with compact spacing. + * + * + *
+ *
+ * ``` + * + * Components that support density variants are recognizable because the use the + * `useDensity()` hook, and then style their elements based on the value they + * receive from that hook: + * + * ```tsx + * const SomeStyledComponent = styled.div<{ $density: Density }>` + * padding: ${props => props.theme.spacing(props.$density === 'sparse' ? 4 : 2)}; + * ` + * + * const MyComponent = () => { + * const density = useDensity(); + * + * return + * } + * ``` + * + * In some specific situations, you may want to make sure that a given component + * that supports density variants always is rendered at a specific density. For + * example, let's say you have an icon-only button as the `startAdornment` for a + * ``, and you want to make sure that icon-only button always + * renders as `compact` density. In that case, simply wrap the button in + * ``. Then, it will always be compact, even if there's a + * higher-up ``: + * + * ```tsx + * + * + * + * } + * /> + * ``` + */ +export const Density = ({ + children, + sparse, +}: DensityProps) => ( + + {children} + +); diff --git a/packages/ui-tailwind/src/Dialog/Content.tsx b/packages/ui-tailwind/src/Dialog/Content.tsx new file mode 100644 index 0000000000..9ffe439910 --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/Content.tsx @@ -0,0 +1,80 @@ +import { Title as RadixDialogTitle, Close as RadixDialogClose } from '@radix-ui/react-dialog'; +import { ReactNode, useContext } from 'react'; +import { X } from 'lucide-react'; +import { DialogContext } from './Context.tsx'; +import { EmptyContent } from './EmptyContent.tsx'; +import { Display } from '../Display'; +import { Grid } from '../Grid'; +import { Text } from '../Text'; +import { Density } from '../Density'; +import { Button } from '../Button'; + +export interface DialogContentProps { + children?: ReactNode; + /** Renders the element after the dialog title. These elements will be sticky to the top of the dialog */ + headerChildren?: ReactNode; + title: string; + /** Buttons rendered in the footer of a dialog */ + buttons?: ReactNode; + /** @deprecated this prop will be removed in the future */ + zIndex?: number; +} + +export const Content = ({ + children, + headerChildren, + title, + buttons, + zIndex, +}: DialogContentProps) => { + const { showCloseButton } = useContext(DialogContext); + + return ( + + + + + + +
+
+
+ + + {title} + + + {headerChildren} +
+ +
+ {children} + + {buttons &&
{buttons}
} +
+ + {/** + * Opening the dialog focuses the first focusable element in the dialog. That's why the Close button + * should be positioned absolutely and rendered as the last element in the dialog content. + */} + {showCloseButton && ( + + +
+ +
+
+
+ )} +
+
+
+ + + +
+
+ ); +}; diff --git a/packages/ui-tailwind/src/Dialog/Context.tsx b/packages/ui-tailwind/src/Dialog/Context.tsx new file mode 100644 index 0000000000..2a897d0cfc --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/Context.tsx @@ -0,0 +1,6 @@ +import { createContext } from 'react'; + +/** Internal use only. */ +export const DialogContext = createContext<{ showCloseButton: boolean }>({ + showCloseButton: true, +}); diff --git a/packages/ui-tailwind/src/Dialog/EmptyContent.tsx b/packages/ui-tailwind/src/Dialog/EmptyContent.tsx new file mode 100644 index 0000000000..5f17d510ba --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/EmptyContent.tsx @@ -0,0 +1,26 @@ +import { ReactNode } from 'react'; +import { + Overlay as RadixDialogOverlay, + Portal as RadixDialogPortal, + Content as RadixDialogContent, +} from '@radix-ui/react-dialog'; + +export interface DialogEmptyContentProps { + children?: ReactNode; + /** @deprecated this prop will be removed in the future */ + zIndex?: number; +} + +export const EmptyContent = ({ children, zIndex }: DialogEmptyContentProps) => { + return ( + + + + +
+ {children} +
+
+
+ ); +}; diff --git a/packages/ui-tailwind/src/Dialog/RadioItem.tsx b/packages/ui-tailwind/src/Dialog/RadioItem.tsx new file mode 100644 index 0000000000..8ef98f8c75 --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/RadioItem.tsx @@ -0,0 +1,100 @@ +import React, { ReactNode, useMemo } from 'react'; +import cn from 'clsx'; +import { RadioGroupItem } from '@radix-ui/react-radio-group'; +import { Text } from '../Text'; +import { + ActionType, + getAriaCheckedOutlineColorByActionType, + getFocusOutlineColorByActionType, +} from '../utils/action-type'; + +export interface DialogRadioItemProps { + /** A required unique string value defining the radio item */ + value: string; + title: ReactNode; + description?: ReactNode; + /** A component rendered on the left side of the item */ + endAdornment?: ReactNode; + /** A component rendered on the right side of the item */ + startAdornment?: ReactNode; + disabled?: boolean; + actionType?: ActionType; + /** A function that closes the dialog on select of the item */ + onClose?: VoidFunction; + /** Fires when the item is clicked or focused using the keyboard */ + onSelect?: VoidFunction; +} + +/** A radio button that selects an asset or a balance from the `AssetSelector` */ +export const RadioItem = ({ + value, + title, + description, + startAdornment, + endAdornment, + disabled, + actionType = 'default', + onClose, + onSelect, +}: DialogRadioItemProps) => { + const handleClick = (event: React.MouseEvent) => { + // Is a click and not an arrow key up/down + if (event.detail > 0) { + onSelect?.(); + onClose?.(); + } + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onSelect?.(); + onClose?.(); + } + }; + + const descriptionText = useMemo(() => { + if (!description) { + return null; + } + + if (typeof description === 'string') { + return ( + + {description} + + ); + } + + return description; + }, [description]); + + return ( + + + + ); +}; diff --git a/packages/ui-tailwind/src/Dialog/RadioItemGroup.tsx b/packages/ui-tailwind/src/Dialog/RadioItemGroup.tsx new file mode 100644 index 0000000000..4e116b2311 --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/RadioItemGroup.tsx @@ -0,0 +1,12 @@ +import { RadioGroup as RadixRadioGroup, RadioGroupProps } from '@radix-ui/react-radio-group'; + +export type DialogRadioGroupProps = Omit; + +/** + * `Dialog.RadioGroup` – a wrapper around the list of `Dialog.RadioItem` that controls + * the selection of the radio items. Doesn't have any UI or HTML elements, – provide your own styles + * as children of this component. + */ +export const RadioGroup = (props: DialogRadioGroupProps) => { + return ; +}; diff --git a/packages/ui-tailwind/src/Dialog/Trigger.tsx b/packages/ui-tailwind/src/Dialog/Trigger.tsx new file mode 100644 index 0000000000..fa578c3f4a --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/Trigger.tsx @@ -0,0 +1,19 @@ +import { ReactNode } from 'react'; +import * as RadixDialog from '@radix-ui/react-dialog'; + +export interface DialogTriggerProps { + children: ReactNode; + /** + * Change the default rendered element for the one passed as a child, merging + * their props and behavior. + * + * Uses Radix UI's `asChild` prop under the hood. + * + * @see https://www.radix-ui.com/primitives/docs/guides/composition + */ + asChild?: boolean; +} + +export const Trigger = ({ children, asChild }: DialogTriggerProps) => ( + {children} +); diff --git a/packages/ui-tailwind/src/Dialog/index.stories.tsx b/packages/ui-tailwind/src/Dialog/index.stories.tsx new file mode 100644 index 0000000000..6442007ddc --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/index.stories.tsx @@ -0,0 +1,111 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Dialog } from '.'; +import { Button } from '../Button'; +import { ComponentType } from 'react'; +import { Text } from '../Text'; +import { AssetIcon } from '../AssetIcon'; +import { Ban, Handshake, ThumbsUp } from 'lucide-react'; +import { OSMO_METADATA, PENUMBRA_METADATA, PIZZA_METADATA } from '../utils/bufs'; + +const meta: Meta = { + component: Dialog, + tags: ['autodocs', '!dev'], + argTypes: { + isOpen: { control: false }, + onClose: { control: false }, + }, + subcomponents: { + // Re: type coercion, see + // https://github.com/storybookjs/storybook/issues/23170#issuecomment-2241802787 + 'Dialog.Content': Dialog.Content as ComponentType, + 'Dialog.Trigger': Dialog.Trigger as ComponentType, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + render: function Render() { + return ( + + + + + + + + + + + } + > +
+ + This is a subheading + + + This is description information. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Ut et massa mi. + + + This is a subheading + + + This is description information. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Ut et massa mi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et + massa mi. + +
+
+
+ ); + }, +}; + +export const WithRadioItems: Story = { + render: function Render() { + return ( + + + + + + + +
+ } + /> + } + /> + } + /> +
+
+
+
+ ); + }, +}; diff --git a/packages/ui-tailwind/src/Dialog/index.tsx b/packages/ui-tailwind/src/Dialog/index.tsx new file mode 100644 index 0000000000..7b4fc0def9 --- /dev/null +++ b/packages/ui-tailwind/src/Dialog/index.tsx @@ -0,0 +1,151 @@ +import { ReactNode } from 'react'; +import * as RadixDialog from '@radix-ui/react-dialog'; +import { DialogContext } from './Context.tsx'; +import { EmptyContent, DialogEmptyContentProps } from './EmptyContent.tsx'; +import { Content, DialogContentProps } from './Content.tsx'; +import { Trigger, DialogTriggerProps } from './Trigger.tsx'; +import { RadioGroup, DialogRadioGroupProps } from './RadioItemGroup.tsx'; +import { RadioItem, DialogRadioItemProps } from './RadioItem'; + +interface ControlledDialogProps { + /** + * Whether the dialog is currently open. If left `undefined`, this will be + * treated as an uncontrolled dialog — that is, it will open and close based + * on user interactions rather than on state variables. + */ + isOpen: boolean; + /** + * Callback for when the user closes the dialog. Should update the state + * variable being passed in via `isOpen`. If left `undefined`, users will not + * be able to close it -- that is, it will only be able to be closed + * programmatically, and no Close button will be rendered. + */ + onClose?: VoidFunction; +} + +interface UncontrolledDialogProps { + isOpen?: false | undefined; + onClose?: undefined; +} + +export type DialogProps = { + children?: ReactNode; +} & (ControlledDialogProps | UncontrolledDialogProps); + +/** + * A dialog box that appears over other content. + * + * To render a dialog, compose it using a few components: ``, + * ``, and ``. The latter two must be + * descendents of `` in the component tree, and siblings to each + * other. (`` is optional, though — more on that in a moment.) + * + * ```tsx + * + * + * + * + * + * Dialog content here + * + * ``` + * + * Depending on your use case, you may want to use `` either as a + * controlled component, or as an uncontrolled component. + * + * ## Usage as a controlled component + * Use `` as a controlled component when you want to control its + * open/closed state yourself (e.g., via a state management solution like + * Zustand or Redux). You can accomplish this by passing `isOpen` and `onClose` + * props to the `` component, and omitting ``: + * + * ```tsx + * + * + * setIsOpen(false)}> + * Dialog content here + * + * ``` + * + * Note that, in the example above, the ` + * + * setIsOpen(false)}> + * + * This dialog can not be closed by the user. + * + * + * ``` + * + * ## Usage as an uncontrolled component + * If you want to render `` as an uncontrolled component, don't pass + * `isOpen` or `onClose` to ``, and make sure to include a + * `` component inside the ``: + + * ```tsx + * + * + * + * + * + * Dialog content here + * + * ``` + * + * ## Animating a dialog out of its trigger + * + * You can use the `motion` prop with a layout ID to make a dialog appear to + * animate out of the trigger button: + * + * ```tsx + * const layoutId = useId(); + * + * return ( + * + * + * + * + * + * ... + * + * + * ); + * ``` + */ +export const Dialog = ({ children, onClose, isOpen }: DialogProps) => { + const isControlledComponent = isOpen !== undefined; + const showCloseButton = (isControlledComponent && !!onClose) || !isControlledComponent; + + return ( + + onClose && !value && onClose()}> + {children} + + + ); +}; + +Dialog.EmptyContent = EmptyContent; +Dialog.Content = Content; +Dialog.Trigger = Trigger; +Dialog.RadioGroup = RadioGroup; +Dialog.RadioItem = RadioItem; + +export type { + DialogTriggerProps, + DialogEmptyContentProps, + DialogContentProps, + DialogRadioGroupProps, + DialogRadioItemProps, +}; diff --git a/packages/ui-tailwind/src/Display/index.stories.tsx b/packages/ui-tailwind/src/Display/index.stories.tsx new file mode 100644 index 0000000000..fab620c930 --- /dev/null +++ b/packages/ui-tailwind/src/Display/index.stories.tsx @@ -0,0 +1,46 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Display } from '.'; +import { Text } from '../Text'; + +const meta: Meta = { + component: Display, + tags: ['autodocs'], + argTypes: { + children: { control: false }, + }, + decorators: [ + Story => ( +
+ +
+ ), + ], +}; +export default meta; + +type Story = StoryObj; + +export const FullWidth: Story = { + args: { + children: ( +
+ + The white background that this text sits inside of represents the{' '} + inside width of the <Display />{' '} + component. The white border to the left and right of this white bar represent the{' '} + outside width of the <Display />{' '} + component. + + + You can resize your window to see how the margins at left and right change depending on + the size of the browser window. + + + To test <Display /> at full width, click the "Full + Width" item in the left sidebar, and try resizing your browser. + +
+ ), + }, +}; diff --git a/packages/ui-tailwind/src/Display/index.tsx b/packages/ui-tailwind/src/Display/index.tsx new file mode 100644 index 0000000000..f428f9d2f7 --- /dev/null +++ b/packages/ui-tailwind/src/Display/index.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from 'react'; + +export interface DisplayProps { + children?: ReactNode; +} + +/** + * Wrap your top-level component for a given page (usually a ``) in + * `` to adhere to PenumbraUI guidelines regarding maximum layouts + * widths, horizontal margins, etc. + * + * ```tsx + * + * + * Column one + * Column two + * + * + * ``` + */ +export const Display = ({ children }: DisplayProps) => { + return ( +
+
{children}
+
+ ); +}; diff --git a/packages/ui-tailwind/src/DropdownMenu/CheckboxItem.tsx b/packages/ui-tailwind/src/DropdownMenu/CheckboxItem.tsx new file mode 100644 index 0000000000..8b215708ba --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/CheckboxItem.tsx @@ -0,0 +1,39 @@ +import { + CheckboxItem as RadixDropdownMenuCheckboxItem, + ItemIndicator as RadixDropdownMenuItemIndicator, +} from '@radix-ui/react-dropdown-menu'; +import { ReactNode } from 'react'; +import { Check } from 'lucide-react'; +import { Text } from '../Text'; +import { DropdownMenuItemBase, getMenuItem } from '../utils/menu-item.ts'; + +export interface DropdownMenuCheckboxItemProps extends DropdownMenuItemBase { + children?: ReactNode; + checked?: boolean; + onChange?: (value: boolean) => void; +} + +export const CheckboxItem = ({ + children, + actionType = 'default', + disabled, + checked, + onChange, +}: DropdownMenuCheckboxItemProps) => { + return ( + +
+ + + + + {children} +
+
+ ); +}; diff --git a/packages/ui-tailwind/src/DropdownMenu/Content.tsx b/packages/ui-tailwind/src/DropdownMenu/Content.tsx new file mode 100644 index 0000000000..c0b0c3a825 --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/Content.tsx @@ -0,0 +1,29 @@ +import { ReactNode } from 'react'; +import { + Content as RadixDropdownMenuContent, + Portal as RadixDropdownMenuPortal, + DropdownMenuContentProps as RadixDropdownMenuContentProps, +} from '@radix-ui/react-dropdown-menu'; +import { getPopoverContent, PopoverContext } from '../utils/popover.ts'; + +export interface DropdownMenuContentProps { + children?: ReactNode; + side?: RadixDropdownMenuContentProps['side']; + align?: RadixDropdownMenuContentProps['align']; + context?: PopoverContext; +} + +export const Content = ({ + children, + side, + align, + context = 'default', +}: DropdownMenuContentProps) => { + return ( + + +
{children}
+
+
+ ); +}; diff --git a/packages/ui-tailwind/src/DropdownMenu/Item.tsx b/packages/ui-tailwind/src/DropdownMenu/Item.tsx new file mode 100644 index 0000000000..bb6f6dbe71 --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/Item.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from 'react'; +import { Item as RadixDropdownMenuItem } from '@radix-ui/react-dropdown-menu'; +import { Text } from '../Text'; +import { DropdownMenuItemBase, getMenuItem } from '../utils/menu-item.ts'; + +export interface DropdownMenuItemProps extends DropdownMenuItemBase { + children?: ReactNode; + onSelect?: (event: Event) => void; + icon?: ReactNode; +} + +export const Item = ({ + children, + icon, + actionType = 'default', + disabled, + onSelect, +}: DropdownMenuItemProps) => { + return ( + +
+ {icon} + {children} +
+
+ ); +}; diff --git a/packages/ui-tailwind/src/DropdownMenu/RadioGroup.tsx b/packages/ui-tailwind/src/DropdownMenu/RadioGroup.tsx new file mode 100644 index 0000000000..c15a3caa83 --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/RadioGroup.tsx @@ -0,0 +1,16 @@ +import { ReactNode } from 'react'; +import { RadioGroup as RadixDropdownMenuRadioGroup } from '@radix-ui/react-dropdown-menu'; + +export interface DropdownMenuRadioGroupProps { + children?: ReactNode; + value?: string; + onChange?: (value: string) => void; +} + +export const RadioGroup = ({ children, value, onChange }: DropdownMenuRadioGroupProps) => { + return ( + + {children} + + ); +}; diff --git a/packages/ui-tailwind/src/DropdownMenu/RadioItem.tsx b/packages/ui-tailwind/src/DropdownMenu/RadioItem.tsx new file mode 100644 index 0000000000..47f0187d9c --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/RadioItem.tsx @@ -0,0 +1,32 @@ +import { + RadioItem as RadixDropdownMenuRadioItem, + ItemIndicator as RadixDropdownMenuItemIndicator, +} from '@radix-ui/react-dropdown-menu'; +import { ReactNode } from 'react'; +import { Check } from 'lucide-react'; +import { Text } from '../Text'; +import { DropdownMenuItemBase, getMenuItem } from '../utils/menu-item.ts'; + +export interface DropdownMenuRadioItemProps extends DropdownMenuItemBase { + children?: ReactNode; + value: string; +} + +export const RadioItem = ({ + children, + value, + actionType = 'default', + disabled, +}: DropdownMenuRadioItemProps) => { + return ( + +
+ + + + + {children} +
+
+ ); +}; diff --git a/packages/ui-tailwind/src/DropdownMenu/Root.tsx b/packages/ui-tailwind/src/DropdownMenu/Root.tsx new file mode 100644 index 0000000000..d27a292fb7 --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/Root.tsx @@ -0,0 +1,90 @@ +import { ReactNode } from 'react'; +import { Root as RadixDropdownMenuRoot } from '@radix-ui/react-dropdown-menu'; +import { Trigger } from './Trigger.tsx'; +import { Content } from './Content.tsx'; +import { RadioGroup } from './RadioGroup.tsx'; +import { RadioItem } from './RadioItem.tsx'; +import { CheckboxItem } from './CheckboxItem.tsx'; +import { Item } from './Item.tsx'; + +interface ControlledDropdownMenuProps { + /** + * Whether the popover is currently open. If left `undefined`, this will be + * treated as an uncontrolled popover — that is, it will open and close based + * on user interactions rather than on state variables. + */ + isOpen: boolean; + /** + * Callback for when the user closes the popover. Should update the state + * variable being passed in via `isOpen`. If left `undefined`, users will not + * be able to close it -- that is, it will only be able to be closed programmatically + */ + onClose?: VoidFunction; +} + +interface UncontrolledDropdownMenuProps { + isOpen?: undefined; + onClose?: undefined; +} + +export type DropdownMenuProps = { + children?: ReactNode; +} & (ControlledDropdownMenuProps | UncontrolledDropdownMenuProps); + +/** + * A dropdown menu with a set of subcomponents for composing complex menus. + * + * `` can be controlled or uncontrolled. If `isOpen` is not provided + * but `` is present, it will open itself. + * + * You can nest multiple components inside the ``: + * - `` as an action button in the dropdown + * - `` with `` as a group of radio buttons + * - `` as a checkbox + * + * Example: + * + * ```tsx + * const [radioValue, setRadioValue] = useState('1'); + * const [apple, setApple] = useState(false); + * const [banana, setBanana] = useState(false); + * + * + * + * + * + * + * + * Default item + * Destructive item + * + * + * Default + * Disabled + * + * + * Apple + * Banana + * + * + * ``` + */ +export const DropdownMenu = ({ children, onClose, isOpen }: DropdownMenuProps) => { + return ( + !value && onClose() : undefined} + > + {children} + + ); +}; + +DropdownMenu.Trigger = Trigger; +DropdownMenu.Content = Content; +DropdownMenu.RadioGroup = RadioGroup; +DropdownMenu.RadioItem = RadioItem; +DropdownMenu.CheckboxItem = CheckboxItem; +DropdownMenu.Item = Item; diff --git a/packages/ui-tailwind/src/DropdownMenu/Trigger.tsx b/packages/ui-tailwind/src/DropdownMenu/Trigger.tsx new file mode 100644 index 0000000000..b056446e87 --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/Trigger.tsx @@ -0,0 +1,10 @@ +import type { ReactNode } from 'react'; +import { Trigger as RadixDropdownMenuTrigger } from '@radix-ui/react-dropdown-menu'; + +export interface DropdownMenuTriggerProps { + children: ReactNode; +} + +export const Trigger = ({ children }: DropdownMenuTriggerProps) => ( + {children} +); diff --git a/packages/ui-tailwind/src/DropdownMenu/index.stories.tsx b/packages/ui-tailwind/src/DropdownMenu/index.stories.tsx new file mode 100644 index 0000000000..6250dbb2f5 --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/index.stories.tsx @@ -0,0 +1,117 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { DropdownMenu } from '.'; +import { Button } from '../Button'; +import { ComponentType, useState } from 'react'; +import { Filter } from 'lucide-react'; + +const meta: Meta = { + component: DropdownMenu, + tags: ['autodocs', '!dev'], + argTypes: { + isOpen: { control: false }, + onClose: { control: false }, + }, + subcomponents: { + // Re: type coercion, see + // https://github.com/storybookjs/storybook/issues/23170#issuecomment-2241802787 + 'DropdownMenu.Content': DropdownMenu.Content as ComponentType, + 'DropdownMenu.Trigger': DropdownMenu.Trigger as ComponentType, + 'DropdownMenu.RadioGroup': DropdownMenu.RadioGroup as ComponentType, + 'DropdownMenu.RadioItem': DropdownMenu.RadioItem as ComponentType, + 'DropdownMenu.CheckboxItem': DropdownMenu.CheckboxItem as ComponentType, + 'DropdownMenu.Item': DropdownMenu.Item as ComponentType, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + render: function Render() { + return ( + + + + + + + Destructive + Accent + Unshield + Default + + Disabled + + + + ); + }, +}; + +export const Radio: Story = { + render: function Render() { + const [isOpen, setIsOpen] = useState(false); + const [value, setValue] = useState('1'); + + return ( + setIsOpen(false)}> + + + + + + + + Destructive + + + Accent + + + Unshield + + + Default + + + Disabled + + + + + ); + }, +}; + +export const Checkbox: Story = { + render: function Render() { + const [isOpen, setIsOpen] = useState(false); + + const [apple, setApple] = useState(false); + const [banana, setBanana] = useState(false); + + return ( + setIsOpen(false)}> + + + + + + + Apple + + + Banana + + + + ); + }, +}; diff --git a/packages/ui-tailwind/src/DropdownMenu/index.tsx b/packages/ui-tailwind/src/DropdownMenu/index.tsx new file mode 100644 index 0000000000..d0959d4f1d --- /dev/null +++ b/packages/ui-tailwind/src/DropdownMenu/index.tsx @@ -0,0 +1,9 @@ +export { DropdownMenu } from './Root.tsx'; + +export type { DropdownMenuProps } from './Root.tsx'; +export type { DropdownMenuTriggerProps } from './Trigger.tsx'; +export type { DropdownMenuContentProps } from './Content.tsx'; +export type { DropdownMenuRadioGroupProps } from './RadioGroup.tsx'; +export type { DropdownMenuRadioItemProps } from './RadioItem.tsx'; +export type { DropdownMenuCheckboxItemProps } from './CheckboxItem.tsx'; +export type { DropdownMenuItemProps } from './Item.tsx'; diff --git a/packages/ui-tailwind/src/Grid/index.stories.tsx b/packages/ui-tailwind/src/Grid/index.stories.tsx new file mode 100644 index 0000000000..a69f42c628 --- /dev/null +++ b/packages/ui-tailwind/src/Grid/index.stories.tsx @@ -0,0 +1,65 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Grid } from '.'; +import { Text } from '../Text'; + +const meta: Meta = { + component: Grid, + title: 'Grid', + tags: ['autodocs', '!dev'], + argTypes: { + container: { control: false }, + mobile: { control: false }, + tablet: { control: false }, + desktop: { control: false }, + lg: { control: false }, + xl: { control: false }, + as: { control: false }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Demo: Story = { + render: function Render() { + return ( + + +
+ mobile=12 +
+
+ + {Array(2) + .fill(null) + .map((_, index) => ( + +
+ mobile=12 tablet=6 +
+
+ ))} + + {Array(4) + .fill(null) + .map((_, index) => ( + +
+ mobile=6 tablet=6 desktop=3 +
+
+ ))} + + {Array(48) + .fill(null) + .map((_, index) => ( + +
+ lg=1 +
+
+ ))} +
+ ); + }, +}; diff --git a/packages/ui-tailwind/src/Grid/index.tsx b/packages/ui-tailwind/src/Grid/index.tsx new file mode 100644 index 0000000000..3a9abb0f20 --- /dev/null +++ b/packages/ui-tailwind/src/Grid/index.tsx @@ -0,0 +1,169 @@ +import { PropsWithChildren } from 'react'; +import cn from 'clsx'; + +type GridElement = 'div' | 'main' | 'section'; + +interface BaseGridProps extends Record { + /** Which element to use. Defaults to `'div'`. */ + as?: GridElement; +} + +interface GridContainerProps extends BaseGridProps { + /** Whether this is a grid container, vs. an item. */ + container: true; + + // For some reason, Storybook needs these properties to be defined on the + // container props interface in order to show their typings properly. + mobile?: undefined; + tablet?: undefined; + desktop?: undefined; + lg?: undefined; + xl?: undefined; +} + +interface GridItemProps extends BaseGridProps { + /** Whether this is a grid container, vs. an item. */ + container?: false; + /** + * The number of columns this grid item should span on mobile. + * + * The mobile grid layout can only be split in half, so you can only set a + * grid item to 6 or 12 columns on mobile. 0 hides the container from mobile screens. + */ + mobile?: 0 | 6 | 12; + /** + * The number of columns this grid item should span on tablet. + * + * The tablet grid layout can only be split into six columns. + */ + tablet?: 0 | 2 | 4 | 6 | 8 | 10 | 12; + /** The number of columns this grid item should span on desktop. */ + desktop?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + /** The number of columns this grid item should span on large screens. */ + lg?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; + /** The number of columns this grid item should span on XL screens. */ + xl?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; +} + +const MOBILE_MAP: Record['mobile'], string> = { + 0: 'hidden', + 6: 'col-span-6', + 12: 'col-span-12', +}; + +const TABLET_MAP: Record['tablet'], string> = { + 0: 'tablet:hidden', + 2: 'tablet:col-span-2', + 4: 'tablet:col-span-4', + 6: 'tablet:col-span-6', + 8: 'tablet:col-span-8', + 10: 'tablet:col-span-10', + 12: 'tablet:col-span-12', +}; + +const DESKTOP_MAP: Record['desktop'], string> = { + 0: 'desktop:hidden', + 1: 'desktop:col-span-1', + 2: 'desktop:col-span-2', + 3: 'desktop:col-span-3', + 4: 'desktop:col-span-4', + 5: 'desktop:col-span-5', + 6: 'desktop:col-span-6', + 7: 'desktop:col-span-7', + 8: 'desktop:col-span-8', + 9: 'desktop:col-span-9', + 10: 'desktop:col-span-10', + 11: 'desktop:col-span-11', + 12: 'desktop:col-span-12', +}; + +const LG_MAP: Record['lg'], string> = { + 0: 'lg:hidden', + 1: 'lg:col-span-1', + 2: 'lg:col-span-2', + 3: 'lg:col-span-3', + 4: 'lg:col-span-4', + 5: 'lg:col-span-5', + 6: 'lg:col-span-6', + 7: 'lg:col-span-7', + 8: 'lg:col-span-8', + 9: 'lg:col-span-9', + 10: 'lg:col-span-10', + 11: 'lg:col-span-11', + 12: 'lg:col-span-12', +}; + +const XL_MAP: Record['xl'], string> = { + 0: 'xl:hidden', + 1: 'xl:col-span-1', + 2: 'xl:col-span-2', + 3: 'xl:col-span-3', + 4: 'xl:col-span-4', + 5: 'xl:col-span-5', + 6: 'xl:col-span-6', + 7: 'xl:col-span-7', + 8: 'xl:col-span-8', + 9: 'xl:col-span-9', + 10: 'xl:col-span-10', + 11: 'xl:col-span-11', + 12: 'xl:col-span-12', +}; + +export type GridProps = PropsWithChildren; + +/** + * A responsive grid component that makes 12-column layouts super easy to build. + * + * Pass the `container` prop to the root `` component; then, any nested + * children ``s will be treated as grid items. You can customize which + * HTML element to use for each grid container or item by passing the element's + * name via the optional `as` prop. + * + * Use the `` component — rather than styling your own HTML elements + * with `display: grid` — to ensure consistent behavior (such as grid gutters) + * throughout your app. + * + * ```tsx + * + * This will span the full width on all screen sizes. + * + * So will this. + * + * + * These will span the full width on mobile... + * + * + * + * ...but half the width on desktop. + * + * + * + * These will... + * + * + * + * ...take up... + * + * + * + * ...one third each. + * + * + * ``` + */ +export const Grid = ({ container, children, as: Container = 'div', ...props }: GridProps) => + container ? ( + {children} + ) : ( + + {children} + + ); diff --git a/packages/ui-tailwind/src/Icon/index.stories.ts b/packages/ui-tailwind/src/Icon/index.stories.ts new file mode 100644 index 0000000000..b7f494b781 --- /dev/null +++ b/packages/ui-tailwind/src/Icon/index.stories.ts @@ -0,0 +1,25 @@ +import { ArrowRightLeft, Send, Wallet } from 'lucide-react'; +import { Meta, StoryObj } from '@storybook/react'; + +import { Icon } from '.'; + +const meta: Meta = { + component: Icon, + tags: ['autodocs', '!dev'], + argTypes: { + IconComponent: { + options: ['ArrowRightLeft', 'Send', 'Wallet'], + mapping: { ArrowRightLeft, Send, Wallet }, + }, + }, +}; + +export default meta; + +export const Basic: StoryObj = { + args: { + IconComponent: ArrowRightLeft, + size: 'sm', + color: 'text.primary', + }, +}; diff --git a/packages/ui-tailwind/src/Icon/index.tsx b/packages/ui-tailwind/src/Icon/index.tsx new file mode 100644 index 0000000000..9746156fab --- /dev/null +++ b/packages/ui-tailwind/src/Icon/index.tsx @@ -0,0 +1,66 @@ +import { LucideIcon } from 'lucide-react'; +import { ComponentProps, FC } from 'react'; +import { ThemeColor, getThemeColorClass } from '../utils/color'; +import cn from 'clsx'; + +export type IconSize = 'sm' | 'md' | 'lg'; + +export interface IconProps { + /** + * The icon import from `lucide-react` to render. + * + * ```tsx + * import { ChevronRight } from 'lucide-react'; + * + * ``` + */ + IconComponent: LucideIcon | FC; + /** + * - `sm`: 16px square + * - `md`: 24px square + * - `lg`: 32px square + */ + size: IconSize; + /** A string representing the color key from the Tailwind theme (e.g. 'primary.light') */ + color?: ThemeColor; +} + +const PROPS_BY_SIZE: Record> = { + sm: { + size: 16, + strokeWidth: 1, + }, + md: { + size: 24, + strokeWidth: 1.5, + }, + lg: { + size: 32, + strokeWidth: 2, + }, +}; + +/** + * Renders the Lucide icon passed in via the `IconComponent` prop. Use this + * component rather than rendering Lucide icon components directly, since this + * component standardizes the stroke width and sizes throughout the Penumbra + * ecosystem. + * + * ```tsx + * + * ``` + */ +export const Icon = ({ IconComponent, size = 'sm', color }: IconProps) => { + return ( + + ); +}; diff --git a/packages/ui-tailwind/src/Identicon/generate.ts b/packages/ui-tailwind/src/Identicon/generate.ts new file mode 100644 index 0000000000..296ee3c0d9 --- /dev/null +++ b/packages/ui-tailwind/src/Identicon/generate.ts @@ -0,0 +1,43 @@ +// Inspired by: https://github.com/vercel/avatar + +import color from 'tinycolor2'; +import Murmur from 'murmurhash3js'; + +// Deterministically getting a gradient from a string for use as an identicon +export const generateGradient = (str: string) => { + // Get first color + const hash = Murmur.x86.hash32(str); + const c = color({ h: hash % 360, s: 0.95, l: 0.5 }); + + const tetrad = c.tetrad(); // 4 colors spaced around the color wheel, the first being the input + const secondColorOptions = tetrad.slice(1); + const index = hash % 3; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- TODO: justify non-null assertion + const toColor = secondColorOptions[index]!.toHexString(); + + return { + fromColor: c.toHexString(), + toColor, + }; +}; + +export const generateSolidColor = (str: string) => { + // Get color + const hash = Murmur.x86.hash32(str); + const c = color({ h: hash % 360, s: 0.95, l: 0.5 }) + .saturate(0) + .darken(20); + return { + bg: c.toHexString(), + // get readable text color + text: color + .mostReadable(c, ['white', 'black'], { + includeFallbackColors: true, + level: 'AAA', + size: 'small', + }) + .saturate() + .darken(20) + .toHexString(), + }; +}; diff --git a/packages/ui-tailwind/src/Identicon/index.stories.tsx b/packages/ui-tailwind/src/Identicon/index.stories.tsx new file mode 100644 index 0000000000..48bb6ccb25 --- /dev/null +++ b/packages/ui-tailwind/src/Identicon/index.stories.tsx @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Identicon } from '.'; + +const meta: Meta = { + component: Identicon, + tags: ['autodocs', '!dev'], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + uniqueIdentifier: 'abc123', + type: 'solid', + size: 24, + }, +}; diff --git a/packages/ui-tailwind/src/Identicon/index.tsx b/packages/ui-tailwind/src/Identicon/index.tsx new file mode 100644 index 0000000000..fb518243e2 --- /dev/null +++ b/packages/ui-tailwind/src/Identicon/index.tsx @@ -0,0 +1,94 @@ +import { useMemo } from 'react'; +import { generateGradient, generateSolidColor } from './generate'; + +/** + * The view box size is separate from the passed-in `size` prop. + * + * The view box controls how the elements inside the SVG are sized in relation + * to the SVG as a whole. The passed-in `size` prop controls how big the SVG as + * a whole is. + */ +const VIEW_BOX_SIZE = 24; + +export interface IdenticonProps { + /** + * The ID or other string representation of the object you want an identicon + * for. `` will deterministically generate a solid color or + * gradient (depending on the value of `type`) based on the value of + * `uniqueIdentifier`. + */ + uniqueIdentifier: string; + /** The identicon size, in pixels. */ + size?: number; + /** + * When `solid`, will render a solid color along with the (upper-cased) first + * character of `uniqueIdentifier`. When `gradient`, will render just a + * gradient. + */ + type: 'gradient' | 'solid'; +} + +/** + * Renders an SVG icon whose color or gradient is deterministically generated + * based on the value of the `uniqueIdentifier` prop. + * + * Use this for assets, addresses, etc. that don't otherwise have an icon. + */ +export const Identicon = (props: IdenticonProps) => { + if (props.type === 'gradient') { + return ; + } + return ; +}; + +const IdenticonGradient = ({ uniqueIdentifier, size = 120 }: IdenticonProps) => { + const gradient = useMemo(() => generateGradient(uniqueIdentifier), [uniqueIdentifier]); + const gradientId = useMemo(() => `gradient-${uniqueIdentifier}`, [uniqueIdentifier]); + + return ( + + + + + + + + + + + + ); +}; + +const IdenticonSolid = ({ uniqueIdentifier, size = 120 }: IdenticonProps) => { + const color = useMemo(() => generateSolidColor(uniqueIdentifier), [uniqueIdentifier]); + + return ( + + + + {uniqueIdentifier[0]} + + + ); +}; diff --git a/packages/ui-tailwind/src/MenuItem/index.stories.tsx b/packages/ui-tailwind/src/MenuItem/index.stories.tsx new file mode 100644 index 0000000000..e9647c1917 --- /dev/null +++ b/packages/ui-tailwind/src/MenuItem/index.stories.tsx @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { MenuItem } from '.'; +import { ArrowLeftRight, Check, Copy } from 'lucide-react'; + +const meta: Meta = { + component: MenuItem, + tags: ['autodocs', '!dev'], + argTypes: { + icon: { + control: 'select', + options: ['None', 'Copy', 'Check', 'ArrowLeftRight'], + mapping: { None: undefined, Copy, Check, ArrowLeftRight }, + }, + label: { + type: 'string', + }, + onClick: { control: false }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + label: 'Menu Item', + icon: Check, + disabled: false, + }, +}; diff --git a/packages/ui-tailwind/src/MenuItem/index.tsx b/packages/ui-tailwind/src/MenuItem/index.tsx new file mode 100644 index 0000000000..1e661de27d --- /dev/null +++ b/packages/ui-tailwind/src/MenuItem/index.tsx @@ -0,0 +1,32 @@ +import type { LucideIcon } from 'lucide-react'; +import type { FC, MouseEventHandler } from 'react'; +import { getMenuItem, DropdownMenuItemBase } from '../utils/menu-item'; +import { Text } from '../Text'; + +export interface MenuItemProps extends DropdownMenuItemBase { + label: string; + icon?: LucideIcon | FC; + onClick?: MouseEventHandler; +} + +/** + * A button generally used in menus or selectable lists + */ +export const MenuItem = ({ + actionType = 'default', + icon: IconComponent, + label, + onClick, + disabled, +}: MenuItemProps) => { + return ( + + ); +}; diff --git a/packages/ui-tailwind/src/Pill/index.stories.tsx b/packages/ui-tailwind/src/Pill/index.stories.tsx new file mode 100644 index 0000000000..0b5d3c8ad4 --- /dev/null +++ b/packages/ui-tailwind/src/Pill/index.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Pill } from '.'; + +const meta: Meta = { + component: Pill, + tags: ['autodocs', '!dev', 'density'], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + children: 'Label', + priority: 'primary', + }, +}; diff --git a/packages/ui-tailwind/src/Pill/index.tsx b/packages/ui-tailwind/src/Pill/index.tsx new file mode 100644 index 0000000000..76bd17acb5 --- /dev/null +++ b/packages/ui-tailwind/src/Pill/index.tsx @@ -0,0 +1,86 @@ +import { ReactNode } from 'react'; +import { body, technical, detail, detailTechnical } from '../utils/typography'; +import { Density, useDensity } from '../utils/density'; +import cn from 'clsx'; + +type Priority = 'primary' | 'secondary'; +type Context = + | 'default' + | 'technical-default' + | 'technical-success' + | 'technical-caution' + | 'technical-destructive'; + +const getFont = (context: Context, density: Density) => { + if (context === 'default') { + return density === 'sparse' ? body : detail; + } + return density === 'sparse' ? technical : detailTechnical; +}; + +const getXPadding = (priority: Priority, density: Density): string => { + if (priority === 'secondary') { + return density === 'sparse' ? 'pr-[10px] pl-[10px]' : 'pr-[6px] pl-[6px]'; + } + return density === 'sparse' ? 'pr-3 pl-3' : 'pr-2 pl-2'; +}; + +const getBackgroundColor = (priority: Priority, context: Context) => { + if (priority === 'secondary') { + return 'bg-transparent'; + } + + const colorMap: Record = { + default: cn('bg-other-tonalFill10'), + 'technical-default': cn('bg-other-tonalFill10'), + 'technical-success': cn('bg-secondary-light'), + 'technical-caution': cn('bg-caution-light'), + 'technical-destructive': cn('bg-destructive-light'), + }; + return colorMap[context]; +}; + +const getColor = (priority: Priority, context: Context): string => { + if (priority === 'primary') { + return context === 'default' || context === 'technical-default' + ? cn('text-text-primary') + : cn('text-secondary-dark'); + } + + const colorMap: Record = { + default: cn('text-text-primary'), + 'technical-default': cn('text-text-primary'), + 'technical-success': cn('text-secondary-light'), + 'technical-caution': cn('text-caution-light'), + 'technical-destructive': cn('text-destructive-light'), + }; + return colorMap[context]; +}; + +export interface PillProps { + children: ReactNode; + priority?: Priority; + context?: Context; +} + +export const Pill = ({ children, priority = 'primary', context = 'default' }: PillProps) => { + const density = useDensity(); + + return ( + + {children} + + ); +}; diff --git a/packages/ui-tailwind/src/Popover/index.stories.tsx b/packages/ui-tailwind/src/Popover/index.stories.tsx new file mode 100644 index 0000000000..8bb09f83cf --- /dev/null +++ b/packages/ui-tailwind/src/Popover/index.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Popover } from '.'; +import { Button } from '../Button'; +import { ComponentType, useState } from 'react'; +import { Text } from '../Text'; +import { Shield } from 'lucide-react'; +import { Density } from '../Density'; + +const meta: Meta = { + component: Popover, + tags: ['autodocs', '!dev'], + argTypes: { + isOpen: { control: false }, + onClose: { control: false }, + }, + subcomponents: { + // Re: type coercion, see + // https://github.com/storybookjs/storybook/issues/23170#issuecomment-2241802787 + 'Popover.Content': Popover.Content as ComponentType, + 'Popover.Trigger': Popover.Trigger as ComponentType, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + render: function Render() { + const [isOpen, setIsOpen] = useState(false); + + return ( + setIsOpen(false)}> + + + + + +
+ + This is a heading + + + This is description information. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Ut et massa mi. + +
+ + + +
+
+
+
+ ); + }, +}; diff --git a/packages/ui-tailwind/src/Popover/index.tsx b/packages/ui-tailwind/src/Popover/index.tsx new file mode 100644 index 0000000000..c19f573f2a --- /dev/null +++ b/packages/ui-tailwind/src/Popover/index.tsx @@ -0,0 +1,127 @@ +import { ReactNode } from 'react'; +import * as RadixPopover from '@radix-ui/react-popover'; +import type { PopoverContentProps as RadixPopoverContentProps } from '@radix-ui/react-popover'; +import { getPopoverContent, PopoverContext } from '../utils/popover.ts'; + +interface ControlledPopoverProps { + /** + * Whether the popover is currently open. If left `undefined`, this will be + * treated as an uncontrolled popover — that is, it will open and close based + * on user interactions rather than on state variables. + */ + isOpen: boolean; + /** + * Callback for when the user closes the popover. Should update the state + * variable being passed in via `isOpen`. If left `undefined`, users will not + * be able to close it -- that is, it will only be able to be closed programmatically + */ + onClose?: VoidFunction; +} + +interface UncontrolledPopoverProps { + isOpen?: undefined; + onClose?: undefined; +} + +export type PopoverProps = { + children?: ReactNode; +} & (ControlledPopoverProps | UncontrolledPopoverProps); + +/** + * A popover box that appears next to the trigger element. + * + * To render a popover, compose it using a few components: ``, + * ``, and ``. The latter two must be + * descendents of `` in the component tree, and siblings to each + * other. (`` is optional, though — more on that in a moment.) + * + * ```tsx + * + * + * + * + * + * Popover content here + * + * ``` + * + * Depending on your use case, you may want to use `` either as a + * controlled component, or as an uncontrolled component. + * + * ## Usage as a controlled component + * + * Use `` as a controlled component when you want to control its + * open/closed state yourself (e.g., via a state management solution like + * Zustand or Redux). You can accomplish this by passing `isOpen` and `onClose` + * props to the `` component, and omitting ``: + * + * ```tsx + * + * + * setIsOpen(false)}> + * Popover content here + * + * ``` + * + * Note that, in the example above, the ` + * + * + * Popover content here + * + * ``` + */ +export const Popover = ({ children, onClose, isOpen }: PopoverProps) => { + return ( + onClose && !value && onClose()}> + {children} + + ); +}; + +export interface PopoverTriggerProps { + children: ReactNode; +} + +const Trigger = ({ children }: PopoverTriggerProps) => ( + {children} +); +Popover.Trigger = Trigger; + +export interface PopoverContentProps { + children?: ReactNode; + side?: RadixPopoverContentProps['side']; + align?: RadixPopoverContentProps['align']; + context?: PopoverContext; +} + +/** + * Popover content. Must be a child of ``. + * + * Control the position of the Popover relative to the trigger element by passing + * `side` and `align` props. + */ +const Content = ({ children, side, align, context = 'default' }: PopoverContentProps) => { + return ( + + +
{children}
+
+
+ ); +}; +Popover.Content = Content; + +export type { PopoverContext }; diff --git a/packages/ui-tailwind/src/Progress/index.stories.tsx b/packages/ui-tailwind/src/Progress/index.stories.tsx new file mode 100644 index 0000000000..de42b78acd --- /dev/null +++ b/packages/ui-tailwind/src/Progress/index.stories.tsx @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Progress } from '.'; + +const meta: Meta = { + component: Progress, + tags: ['autodocs', '!dev'], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + value: 0.3, + loading: false, + error: false, + }, +}; diff --git a/packages/ui-tailwind/src/Progress/index.tsx b/packages/ui-tailwind/src/Progress/index.tsx new file mode 100644 index 0000000000..871aebb6f4 --- /dev/null +++ b/packages/ui-tailwind/src/Progress/index.tsx @@ -0,0 +1,57 @@ +import * as ProgressPrimitive from '@radix-ui/react-progress'; +import cn from 'clsx'; + +export const getIndicatorColor = (value: number, error: boolean): string => { + if (error) { + return cn('bg-destructive-light'); + } + + if (value === 1) { + return cn('bg-secondary-light'); + } + + return cn('bg-caution-light'); +}; + +export interface ProgressProps { + /** Percentage value from 0 to 1 */ + value: number; + /** Displays the skeleton-like moving shade */ + loading?: boolean; + /** Renders red indicator while the progress continues */ + error?: boolean; +} + +/** + * Progress bar with loading and error states + */ +export const Progress = ({ value, loading, error = false }: ProgressProps) => ( + + +
+ {loading && ( +
+ )} +
+ + +); diff --git a/packages/ui-tailwind/src/Table/index.stories.tsx b/packages/ui-tailwind/src/Table/index.stories.tsx new file mode 100644 index 0000000000..4509586ad0 --- /dev/null +++ b/packages/ui-tailwind/src/Table/index.stories.tsx @@ -0,0 +1,58 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Table } from '.'; +import { Text } from '../Text'; +import { ComponentType } from 'react'; + +const meta: Meta = { + component: Table, + tags: ['autodocs', '!dev', 'density'], + subcomponents: { + // Re: type coercion, see + // https://github.com/storybookjs/storybook/issues/23170#issuecomment-2241802787 + 'Table.Thead': Table.Thead as ComponentType, + 'Table.Tbody': Table.Tbody as ComponentType, + 'Table.Tr': Table.Tr as ComponentType, + 'Table.Th': Table.Th as ComponentType, + 'Table.Td': Table.Td as ComponentType, + }, +}; +export default meta; + +type Story = StoryObj; + +const DATA = [ + [32768, 'Send', '9d80ffa9113f7eed74ddeff8eddfda6a89106c6cdf336565f9cbaf90810396bf'], + [16384, 'Receive', '9d80ffa9113f7eed74ddeff8eddfda6a89106c6cdf336565f9cbaf90810396bf'], + [8192, 'Swap Claim', '9d80ffa9113f7eed74ddeff8eddfda6a89106c6cdf336565f9cbaf90810396bf'], + [4096, 'Swap', '9d80ffa9113f7eed74ddeff8eddfda6a89106c6cdf336565f9cbaf90810396bf'], + [2048, 'Internal Transfer', '9d80ffa9113f7eed74ddeff8eddfda6a89106c6cdf336565f9cbaf90810396bf'], + [1024, 'Delegate', '9d80ffa9113f7eed74ddeff8eddfda6a89106c6cdf336565f9cbaf90810396bf'], +]; + +export const Basic: Story = { + render: function Render() { + return ( + Recent transactions}> + + + Block height + Description + Transaction hash + + + + {DATA.map(([blockHeight, description, hash]) => ( + + {blockHeight} + {description} + + {hash} + + + ))} + +
+ ); + }, +}; diff --git a/packages/ui-tailwind/src/Table/index.tsx b/packages/ui-tailwind/src/Table/index.tsx new file mode 100644 index 0000000000..f4c23a75db --- /dev/null +++ b/packages/ui-tailwind/src/Table/index.tsx @@ -0,0 +1,161 @@ +import { PropsWithChildren, ReactNode } from 'react'; +import cn from 'clsx'; +import { tableHeading, tableItem } from '../utils/typography'; +import { Density, useDensity } from '../utils/density'; +import { ConditionalWrap } from '../ConditionalWrap'; + +export interface TableProps { + /** Content that will appear above the table. */ + title?: ReactNode; + children: ReactNode; + /** Which CSS `table-layout` property to use. */ + tableLayout?: 'fixed' | 'auto'; +} + +/** + * A styled HTML table. + * + * To build a table, you only need to import the `` component. All + * other components are properties on `Table`. + * + * ```tsx + *
+ * + * + * Header cell + * Header cell 2 + * + * + * + * + * Body cell + * Body cell 2 + * + * + *
+ * ``` + * + * By design, `` elements have limited props. No styling or + * customization is permitted. This ensures that all tables look consistent + * throughout the Penumbra UI. + * + * To render title content above the table, pass a `title` prop: + * + * ```tsx + * + * ... + *
+ * + * // or... + * + * Here is some rich table title content}> + * ... + *
+ * ``` + */ +export const Table = ({ title, children, tableLayout }: TableProps) => ( + ( +
+
{title}
+ {children} +
+ )} + > + + {children} +
+
+); + +const Thead = ({ children }: PropsWithChildren) => {children}; +Table.Thead = Thead; + +const Tbody = ({ children }: PropsWithChildren) => {children}; +Table.Tbody = Tbody; + +const Tr = ({ children }: PropsWithChildren) => ( + {children} +); +Table.Tr = Tr; + +type HAlign = 'left' | 'center' | 'right'; +type VAlign = 'top' | 'middle' | 'bottom'; + +const getCell = (density: Density) => + cn('box-border', 'pl-3 pr-3', density === 'sparse' ? 'pt-4 pb-4' : 'pt-3 pb-3'); + +const Th = ({ + children, + colSpan, + hAlign, + vAlign, + width, +}: PropsWithChildren<{ + colSpan?: number; + /** A CSS `width` value to use for this cell. */ + width?: string; + /** Controls the CSS `text-align` property for this cell. */ + hAlign?: HAlign; + /** Controls the CSS `vertical-align` property for this cell. */ + vAlign?: VAlign; +}>) => { + const density = useDensity(); + + return ( + + {children} + + ); +}; +Table.Th = Th; + +const Td = ({ + children, + colSpan, + hAlign, + vAlign, + width, +}: PropsWithChildren<{ + colSpan?: number; + /** A CSS `width` value to use for this cell. */ + width?: string; + /** Controls the CSS `text-align` property for this cell. */ + hAlign?: HAlign; + /** Controls the CSS `vertical-align` property for this cell. */ + vAlign?: VAlign; +}>) => { + const density = useDensity(); + + return ( + + {children} + + ); +}; +Table.Td = Td; diff --git a/packages/ui-tailwind/src/Tabs/index.stories.tsx b/packages/ui-tailwind/src/Tabs/index.stories.tsx new file mode 100644 index 0000000000..83bc0e3ec1 --- /dev/null +++ b/packages/ui-tailwind/src/Tabs/index.stories.tsx @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { useArgs } from '@storybook/preview-api'; + +import { Tabs } from '.'; + +const meta: Meta = { + component: Tabs, + tags: ['autodocs', '!dev', 'density'], + argTypes: { + value: { control: false }, + options: { control: false }, + onChange: { control: false }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + actionType: 'default', + value: 'first', + options: [ + { label: 'First', value: 'first' }, + { label: 'Second', value: 'second' }, + { label: 'Third', value: 'third' }, + { label: 'Fourth (disabled)', value: 'fourth', disabled: true }, + ], + }, + + render: function Render(props) { + const [, updateArgs] = useArgs(); + + const onChange = (value: { toString: () => string }) => updateArgs({ value }); + + return ; + }, +}; diff --git a/packages/ui-tailwind/src/Tabs/index.tsx b/packages/ui-tailwind/src/Tabs/index.tsx new file mode 100644 index 0000000000..f58afd9b78 --- /dev/null +++ b/packages/ui-tailwind/src/Tabs/index.tsx @@ -0,0 +1,113 @@ +import { tab, tabSmall } from '../utils/typography'; +import { buttonBase, getOverlays } from '../utils/button'; +import * as RadixTabs from '@radix-ui/react-tabs'; +import { ActionType } from '../utils/action-type'; +import { useDensity } from '../utils/density'; +import cn from 'clsx'; + +type LimitedActionType = Exclude; + +const getIndicatorColor = (actionType: LimitedActionType): string => { + if (actionType === 'accent') { + return cn('bg-tabAccent'); + } + if (actionType === 'unshield') { + return cn('bg-tabUnshield'); + } + return cn('bg-tabNeutral'); +}; + +const getBorderColor = (actionType: LimitedActionType): string => { + if (actionType === 'accent') { + return cn('border-action-primaryFocusOutline'); + } + if (actionType === 'unshield') { + return cn('border-action-unshieldFocusOutline'); + } + return cn('border-action-neutralFocusOutline'); +}; + +export interface TabsTab { + value: string; + label: string; + disabled?: boolean; +} + +export interface TabsProps { + value: string; + onChange: (value: string) => void; + options: TabsTab[]; + actionType?: LimitedActionType; +} + +/** + * Use tabs for switching between related pages or views. + * + * Built atop Radix UI's `` component, so it's fully accessible and + * supports keyboard navigation. + * + * ```TSX + * + * ``` + */ +export const Tabs = ({ value, onChange, options, actionType = 'default' }: TabsProps) => { + const density = useDensity(); + + return ( + + +
+ {options.map(option => ( + + + + ))} +
+
+
+ ); +}; diff --git a/packages/ui-tailwind/src/Text/index.stories.tsx b/packages/ui-tailwind/src/Text/index.stories.tsx new file mode 100644 index 0000000000..5756524324 --- /dev/null +++ b/packages/ui-tailwind/src/Text/index.stories.tsx @@ -0,0 +1,151 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Text } from '.'; +import { useArgs } from '@storybook/preview-api'; + +const meta: Meta = { + component: Text, + tags: ['autodocs', '!dev'], + argTypes: { + h1: { control: false }, + h2: { control: false }, + h3: { control: false }, + h4: { control: false }, + xxl: { control: false }, + large: { control: false }, + body: { control: false }, + p: { control: false }, + strong: { control: false }, + detail: { control: false }, + small: { control: false }, + technical: { control: false }, + detailTechnical: { control: false }, + + as: { + options: ['span', 'div', 'h1', 'h2', 'h3', 'h4', 'p', 'main', 'section'], + }, + }, +}; +export default meta; + +const OPTIONS = [ + 'h1', + 'h2', + 'h3', + 'h4', + 'large', + 'body', + 'p', + 'strong', + 'detail', + 'small', + 'technical', + 'detailTechnical', +] as const; + +const Option = ({ + value, + checked, + onSelect, +}: { + value: (typeof OPTIONS)[number]; + checked: boolean; + onSelect: (value: (typeof OPTIONS)[number]) => void; +}) => ( + +); +export const KitchenSink: StoryObj = { + args: { + children: 'The quick brown fox jumps over the lazy dog.', + h1: true, + as: 'span', + truncate: false, + }, + + render: function Render(props) { + const [, updateArgs] = useArgs(); + + const onSelect = (option: (typeof OPTIONS)[number]) => + updateArgs( + OPTIONS.reduce( + (prev, curr) => ({ + ...prev, + [curr]: curr === option ? true : undefined, + }), + {}, + ), + ); + + return ( +
+
+ Text style: + {OPTIONS.map(option => ( +
+ + + + ); + }, +}; + +export const UsageExample: StoryObj = { + render: function Render() { + return ( +
+ h1. Typography + h2. This is a section + + Here is some filler text: Giggster kickstarter painting with light + academy award charlie kaufman shotdeck breakdown services indie white balance. Student + emmys sound design ots character arc low angle coming-of-age composition. Storyboard beat + sheet greenlight cowboy shot margarita shot blocking foley stage seed&spark. + + + + Shot list low angle mit out sound telephoto rec.709 high angle eyeline assembly cut 8 1/2 + dga. Post-viz circle of confusion location scout unpaid internship reality of doing genre + film. Jean-luc godard ilm symbolism alexa mini white balance margarita shot. Jordan peele + log line ryan coogler actors access. + + + h2. Section two + + Silent film conflict sound design blocking script treatment. Teal and orange composition + fotokem third act blackmagic ingmar bergman jordan peele rembrandt lighting critical + darling silent film. Wes anderson arthouse diegetic sound after effects. + + + This is some large text. + + + White balance crafty debut film pan up 180-degree rule academy award exposure triangle + director's vision. Lavs led wall the actor prepares wrylies character arc stinger + sanford meisner. Given circumstances under-exposed jordan peele color grade nomadland team + deakins crafty dogme 95. French new wave pan up save the cat contrast ratio blue filter + cinema studies super 16 jump cut cannes unreal engine. + + + + Establishing shot stella adler ludwig göransson first-time director shotdeck fotokem + over-exposed flashback reality of doing color grade. Fetch coffee student emmys indie key + light rembrandt lighting. Undercranking beat beat scriptnotes podcast. Sound design + academy award day-for-night christopher nolan undercranking. Unreal engine visionary match + cut grain vs. noise 35mm anti-hero production design. + +
+ ); + }, +}; diff --git a/packages/ui-tailwind/src/Text/index.tsx b/packages/ui-tailwind/src/Text/index.tsx new file mode 100644 index 0000000000..b8b08fdc94 --- /dev/null +++ b/packages/ui-tailwind/src/Text/index.tsx @@ -0,0 +1,199 @@ +import cn from 'clsx'; + +import { + body, + detail, + h1, + h2, + h3, + h4, + large, + small, + detailTechnical, + strong, + technical, + xxl, + p, + getTextBase, +} from '../utils/typography'; +import { ElementType, ReactNode } from 'react'; +import { ThemeColor } from '../utils/color'; +import { TextType } from './types'; + +export type TextProps = TextType & { + children?: ReactNode; + /** + * Which component or HTML element to render this text as. + * + * @example + * ```tsx + * This is a span with H1 styling + * ``` + */ + as?: ElementType; + /** + * When `true`, will apply styles that 1) prevent text wrapping, 2) hide + * overflow, 3) add an ellpsis when the text overflows. + */ + truncate?: boolean; + /** A string representing the color key from the Tailwind theme (e.g. 'primary.light') */ + color?: ThemeColor; + /** + * The text alignment + */ + align?: 'left' | 'center' | 'right'; + /** + * The text decoration + */ + decoration?: 'line-through' | 'underline'; + /** + * The text transform + */ + transform?: 'uppercase' | 'lowercase' | 'capitalize'; + /** + * Controls how the text breaks. + */ + break?: 'words' | 'all' | 'keep'; + /** + * Controls how whitespace is handled. + */ + whitespace?: 'nowrap' | 'pre' | 'pre-line' | 'pre-wrap' | 'break-spaces'; +}; + +const ALIGN_MAP: Record['align'], string> = { + center: cn('text-center'), + left: cn('text-left'), + right: cn('text-right'), +}; + +const DECORATION_MAP: Record['decoration'], string> = { + 'line-through': cn('line-through'), + underline: cn('underline'), +}; + +const TRANSFORM_MAP: Record['transform'], string> = { + uppercase: cn('uppercase'), + lowercase: cn('lowercase'), + capitalize: cn('capitalize'), +}; + +const BREAK_MAP: Record['break'], string> = { + all: cn('break-all'), + words: cn('break-words'), + keep: cn('break-keep'), +}; + +const WHITESPACE_MAP: Record['whitespace'], string> = { + nowrap: cn('whitespace-nowrap'), + pre: cn('whitespace-pre'), + 'pre-line': cn('whitespace-pre-line'), + 'pre-wrap': cn('whitespace-pre-wrap'), + 'break-spaces': cn('whitespace-break-spaces'), +}; + +// Composes all props to the Tailwind class list +const getTextOptionClasses = ({ + color, + truncate, + align, + decoration, + transform, + break: breakProp, + whitespace, +}: TextProps): string => { + const truncateClass = truncate ? cn('truncate') : ''; + const alignClass = align && ALIGN_MAP[align]; + const decorationClass = decoration && DECORATION_MAP[decoration]; + const transformClass = transform && TRANSFORM_MAP[transform]; + const breakClass = breakProp && BREAK_MAP[breakProp]; + const whitespaceClass = whitespace && WHITESPACE_MAP[whitespace]; + + return cn( + getTextBase(color), + truncateClass, + alignClass, + decorationClass, + transformClass, + breakClass, + whitespaceClass, + ); +}; + +/** + * All-purpose text wrapper for quickly styling text per the Penumbra UI + * guidelines. + * + * Use with a _single_ text style name: + * + * ```tsx + * This will be rendered with the `h1` style. + * This will be rendered with the `body` style. + * + * INCORRECT: This will result in a TypeScript error. Only use one text style + * at a time. + * + * ``` + * + * When no text style is passed, it will render using the `body` style. + * + * The heading text styles are rendered as their corresponding heading tags + * (`

`, `

`, etc.), and the `p` style is rendered as a `

` tag. + * All other styles are rendered as ``s. To customize which tag is + * rendered without affecting its appearance, use the `as` prop: + * + * ```tsx + * + * This will render with the h1 style, but inside an inline span tag. + * + * ``` + */ +export const Text = (props: TextProps) => { + const classes = getTextOptionClasses(props); + const SpanElement = props.as ?? 'span'; + + if (props.h1) { + const Element = props.as ?? 'h1'; + return {props.children}; + } + if (props.h2) { + const Element = props.as ?? 'h2'; + return {props.children}; + } + if (props.h3) { + const Element = props.as ?? 'h3'; + return {props.children}; + } + if (props.h4) { + const Element = props.as ?? 'h4'; + return {props.children}; + } + + if (props.xxl) { + return {props.children}; + } + if (props.large) { + return {props.children}; + } + if (props.strong) { + return {props.children}; + } + if (props.detail) { + return {props.children}; + } + if (props.small) { + return {props.children}; + } + if (props.detailTechnical) { + return {props.children}; + } + if (props.technical) { + return {props.children}; + } + + if (props.p) { + const Element = props.as ?? 'p'; + return {props.children}; + } + + return {props.children}; +}; diff --git a/packages/ui-tailwind/src/Text/types.ts b/packages/ui-tailwind/src/Text/types.ts new file mode 100644 index 0000000000..a1ab764c5f --- /dev/null +++ b/packages/ui-tailwind/src/Text/types.ts @@ -0,0 +1,132 @@ +/** + * Utility interface to be used below to ensure that only one text type is used + * at a time. + */ +interface NeverTextTypes { + h1?: never; + h2?: never; + h3?: never; + h4?: never; + xxl?: never; + large?: never; + p?: never; + strong?: never; + detail?: never; + small?: never; + detailTechnical?: never; + technical?: never; + body?: never; +} + +export type TextType = + | (Omit & { + /** + * Renders a styled `

`. Pass the `as` prop to use a different HTML + * element with the same styling. + */ + h1: true; + }) + | (Omit & { + /** + * Renders a styled `

`. Pass the `as` prop to use a different HTML + * element with the same styling. + */ + h2: true; + }) + | (Omit & { + /** + * Renders a styled `

`. Pass the `as` prop to use a different HTML + * element with the same styling. + */ + h3: true; + }) + | (Omit & { + /** + * Renders a styled `

`. Pass the `as` prop to use a different HTML + * element with the same styling. + */ + h4: true; + }) + | (Omit & { + /** + * Renders bigger text used for section titles. Renders a `` by + * default; pass the `as` prop to use a different HTML element with the + * same styling. + */ + xxl: true; + }) + | (Omit & { + /** + * Renders big text used for section titles. Renders a `` by + * default; pass the `as` prop to use a different HTML element with the + * same styling. + */ + large: true; + }) + | (Omit & { + /** + * Renders a styled `

` tag with a bottom-margin (unless it's the last + * child). Aside from the margin, `

` is identical to ``. + * + * Note that this is the only component in the entire Penumbra UI library + * that renders an external margin. It's a convenience for developers who + * don't want to wrap each `` in a `

` with the + * appropriate margin, or a flex columnn with a gap. + */ + p: true; + }) + | (Omit & { + /** + * Emphasized body text. + * + * Renders a `` by default; pass the `as` prop to use a different + * HTML element with the same styling. + */ + strong: true; + }) + | (Omit & { + /** + * Detail text used for small bits of tertiary information. + * + * Renders a `` by default; pass the `as` prop to use a different + * HTML element with the same styling. + */ + detail: true; + }) + | (Omit & { + /** + * Small text used for secondary information. + * + * Renders a `` by default; pass the `as` prop to use a different + * HTML element with the same styling. + */ + small: true; + }) + | (Omit & { + /** + * Small monospaced text used for code, values, and other technical + * information. + * + * Renders a `` by default; pass the `as` prop to use a different + * HTML element with the same styling. + */ + detailTechnical: true; + }) + | (Omit & { + /** + * Monospaced text used for code, values, and other technical information. + * + * Renders a `` by default; pass the `as` prop to use a different + * HTML element with the same styling. + */ + technical: true; + }) + | (Omit & { + /** + * Body text used throughout most of our UIs. + * + * Renders a `` by default; pass the `as` prop to use a different + * HTML element with the same styling. + */ + body?: true; + }); diff --git a/packages/ui-tailwind/src/TextInput/index.stories.tsx b/packages/ui-tailwind/src/TextInput/index.stories.tsx new file mode 100644 index 0000000000..c3812d2312 --- /dev/null +++ b/packages/ui-tailwind/src/TextInput/index.stories.tsx @@ -0,0 +1,64 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { useArgs } from '@storybook/preview-api'; + +import { TextInput } from '.'; +import { Icon } from '../Icon'; +import { BookUser, Send } from 'lucide-react'; +import { Button } from '../Button'; +import { Density } from '../Density'; + +const SampleButton = () => ( + + + +); + +const addressBookIcon = ; + +const meta: Meta = { + component: TextInput, + tags: ['autodocs', '!dev'], + argTypes: { + startAdornment: { + options: ['Address book icon', 'None'], + mapping: { + 'Address book icon': addressBookIcon, + None: undefined, + }, + }, + endAdornment: { + options: ['Sample button', 'None'], + mapping: { + 'Sample button': , + None: undefined, + }, + }, + max: { control: false }, + min: { control: false }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + actionType: 'default', + placeholder: 'penumbra1abc123...', + value: '', + disabled: false, + type: 'text', + startAdornment: addressBookIcon, + endAdornment: , + }, + + render: function Render(props) { + const [, updateArgs] = useArgs(); + + const onChange = (value: string) => updateArgs({ value }); + + return ; + }, +}; diff --git a/packages/ui-tailwind/src/TextInput/index.tsx b/packages/ui-tailwind/src/TextInput/index.tsx new file mode 100644 index 0000000000..10410210e8 --- /dev/null +++ b/packages/ui-tailwind/src/TextInput/index.tsx @@ -0,0 +1,90 @@ +import { forwardRef, ReactNode } from 'react'; +import { small } from '../utils/typography'; +import { ActionType, getFocusWithinOutlineColorByActionType } from '../utils/action-type'; +import { useDisabled } from '../utils/disabled-context'; +import cn from 'clsx'; + +export interface TextInputProps { + value?: string; + onChange?: (value: string) => void; + placeholder?: string; + actionType?: ActionType; + disabled?: boolean; + type?: 'email' | 'number' | 'password' | 'tel' | 'text' | 'url'; + /** + * Markup to render inside the text input's visual frame, before the text + * input itself. + */ + startAdornment?: ReactNode; + /** + * Markup to render inside the text input's visual frame, after the text input + * itself. + */ + endAdornment?: ReactNode; + max?: string | number; + min?: string | number; +} + +/** + * A simple text field. + * + * Can be enriched with start and end adornments, which are markup that render + * inside the text input's visual frame. + */ +// eslint-disable-next-line react/display-name -- exotic component +export const TextInput = forwardRef( + ( + { + value, + onChange, + placeholder, + actionType = 'default', + disabled, + type = 'text', + startAdornment = null, + endAdornment = null, + max, + min, + }: TextInputProps, + ref, + ) => ( +
+ {startAdornment} + + onChange?.(e.target.value)} + placeholder={placeholder} + disabled={useDisabled(disabled)} + type={type} + max={max} + min={min} + ref={ref} + className={cn( + 'box-border grow appearance-none border-none bg-base-transparent py-2', + startAdornment ? 'pl-0' : 'pl-3', + endAdornment ? 'pr-0' : 'pr-3', + disabled ? 'text-text-muted' : 'text-text-primary', + small, + 'placeholder:text-text-secondary', + 'disabled:cursor-not-allowed', + 'disabled:placeholder:text-text-muted', + 'focus:outline-0', + '[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none', + )} + /> + + {endAdornment} +
+ ), +); diff --git a/packages/ui-tailwind/src/Toast/index.stories.tsx b/packages/ui-tailwind/src/Toast/index.stories.tsx new file mode 100644 index 0000000000..8ac45aeb72 --- /dev/null +++ b/packages/ui-tailwind/src/Toast/index.stories.tsx @@ -0,0 +1,95 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { openToast, ToastProvider, ToastType } from '.'; +import { Button } from '../Button'; +import { Tooltip, TooltipProvider } from '../Tooltip'; +import { Text } from '../Text'; + +const meta: Meta = { + component: ToastProvider, + tags: ['autodocs', '!dev'], + argTypes: {}, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + render: function Render() { + const toast = (type: ToastType) => { + openToast({ + type, + message: 'Hello, world!', + description: 'Additional text can possibly be long enough lorem ipsum dolor sit amet.', + }); + }; + + const upload = () => { + const t = openToast({ + type: 'loading', + message: 'Hello, world!', + }); + + setTimeout(() => { + t.update({ + type: 'error', + message: 'Failed!', + description: 'Unknown error', + }); + }, 2000); + }; + + const action = () => { + openToast({ + type: 'warning', + message: 'Do you confirm?', + dismissible: false, + persistent: true, + action: { + label: 'Yes!', + onClick: () => { + openToast({ + type: 'success', + message: 'Confirmed!', + dismissible: false, + }); + }, + }, + }); + }; + + return ( + +
+ + + All style types of toasts + +
+ + + + + + + +
+ + Updating toast + +
+ + + +
+ + Action toast + +
+ +
+
+
+ ); + }, +}; diff --git a/packages/ui-tailwind/src/Toast/index.tsx b/packages/ui-tailwind/src/Toast/index.tsx new file mode 100644 index 0000000000..459164dba8 --- /dev/null +++ b/packages/ui-tailwind/src/Toast/index.tsx @@ -0,0 +1,3 @@ +export { ToastProvider } from './provider'; +export { openToast } from './open'; +export type { Toast, ToastProps, ToastType } from './open'; diff --git a/packages/ui-tailwind/src/Toast/open.ts b/packages/ui-tailwind/src/Toast/open.ts new file mode 100644 index 0000000000..e36adb1806 --- /dev/null +++ b/packages/ui-tailwind/src/Toast/open.ts @@ -0,0 +1,107 @@ +import { toast, ExternalToast } from 'sonner'; +import { ReactNode } from 'react'; + +export type ToastType = 'success' | 'info' | 'warning' | 'error' | 'loading'; +type ToastFn = (message: ReactNode, options?: ExternalToast) => string | number; +type ToastId = string | number; + +const toastFnMap: Record = { + success: toast.success, + info: toast.info, + warning: toast.warning, + error: toast.error, + loading: toast.loading, +}; + +export interface ToastProps { + type: ToastType; + message: string; + description?: string; + persistent?: boolean; + dismissible?: boolean; + action?: ExternalToast['action']; +} + +export interface Toast { + update: (newProps: Partial) => void; + dismiss: VoidFunction; +} + +/** + * If `` exists in the document, opens a toast with provided type and options. + * By default, the toast is dismissible and has a duration of 4000 milliseconds. It can + * be programmatically updated to another type and content without re-opening the toast. + * + * Example: + * + * ```tsx + * import { ToastProvider, openToast } from '@penumbra-zone/ui/Toast'; + * import { ToastProvider, openToast } from '@penumbra-zone/ui/Button'; + * + * const Component = () => { + * const open = () => { + * const toast = openToast({ + * type: 'loading', + * message: 'Loading...', + * }); + * + * setTimeout(() => { + * toast.update({ + * type: 'error', + * message: 'Failed!', + * description: 'Unknown error' + * }); + * }, 2000); + * }; + * + * return ( + * <> + * + * + * + * ); + * }; + * ``` + */ +export const openToast = (props: ToastProps): Toast => { + let options = props; + let id: ToastId | undefined = undefined; + + const open = () => { + const fn = toastFnMap[options.type]; + + id = fn(options.message, { + id, + description: options.description, + closeButton: options.dismissible ?? true, + dismissible: options.dismissible ?? true, + duration: options.persistent ? Infinity : 4000, + action: options.action, + }); + }; + + const dismiss: Toast['dismiss'] = () => { + if (typeof id === 'undefined') { + return; + } + + toast.dismiss(id); + id = undefined; + }; + + const update: Toast['update'] = newProps => { + if (typeof id === 'undefined') { + return; + } + + options = { ...options, ...newProps }; + open(); + }; + + open(); + + return { + dismiss, + update, + }; +}; diff --git a/packages/ui-tailwind/src/Toast/provider.tsx b/packages/ui-tailwind/src/Toast/provider.tsx new file mode 100644 index 0000000000..0739757842 --- /dev/null +++ b/packages/ui-tailwind/src/Toast/provider.tsx @@ -0,0 +1,41 @@ +import { Toaster } from 'sonner'; + +/** + * If `` exists in the document, you can call `openToast` function to open a toast with provided type and options. + * By default, the toast is dismissible and has a duration of 4000 milliseconds. It can + * be programmatically updated to another type and content without re-opening the toast. + * + * Example: + * + * ```tsx + * import { ToastProvider, openToast } from '@penumbra-zone/ui/Toast'; + * import { ToastProvider, openToast } from '@penumbra-zone/ui/Button'; + * + * const Component = () => { + * const open = () => { + * const toast = openToast({ + * type: 'loading', + * message: 'Loading...', + * }); + * + * setTimeout(() => { + * toast.update({ + * type: 'error', + * message: 'Failed!', + * description: 'Unknown error' + * }); + * }, 2000); + * }; + * + * return ( + * <> + * + * + * + * ); + * }; + * ``` + */ +export const ToastProvider: typeof Toaster = ({ ...props }) => { + return ; +}; diff --git a/packages/ui-tailwind/src/Tooltip/index.stories.tsx b/packages/ui-tailwind/src/Tooltip/index.stories.tsx new file mode 100644 index 0000000000..937b2d1e52 --- /dev/null +++ b/packages/ui-tailwind/src/Tooltip/index.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Tooltip, TooltipProvider } from './index'; +import { Text } from '../Text'; + +const meta: Meta = { + component: Tooltip, + tags: ['autodocs', '!dev'], + argTypes: { + children: { control: false }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + render: args => ( + + + + ), + args: { + title: 'This is a heading', + message: + 'This is description information. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et massa mi.', + children: Hover over this text., + }, +}; diff --git a/packages/ui-tailwind/src/Tooltip/index.tsx b/packages/ui-tailwind/src/Tooltip/index.tsx new file mode 100644 index 0000000000..d22d7dfd1b --- /dev/null +++ b/packages/ui-tailwind/src/Tooltip/index.tsx @@ -0,0 +1,5 @@ +export { TooltipProvider } from '@radix-ui/react-tooltip'; +export type { TooltipProviderProps } from '@radix-ui/react-tooltip'; + +export { Tooltip } from './tooltip'; +export type { TooltipProps } from './tooltip'; diff --git a/packages/ui-tailwind/src/Tooltip/tooltip.tsx b/packages/ui-tailwind/src/Tooltip/tooltip.tsx new file mode 100644 index 0000000000..429ab4f2f8 --- /dev/null +++ b/packages/ui-tailwind/src/Tooltip/tooltip.tsx @@ -0,0 +1,70 @@ +import * as RadixTooltip from '@radix-ui/react-tooltip'; +import { ReactNode } from 'react'; +import cn from 'clsx'; +import { Text } from '../Text'; +import { buttonBase } from '../utils/button'; +import { small } from '../utils/typography'; + +export interface TooltipProps { + /** An optional title to show in larger text above the message. */ + title?: string; + /** + * A string message to show in the tooltip. Note that only strings are + * allowed; for interactive content, use a `` or a ``. + */ + message: string; + /** + * The trigger for the tooltip. + * + * Note that the trigger will be wrapped in an HTML button element, so only pass content that can be validly nested inside a button (i.e., don't pass another button). + */ + children: ReactNode; +} + +/** + * Use this for small informational text that should appear adjacent to a piece + * of content. + * + * ```tsx + * + * Hover me + * + * ``` + * + * ## Differences between ``, ``, and ``. + * + * These three components provide similar functionality, but are meant to be + * used in distinct ways. + * + * - ``: Use dialogs for interactive or informational content that + * should take the user's attention above everything else on the page. Dialogs + * are typically opened in response to a click from a user, but may also be + * opened and closed programmatically. + * - ``: Use popovers for interactive or informational content that + * should be visually tied to a specific element on the page, such as the + * dropdown menu underneath the menu button. Popovers are typically opened in + * response to a click from a user, but may also be opened and closed + * programmatically. + * - ``: Use tooltips for plain-text informational content that + * should be visually tied to a specific element on the page. Tooltips are + * opened in response to the user hovering over that element. + */ +export const Tooltip = ({ title, message, children }: TooltipProps) => ( + + {children} + + + {title &&
{title}
} + + {message} +
+
+
+); diff --git a/packages/ui-tailwind/src/ValueView/index.stories.tsx b/packages/ui-tailwind/src/ValueView/index.stories.tsx new file mode 100644 index 0000000000..95489668fc --- /dev/null +++ b/packages/ui-tailwind/src/ValueView/index.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ValueViewComponent } from '.'; +import { + DELEGATION_VALUE_VIEW, + PENUMBRA_VALUE_VIEW, + UNBONDING_VALUE_VIEW, + UNKNOWN_ASSET_ID_VALUE_VIEW, + UNKNOWN_ASSET_VALUE_VIEW, +} from '../utils/bufs'; + +const meta: Meta = { + component: ValueViewComponent, + tags: ['autodocs', '!dev', 'density'], + argTypes: { + valueView: { + options: [ + 'Penumbra', + 'Delegation token', + 'Unbonding token', + 'Unknown asset', + 'Unknown asset ID', + ], + mapping: { + Penumbra: PENUMBRA_VALUE_VIEW, + 'Delegation token': DELEGATION_VALUE_VIEW, + 'Unbonding token': UNBONDING_VALUE_VIEW, + 'Unknown asset': UNKNOWN_ASSET_VALUE_VIEW, + 'Unknown asset ID': UNKNOWN_ASSET_ID_VALUE_VIEW, + }, + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + valueView: PENUMBRA_VALUE_VIEW, + context: 'default', + priority: 'primary', + }, +}; diff --git a/packages/ui-tailwind/src/ValueView/index.tsx b/packages/ui-tailwind/src/ValueView/index.tsx new file mode 100644 index 0000000000..d2fe2e9ab3 --- /dev/null +++ b/packages/ui-tailwind/src/ValueView/index.tsx @@ -0,0 +1,95 @@ +import { ReactNode } from 'react'; +import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { getMetadata } from '@penumbra-zone/getters/value-view'; +import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view'; +import { ConditionalWrap } from '../ConditionalWrap'; +import { Pill, PillProps } from '../Pill'; +import { Text } from '../Text'; +import { AssetIcon } from '../AssetIcon'; +import { Density, useDensity } from '../utils/density'; +import cn from 'clsx'; + +type Context = 'default' | 'table'; + +const ValueText = ({ children, density }: { children: ReactNode; density: Density }) => { + if (density === 'sparse') { + return {children}; + } + + return {children}; +}; + +export interface ValueViewComponentProps { + valueView?: ValueView; + /** + * A `ValueViewComponent` will be rendered differently depending on which + * context it's rendered in. By default, it'll be rendered in a pill. But in a + * table context, it'll be rendered as just an icon and text. + */ + context?: SelectedContext; + /** + * Use `primary` in most cases, or `secondary` when this value view + * represents a secondary value, such as when it's an equivalent value of a + * numeraire. + */ + priority?: PillProps['priority']; +} + +/** + * `ValueViewComponent` renders a `ValueView` — its amount, icon, and symbol. + * Use this anywhere you would like to render a `ValueView`. + * + * Note that `ValueViewComponent` only has density variants when the `context` + * is `default`. For the `table` context, there is only one density. + */ +export const ValueViewComponent = ({ + valueView, + context, + priority = 'primary', +}: ValueViewComponentProps) => { + const density = useDensity(); + + if (!valueView) { + return null; + } + + const formattedAmount = getFormattedAmtFromValueView(valueView, true); + const metadata = getMetadata.optional(valueView); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- possibly empty string + const symbol = metadata?.symbol || 'Unknown'; + + return ( + ( + +
+ {children} +
+
+ )} + > + +
+ +
+ +
+
+ {formattedAmount} +
+
+ {symbol} +
+
+
+
+ ); +}; diff --git a/packages/ui-tailwind/src/WalletBalance/index.stories.tsx b/packages/ui-tailwind/src/WalletBalance/index.stories.tsx new file mode 100644 index 0000000000..c1e5c7d0ff --- /dev/null +++ b/packages/ui-tailwind/src/WalletBalance/index.stories.tsx @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { WalletBalance } from '.'; +import { OSMO_BALANCE, PENUMBRA2_BALANCE, PENUMBRA_BALANCE } from '../utils/bufs'; + +const meta: Meta = { + component: WalletBalance, + tags: ['autodocs', '!dev'], + argTypes: { + balance: { + options: ['Penumbra balance', 'Account 2', 'Osmo balance'], + mapping: { + 'Penumbra balance': PENUMBRA_BALANCE, + 'Account 2': PENUMBRA2_BALANCE, + 'Osmo balance': OSMO_BALANCE, + }, + }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + balance: PENUMBRA_BALANCE, + }, +}; diff --git a/packages/ui-tailwind/src/WalletBalance/index.tsx b/packages/ui-tailwind/src/WalletBalance/index.tsx new file mode 100644 index 0000000000..50bb0cc80e --- /dev/null +++ b/packages/ui-tailwind/src/WalletBalance/index.tsx @@ -0,0 +1,83 @@ +import type { MouseEventHandler } from 'react'; +import { Wallet } from 'lucide-react'; +import cn from 'clsx'; +import type { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view'; +import { + getAddressIndex, + getBalanceView, + getMetadataFromBalancesResponse, +} from '@penumbra-zone/getters/balances-response'; +import { ActionType, getOutlineColorByActionType } from '../utils/action-type'; +import { Text } from '../Text'; + +const getColorByActionType = (actionType: ActionType, disabled?: boolean): string => { + if (disabled) { + return cn('text-text-muted'); + } + if (actionType === 'destructive') { + return cn('text-destructive-light'); + } + return cn('text-text-secondary'); +}; + +export interface WalletBalanceProps { + balance?: BalancesResponse; + actionType?: ActionType; + disabled?: boolean; + onClick?: MouseEventHandler; +} + +/** + * `WalletBalance` renders a `BalancesResponse` — its account index, amount,, and symbol. + * Use this anywhere you would like to render a `BalancesResponse`. + * + * Allows clicking on the wallet icon. + */ +export const WalletBalance = ({ + balance, + actionType = 'default', + disabled, + onClick, +}: WalletBalanceProps) => { + const account = getAddressIndex.optional(balance); + const valueView = getBalanceView.optional(balance); + const metadata = getMetadataFromBalancesResponse.optional(balance); + + if (!valueView || !account || !metadata) { + return null; + } + + return ( +
+ + + + {getFormattedAmtFromValueView(valueView, true)} {metadata.symbol || 'Unknown'} + +
+ ); +}; diff --git a/packages/ui-tailwind/src/theme/fonts.css b/packages/ui-tailwind/src/theme/fonts.css new file mode 100644 index 0000000000..4ffe3e1047 --- /dev/null +++ b/packages/ui-tailwind/src/theme/fonts.css @@ -0,0 +1,120 @@ +@font-face { + font-family: 'Iosevka Term'; + font-weight: 400; + src: url('./fonts/IosevkaTerm-Regular.woff2') format('woff2'); +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('./fonts/Poppins-Italic-LatinExt.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, + U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url('./fonts/Poppins-Italic.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, + U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url('./fonts/Poppins-MediumItalic-LatinExt.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, + U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url('./fonts/Poppins-MediumItalic.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, + U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./fonts/Poppins-Regular-LatinExt.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, + U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('./fonts/Poppins-Regular.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, + U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('./fonts/Poppins-Medium-LatinExt.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, + U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Poppins'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('./fonts/Poppins-Medium.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, + U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; +} +/* vietnamese */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('./fonts/WorkSans-Medium-Vietnamese.woff2') format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, + U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('./fonts/WorkSans-Medium-LatinExt.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, + U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('./fonts/WorkSans-Medium.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, + U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, + U+FFFD; +} diff --git a/packages/ui-tailwind/src/theme/fonts/IosevkaTerm-Regular.woff2 b/packages/ui-tailwind/src/theme/fonts/IosevkaTerm-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..4c0d1e4810e5d1df69e3a64814068a31444c4ba1 GIT binary patch literal 108736 zcmV)8K*qm!Pew8T0RR910jR(L5dZ)H1=Xkk0jOL60000000000000000000000000 z0000Qf_@u;`hFauSO#DKi(CkSRtcJI5eN#B@;r~`RRJ~vBm?Ct3-DwB1Rw>Ibq62} zfk#^})|3w)j_2N;GgRbbwnVUVv~B5K@E_Et?j0(Q(F{wyEFFNY5xh;WLcr=FlB%}t9YEM=4t z5tNaAw-^U5hNxnS$f7nFa&8=LJ3<+K0Cz+*!u1$lv9R1}RUgAKMOSl@rn4qtp8BK< zchq$hF(oj^$S4wXHd0wAt+I4c<0c>#wd|?*AbDzDJSA6%6`xT z|H=jSeMcX}<4b_43l^P*1SzSsW;&@onkoL|uJ9EmA{{5g1lKKOVX!JobrQGN{txR+MsM($M z#Ö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 literal 0 HcmV?d00001 diff --git a/packages/ui-tailwind/src/theme/fonts/Poppins-Italic-LatinExt.woff2 b/packages/ui-tailwind/src/theme/fonts/Poppins-Italic-LatinExt.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..98bf7e0a4af8c9f825e0f8a79a1b43ae33140455 GIT binary patch literal 5900 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 literal 0 HcmV?d00001 diff --git a/packages/ui-tailwind/src/theme/fonts/Poppins-Italic.woff2 b/packages/ui-tailwind/src/theme/fonts/Poppins-Italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..7c769e4d9b12fa2c71a6c7142b6eb8ab04653cfd GIT binary patch literal 8676 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 C4awF3 literal 0 HcmV?d00001 diff --git a/packages/ui-tailwind/src/theme/fonts/Poppins-Medium-LatinExt.woff2 b/packages/ui-tailwind/src/theme/fonts/Poppins-Medium-LatinExt.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e567af4a1bb974e3d8917e16ddd2b897ada90e58 GIT binary patch literal 5472 zcmV-m6`$&NPew8T0RR9102N>W3;+NC05a4702K`Y0RR9100000000000000000000 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 K1$L7W0{{R-Q7z^G literal 0 HcmV?d00001 diff --git a/packages/ui-tailwind/src/theme/fonts/Poppins-MediumItalic.woff2 b/packages/ui-tailwind/src/theme/fonts/Poppins-MediumItalic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..3fede4b83b6cc00656ea610c081a3f9f8e217090 GIT binary patch literal 8516 zcmV-KA-mppPew8T0RR9103k#G3;+NC07O&(03h)I0RR9100000000000000000000 z0000R9vm(PU;u$s3h+?bDhq=)00A}vBm;(M1Rw>33I~J>8z45bBG@P^)D%2_Vl-j^03 zL)aTct@9@>NQB5kVd;a*z^moc;gVlw09-i;#EJ+<%U-GVk$r zkC-5p`PXT#Uxowr^A+J}D-26rph+$7NR#!Yulv&X&q+$$Sa#S43QKnQ$kGAv))C3c zk&m4e0pE^u=e{me_x*--uBA-Pp9i=E>iZd~!^U7a_-X?X4_p-J80gOVAhvOjQ2_g; zND*-B?P?PU7Ys1}gTz63@&s8EUa0m{K+Fv)c`2L5=C@_fFG7HRTGOX8ImVvoY#;aOg)!(PbQ&6q#-UkgF?!zwi;cp@dJQ#vXzzA592AXCK&J#U z|5nZXI=AQCteV!H@SlHw|M#1h(>^lgpnW!5CFjNA&py8Z0z7s7ftt?+{Jn)EdjL4? zTL9KcApgC2cadJsFGz?$0nkHE#C96^S{fv9PO%l~2L_;xC}Zy*x13lKYa&mW1p@XC z{(wM!2A*wPv(f_v=YrtL=ZV*JA}6Wkj3Au_vuqs6LP*k}vNLwP*NB7`6%=c09~kK4 z5S$=OSqEsP9C#Cm+R>AKXG3w5Qbu9{+vmURf9ph(L>w24I@uH@XR2qRvv0Y?A$oWR zH1xv5vDrb;nRXb=gl$K1S7|!8+u2X4k5|0AjM;819pFDR*L74c)>@V9QHc!pXjL^^ z$rAznGWT?hez`Bdx2kX}1fjoE(DT%7+Fit+f&{M!_>^!9< z=;+JkfSrc9k_@Q#NR~j_IAI74wZ;dh^W0siJMmM(9+=$psR=$PI|!X41TdsW8FiCp zWY*=7>Y92{@Cs8yBDNdAiO?oC++-JbG5D}Ht`{U56*SLrs@e3N@rZB01OT@wH8|O* z2k}KIz1Wd*?iC;zuX;xzIMc0bzE2TUR2won7R6~B`JPuoe1iwMKssb3Bimw;3xd=n1Y& zh$NZT+zN%T0*fA$P$6s*@(@@cJf&Ayk-P^17Nh z6$~N~=~!KxX{XrZ<lVh+at{+bWFC6VLGa}=8zQ{B4pwm1!zDQ8EUO9XrpEo zb3bX}`j%8P+PZt=1l%!2sG*Vs{`V)?n1m`d5DLc#e?JwJLv-|gM9N^$!n7|AIE-nSrK&;i zK*Dz`0Q%-jZB7yQZ5CZQr`(83&U4bJA1AG9(2uKN?FQy^z^ z#MzLI2?S=`MSjBr(R}s;qi!iAbpHgK0Tb@Bmf}CAY`(r69-}dz7v|*7g^}<_A*Y(6 zt#f)?My3~fNR~2bTN0gFmasI93wt9WZ^fGwtr~La_seC zldwv7cEJp+^Y{Dw$scLn=kVS%9=$HH`n@IviXg_K7 zJRUGSbzbLOm+2OFOLifV>!bJWlmewxOKuDrV!6ZJ2nkZ&D3HymGh{E)i)5^J$Krq0 z?7Z9erhY@i=j*m)YVgAD$l;iGwy*k`<@CH0F3B<;EuEGNngh3<@ucBRsv)V|Znxm+tpp=)diTdN0pW{+Y~k0p@vi@3O^GwC${Cx+Qq_5_JM zcN%vRDCX8;Sw!*Q6z0-M*KyjMQD?EtbA-W}mNH(D&NDvIL$Z)bYs8FuCnkBjOL>j^ zNy4!1@+^%q;eDzeW7??P&MVI4iF7h2D4Wn@m3TZ2xee8lRi#XK?wo2XcGD3tCQxt~ zwpJ2HK$ZP9gq@V@rW$*);}`DTJby7P{qJxt2Sxqr-$k-&=3eOO&pU6=-m!Li&rwO~ zMcCwkYpoP4fTyPtuNKE${qHR;I502TH;Qs?mK|v#a)Iz>7d{w(9FZPwB65MaJSFTx z5UI;hH6t-4 z%Ohr5s2u|=n2`u8q^2(JCcskmCNzCea1c|%D@1yVjB(>cJ+W2pN#G%jq8|vr-MMt< zI;r`+UXr4^&0#V%k2ul;2+V>~H(97Ms+cf>FE}C*nu0@NG88Ez3==4+Gu0CO~H?ZEEFQwwGH z6uKn8ST4TVJ*tK;|H4@euIxzDuA=l)*!rr{pq2LAm4jty(@IJoiLG~+hOBZBR{;-w zKK)7~70#ooD_;TZo!Awv^hblS_}%;TphKzd@|ILnIq<}-KfA(P+1bc~s#15?vurAa zYPM_h1XL-Xua;yr!HQvOqVVttcK&IB%Mn#`v-e_Ky#v&2ZPfMG+FQoZh}{2aERFJc zb6o3&PEWa~tT=%N5tk2k)Y!ywF4FlK+yE90uB-7lTxJOhvtL`17W|wnEl8vxN|(2K zt2*o@@yX4>k;F$Vo~CF74Wn!G4M|8xvJNp)dG zk~Xv?Z7`kBuJ{IM{95en7HcN4#$KmS(nPmzUe?{@Z3wV>2|@-_W;R2m)L7ajHd#rk zYgBAM0={>o(zI2bNlTL(i7bn&+=7uqB+4c~Pu=G7c?Ad?zbQ~$Lcy7^b$9kv${}J* z6#X9_-=c{WP-3Z@*g!fkHqg7;??X6WgRT$FDC~NPwVhU6Q(G!WVK&H2xU*y>4a$ay zjP8Dhs=Bkn6iz$3wLbx$*K)S8d=yX+9Y7n7y4|nU8`6(~#bIEZYGwZ+z|J!sOK}4AcUx6m zk1BS>y@jc0p+e;esO(fCL&jXwutnx*qKY0~N~HhKIXU`)(X!sHW32L&NvUtue(uPp zYAkl0nm629EGx+wbdggtkV%SU99@~zMC)HOI_W>=tvla2uwitvT#ALOY`E5@ZTiyn z?iC|ne?75IMH7>Ip>(el=r3usLl4** zI+8FVF}08+aoy7q1WDkD#(Uz;qH)B7=LVcDlC>v7LEvsBDsAUN0k-juD< zySXvOwK&~O<4gQpT`N>6h_^M=+4yxd>f%Dz=?1T5T&5Z{qmAI+^rTSPW6p9#)pcw+ zZ1BG6o_+%>-@G_xp&`xQ;I3`81+{{oof)@($R$7E9>WpVf$o_BgcamWBdl2&w?9La zd`74Ex~GRXImT;~OGC3_}s zN>86ry3APSskIyW43ed$GEZF@P)rp$7P2gTL2-it_%Trfo*mn?oHQnG)!I+SuDz7QJbHPMYtd$fyOsVq> zJokCgv%l>VsQF{yc4#%8UCanWwQ`@bcoGUJ6|Z{zNRdrb6!Q4?7P3s{A<#_t&cgKt zEHN!V1#iFeM43xhdNS~BZ4?zSOe3sE>m5GA$Bc|1a9VSO8p{^51gZ)xtk{8lNDntT zJ4Tr*PhX7-BfnoRaA(>hpb5JAr$?4d1L4$+e0q=$`z`eY#Y|T&{eSvEKILZ$SYz8{ z8}R;f`k#SV+vc)?mRb|AcA9!D?{6RO$;M;-F2fT8SYIVVRb56{H~&>sZZ}A6JA`yp zM~b#pBb_&)?g{`3`lP&tP2j(yHxbS-FlL8v+yuir{R%|mO2k6XC}_z-)e-2W1ljMw zjdP923P7s0^$$zs!V-!ZrfsXhwZZj_Myh~UQNZws(wwu%I8m{hooCG98a7hm#Kpz- zQ=EQT&|H5tdR;+ z9@o5a*yn0sOrmwSI>SnOI^U*9T~!FHHl|1@WxEKL9-MJ)RaI@5-?KpHVHyRaoG?a+ zXd8O-x}*$+^%McW@^gEQ*xgnYucg29K`5Lpm#`gVbx)>hO+}@ohR$@0Q+04t*}#NE z-}pSdK+b1zD>bMh3Se%J@AG&Nc8_kpO*bz@*o7XXv(3_oaDoj)8-)(jwp9{gDx-f? zWKuK{3QE|mN)?SQaT#G+L>Z%j#`i84Zh&^aR)#y-@h^!kyCq)06t^;{Y-qteLuf6A zC;}OD1aCR-A8dIh9c=TyLp5^b6*wO`bUYmS6PV5+*RYZ0S?Ni|ZL56%9d#{g^LyW9 ze`BkOYTu9FZv~JyCbw=IV4Ne>I4tqnaf7z~^jiio7`_Ue?_hu(M=5d^WCXOaxZpFQ z$6gZWv0a=&y|&k^b|Od@@U3gDV2l8&iX0SEoslPFsGMM1s>aWi9P;g7UxNbl{mj%IfX0D%E z->Jm4XC6CyY$5i@sUv_E-yc6{KDN?T_zFHy%N88Q>9&2f&7T&Ohku+GlnEyN~>CoE-@IM12=XmbNC%?3K@<8GqS1C+Gjpc}i+}bRCeYCXCSrP^>5yCC@ z@bV^ef;Ost#fp{)a4Ul`%ux|6sh4&$z^8wY$A`L-q2@&jXi14>=V8Tr<1&yHT4^o9 zeq%DFz#1u(Mm58V&Du1K66IdiUhOQitKnR5?d3mf6lPQ7U+?E7vYE9~_G&TfSw<9g zOJn3z{vS-{gq%BMnu6muVeul?Fae6cip2|9BjEg!KA3)Yz!J?Rn0_u{sgLm7v;VwC zxicWvSCjkhysZHMrDA;IVP6ca^yGm(aZv17Hfcw7aCr2x{cc^FRzZiMM^t)2LNEqP z;g)!5GLuUx@Drc#q1RU@1}E>-rs>zqfD_iK4Zj6bFcjVv3*EnY|JOI^c_Oq0jl0Fj z6=5uBP}guJHf2w0-0|4d-6`wqS%ciuBNA!93Fkt2+TY)A^OTNArG2KX<*R9b_X}Q` zw^m)b6yaz{_KpntZEH1^%MkX46z_1qzgX8;sH%a8RB;A(rC!~mOB2>E(BtzmY4KEu zOrxUXUh!)~Bu;fP&`Vf4Q$8Vo&8fFaIgZk|G}4TGo?0i2!s0?v_-`}P`nfsT-(}>8 z*~6f2A?HB%f#$vgf&+%@O9!q`Wxe*rFHS~S!PYZQy9&nMD{?1IW*5MfubVyPl1EIJ((wMJ+=sAKX99^gZhfpyYa$fJv?S?LG< z2&nxqVZlerR02yx&VvM7<2vG#@)D3kahaR2$=Qj>%Gli3W$j@0T=vUUGEU#&09@Qgx6G;D&r}L(q-)($zYK9 z1kbiexg$_n6Z%aua!F!AY`1DX(Dpz?E4byQZ5mM&W`u8WDfZ|hD22MH)vDD9sw(H`~ywg13fqKk`El_(m!GUG1K_xjJZ7@QdCM?KQ?&g z>qYoa^F8nNzvlvLdKj{iz>;rg5DR$mbKH*A8l%+$ooSG1^SzjTJ1MO)3m$iAY?Cz@ zaAw^89$xZ2?lBBu?N68ts-9f*=gd{R#iEGV!P{V4#R%axQ_J?$yTiJv`TllCdsSf_ zN1D$T@Nt`;P%|`9wQ3svlXI-4QPY}!b;?;Wm42zrKSUsP0p~ z2+Fa(Dl#anB30696?|+ON+FNepp6koPsWqLH`Zz)SbEY`gB9km+UEL}HJKw_ZT2)B z+=QwxKWNx(PEz~E@%^Y;*Xl`yK~l$Hy2WWuxY0Q{CQ*^4$yfhhR?pgJNIZycM+FVx zSD#D}Yn1FfT@K5%myw7;{CV}g!1f2z8Nb<4?*C}^ExO060O{sp9Zuo$SqG}Mo4RzQ zwXUk)^0j|gy`36rV9`}X*Sp^lLou1qb)CRJzcjIgnDuKCmT)NiMjs^lD<36|x@D60 zB@Q@S{JZ1_M9B}Ck4CSXiEg%yv9yL^nR3XCHkNW$`)zh@8#6qrZUAA0_haI14fVEn zAD-Ul($cc>9tH}hNJxIT?O{l^sEFlc)?Sl~J^oogN0P)}OXc8FiB??8{$RF?B@=|E z?rn9bFrrW*+r8C{u)b~RHz2IQ{6L{SnjY?89_qq{lt*)Y_{CPCsOnYN^Sl>kMpa|^)GCS-X$l>qvCo0s(68hP8? zqSfE(z|8cjd*-y7!viLTz27#17M_Qu7^LB~)W))LrT8Xx0y4dU54FqQ<*W&DR=7 ze(KjS&0ScPNC8ZzAtEg)*+xy)LPYnu<%Xd*tz@AfSPRTl$ma;&IbEGJ;Uk?r}+nrG>^1D?nfqwR^=j=I`+4Y|IF9o>q zY>{^Z{p?xe{(2+sJhn($pr1V}#9uFvlD%`^cT;LbSzr?d`q{GyWzYG^U!Nda=nwO= zNP&LVtjCw%}gI#aNf5}s~oGo%V(9fRrmcQO6=GT1;`Q0B{Bvj&vkGf_St?G}!SA-IA6;+@@t81#*xEUbnFY!)_u+Fat~(~-zF zxbNqJBou7T#`zBD3u`~QR~5dqJml^0&5Os!bDh|`%GIIfPC)WLA9_!wM?U=8`o;1f z&cFS29VYK4Z?mr#@8k}x{oYm94i=!F%5_VbzdNx~$OC%z9{q&eg+w81V-Mg2`W(A{ zrLgx_|C0-Zo{_Qi{@#;XvozbvWKTrTKEHC`qR&5VSScRRte=S5`M0&;iSs4uGap)K zV)z<2-_rSN^d+rB0svb=(Ps(2w1xA3cu>$60NxcXzoNG@H_9n!LxK}M`m%#Nl`U(Tl_HpMMz5JP+=lO%H zm(_VSN2^fgM_~>8|Emt-#K}^jfEu7XF1Gy&Iz(9#QTybyht{vcLYlm8%_|htwwS3& z)LS5s_UL?9zxWjbvU^c{p&-6NZQyLzgaew|E*qTax_r_t{Qx9ljZ8G?ENuyLZnn-& z;dPpAea_ak^Ish-B}gmzl&~?*=aZWKmxHggX8vR74k;Xsua!9gbtO^Dw9Dr2F7^TP z(Eo>;!|H|r7b|U_)I9l1Jv9T(8YedGnDWffD^ReQ3i9*xR4OVD=8p!0iHKnu48of& z34ieF7CWV&W3bIYs6(_|M~k3RNV8&*Ge?>)<@XyDvNQykGvt41_A2T%%9v!ug5XEI zE}t*!C-e(Q;%A9lbU8tiFGY6`NKdl<87>F}`*Cr6ZJLJ+pf);@gb(jc5wUQjk{;Kj zYjZCiHiiV_HmrMuV(+}^vT^y%RO-mthoVJNV&Tk@ogBJ0U&O=4kYLQb$~(3R7bIZ4uFxbj_U9U{Z-j zL3-8EOx|f#dxLe2gH#_UE-hWCLnpOZL7A!J(T&xpyXhUO-pMXrO^EH-V{^B)nVU}+ z9LBR|3k2pjUarF^f3?ye4G023e?z#_K)48zAfRC25Rg!!M2itCPP_z(k|ax!D$N3D z7+C2tWWvdUmn}!GJoyNSNXRItXy_OU6k=jwucXytX`c4O&T?O zmshKnc*59z7W@|aN~dmJdh}Z4i1+mCGhom+zH`h2hkTeY@X#}lO!>&P85ex)u#3j+ yb;@Zc5)PL6J`usikEzvxqKCse-l`*!gru@Z+`WiiMkx{n|L1eqP)4DU=>Py*2w6b@ literal 0 HcmV?d00001 diff --git a/packages/ui-tailwind/src/theme/fonts/Poppins-Regular-LatinExt.woff2 b/packages/ui-tailwind/src/theme/fonts/Poppins-Regular-LatinExt.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..23b9ce118ee823d39679b65dbabce4c684a48a52 GIT binary patch literal 5532 zcmV;N6=UjmPew8T0RR9102Q173;+NC05aeJ02N690RR9100000000000000000000 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 GA=3d?@fY|2 literal 0 HcmV?d00001 diff --git a/packages/ui-tailwind/src/theme/fonts/WorkSans-Medium-LatinExt.woff2 b/packages/ui-tailwind/src/theme/fonts/WorkSans-Medium-LatinExt.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e11f4626b604debbb79f4f7948d69ff36f4005e8 GIT binary patch literal 15552 zcmV;xJU_#CPew8T0RR9106f3|4*&oF0GreR06bj)0RR9100000000000000000000 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 Oa=HB;{{>+J0002n31Oy-jgmMRp85@W+ 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 WH3tiX^&d||f0FkF{Ac(hISK$OpgcMN literal 0 HcmV?d00001 diff --git a/packages/ui-tailwind/src/theme/globals.css b/packages/ui-tailwind/src/theme/globals.css new file mode 100644 index 0000000000..6c497d5fbf --- /dev/null +++ b/packages/ui-tailwind/src/theme/globals.css @@ -0,0 +1,18 @@ +::-webkit-scrollbar { + width: 4px; +} + +/* Track */ +::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.25); +} + +/* Handle */ +::-webkit-scrollbar-thumb { + background: #888; +} + +/* Handle on hover */ +::-webkit-scrollbar-thumb:hover { + background: #555; +} diff --git a/packages/ui-tailwind/src/theme/index.tsx b/packages/ui-tailwind/src/theme/index.tsx new file mode 100644 index 0000000000..7d7c15227c --- /dev/null +++ b/packages/ui-tailwind/src/theme/index.tsx @@ -0,0 +1,6 @@ +/** This import of fonts.css creates the 'dist/styles.css' */ +import './fonts.css'; +import './globals.css'; + +export { tailwindConfig, withPenumbra } from './tailwind-config'; +export { theme } from './theme'; diff --git a/packages/ui-tailwind/src/theme/tailwind-config.ts b/packages/ui-tailwind/src/theme/tailwind-config.ts new file mode 100644 index 0000000000..6028b96233 --- /dev/null +++ b/packages/ui-tailwind/src/theme/tailwind-config.ts @@ -0,0 +1,67 @@ +import type { Config } from 'tailwindcss'; +import { theme } from './theme'; + +/** + * For consumers using Tailwind, this file exports a Tailwind config based on + * the Penumbra UI theme values. + */ +export const tailwindConfig = { + content: [ + './node_modules/@penumbra-zone/ui-tailwind/**/*.{js,ts,jsx,tsx,mdx,css}', + './node_modules/@penumbra-zone/ui/**/*.{js,ts,jsx,tsx,mdx,css}', + ], + theme: { + extend: { + borderRadius: theme.borderRadius, + colors: theme.color, + fontFamily: theme.font, + fontSize: theme.fontSize, + lineHeight: theme.lineHeight, + backdropBlur: theme.blur, + backgroundImage: theme.gradient, + keyframes: theme.keyframes, + animation: theme.animation, + screens: Object.keys(theme.breakpoint).reduce( + (prev, curr) => ({ + ...prev, + [curr]: theme.breakpoint[curr as keyof (typeof theme)['breakpoint']].toString() + 'px', + }), + {}, + ), + + // No need to customize spacing, since Tailwind's default is the same as + // Penumbra UI's. + }, + }, +} as const; + +const composeContent = (content: Config['content']): Config['content'] => { + if (typeof content === 'string') { + return [...tailwindConfig.content, content]; + } + + if (Array.isArray(content)) { + return [...tailwindConfig.content, ...content]; + } + + content.files.push(...tailwindConfig.content); + return content; +}; + +/** + * Wrap your Tailwind config with `withPenumbra` function to support Penumbra classes + * and styles of the `@penumbra-zone/ui` library. + */ +export const withPenumbra = (config: Config): Config => { + return { + ...config, + content: composeContent(config.content), + theme: { + ...config.theme, + extend: { + ...tailwindConfig.theme.extend, + ...config.theme?.extend, + }, + }, + }; +}; diff --git a/packages/ui-tailwind/src/theme/theme.ts b/packages/ui-tailwind/src/theme/theme.ts new file mode 100644 index 0000000000..5370c6acff --- /dev/null +++ b/packages/ui-tailwind/src/theme/theme.ts @@ -0,0 +1,295 @@ +import { hexOpacity } from '../utils/hexOpacity'; + +/** + * Used for reference in the `theme` object below. Not intended to be used + * directly by consumers, but rather as a semantic reference for building the + * theme. + */ +const PALETTE = { + green: { + 50: '#f0fdf4', + 100: '#DEFAE8', + 200: '#BFF3D1', + 300: '#8DE8AE', + 400: '#55D383', + 500: '#2DBA61', + 600: '#1F9A4C', + 700: '#1C793F', + 800: '#1C5F36', + 900: '#194E2E', + 950: '#03160B', + }, + neutral: { + 50: '#fafafa', + 100: '#f5f5f5', + 200: '#e5e5e5', + 300: '#d4d4d4', + 400: '#a3a3a3', + 500: '#737373', + 600: '#525252', + 700: '#404040', + 800: '#262626', + 900: '#171717', + 950: '#0a0a0a', + }, + orange: { + 50: '#FFF8ED', + 100: '#FDEED6', + 200: '#FBDBAD', + 300: '#F8C079', + 400: '#F49C43', + 500: '#F07E1C', + 600: '#E16615', + 700: '#BA4D14', + 800: '#933E19', + 900: '#773517', + 950: '#200B04', + }, + purple: { + 50: '#FAF7FC', + 100: '#F5F0F7', + 200: '#E9E0EE', + 300: '#D8C7E0', + 400: '#C1A6CC', + 500: '#A582B3', + 600: '#886693', + 700: '#705279', + 800: '#5F4766', + 900: '#4F3C53', + 950: '#180E1B', + }, + red: { + 50: '#fef2f2', + 100: '#FCE4E4', + 200: '#FBCDCD', + 300: '#F8A9A9', + 400: '#F17878', + 500: '#E54E4E', + 600: '#CF3333', + 700: '#AF2626', + 800: '#902424', + 900: '#772525', + 950: '#1E0606', + }, + teal: { + 50: '#f1fcfa', + 100: '#D4F3EE', + 200: '#92DFD5', + 300: '#77D1C8', + 400: '#53AEA8', + 500: '#319B96', + 600: '#257C79', + 700: '#226362', + 800: '#204F4F', + 900: '#1F4242', + 950: '#031516', + }, + yellow: { + 50: '#FDFCE9', + 100: '#FBF7C6', + 200: '#F8EB90', + 300: '#F4DA50', + 400: '#E8C127', + 500: '#DDAD15', + 600: '#C0860E', + 700: '#99610F', + 800: '#7E4D15', + 900: '#6B3F18', + 950: '#201004', + }, + base: { + black: '#000000', + white: '#ffffff', + transparent: 'transparent', + }, +}; + +/** + * Call `theme.spacing(x)`, where `x` is the number of spacing units (in the + * Penumbra theme, 1 spacing unit = 4px) that you want to interpolate into your + * CSS or JavaScript. By default, returns a string with the number of pixels + * suffixed with `px` -- e.g., `theme.spacing(4)` returns `'16px'`. Pass + * `number` as the second argument to get back a number of pixels -- e.g., + * `theme.spacing(4, 'number')` returns `16`. + */ +function spacing(spacingUnits: number, returnType?: 'string'): string; +function spacing(spacingUnits: number, returnType: 'number'): number; +function spacing(spacingUnits: number, returnType?: 'string' | 'number'): string | number { + if (returnType === 'number') { + return spacingUnits * 4; + } + return `${spacingUnits * 4}px`; +} + +export const theme = { + blur: { + none: '0px', + xs: '4px', + sm: '8px', + md: '16px', + lg: '32px', + xl: '64px', + }, + borderRadius: { + none: '0px', + xs: '4px', + sm: '8px', + md: '12px', + lg: '16px', + xl: '20px', + '2xl': '24px', + full: '9999px', + }, + breakpoint: { + mobile: 0, + tablet: 600, + desktop: 900, + lg: 1200, + xl: 1600, + }, + color: { + neutral: { + main: PALETTE.neutral['700'], + light: PALETTE.neutral['400'], + dark: PALETTE.neutral['900'], + contrast: PALETTE.neutral['50'], + }, + primary: { + main: PALETTE.orange['700'], + light: PALETTE.orange['400'], + dark: PALETTE.orange['950'], + contrast: PALETTE.orange['50'], + }, + secondary: { + main: PALETTE.teal['700'], + light: PALETTE.teal['400'], + dark: PALETTE.teal['950'], + contrast: PALETTE.teal['50'], + }, + unshield: { + main: PALETTE.purple['700'], + light: PALETTE.purple['400'], + dark: PALETTE.purple['950'], + contrast: PALETTE.purple['50'], + }, + destructive: { + main: PALETTE.red['700'], + light: PALETTE.red['400'], + dark: PALETTE.red['950'], + contrast: PALETTE.red['50'], + }, + caution: { + main: PALETTE.yellow['700'], + light: PALETTE.yellow['400'], + dark: PALETTE.yellow['950'], + contrast: PALETTE.yellow['50'], + }, + success: { + main: PALETTE.green['700'], + light: PALETTE.green['400'], + dark: PALETTE.green['950'], + contrast: PALETTE.green['50'], + }, + base: { + black: PALETTE.base.black, + white: PALETTE.base.white, + transparent: PALETTE.base.transparent, + }, + text: { + primary: PALETTE.neutral['50'], + secondary: PALETTE.neutral['400'], + muted: PALETTE.neutral['700'], + special: PALETTE.orange['400'], + }, + action: { + hoverOverlay: PALETTE.teal['400'] + hexOpacity(0.15), + activeOverlay: PALETTE.neutral['950'] + hexOpacity(0.15), + disabledOverlay: PALETTE.neutral['950'] + hexOpacity(0.8), + primaryFocusOutline: PALETTE.orange['400'], + secondaryFocusOutline: PALETTE.teal['400'], + unshieldFocusOutline: PALETTE.purple['400'], + neutralFocusOutline: PALETTE.neutral['400'], + destructiveFocusOutline: PALETTE.red['400'], + }, + other: { + tonalStroke: PALETTE.neutral['50'] + hexOpacity(0.15), + tonalFill5: PALETTE.neutral['50'] + hexOpacity(0.05), + tonalFill10: PALETTE.neutral['50'] + hexOpacity(0.1), + solidStroke: PALETTE.neutral['700'], + dialogBackground: PALETTE.teal['700'] + hexOpacity(0.1), + overlay: PALETTE.base.black + hexOpacity(0.5), + }, + }, + gradient: { + card: 'linear-gradient(136deg, rgba(250, 250, 250, 0.1) 6.32%, rgba(250, 250, 250, 0.01) 75.55%)', + tabNeutral: 'radial-gradient(at 50% 100%, rgba(163, 163, 163, 0.35) 0%, transparent 50%)', + tabAccent: 'radial-gradient(at 50% 100%, rgba(244, 156, 67, 0.35) 0%, transparent 50%)', + tabUnshield: 'radial-gradient(at 50% 100%, rgba(193, 166, 204, 0.35) 0%, transparent 50%)', + dialogSuccess: `radial-gradient(100% 100% at 0% 0%, rgba(83, 174, 168, 0.20) 0%, rgba(83, 174, 168, 0.02) 100%)`, + dialogCaution: `radial-gradient(100% 100% at 0% 0%, rgba(153, 97, 15, 0.20) 0%, rgba(153, 97, 15, 0.02) 100%)`, + dialogError: `radial-gradient(100% 100% at 0% 0%, rgba(175, 38, 38, 0.20) 0%, rgba(175, 38, 38, 0.02) 100%)`, + buttonHover: + 'linear-gradient(0deg, rgba(83, 174, 168, 0.15) 0%, rgba(83, 174, 168, 0.15) 100%)', + buttonDisabled: 'linear-gradient(0deg, rgba(10, 10, 10, 0.8) 0%, rgba(10, 10, 10, 0.8) 100%)', + progressLoading: + 'linear-gradient(90deg,rgba(255, 255, 255, 0) 0%,#fff 50%,rgba(255, 255, 255, 0) 100%)', + }, + font: { + default: 'Poppins', + mono: 'Iosevka Term, monospace', + heading: 'Work Sans', + }, + fontSize: { + text9xl: '8rem', + text8xl: '6rem', + text7xl: '4.5rem', + text6xl: '3.75rem', + text5xl: '3rem', + text4xl: '2.25rem', + text3xl: '1.875rem', + text2xl: '1.5rem', + textXl: '1.25rem', + textLg: '1.125rem', + textBase: '1rem', + textSm: '0.875rem', + textXs: '0.75rem', + }, + lineHeight: { + text9xl: '8.25rem', + text8xl: '6.25rem', + text7xl: '5rem', + text6xl: '4.25rem', + text5xl: '3.5rem', + text4xl: '2.75rem', + text3xl: '2.5rem', + text2xl: '2.25rem', + textXl: '2rem', + textLg: '1.75rem', + textBase: '1.5rem', + textSm: '1.25rem', + textXs: '1rem', + }, + spacing, + zIndex: { + disabledOverlay: 10, + }, + keyframes: { + scale: { + '0%': { opacity: '0', transform: 'scale(0)' }, + '100%': { opacity: '1', transform: 'scale(1)' }, + }, + progress: { + '0%': { left: '-20%' }, + '100%': { left: '100%' }, + }, + }, + animation: { + scale: 'scale 0.15s ease-out', + progress: 'progress 1s linear infinite', + }, +} as const; + +type Theme = typeof theme; +export type Color = keyof Theme['color']; +export type ColorVariant = keyof Theme['color']['neutral']; +export type TextColorVariant = keyof Theme['color']['text']; diff --git a/packages/ui-tailwind/src/utils/action-type.ts b/packages/ui-tailwind/src/utils/action-type.ts new file mode 100644 index 0000000000..34540f71ea --- /dev/null +++ b/packages/ui-tailwind/src/utils/action-type.ts @@ -0,0 +1,98 @@ +import cn from 'clsx'; + +export type ActionType = 'default' | 'accent' | 'unshield' | 'destructive'; + +export const getColorByActionType = (actionType: ActionType): string => { + if (actionType === 'destructive') { + return cn('text-destructive-light'); + } + return cn('text-text-primary'); +}; + +const AFTER_OUTLINE_COLOR_MAP: Record = { + default: cn('focus-within:after:outline-action-neutralFocusOutline'), + accent: cn('focus-within:after:outline-action-primaryFocusOutline'), + unshield: cn('focus-within:after:outline-action-unshieldFocusOutline'), + destructive: cn('focus-within:after:outline-action-destructiveFocusOutline'), +}; + +const OUTLINE_COLOR_MAP: Record = { + default: cn('outline-neutral-main'), + accent: cn('outline-primary-main'), + unshield: cn('outline-unshield-main'), + destructive: cn('outline-destructive-main'), +}; + +const BEFORE_OUTLINE_COLOR_MAP: Record = { + default: cn('focus:before:outline-action-neutralFocusOutline'), + accent: cn('focus:before:outline-action-primaryFocusOutline'), + unshield: cn('focus:before:outline-action-unshieldFocusOutline'), + destructive: cn('focus:before:outline-action-destructiveFocusOutline'), +}; + +const FOCUS_OUTLINE_COLOR_MAP: Record = { + default: cn('focus:outline-action-neutralFocusOutline'), + accent: cn('focus:outline-action-primaryFocusOutline'), + unshield: cn('focus:outline-action-unshieldFocusOutline'), + destructive: cn('focus:outline-action-destructiveFocusOutline'), +}; + +const FOCUS_WITHIN_OUTLINE_COLOR_MAP: Record = { + default: cn('focus-within:outline-action-neutralFocusOutline'), + accent: cn('focus-within:outline-action-primaryFocusOutline'), + unshield: cn('focus-within:outline-action-unshieldFocusOutline'), + destructive: cn('focus-within:outline-action-destructiveFocusOutline'), +}; + +const ARIA_CHECKED_OUTLINE_COLOR_MAP: Record = { + default: cn('aria-checked:outline-action-neutralFocusOutline'), + accent: cn('aria-checked:outline-action-primaryFocusOutline'), + unshield: cn('aria-checked:outline-action-unshieldFocusOutline'), + destructive: cn('aria-checked:outline-action-destructiveFocusOutline'), +}; + +const BORDER_COLOR_MAP: Record = { + default: cn('border-neutral-main'), + accent: cn('border-primary-main'), + unshield: cn('border-unshield-main'), + destructive: cn('border-destructive-main'), +}; + +const BACKGROUND_COLOR_MAP: Record = { + default: cn('bg-neutral-main'), + accent: cn('bg-primary-main'), + unshield: cn('bg-unshield-main'), + destructive: cn('bg-destructive-main'), +}; + +export const getAfterOutlineColorByActionType = (actionType: ActionType): string => { + return AFTER_OUTLINE_COLOR_MAP[actionType]; +}; + +export const getBeforeOutlineColorByActionType = (actionType: ActionType): string => { + return BEFORE_OUTLINE_COLOR_MAP[actionType]; +}; + +export const getOutlineColorByActionType = (actionType: ActionType): string => { + return OUTLINE_COLOR_MAP[actionType]; +}; + +export const getFocusOutlineColorByActionType = (actionType: ActionType): string => { + return FOCUS_OUTLINE_COLOR_MAP[actionType]; +}; + +export const getFocusWithinOutlineColorByActionType = (actionType: ActionType): string => { + return FOCUS_WITHIN_OUTLINE_COLOR_MAP[actionType]; +}; + +export const getAriaCheckedOutlineColorByActionType = (actionType: ActionType): string => { + return ARIA_CHECKED_OUTLINE_COLOR_MAP[actionType]; +}; + +export const getBorderColorByActionType = (actionType: ActionType): string => { + return BORDER_COLOR_MAP[actionType]; +}; + +export const getBackgroundColorByActionType = (actionType: ActionType): string => { + return BACKGROUND_COLOR_MAP[actionType]; +}; diff --git a/packages/ui-tailwind/src/utils/bufs/address-view.ts b/packages/ui-tailwind/src/utils/bufs/address-view.ts new file mode 100644 index 0000000000..500ce31f81 --- /dev/null +++ b/packages/ui-tailwind/src/utils/bufs/address-view.ts @@ -0,0 +1,54 @@ +import { AddressView } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; +import { addressFromBech32m } from '@penumbra-zone/bech32m/penumbra'; + +export const ADDRESS_VIEW_DECODED = new AddressView({ + addressView: { + case: 'decoded', + value: { + address: { inner: new Uint8Array(80) }, + index: { + account: 0, + randomizer: new Uint8Array([0, 0, 0]), + }, + }, + }, +}); + +export const ADDRESS2_VIEW_DECODED = new AddressView({ + addressView: { + case: 'decoded', + value: { + address: { inner: new Uint8Array(80) }, + index: { + account: 2, + randomizer: new Uint8Array([0, 0, 0]), + }, + }, + }, +}); + +export const ADDRESS_VIEW_DECODED_ONE_TIME = new AddressView({ + addressView: { + case: 'decoded', + value: { + address: { inner: new Uint8Array(80) }, + index: { + account: 0, + // A one-time address is defined by a randomizer with at least one + // non-zero byte. + randomizer: new Uint8Array([1, 2, 3]), + }, + }, + }, +}); + +export const ADDRESS_VIEW_OPAQUE = new AddressView({ + addressView: { + case: 'opaque', + value: { + address: addressFromBech32m( + 'penumbra1e8k5cyds484dxvapeamwveh5khqv4jsvyvaf5wwxaaccgfghm229qw03pcar3ryy8smptevstycch0qk3uu0rgkvtjpxy3cu3rjd0agawqtlz6erev28a6sg69u7cxy0t02nd4', + ), + }, + }, +}); diff --git a/packages/ui-tailwind/src/utils/bufs/balances-responses.ts b/packages/ui-tailwind/src/utils/bufs/balances-responses.ts new file mode 100644 index 0000000000..d28ed43357 --- /dev/null +++ b/packages/ui-tailwind/src/utils/bufs/balances-responses.ts @@ -0,0 +1,18 @@ +import { BalancesResponse } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { OSMO_VALUE_VIEW, PENUMBRA_VALUE_VIEW } from './value-view.ts'; +import { ADDRESS2_VIEW_DECODED, ADDRESS_VIEW_DECODED } from './address-view.ts'; + +export const PENUMBRA_BALANCE = new BalancesResponse({ + balanceView: PENUMBRA_VALUE_VIEW, + accountAddress: ADDRESS_VIEW_DECODED, +}); + +export const PENUMBRA2_BALANCE = new BalancesResponse({ + balanceView: PENUMBRA_VALUE_VIEW, + accountAddress: ADDRESS2_VIEW_DECODED, +}); + +export const OSMO_BALANCE = new BalancesResponse({ + balanceView: OSMO_VALUE_VIEW, + accountAddress: ADDRESS_VIEW_DECODED, +}); diff --git a/packages/ui-tailwind/src/utils/bufs/index.ts b/packages/ui-tailwind/src/utils/bufs/index.ts new file mode 100644 index 0000000000..19f47a03ee --- /dev/null +++ b/packages/ui-tailwind/src/utils/bufs/index.ts @@ -0,0 +1,9 @@ +/** + * The `bufs` directory is meant to be used within Storybook or Vitest + * environments only, and should not be used in the resulting library code. + */ + +export * from './metadata'; +export * from './value-view'; +export * from './address-view'; +export * from './balances-responses'; diff --git a/packages/ui-tailwind/src/utils/bufs/metadata.ts b/packages/ui-tailwind/src/utils/bufs/metadata.ts new file mode 100644 index 0000000000..4a1d954bea --- /dev/null +++ b/packages/ui-tailwind/src/utils/bufs/metadata.ts @@ -0,0 +1,78 @@ +import { bech32mIdentityKey } from '@penumbra-zone/bech32m/penumbravalid'; +import { AssetId, Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; + +const u8 = (length: number) => Uint8Array.from({ length }, () => Math.floor(Math.random() * 256)); +const validatorIk = { ik: u8(32) }; +const validatorIkString = bech32mIdentityKey(validatorIk); +const delString = 'delegation_' + validatorIkString; +const udelString = 'udelegation_' + validatorIkString; +const delAsset = { inner: u8(32) }; +const unbondString = 'unbonding_start_at_123_' + validatorIkString; +const uunbondString = 'uunbonding_start_at_123_' + validatorIkString; +const unbondAsset = { inner: u8(32) }; + +export const DELEGATION_TOKEN_METADATA = new Metadata({ + display: delString, + base: udelString, + denomUnits: [{ denom: udelString }, { denom: delString, exponent: 6 }], + name: 'Delegation token', + penumbraAssetId: delAsset, + symbol: `delUM(${validatorIkString})`, +}); + +export const UNBONDING_TOKEN_METADATA = new Metadata({ + display: unbondString, + base: uunbondString, + denomUnits: [{ denom: uunbondString }, { denom: unbondString, exponent: 6 }], + name: 'Unbonding token', + penumbraAssetId: unbondAsset, + symbol: `unbondUMat123(${validatorIkString})`, +}); + +export const PENUMBRA_METADATA = new Metadata({ + denomUnits: [ + { + denom: 'penumbra', + exponent: 6, + }, + { + denom: 'mpenumbra', + exponent: 3, + }, + { + denom: 'upenumbra', + }, + ], + base: 'upenumbra', + name: 'Penumbra', + display: 'penumbra', + symbol: 'UM', + penumbraAssetId: new AssetId({ inner: u8(32) }), + images: [ + { + svg: 'https://raw.githubusercontent.com/prax-wallet/registry/main/images/um.svg', + }, + ], +}); + +export const OSMO_METADATA = new Metadata({ + symbol: 'OSMO', + name: 'Osmosis', + penumbraAssetId: new AssetId({ inner: u8(32) }), + base: 'uosmo', + display: 'osmo', + denomUnits: [{ denom: 'uosmo' }, { denom: 'osmo', exponent: 6 }], +}); + +export const PIZZA_METADATA = new Metadata({ + symbol: 'PIZZA', + name: 'Pizza', + penumbraAssetId: new AssetId({ inner: u8(32) }), + base: 'upizza', + display: 'pizza', + denomUnits: [{ denom: 'upizza' }, { denom: 'pizza', exponent: 6 }], +}); + +export const UNKNOWN_TOKEN_METADATA = new Metadata({ + penumbraAssetId: { inner: new Uint8Array([]) }, +}); diff --git a/packages/ui-tailwind/src/utils/bufs/value-view.ts b/packages/ui-tailwind/src/utils/bufs/value-view.ts new file mode 100644 index 0000000000..3ff1bfa55a --- /dev/null +++ b/packages/ui-tailwind/src/utils/bufs/value-view.ts @@ -0,0 +1,68 @@ +import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { + DELEGATION_TOKEN_METADATA, + OSMO_METADATA, + PENUMBRA_METADATA, + UNBONDING_TOKEN_METADATA, +} from './metadata.ts'; + +export const PENUMBRA_VALUE_VIEW = new ValueView({ + valueView: { + case: 'knownAssetId', + value: { + amount: { hi: 0n, lo: 123_456_789_000n }, + metadata: PENUMBRA_METADATA, + }, + }, +}); + +export const OSMO_VALUE_VIEW = new ValueView({ + valueView: { + case: 'knownAssetId', + value: { + amount: { hi: 0n, lo: 987_000_000n }, + metadata: OSMO_METADATA, + }, + }, +}); + +export const DELEGATION_VALUE_VIEW = new ValueView({ + valueView: { + case: 'knownAssetId', + value: { + amount: { hi: 0n, lo: 123_000_000n }, + metadata: DELEGATION_TOKEN_METADATA, + }, + }, +}); + +export const UNBONDING_VALUE_VIEW = new ValueView({ + valueView: { + case: 'knownAssetId', + value: { + amount: { hi: 0n, lo: 123_000_000n }, + metadata: UNBONDING_TOKEN_METADATA, + }, + }, +}); + +export const UNKNOWN_ASSET_VALUE_VIEW = new ValueView({ + valueView: { + case: 'knownAssetId', + value: { + amount: { hi: 0n, lo: 123_000_000n }, + metadata: { + penumbraAssetId: { inner: new Uint8Array([]) }, + }, + }, + }, +}); + +export const UNKNOWN_ASSET_ID_VALUE_VIEW = new ValueView({ + valueView: { + case: 'unknownAssetId', + value: { + amount: { hi: 0n, lo: 123_000_000n }, + }, + }, +}); diff --git a/packages/ui-tailwind/src/utils/button.ts b/packages/ui-tailwind/src/utils/button.ts new file mode 100644 index 0000000000..3ea2ff425d --- /dev/null +++ b/packages/ui-tailwind/src/utils/button.ts @@ -0,0 +1,76 @@ +import cn from 'clsx'; +import type { Density } from './density'; +import { + getAfterOutlineColorByActionType, + getBeforeOutlineColorByActionType, + ActionType, + getBackgroundColorByActionType, +} from './action-type'; + +export type Priority = 'primary' | 'secondary'; + +interface ButtonStyleAttributes { + density: Density; + iconOnly?: boolean | 'adornment'; + actionType: ActionType; +} + +/** Shared styles to use for any `