Skip to content

Commit

Permalink
[dapp-kit] provide locally scoped CSS resets for our UI components (M…
Browse files Browse the repository at this point in the history
…ystenLabs#14041)

## Description 

This PR adds locally scoped CSS resets to our `dapp-kit` library which
aims to be as minimal / unobtrusive as possible.
There really isn't a great industry solution to this as a web
application can always override library styling. To explain the options
I considered here:

1. ❌ Not shipping any CSS reset and leaving it purely to the consumer
(not everyone uses CSS resets)
2. ❌ Using a globally scoped CSS reset (this is bad as library styling
should never affect broader application styling)
3. [✅] Using locally scoped CSS resets to only reset the styling in our
library with sensible defaults
- [❌] Creating a `reset.css.ts` file with reset styles that have to be
manually applied to every element
- [❌] Doing the above but creating a generic `Box` type component to
apply resets (I really don't like this abstraction)
- [✅] Marking entry points to our UI components using a data attribute
and then applying global styles


[Rainbow-kit](https://github.com/rainbow-me/rainbowkit/blob/252f02e8c024e2f92734653e8ec5fd2180434b07/site/components/Box/Box.tsx#L31)
goes with the `Box` approach,
[ConnectKit](https://github.com/family/connectkit/blob/825f95e8f6bd9fae16803a63efaa14b87137a793/packages/connectkit/src/styles/index.ts#L199)
goes with a similar solution to what I decided on here, and [Radix UI
themes](https://github.com/radix-ui/themes/blob/58209d3b8b3cbebd6bcbf33eb0df30fa6fe5b3ab/packages/radix-ui-themes/src/styles/reset.css#L11)
also goes with a similar solution. I guess I sort of combined the latter
two approaches 🤔

With preflight disabled:
<img width="990" alt="image"
src="https://github.com/MystenLabs/sui/assets/7453188/8632c0fc-2046-43aa-a882-c743a86e765f">




## Test Plan 
- Tested in Sui Explorer with the `preflight: false` setting which
disables our CSS reset
- CI

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [ ] protocol change
- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
williamrobertson13 authored Oct 2, 2023
1 parent c253ca8 commit 261b567
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 70 deletions.
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/dapp-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"@mysten/wallet-standard": "workspace:*",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-slot": "^1.0.2",
"@vanilla-extract/css": "^1.13.0",
"clsx": "^2.0.0",
"zustand": "^4.4.1"
Expand Down
51 changes: 28 additions & 23 deletions sdk/dapp-kit/src/components/AccountDropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useSwitchAccount } from '../hooks/wallet/useSwitchAccount.js';
import * as styles from './AccountDropdownMenu.css.js';
import { CheckIcon } from './icons/CheckIcon.js';
import { ChevronIcon } from './icons/ChevronIcon.js';
import { StyleMarker } from './styling/StyleMarker.js';

type AccountDropdownMenuProps = {
currentAccount: WalletAccount;
Expand All @@ -23,35 +24,39 @@ export function AccountDropdownMenu({ currentAccount }: AccountDropdownMenuProps

return (
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger className={styles.triggerButton}>
{formatAddress(currentAccount.address)}
<ChevronIcon />
</DropdownMenu.Trigger>
<StyleMarker>
<DropdownMenu.Trigger className={styles.triggerButton}>
{formatAddress(currentAccount.address)}
<ChevronIcon />
</DropdownMenu.Trigger>
</StyleMarker>
<DropdownMenu.Portal>
<DropdownMenu.Content className={styles.menuContent}>
{accounts.map((account) => (
<DropdownMenu.Item key={account.address} asChild>
<StyleMarker>
<DropdownMenu.Content className={styles.menuContent}>
{accounts.map((account) => (
<DropdownMenu.Item key={account.address} asChild>
<button
type="button"
className={styles.switchAccountButton}
onClick={() => switchAccount({ account })}
>
{formatAddress(account.address)}
{currentAccount.address === account.address ? <CheckIcon /> : null}
</button>
</DropdownMenu.Item>
))}
<DropdownMenu.Separator className={styles.separator} />
<DropdownMenu.Item asChild>
<button
className={styles.disconnectButton}
type="button"
className={styles.switchAccountButton}
onClick={() => switchAccount({ account })}
onClick={() => disconnectWallet()}
>
{formatAddress(account.address)}
{currentAccount.address === account.address ? <CheckIcon /> : null}
Disconnect
</button>
</DropdownMenu.Item>
))}
<DropdownMenu.Separator className={styles.separator} />
<DropdownMenu.Item asChild>
<button
className={styles.disconnectButton}
type="button"
onClick={() => disconnectWallet()}
>
Disconnect
</button>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Content>
</StyleMarker>
</DropdownMenu.Portal>
</DropdownMenu.Root>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export const content = style({
bottom: 16,
left: 16,
right: 16,
zIndex: 999999999,
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
Expand Down
98 changes: 52 additions & 46 deletions sdk/dapp-kit/src/components/connect-modal/ConnectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { ReactNode } from 'react';
import { useConnectWallet } from '../../hooks/wallet/useConnectWallet.js';
import { BackIcon } from '../icons/BackIcon.js';
import { CloseIcon } from '../icons/CloseIcon.js';
import { StyleMarker } from '../styling/StyleMarker.js';
import * as styles from './ConnectModal.css.js';
import { ConnectionStatus } from './views/ConnectionStatus.js';
import { GettingStarted } from './views/GettingStarted.js';
Expand Down Expand Up @@ -68,54 +69,59 @@ export function ConnectModal({ trigger }: ConnectModalProps) {

return (
<Dialog.Root open={isConnectModalOpen} onOpenChange={onOpenChange}>
<Dialog.Trigger className={styles.triggerButton}>{trigger}</Dialog.Trigger>
<StyleMarker>
<Dialog.Trigger className={styles.triggerButton}>{trigger}</Dialog.Trigger>
</StyleMarker>
<Dialog.Portal>
<Dialog.Overlay className={styles.overlay} />
<Dialog.Content className={styles.content} aria-describedby={undefined}>
<div
className={clsx(styles.walletListContainer, {
[styles.walletListContainerWithViewSelected]: !!currentView,
})}
>
<div className={styles.walletListContent}>
<Dialog.Title className={styles.title}>Connect a Wallet</Dialog.Title>
<WalletList
selectedWalletName={selectedWallet?.name}
onPlaceholderClick={() => setCurrentView('getting-started')}
onSelect={(wallet) => {
setSelectedWallet(wallet);
connectWallet(wallet);
}}
/>
</div>
<button
className={styles.whatIsAWalletButton}
onClick={() => setCurrentView('what-is-a-wallet')}
type="button"
>
What is a Wallet?
</button>
</div>
<StyleMarker>
<Dialog.Overlay className={styles.overlay}>
<Dialog.Content className={styles.content} aria-describedby={undefined}>
<div
className={clsx(styles.walletListContainer, {
[styles.walletListContainerWithViewSelected]: !!currentView,
})}
>
<div className={styles.walletListContent}>
<Dialog.Title className={styles.title}>Connect a Wallet</Dialog.Title>
<WalletList
selectedWalletName={selectedWallet?.name}
onPlaceholderClick={() => setCurrentView('getting-started')}
onSelect={(wallet) => {
setSelectedWallet(wallet);
connectWallet(wallet);
}}
/>
</div>
<button
className={styles.whatIsAWalletButton}
onClick={() => setCurrentView('what-is-a-wallet')}
type="button"
>
What is a Wallet?
</button>
</div>

<div
className={clsx(styles.viewContainer, {
[styles.selectedViewContainer]: !!currentView,
})}
>
<button
className={styles.backButton}
onClick={() => resetSelection()}
type="button"
aria-label="Back"
>
<BackIcon />
</button>
{modalContent}
</div>
<Dialog.Close className={styles.closeButton} aria-label="Close">
<CloseIcon />
</Dialog.Close>
</Dialog.Content>
<div
className={clsx(styles.viewContainer, {
[styles.selectedViewContainer]: !!currentView,
})}
>
<button
className={styles.backButton}
onClick={() => resetSelection()}
type="button"
aria-label="Back"
>
<BackIcon />
</button>
{modalContent}
</div>
<Dialog.Close className={styles.closeButton} aria-label="Close">
<CloseIcon />
</Dialog.Close>
</Dialog.Content>
</Dialog.Overlay>
</StyleMarker>
</Dialog.Portal>
</Dialog.Root>
);
Expand Down
46 changes: 46 additions & 0 deletions sdk/dapp-kit/src/components/styling/StyleMarker.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { globalStyle } from '@vanilla-extract/css';

import { styleDataAttributeSelector } from '../../constants/styleDataAttribute.js';

globalStyle(createScopedSelector('*'), {
boxSizing: 'border-box',
});

globalStyle(createScopedSelector('button'), {
appearance: 'none',
backgroundColor: 'transparent',
fontSize: 'inherit',
fontFamily: 'inherit',
lineHeight: 'inherit',
letterSpacing: 'inherit',
outline: 'none',
color: 'inherit',
border: 0,
padding: 0,
margin: 0,
});

globalStyle(createScopedSelector('a'), {
textDecoration: 'none',
color: 'inherit',
outline: 'none',
});

globalStyle(createScopedSelector('ol, ul'), {
listStyle: 'none',
margin: 0,
padding: 0,
});

globalStyle(createScopedSelector('h1, h2, h3, h4, h5, h6'), {
fontSize: 'inherit',
fontWeight: 'inherit',
margin: 0,
});

function createScopedSelector(selector: string) {
return `${styleDataAttributeSelector}:where(${selector}), ${styleDataAttributeSelector} :where(${selector})`;
}
24 changes: 24 additions & 0 deletions sdk/dapp-kit/src/components/styling/StyleMarker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { Slot } from '@radix-ui/react-slot';
import type { ComponentPropsWithoutRef, ElementRef, ReactNode } from 'react';
import { forwardRef } from 'react';

import { styleDataAttribute } from '../../constants/styleDataAttribute.js';

import './StyleMarker.css.js';

type StyleMarker = {
children: ReactNode;
};

export const StyleMarker = forwardRef<
ElementRef<typeof Slot>,
ComponentPropsWithoutRef<typeof Slot>
>(({ children }, forwardedRef) => (
<Slot ref={forwardedRef} {...styleDataAttribute}>
{children}
</Slot>
));
StyleMarker.displayName = 'StyleMarker';
8 changes: 8 additions & 0 deletions sdk/dapp-kit/src/constants/styleDataAttribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

export const styleDataAttributeName = 'data-dapp-kit';

export const styleDataAttributeSelector = `[${styleDataAttributeName}]`;

export const styleDataAttribute = { [styleDataAttributeName]: '' };

0 comments on commit 261b567

Please sign in to comment.