diff --git a/.changeset/fuzzy-games-care.md b/.changeset/fuzzy-games-care.md new file mode 100644 index 00000000..4e39a425 --- /dev/null +++ b/.changeset/fuzzy-games-care.md @@ -0,0 +1,6 @@ +--- +'@repo/ui': major +'chrome-extension': patch +--- + +Refactor UI package: sync it with the penumbra-zone/ui and remove unused components diff --git a/apps/extension/src/routes/page/onboarding/confirm-backup.tsx b/apps/extension/src/routes/page/onboarding/confirm-backup.tsx index 841fecca..ebb57112 100644 --- a/apps/extension/src/routes/page/onboarding/confirm-backup.tsx +++ b/apps/extension/src/routes/page/onboarding/confirm-backup.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Button } from '@repo/ui/components/ui/button'; -import { BackIcon } from '@repo/ui/components/ui/back-icon'; +import { BackIcon } from '@repo/ui/components/ui/icons/back-icon'; import { Card, CardContent, diff --git a/apps/extension/src/routes/page/onboarding/generate.tsx b/apps/extension/src/routes/page/onboarding/generate.tsx index d4cb21e5..c083f846 100644 --- a/apps/extension/src/routes/page/onboarding/generate.tsx +++ b/apps/extension/src/routes/page/onboarding/generate.tsx @@ -2,7 +2,7 @@ import { ExclamationTriangleIcon, LockClosedIcon } from '@radix-ui/react-icons'; import { SeedPhraseLength } from '@penumbra-zone/crypto-web/mnemonic'; import { useEffect, useState } from 'react'; import { Button } from '@repo/ui/components/ui/button'; -import { BackIcon } from '@repo/ui/components/ui/back-icon'; +import { BackIcon } from '@repo/ui/components/ui/icons/back-icon'; import { Card, CardContent, CardHeader, CardTitle } from '@repo/ui/components/ui/card'; import { CopyToClipboard } from '@repo/ui/components/ui/copy-to-clipboard'; import { FadeTransition } from '@repo/ui/components/ui/fade-transition'; diff --git a/apps/extension/src/routes/page/onboarding/import.tsx b/apps/extension/src/routes/page/onboarding/import.tsx index 0ecc9bd9..6e72de95 100644 --- a/apps/extension/src/routes/page/onboarding/import.tsx +++ b/apps/extension/src/routes/page/onboarding/import.tsx @@ -1,4 +1,4 @@ -import { BackIcon } from '@repo/ui/components/ui/back-icon'; +import { BackIcon } from '@repo/ui/components/ui/icons/back-icon'; import { Button } from '@repo/ui/components/ui/button'; import { Card, diff --git a/apps/extension/src/routes/page/onboarding/set-password.tsx b/apps/extension/src/routes/page/onboarding/set-password.tsx index 6bcd1e5c..98140308 100644 --- a/apps/extension/src/routes/page/onboarding/set-password.tsx +++ b/apps/extension/src/routes/page/onboarding/set-password.tsx @@ -1,5 +1,5 @@ import { FormEvent, MouseEvent, useState } from 'react'; -import { BackIcon } from '@repo/ui/components/ui/back-icon'; +import { BackIcon } from '@repo/ui/components/ui/icons/back-icon'; import { Button } from '@repo/ui/components/ui/button'; import { Card, diff --git a/apps/extension/src/routes/page/restore-password/set-password.tsx b/apps/extension/src/routes/page/restore-password/set-password.tsx index 2afcfb4f..f2e5a7b2 100644 --- a/apps/extension/src/routes/page/restore-password/set-password.tsx +++ b/apps/extension/src/routes/page/restore-password/set-password.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Button } from '@repo/ui/components/ui/button'; -import { BackIcon } from '@repo/ui/components/ui/back-icon'; +import { BackIcon } from '@repo/ui/components/ui/icons/back-icon'; import { Card, CardContent, diff --git a/apps/extension/src/routes/popup/approval/transaction/index.tsx b/apps/extension/src/routes/popup/approval/transaction/index.tsx index 7b7f9807..704c2513 100644 --- a/apps/extension/src/routes/popup/approval/transaction/index.tsx +++ b/apps/extension/src/routes/popup/approval/transaction/index.tsx @@ -1,4 +1,4 @@ -import { TransactionViewComponent } from '@repo/ui/components/ui/tx/view/transaction'; +import { TransactionViewComponent } from '@repo/ui/components/ui/tx'; import { useStore } from '../../../../state'; import { txApprovalSelector } from '../../../../state/tx-approval'; import { JsonViewer } from '@repo/ui/components/ui/json-viewer'; diff --git a/apps/extension/src/routes/popup/home/block-sync.tsx b/apps/extension/src/routes/popup/home/block-sync.tsx index 99ad8e15..d6cff5c5 100644 --- a/apps/extension/src/routes/popup/home/block-sync.tsx +++ b/apps/extension/src/routes/popup/home/block-sync.tsx @@ -1,4 +1,4 @@ -import { CondensedBlockSyncStatus } from '@repo/ui/components/ui/block-sync-status/condensed'; +import { CondensedBlockSyncStatus } from '@repo/ui/components/ui/block-sync-status'; import { useSyncProgress } from '../../../hooks/full-sync-height'; export const BlockSync = () => { diff --git a/apps/extension/src/routes/popup/home/index.tsx b/apps/extension/src/routes/popup/home/index.tsx index 50e941f1..10b03ba5 100644 --- a/apps/extension/src/routes/popup/home/index.tsx +++ b/apps/extension/src/routes/popup/home/index.tsx @@ -1,4 +1,4 @@ -import { SelectAccount } from '@repo/ui/components/ui/select-account'; +import { SelectAccount } from '@repo/ui/components/ui/select'; import { IndexHeader } from './index-header'; import { useStore } from '../../../state'; import { BlockSync } from './block-sync'; diff --git a/apps/extension/src/routes/popup/settings/settings-screen/settings-header.tsx b/apps/extension/src/routes/popup/settings/settings-screen/settings-header.tsx index a893bef2..6a67ec6a 100644 --- a/apps/extension/src/routes/popup/settings/settings-screen/settings-header.tsx +++ b/apps/extension/src/routes/popup/settings/settings-screen/settings-header.tsx @@ -1,4 +1,4 @@ -import { BackIcon } from '@repo/ui/components/ui/back-icon'; +import { BackIcon } from '@repo/ui/components/ui/icons/back-icon'; import { usePopupNav } from '../../../../utils/navigate'; export const SettingsHeader = ({ title }: { title: string }) => { diff --git a/apps/extension/src/shared/components/default-frontend-form/index.tsx b/apps/extension/src/shared/components/default-frontend-form/index.tsx index c338c516..8deb917b 100644 --- a/apps/extension/src/shared/components/default-frontend-form/index.tsx +++ b/apps/extension/src/shared/components/default-frontend-form/index.tsx @@ -1,4 +1,4 @@ -import { SelectList } from '@repo/ui/components/ui/select-list'; +import { SelectList } from '@repo/ui/components/ui/select'; import { ChainRegistryClient } from '@penumbra-labs/registry'; import { AllSlices } from '../../../state'; import { useStoreShallow } from '../../../utils/use-store-shallow'; diff --git a/apps/extension/src/shared/components/default-frontend-form/new-frontend-input.tsx b/apps/extension/src/shared/components/default-frontend-form/new-frontend-input.tsx index d390760e..fbc48d37 100644 --- a/apps/extension/src/shared/components/default-frontend-form/new-frontend-input.tsx +++ b/apps/extension/src/shared/components/default-frontend-form/new-frontend-input.tsx @@ -7,7 +7,7 @@ import { useRef, useState, } from 'react'; -import { SelectList } from '@repo/ui/components/ui/select-list'; +import { SelectList } from '@repo/ui/components/ui/select'; import { cn } from '@repo/ui/lib/utils'; import { isValidUrl } from '../../utils/is-valid-url'; diff --git a/apps/extension/src/shared/components/grpc-endpoint-form/index.tsx b/apps/extension/src/shared/components/grpc-endpoint-form/index.tsx index 88a8fb29..bea04e5c 100644 --- a/apps/extension/src/shared/components/grpc-endpoint-form/index.tsx +++ b/apps/extension/src/shared/components/grpc-endpoint-form/index.tsx @@ -1,5 +1,5 @@ import { FormEvent, useRef } from 'react'; -import { SelectList } from '@repo/ui/components/ui/select-list'; +import { SelectList } from '@repo/ui/components/ui/select'; import { Button } from '@repo/ui/components/ui/button'; import { Network, Loader2 } from 'lucide-react'; import { useGrpcEndpointForm } from './use-grpc-endpoint-form'; diff --git a/apps/extension/src/shared/components/numeraires-form.tsx b/apps/extension/src/shared/components/numeraires-form.tsx index 7342b5c3..2a33bbf9 100644 --- a/apps/extension/src/shared/components/numeraires-form.tsx +++ b/apps/extension/src/shared/components/numeraires-form.tsx @@ -3,7 +3,7 @@ import { AllSlices, useStore } from '../../state'; import { useChainIdQuery } from '../../hooks/chain-id'; import { useMemo, useState } from 'react'; import { ServicesMessage } from '../../message/services'; -import { SelectList } from '@repo/ui/components/ui/select-list'; +import { SelectList } from '@repo/ui/components/ui/select'; import { bech32mAssetId } from '@penumbra-zone/bech32m/passet'; import { getAssetId } from '@penumbra-zone/getters/metadata'; import { Button } from '@repo/ui/components/ui/button'; diff --git a/packages/ui/components/ui/account-switcher.test.tsx b/packages/ui/components/ui/account-switcher/account-switcher.test.tsx similarity index 98% rename from packages/ui/components/ui/account-switcher.test.tsx rename to packages/ui/components/ui/account-switcher/account-switcher.test.tsx index d7917b1b..b57c7323 100644 --- a/packages/ui/components/ui/account-switcher.test.tsx +++ b/packages/ui/components/ui/account-switcher/account-switcher.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { AccountSwitcher } from './account-switcher'; +import { AccountSwitcher } from '.'; import { fireEvent, render } from '@testing-library/react'; describe('', () => { diff --git a/packages/ui/components/ui/account-switcher.tsx b/packages/ui/components/ui/account-switcher/index.tsx similarity index 97% rename from packages/ui/components/ui/account-switcher.tsx rename to packages/ui/components/ui/account-switcher/index.tsx index a56e4613..13ec9b3d 100644 --- a/packages/ui/components/ui/account-switcher.tsx +++ b/packages/ui/components/ui/account-switcher/index.tsx @@ -1,8 +1,8 @@ import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-react'; import { useMemo, useState } from 'react'; -import { cn } from '../../lib/utils'; -import { Button } from './button'; -import { Input } from './input'; +import { cn } from '../../../lib/utils'; +import { Button } from '../button'; +import { Input } from '../input'; const MAX_INDEX = 2 ** 32; diff --git a/packages/ui/components/ui/address-view/address-view.stories.tsx b/packages/ui/components/ui/address-view/address-view.stories.tsx new file mode 100644 index 00000000..c6a21394 --- /dev/null +++ b/packages/ui/components/ui/address-view/address-view.stories.tsx @@ -0,0 +1,63 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { AddressViewComponent } from '.'; +import { + Address, + AddressIndex, + AddressView, + AddressView_Decoded, +} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; +import { addressFromBech32m } from '@penumbra-zone/bech32m/penumbra'; + +const meta: Meta = { + component: AddressViewComponent, + title: 'AddressViewComponent', + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +const EXAMPLE_VIEW = new AddressView({ + addressView: { + case: 'decoded', + + value: new AddressView_Decoded({ + address: new Address({ inner: new Uint8Array(80) }), + index: new AddressIndex({ + account: 0, + randomizer: new Uint8Array([0, 0, 0]), + }), + }), + }, +}); + +const EXAMPLE_VIEW_OPAQUE = new AddressView({ + addressView: { + case: 'opaque', + value: { + address: addressFromBech32m( + 'penumbra1e8k5cyds484dxvapeamwveh5khqv4jsvyvaf5wwxaaccgfghm229qw03pcar3ryy8smptevstycch0qk3uu0rgkvtjpxy3cu3rjd0agawqtlz6erev28a6sg69u7cxy0t02nd4', + ), + }, + }, +}); + +export const Decoded: Story = { + args: { + view: EXAMPLE_VIEW, + }, +}; + +export const Copiable: Story = { + args: { + view: EXAMPLE_VIEW, + copyable: true, + }, +}; + +export const Opaque: Story = { + args: { + view: EXAMPLE_VIEW_OPAQUE, + }, +}; diff --git a/packages/ui/components/ui/tx/view/address-view.test.tsx b/packages/ui/components/ui/address-view/address-view.test.tsx similarity index 97% rename from packages/ui/components/ui/tx/view/address-view.test.tsx rename to packages/ui/components/ui/address-view/address-view.test.tsx index 536d65ec..21ea5922 100644 --- a/packages/ui/components/ui/tx/view/address-view.test.tsx +++ b/packages/ui/components/ui/address-view/address-view.test.tsx @@ -4,7 +4,7 @@ import { AddressView, AddressView_Decoded, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; -import { AddressViewComponent } from './address-view'; +import { AddressViewComponent } from '.'; import { describe, expect, test } from 'vitest'; import { render } from '@testing-library/react'; diff --git a/packages/ui/components/ui/tx/view/address-view.tsx b/packages/ui/components/ui/address-view/index.tsx similarity index 88% rename from packages/ui/components/ui/tx/view/address-view.tsx rename to packages/ui/components/ui/address-view/index.tsx index 461fd0db..d6a36dd2 100644 --- a/packages/ui/components/ui/tx/view/address-view.tsx +++ b/packages/ui/components/ui/address-view/index.tsx @@ -1,7 +1,7 @@ -import { AddressIcon } from '../../address-icon'; +import { AddressIcon } from '../address/address-icon'; import { AddressView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; -import { CopyToClipboardIconButton } from '../../copy-to-clipboard-icon-button'; -import { AddressComponent } from '../../address-component'; +import { CopyToClipboardIconButton } from '../copy-to-clipboard/copy-to-clipboard-icon-button'; +import { AddressComponent } from '../address/address-component'; import { bech32mAddress } from '@penumbra-zone/bech32m/penumbra'; interface AddressViewProps { diff --git a/packages/ui/components/ui/address.tsx b/packages/ui/components/ui/address.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/ui/components/ui/address-component.test.tsx b/packages/ui/components/ui/address/address-component.test.tsx similarity index 100% rename from packages/ui/components/ui/address-component.test.tsx rename to packages/ui/components/ui/address/address-component.test.tsx diff --git a/packages/ui/components/ui/address-component.tsx b/packages/ui/components/ui/address/address-component.tsx similarity index 93% rename from packages/ui/components/ui/address-component.tsx rename to packages/ui/components/ui/address/address-component.tsx index 40d9ca22..4d0ec27e 100644 --- a/packages/ui/components/ui/address-component.tsx +++ b/packages/ui/components/ui/address/address-component.tsx @@ -1,7 +1,7 @@ import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { bech32mAddress } from '@penumbra-zone/bech32m/penumbra'; -interface AddressComponentProps { +export interface AddressComponentProps { address: Address; ephemeral?: boolean; } diff --git a/packages/ui/components/ui/address-icon.tsx b/packages/ui/components/ui/address/address-icon.tsx similarity index 85% rename from packages/ui/components/ui/address-icon.tsx rename to packages/ui/components/ui/address/address-icon.tsx index fc14ce11..5b6e1fe4 100644 --- a/packages/ui/components/ui/address-icon.tsx +++ b/packages/ui/components/ui/address/address-icon.tsx @@ -1,8 +1,8 @@ -import { Identicon } from './identicon'; +import { Identicon } from '../identicon'; import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb'; import { bech32mAddress } from '@penumbra-zone/bech32m/penumbra'; -interface AddressIconProps { +export interface AddressIconProps { address: Address; size: number; } diff --git a/packages/ui/components/ui/address/index.tsx b/packages/ui/components/ui/address/index.tsx new file mode 100644 index 00000000..fab022d0 --- /dev/null +++ b/packages/ui/components/ui/address/index.tsx @@ -0,0 +1,2 @@ +export { AddressIcon, type AddressIconProps } from './address-icon'; +export { AddressComponent, type AddressComponentProps } from './address-component'; diff --git a/packages/ui/components/ui/asset-icon/asset-icon.stories.tsx b/packages/ui/components/ui/asset-icon/asset-icon.stories.tsx new file mode 100644 index 00000000..f97205a4 --- /dev/null +++ b/packages/ui/components/ui/asset-icon/asset-icon.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { AssetIcon } from '.'; +import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; + +const meta: Meta = { + component: AssetIcon, + title: 'AssetIcon', + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +const EXAMPLE_METADATA = new Metadata({ + base: 'upenumbra', + display: 'penumbra', + symbol: 'UM', + images: [ + { + svg: 'https://raw.githubusercontent.com/prax-wallet/registry/main/images/um.svg', + }, + ], +}); + +export const Small: Story = { + args: { + metadata: EXAMPLE_METADATA, + }, +}; + +export const ExtraSmall: Story = { + args: { + metadata: EXAMPLE_METADATA, + size: 'xs', + }, +}; + +export const Large: Story = { + args: { + metadata: EXAMPLE_METADATA, + size: 'lg', + }, +}; diff --git a/packages/ui/components/ui/tx/view/asset-icon/delegation-token-icon.tsx b/packages/ui/components/ui/asset-icon/delegation-token-icon.tsx similarity index 100% rename from packages/ui/components/ui/tx/view/asset-icon/delegation-token-icon.tsx rename to packages/ui/components/ui/asset-icon/delegation-token-icon.tsx diff --git a/packages/ui/components/ui/tx/view/asset-icon/index.tsx b/packages/ui/components/ui/asset-icon/index.tsx similarity index 94% rename from packages/ui/components/ui/tx/view/asset-icon/index.tsx rename to packages/ui/components/ui/asset-icon/index.tsx index 79bfb363..0d524c2c 100644 --- a/packages/ui/components/ui/tx/view/asset-icon/index.tsx +++ b/packages/ui/components/ui/asset-icon/index.tsx @@ -1,6 +1,6 @@ import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import { Identicon } from '../../../identicon'; -import { cn } from '../../../../../lib/utils'; +import { Identicon } from '../identicon'; +import { cn } from '../../../lib/utils'; import { DelegationTokenIcon } from './delegation-token-icon'; import { getDisplay } from '@penumbra-zone/getters/metadata'; import { assetPatterns } from '@penumbra-zone/types/assets'; diff --git a/packages/ui/components/ui/tx/view/asset-icon/unbonding-token-icon.tsx b/packages/ui/components/ui/asset-icon/unbonding-token-icon.tsx similarity index 100% rename from packages/ui/components/ui/tx/view/asset-icon/unbonding-token-icon.tsx rename to packages/ui/components/ui/asset-icon/unbonding-token-icon.tsx diff --git a/packages/ui/components/ui/avatar.tsx b/packages/ui/components/ui/avatar.tsx deleted file mode 100644 index 5b6d80ea..00000000 --- a/packages/ui/components/ui/avatar.tsx +++ /dev/null @@ -1,46 +0,0 @@ -'use client'; - -import * as React from 'react'; -import * as AvatarPrimitive from '@radix-ui/react-avatar'; -import { cn } from '../../lib/utils'; - -const Avatar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; - -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; - -export { Avatar, AvatarImage, AvatarFallback }; diff --git a/packages/ui/components/ui/balance-value-view.test.tsx b/packages/ui/components/ui/balance-value-view.test.tsx deleted file mode 100644 index 6ea5ff57..00000000 --- a/packages/ui/components/ui/balance-value-view.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, test, vi } from 'vitest'; -import { fireEvent, render, screen } from '@testing-library/react'; -import { - Metadata, - ValueView, -} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import { BalanceValueView } from './balance-value-view'; -import { base64ToUint8Array } from '@penumbra-zone/types/base64'; - -// Mocking the WalletIcon component -vi.mock('./icons/wallet', () => ({ - WalletIcon: ({ className }: { className: string }) => ( -
- ), -})); - -describe('', () => { - const penumbraMetadata = new Metadata({ - base: 'upenumbra', - display: 'penumbra', - symbol: 'UM', - penumbraAssetId: { - inner: base64ToUint8Array('KeqcLzNx9qSH5+lcJHBB9KNW+YPrBk5dKzvPMiypahA='), - }, - images: [ - { - png: 'https://raw.githubusercontent.com/penumbra-zone/web/main/apps/minifront/public/favicon.png', - }, - ], - denomUnits: [ - { - denom: 'penumbra', - exponent: 6, - }, - { - denom: 'mpenumbra', - exponent: 3, - }, - { - denom: 'upenumbra', - exponent: 0, - }, - ], - }); - - const valueView = new ValueView({ - valueView: { - case: 'knownAssetId', - value: { - amount: { - hi: 0n, - lo: 123_456_789n, - }, - metadata: penumbraMetadata, - }, - }, - }); - - test('renders value and wallet icon without cursor-pointer', () => { - const { container } = render(); - - expect(container).toHaveTextContent(`123.456789`); - expect(screen.getByTestId('wallet-icon')).toBeInTheDocument(); - const clickableElement = screen.getByTestId('wallet-icon').parentElement; - expect(clickableElement).not.toHaveClass('cursor-pointer'); - }); - - test('renders with cursor-pointer class', () => { - const handleClick = vi.fn(); - const { container } = render(); - - expect(container).toHaveTextContent(`123.456789`); - expect(screen.getByTestId('wallet-icon')).toBeInTheDocument(); - const clickableElement = screen.getByTestId('wallet-icon').parentElement; - expect(clickableElement).toHaveClass('cursor-pointer'); - }); - - test('calls onClick when clicked', () => { - const handleClick = vi.fn(); - render(); - const clickableElement = screen.getByTestId('wallet-icon').parentElement!; - - fireEvent.click(clickableElement); - expect(handleClick).toHaveBeenCalledTimes(1); - expect(handleClick).toHaveBeenCalledWith(valueView); - }); -}); diff --git a/packages/ui/components/ui/balance-value-view.tsx b/packages/ui/components/ui/balance-value-view.tsx deleted file mode 100644 index f8c8ea8c..00000000 --- a/packages/ui/components/ui/balance-value-view.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { ValueView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import { WalletIcon } from './icons/wallet'; -import { getAmount, getDisplayDenomExponentFromValueView } from '@penumbra-zone/getters/value-view'; -import { formatAmount } from '@penumbra-zone/types/amount'; -import { Amount } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/num/v1/num_pb'; -import { cn } from '../../lib/utils'; - -/** - * Renders a `ValueView` as a balance with a wallet icon. - * Optionally can pass an `onClick` method that gets called when clicked. - */ -export const BalanceValueView = ({ - valueView, - onClick, -}: { - valueView: ValueView; - onClick?: (valueView: ValueView) => void; -}) => { - const exponent = getDisplayDenomExponentFromValueView.optional()(valueView); - const amount = getAmount.optional()(valueView) ?? new Amount({ hi: 0n, lo: 0n }); - const formattedAmount = formatAmount({ amount, exponent, commas: true }); - - return ( -
onClick(valueView) : undefined} - role={onClick ? 'button' : undefined} - > - - {formattedAmount} -
- ); -}; diff --git a/packages/ui/components/ui/block-sync-status/condensed.tsx b/packages/ui/components/ui/block-sync-status/index.tsx similarity index 100% rename from packages/ui/components/ui/block-sync-status/condensed.tsx rename to packages/ui/components/ui/block-sync-status/index.tsx diff --git a/packages/ui/components/ui/box/box.stories.tsx b/packages/ui/components/ui/box/box.stories.tsx new file mode 100644 index 00000000..919c344d --- /dev/null +++ b/packages/ui/components/ui/box/box.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Box } from '.'; + +const meta: Meta = { + component: Box, + title: 'Box', + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + children: 'Box', + }, +}; + +export const WithLabel: Story = { + args: { + children: 'Box', + label: 'Label', + headerContent: 'Header content', + }, +}; + +export const SpacingCompact: Story = { + args: { + children: 'Box', + spacing: 'compact', + }, +}; + +export const RedBorder: Story = { + args: { + children: 'Box', + state: 'error', + }, +}; diff --git a/packages/ui/components/ui/box.tsx b/packages/ui/components/ui/box/index.tsx similarity index 98% rename from packages/ui/components/ui/box.tsx rename to packages/ui/components/ui/box/index.tsx index 453c7080..57e56e8b 100644 --- a/packages/ui/components/ui/box.tsx +++ b/packages/ui/components/ui/box/index.tsx @@ -1,8 +1,8 @@ import { cva, VariantProps } from 'class-variance-authority'; import { motion } from 'framer-motion'; import { PropsWithChildren, ReactNode } from 'react'; +import { cn } from '../../../lib/utils'; import { RESOLVED_TAILWIND_CONFIG } from '@repo/tailwind-config/resolved-tailwind-config'; -import { cn } from '../../lib/utils'; const variants = cva('rounded-lg border bg-background', { variants: { diff --git a/packages/ui/components/ui/button.stories.tsx b/packages/ui/components/ui/button/button.stories.tsx similarity index 91% rename from packages/ui/components/ui/button.stories.tsx rename to packages/ui/components/ui/button/button.stories.tsx index 3e757042..092663f5 100644 --- a/packages/ui/components/ui/button.stories.tsx +++ b/packages/ui/components/ui/button/button.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { Button } from './button'; +import { Button } from '.'; const meta: Meta = { component: Button, diff --git a/packages/ui/components/ui/button.tsx b/packages/ui/components/ui/button/index.tsx similarity index 98% rename from packages/ui/components/ui/button.tsx rename to packages/ui/components/ui/button/index.tsx index 1396535f..d0f90f81 100644 --- a/packages/ui/components/ui/button.tsx +++ b/packages/ui/components/ui/button/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from '../../lib/utils'; +import { cn } from '../../../lib/utils'; const buttonVariants = cva( 'inline-flex items-center justify-center rounded-lg px-4 font-bold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', diff --git a/packages/ui/components/ui/candlestick-plot.tsx b/packages/ui/components/ui/candlestick-plot.tsx deleted file mode 100644 index cbc1034f..00000000 --- a/packages/ui/components/ui/candlestick-plot.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; -import { CandlestickData } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/dex/v1/dex_pb'; -import { AxisBottom, AxisLeft } from '@visx/axis'; -import { curveLinear } from '@visx/curve'; -import { GridRows } from '@visx/grid'; -import { Group } from '@visx/group'; -import { useParentSize } from '@visx/responsive'; -import { scaleLinear } from '@visx/scale'; -import { LinePath } from '@visx/shape'; -import { BoxPlot } from '@visx/stats'; -import { Threshold } from '@visx/threshold'; -import { Tooltip, withTooltip } from '@visx/tooltip'; -import { WithTooltipProvidedProps } from '@visx/tooltip/lib/enhancers/withTooltip'; -import { ArrowDownRight, ArrowUpRight } from 'lucide-react'; -import { useCallback, useEffect, useMemo, useState } from 'react'; - -// accessors -const blockHeight = (d: CandlestickData) => Number(d.height); -const lowPrice = (d: CandlestickData) => d.low; -const highPrice = (d: CandlestickData) => d.high; -const openPrice = (d: CandlestickData) => d.open; -const closePrice = (d: CandlestickData) => d.close; -const midPrice = (d: CandlestickData) => (openPrice(d) + closePrice(d)) / 2; -const priceMovement = (d: CandlestickData) => closePrice(d) - openPrice(d); -const priceMovementColor = (d: CandlestickData) => { - const movement = priceMovement(d); - if (movement > 0) return 'green'; - else if (movement < 0) return 'red'; - else return 'white'; -}; - -type GetBlockDateFn = (h: bigint, s?: AbortSignal) => Promise; - -interface CandlestickPlotProps { - className: React.HTMLAttributes['className']; - - parentWidth?: number; - parentHeight?: number; - width?: number; - height?: number; - candles: CandlestickData[]; - latestKnownBlockHeight?: number; - startMetadata: Metadata; - endMetadata: Metadata; - getBlockDate: GetBlockDateFn; -} - -interface CandlestickTooltipProps { - top?: number; - left?: number; - data: CandlestickData; - startMetadata: Metadata; - endMetadata: Metadata; - getBlockDate: GetBlockDateFn; -} - -export const CandlestickPlot = withTooltip( - ({ - className, - - // plot props - candles, - startMetadata, - endMetadata, - latestKnownBlockHeight, - getBlockDate, - - // withTooltip props - tooltipOpen, - tooltipLeft, - tooltipTop, - tooltipData, - showTooltip, - hideTooltip, - }: CandlestickPlotProps & WithTooltipProvidedProps) => { - const { parentRef, width: w, height: h } = useParentSize({ debounceTime: 150 }); - - const { maxPrice, minPrice } = useMemo( - () => - candles.reduce( - (acc, d) => ({ - minPrice: Math.min(acc.minPrice, lowPrice(d)), - maxPrice: Math.max(acc.maxPrice, highPrice(d)), - }), - { minPrice: Infinity, maxPrice: -Infinity }, - ), - [candles], - ); - const maxSpread = maxPrice - minPrice; - - const useTooltip = useCallback( - (d: CandlestickData) => ({ - onMouseOver: () => { - showTooltip({ - tooltipTop: priceScale(midPrice(d)), - tooltipLeft: blockScale(blockHeight(d)), - tooltipData: d, - }); - }, - onMouseLeave: () => { - hideTooltip(); - }, - }), - [], - ); - - if (!candles.length) return null; - - // assertions here okay because we've just checked length - const startBlock = blockHeight(candles[0]!); - const endBlock = blockHeight(candles[candles.length - 1]!); - - // candle width as fraction of graph width. likely too thin to really - // matter. if there's lots of records this will overlap, and we'll need to - // implement some kind of binning. - const blockWidth = Math.min(w / candles.length, 6); - - const blockScale = scaleLinear({ - range: [50, w - 5], - domain: [startBlock, latestKnownBlockHeight ?? endBlock], - }); - - const priceScale = scaleLinear({ - range: [h, 0], - domain: [minPrice - maxSpread / 2, maxPrice + maxSpread / 2], - }); - - return ( - <> -
- - - - - Number(value).toFixed(2)} - tickLabelProps={{ - fill: 'white', - textAnchor: 'end', - }} - left={60} - scale={priceScale} - numTicks={3} - /> - blockScale(blockHeight(d))} - y0={(d: CandlestickData) => priceScale(lowPrice(d))} - y1={(d: CandlestickData) => priceScale(highPrice(d))} - clipAboveTo={0} - clipBelowTo={h} - belowAreaProps={{ - fill: 'white', - fillOpacity: 0.1, - }} - /> - blockScale(blockHeight(d))} - y={(d: CandlestickData) => priceScale((openPrice(d) + closePrice(d)) * 0.5)} - stroke='white' - strokeOpacity={0.2} - strokeWidth={2} - /> - { - // render a candle for every price record - candles.map((d: CandlestickData) => { - const movementColor = priceMovementColor(d); - const open = openPrice(d); - const close = closePrice(d); - const useTooltipProps = useTooltip(d); - - return ( - - - - ); - }) - } - - -
- {tooltipOpen && tooltipData && ( - - )} - - ); - }, -); - -export const CandlesticksTooltip = ({ - top, - left, - data, - getBlockDate, - endMetadata, - startMetadata, -}: CandlestickTooltipProps) => { - const [blockDate, setBlockDate] = useState(); - useEffect(() => { - const ac = new AbortController(); - void getBlockDate(data.height, ac.signal).then(setBlockDate); - return () => ac.abort('Abort tooltip date query'); - }, [data]); - - const endBase = endMetadata.denomUnits.filter(d => !d.exponent)[0]!; - const startBase = startMetadata.denomUnits.filter(d => !d.exponent)[0]!; - return ( - -
-
-
- block {String(data.height)} ({blockDate?.toLocaleString()}) -
-
- Price of {endBase.denom} in {startBase.denom} -
-
- { - // if difference is more significant than 0.0001, show arrow - Boolean(priceMovement(data) * 1000) && ( -
- {priceMovement(data) > 0 ? ( - - ) : ( - - )} -
- ) - } -
-
-
- high: {Number(data.high).toFixed(4)} {startBase.denom} -
-
- low: {Number(data.low).toFixed(4)} {startBase.denom} -
-
- open: {Number(data.open).toFixed(4)} {startBase.denom} -
-
- close: {Number(data.close).toFixed(4)} {startBase.denom} -
-
-
{Number(data.directVolume)} direct trades
-
{Number(data.swapVolume)} indirect trades
-
- ); -}; diff --git a/packages/ui/components/ui/card/card.stories.tsx b/packages/ui/components/ui/card/card.stories.tsx new file mode 100644 index 00000000..6a5749a0 --- /dev/null +++ b/packages/ui/components/ui/card/card.stories.tsx @@ -0,0 +1,48 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Card, CardHeader, CardTitle, CardContent, CardDescription, CardFooter } from '.'; + +const meta: Meta = { + component: Card, + title: 'Card', + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + children: 'Save', + }, +}; + +export const Gradient: Story = { + args: { + children: 'Save', + gradient: true, + }, +}; + +export const Light: Story = { + args: { + children: 'Save', + light: true, + }, +}; + +export const Full: Story = { + args: {}, + render: args => { + return ( + + + Card Title + + Card Description + This is content + Card footer + + ); + }, +}; diff --git a/packages/ui/components/ui/card.tsx b/packages/ui/components/ui/card/index.tsx similarity index 98% rename from packages/ui/components/ui/card.tsx rename to packages/ui/components/ui/card/index.tsx index e2024900..3b0b9b20 100644 --- a/packages/ui/components/ui/card.tsx +++ b/packages/ui/components/ui/card/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { cn } from '../../lib/utils'; +import { cn } from '../../../lib/utils'; import { motion } from 'framer-motion'; export interface CardProps extends React.HTMLAttributes { diff --git a/packages/ui/components/ui/command.tsx b/packages/ui/components/ui/command.tsx deleted file mode 100644 index d5bb1c87..00000000 --- a/packages/ui/components/ui/command.tsx +++ /dev/null @@ -1,145 +0,0 @@ -'use client'; - -import * as React from 'react'; -import { type DialogProps } from '@radix-ui/react-dialog'; -import { Command as CommandPrimitive } from 'cmdk'; -import { Search } from 'lucide-react'; -import { cn } from '../../lib/utils'; -import { Dialog, DialogContent } from './dialog'; - -const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Command.displayName = CommandPrimitive.displayName; - -type CommandDialogProps = DialogProps; - -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { - return ( - - - - {children} - - - - ); -}; - -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - // eslint-disable-next-line react/no-unknown-property -
- - -
-)); - -CommandInput.displayName = CommandPrimitive.Input.displayName; - -const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandList.displayName = CommandPrimitive.List.displayName; - -const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->((props, ref) => ( - -)); - -CommandEmpty.displayName = CommandPrimitive.Empty.displayName; - -const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandGroup.displayName = CommandPrimitive.Group.displayName; - -const CommandSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -CommandSeparator.displayName = CommandPrimitive.Separator.displayName; - -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandItem.displayName = CommandPrimitive.Item.displayName; - -const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => { - return ( - - ); -}; -CommandShortcut.displayName = 'CommandShortcut'; - -export { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, - CommandSeparator, -}; diff --git a/packages/ui/components/ui/copy-to-clipboard-icon-button.tsx b/packages/ui/components/ui/copy-to-clipboard/copy-to-clipboard-icon-button.tsx similarity index 100% rename from packages/ui/components/ui/copy-to-clipboard-icon-button.tsx rename to packages/ui/components/ui/copy-to-clipboard/copy-to-clipboard-icon-button.tsx diff --git a/packages/ui/components/ui/copy-to-clipboard.tsx b/packages/ui/components/ui/copy-to-clipboard/copy-to-clipboard.tsx similarity index 94% rename from packages/ui/components/ui/copy-to-clipboard.tsx rename to packages/ui/components/ui/copy-to-clipboard/copy-to-clipboard.tsx index 554eaf68..b0c8524b 100644 --- a/packages/ui/components/ui/copy-to-clipboard.tsx +++ b/packages/ui/components/ui/copy-to-clipboard/copy-to-clipboard.tsx @@ -3,8 +3,8 @@ import * as React from 'react'; import { useState } from 'react'; import { CheckCircledIcon } from '@radix-ui/react-icons'; -import { Button } from './button'; -import { cn } from '../../lib/utils'; +import { Button } from '../button'; +import { cn } from '../../../lib/utils'; export interface CopyToClipboardProps extends React.ButtonHTMLAttributes { text: string; diff --git a/packages/ui/components/ui/copy-to-clipboard/index.tsx b/packages/ui/components/ui/copy-to-clipboard/index.tsx new file mode 100644 index 00000000..2f63825b --- /dev/null +++ b/packages/ui/components/ui/copy-to-clipboard/index.tsx @@ -0,0 +1,2 @@ +export { CopyToClipboardIconButton } from './copy-to-clipboard-icon-button'; +export { CopyToClipboard, type CopyToClipboardProps } from './copy-to-clipboard'; diff --git a/packages/ui/components/ui/dialog/dialog.stories.tsx b/packages/ui/components/ui/dialog/dialog.stories.tsx new file mode 100644 index 00000000..780050fd --- /dev/null +++ b/packages/ui/components/ui/dialog/dialog.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Dialog, DialogHeader, DialogClose, DialogContent, DialogTrigger } from '.'; +import { Button } from '../button'; + +const meta: Meta = { + component: Dialog, + title: 'Dialog', + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +export const Full: Story = { + args: {}, + render: args => { + return ( + + + + + + + Header here, which includes a built-in close button. +

Content here

+ +
Clicking anything inside here will close the dialog.
+
+
+
+ ); + }, +}; diff --git a/packages/ui/components/ui/dialog.tsx b/packages/ui/components/ui/dialog/index.tsx similarity index 99% rename from packages/ui/components/ui/dialog.tsx rename to packages/ui/components/ui/dialog/index.tsx index 31a52702..67ad4393 100644 --- a/packages/ui/components/ui/dialog.tsx +++ b/packages/ui/components/ui/dialog/index.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { Cross2Icon } from '@radix-ui/react-icons'; -import { cn } from '../../lib/utils'; +import { cn } from '../../../lib/utils'; import { cva, VariantProps } from 'class-variance-authority'; import { motion } from 'framer-motion'; diff --git a/packages/ui/components/ui/fade-transition.tsx b/packages/ui/components/ui/fade-transition/index.tsx similarity index 93% rename from packages/ui/components/ui/fade-transition.tsx rename to packages/ui/components/ui/fade-transition/index.tsx index a9686793..b6890d05 100644 --- a/packages/ui/components/ui/fade-transition.tsx +++ b/packages/ui/components/ui/fade-transition/index.tsx @@ -2,7 +2,7 @@ import { motion } from 'framer-motion'; import React from 'react'; -import { cn } from '../../lib/utils'; +import { cn } from '../../../lib/utils'; const FadeTransition = ({ children, diff --git a/packages/ui/components/ui/gradient-header.tsx b/packages/ui/components/ui/gradient-header.tsx deleted file mode 100644 index 64374290..00000000 --- a/packages/ui/components/ui/gradient-header.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { motion } from 'framer-motion'; - -/** - * A header with text whose color is a gradient of brand colors. - */ -export const GradientHeader = ({ - children, - layout, - layoutId, -}: { - children: string; - layout?: boolean; - layoutId?: string; -}) => ( - - {children} - -); diff --git a/packages/ui/components/ui/icon-input.tsx b/packages/ui/components/ui/icon-input/index.tsx similarity index 94% rename from packages/ui/components/ui/icon-input.tsx rename to packages/ui/components/ui/icon-input/index.tsx index d70838a1..0a4fa5b6 100644 --- a/packages/ui/components/ui/icon-input.tsx +++ b/packages/ui/components/ui/icon-input/index.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import { Input } from './input'; +import { Input } from '../input'; /** * Use this to render an input with an icon to its left, such as a search field diff --git a/packages/ui/components/ui/back-icon.tsx b/packages/ui/components/ui/icons/back-icon.tsx similarity index 93% rename from packages/ui/components/ui/back-icon.tsx rename to packages/ui/components/ui/icons/back-icon.tsx index ed37834a..8958ffae 100644 --- a/packages/ui/components/ui/back-icon.tsx +++ b/packages/ui/components/ui/icons/back-icon.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { ArrowLeftIcon } from '@radix-ui/react-icons'; import { IconProps } from '@radix-ui/react-icons/dist/types'; -import { cn } from '../../lib/utils'; +import { cn } from '../../../lib/utils'; export type BackIconProps = IconProps & React.RefAttributes; diff --git a/packages/ui/components/ui/input.tsx b/packages/ui/components/ui/input/index.tsx similarity index 97% rename from packages/ui/components/ui/input.tsx rename to packages/ui/components/ui/input/index.tsx index 99df8010..6cf28cee 100644 --- a/packages/ui/components/ui/input.tsx +++ b/packages/ui/components/ui/input/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { cva, VariantProps } from 'class-variance-authority'; -import { cn } from '../../lib/utils'; +import { cn } from '../../../lib/utils'; const inputVariants = cva( 'flex h-11 w-full rounded-lg border bg-background px-3 py-2 ring-offset-background [appearance:textfield] file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none', diff --git a/packages/ui/components/ui/json-viewer.tsx b/packages/ui/components/ui/json-viewer/index.tsx similarity index 100% rename from packages/ui/components/ui/json-viewer.tsx rename to packages/ui/components/ui/json-viewer/index.tsx diff --git a/packages/ui/components/ui/conditional-wrap.tsx b/packages/ui/components/ui/network/conditional-wrap.tsx similarity index 100% rename from packages/ui/components/ui/conditional-wrap.tsx rename to packages/ui/components/ui/network/conditional-wrap.tsx diff --git a/packages/ui/components/ui/network.tsx b/packages/ui/components/ui/network/index.tsx similarity index 100% rename from packages/ui/components/ui/network.tsx rename to packages/ui/components/ui/network/index.tsx diff --git a/packages/ui/components/ui/network.stories.tsx b/packages/ui/components/ui/network/network.stories.tsx similarity index 84% rename from packages/ui/components/ui/network.stories.tsx rename to packages/ui/components/ui/network/network.stories.tsx index 357345f4..25b5dac4 100644 --- a/packages/ui/components/ui/network.stories.tsx +++ b/packages/ui/components/ui/network/network.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { Network } from './network'; +import { Network } from '.'; const meta: Meta = { component: Network, @@ -13,7 +13,6 @@ type Story = StoryObj; export const Basic: Story = { args: { name: 'penumbra-testnet-deimos-6', - connectIndicator: true, href: 'https://app.testnet.penumbra.zone', }, }; diff --git a/packages/ui/components/ui/pill.tsx b/packages/ui/components/ui/pill/index.tsx similarity index 100% rename from packages/ui/components/ui/pill.tsx rename to packages/ui/components/ui/pill/index.tsx diff --git a/packages/ui/components/ui/pill/pill.stories.tsx b/packages/ui/components/ui/pill/pill.stories.tsx new file mode 100644 index 00000000..8e7ad193 --- /dev/null +++ b/packages/ui/components/ui/pill/pill.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Pill } from '.'; + +const meta: Meta = { + component: Pill, + title: 'Pill', + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + args: { + children: 'Pill', + }, +}; + +export const Dashed: Story = { + args: { + children: 'Pill', + variant: 'dashed', + }, +}; diff --git a/packages/ui/components/ui/popover.tsx b/packages/ui/components/ui/popover.tsx deleted file mode 100644 index 14a71f2c..00000000 --- a/packages/ui/components/ui/popover.tsx +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; - -import * as React from 'react'; -import * as PopoverPrimitive from '@radix-ui/react-popover'; -import { cn } from '../../lib/utils'; - -const Popover = PopoverPrimitive.Root; - -const PopoverTrigger = PopoverPrimitive.Trigger; - -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( - - - -)); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; - -export { Popover, PopoverTrigger, PopoverContent }; diff --git a/packages/ui/components/ui/progress.tsx b/packages/ui/components/ui/progress/index.tsx similarity index 97% rename from packages/ui/components/ui/progress.tsx rename to packages/ui/components/ui/progress/index.tsx index 50da053c..6fa3346b 100644 --- a/packages/ui/components/ui/progress.tsx +++ b/packages/ui/components/ui/progress/index.tsx @@ -1,7 +1,7 @@ 'use client'; import * as ProgressPrimitive from '@radix-ui/react-progress'; -import { cn } from '../../lib/utils'; +import { cn } from '../../../lib/utils'; import { cva, VariantProps } from 'class-variance-authority'; const progressVariants = cva('', { diff --git a/packages/ui/components/ui/segmented-picker.stories.tsx b/packages/ui/components/ui/segmented-picker.stories.tsx deleted file mode 100644 index c12cc04d..00000000 --- a/packages/ui/components/ui/segmented-picker.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { useArgs } from '@storybook/preview-api'; - -import { SegmentedPicker } from './segmented-picker'; - -const meta: Meta = { - component: SegmentedPicker, - title: 'SegmentedPicker', - tags: ['autodocs'], - argTypes: { - options: { - control: false, - }, - }, -}; -export default meta; - -type Story = StoryObj; - -export const Basic: Story = { - args: { - value: 'one', - }, - - render: function Render({ value }) { - const [, updateArgs] = useArgs(); - - const onChange = (value: unknown) => updateArgs({ value }); - - const options = [ - { value: 'one', label: 'One' }, - { value: 'two', label: 'Two' }, - { value: 'three', label: 'Three' }, - ]; - - return ; - }, -}; diff --git a/packages/ui/components/ui/segmented-picker.test.tsx b/packages/ui/components/ui/segmented-picker.test.tsx deleted file mode 100644 index 4cf7ceff..00000000 --- a/packages/ui/components/ui/segmented-picker.test.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { fireEvent, render } from '@testing-library/react'; -import { SegmentedPicker } from './segmented-picker'; - -describe('', () => { - const onChange = vi.fn(); - const options = [ - { value: 'one', label: 'One' }, - { value: 'two', label: 'Two' }, - { value: 'three', label: 'Three' }, - ]; - - beforeEach(() => { - onChange.mockReset(); - }); - - it('renders all passed-in options', () => { - const { container } = render( - , - ); - - expect(container).toHaveTextContent('One'); - expect(container).toHaveTextContent('Two'); - expect(container).toHaveTextContent('Three'); - }); - - it('calls the `onClick` handler with the value of the clicked option', () => { - const { getByText } = render( - , - ); - fireEvent.click(getByText('Two', { selector: ':not([aria-hidden])' })); - - expect(onChange).toHaveBeenCalledWith('two'); - }); - - it('applies aria-checked=true to the selected option', () => { - const { getByText } = render( - , - ); - - expect(getByText('One', { selector: ':not([aria-hidden])' }).parentElement).toHaveAttribute( - 'aria-checked', - 'true', - ); - expect(getByText('Two', { selector: ':not([aria-hidden])' }).parentElement).toHaveAttribute( - 'aria-checked', - 'false', - ); - expect(getByText('Three', { selector: ':not([aria-hidden])' }).parentElement).toHaveAttribute( - 'aria-checked', - 'false', - ); - }); -}); diff --git a/packages/ui/components/ui/segmented-picker.tsx b/packages/ui/components/ui/segmented-picker.tsx deleted file mode 100644 index 6cd622b0..00000000 --- a/packages/ui/components/ui/segmented-picker.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useId } from 'react'; -import { cn } from '../../lib/utils'; -import { motion } from 'framer-motion'; - -export interface SegmentedPickerOption { - /** - * The value to pass to the `onChange` handler when clicked. Must be unique - * across all segments, and must be either a string, number, or an object with - * a `.toString()` method so that it can be used as a React key. - */ - value: ValueType; - label: string; -} -const getRoundedClasses = (index: number, optionsLength: number, size: 'md' | 'lg') => - cn( - index === 0 && size === 'md' && 'rounded-l-sm', - index === optionsLength - 1 && size === 'md' && 'rounded-r-sm', - index === 0 && size === 'lg' && 'rounded-l-lg', - index === optionsLength - 1 && size === 'lg' && 'rounded-r-lg', - ); - -const ActiveSegmentIndicator = ({ - layoutId, - roundedClasses, -}: { - layoutId: string; - roundedClasses: string; -}) => ( - -); - -/** - * Renders a segmented picker where only one option can be selected at a time. - * Functionally equivalent to a `