Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jessepinho committed Jul 3, 2024
1 parent cd8feef commit 45c35fd
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { Link } from 'react-router-dom';
import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response';
import { getAddressIndex } from '@penumbra-zone/getters/address-view';
import { balancesByAccountSelector, useBalancesResponses } from '../../../state/shared';
import LoadingIndicator from '@repo/ui/LoadingIndicator';
// import FooBar from '@repo/ui/FooBar';

const getTradeLink = (balance: BalancesResponse): string => {
const metadata = getMetadataFromBalancesResponseOptional(balance);
Expand Down Expand Up @@ -60,6 +62,8 @@ export default function AssetsTable() {
<h2 className='whitespace-nowrap font-bold md:text-base xl:text-xl'>
Account #{account.account}
</h2>
<LoadingIndicator />
{/* <FooBar /> */}
</div>

<div className='max-w-72 truncate'>
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/components/v2/LoadingIndicator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function LoadingIndicator() {
return <div>LoadingIndicator</div>;
}
3 changes: 2 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"./lib/toast/transaction-toast": "./lib/toast/transaction-toast.tsx",
"./lib/utils": "./lib/utils.ts",
"./postcss.config.js": "./postcss.config.js",
"./styles/*": "./styles/*"
"./styles/*": "./styles/*",
"./*": "./components/v2/*/index.tsx"
},
"publishConfig": {
"exports": {
Expand Down
208 changes: 195 additions & 13 deletions packages/ui/readme.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,210 @@
# Penumbra UI

This package contains the UI components for the Penumbra web.
The Penumbra UI library is a set of UI components purpose-built for the Penumbra ecosystem. Use these components to get rendering of various Penumbra data types out of the box, and to create a UI that is consistent with other Penumbra UIs' look and feel.

## Set up

First, install the library:

```bash
npm i @penumbra-zone/ui
npm install @penumbra-zone/ui
```

Then, configure the Tailwind in your project. Edit `tailwind.config.js`:
Then, use components by importing them from their specific files:

```js
export default {
content: [
// Parses the classes of the UI components
'./node_modules/@penumbra-zone/ui/**/*.js',
],
};
```tsx
import ValueViewComponent from '@penumbra-zone/ui/ValueViewComponent';
```

Finally, import the library CSS to the entry point of your app:
Deprecated components can be imported from `@penumbra-zone/ui/components/ui/<component-name>`, where `<component-name>` should be replaced with the kebab-cased component name.

## Development

These guidelines are for maintainers of the components in the Penumbra UI library.

### Components must be located at `./components/v2/<ComponentName>/index.tsx`.

This ensures that the Penumbra UI `package.json` `exports` field works as intended, so that components can be imported via `@penumbra-zone/ui/<ComponentName>`.

Note that `<ComponentName>` should be replaced with the `UpperCamelCase` component name — e.g., `./components/v2/LoadingIndicator/index.tsx`.

### Components that are only used within a parent component must sit in the parent component's directory, rather than be a sibling of the parent directory.

```
- src/components/
- HeaderWithDropdown/
- index.tsx
- Dropdown.tsx ✅ Correct, if Dropdown is only ever used inside HeaderWithDropdown
```

```
- src/components/
- Dropdown.tsx ❌ Wrong, if Dropdown is only ever used inside HeaderWithDropdown
- HeaderWithDropdown/
- index.tsx
```

(One exception to this rule: if you're developing a component that will eventually be used by multiple other components, and just happens to be a child of only a single component at the moment, you can leave it as a sibling of its parent.)

### Components should be located at the most specific possible directory level.

For example, if the `Dropdown` component is used by both `HeaderWithDropdown` and `Menu` components, `Dropdown` should be placed in the lowest-level directory that contains both `HeaderWithDropdown` and `Menu`:

```js
import '@repo/ui/styles/globals.css';
```
- src/components/
- SomeCommonParentOfBothHeaderWithDropdownAndMenu/
- index.tsx
- HeaderWithDropdown/
- index.tsx
- index.test.tsx
- Menu/
- index.tsx
- index.test.tsx
- Dropdown.tsx ✅ Correct - Dropdown is used by both Menu and HeaderWithDropdown, so it's a sibling to both
```

This, as opposed to e.g., placing it inside the `HeaderWithDropdown` directory (and then importing it from there in `Menu`), or inside a root-level directory. This way, components are nested as closely to the components using them as possible.

```
- src/components/
- SomeCommonParentOfBothHeaderWithDropdownAndMenu/
- index.tsx
- HeaderWithDropdown/
- index.tsx
- index.test.tsx
- Dropdown.tsx ❌ Wrong - Menu shouldn't be importing a child of HeaderWithDropdown
- Menu/
- index.tsx
- index.test.tsx
```

### Components must be the default export of the component file.

This allows them to be imported as the default, rather than destructuring an object. It also encourages UI library maintainers to only export one component per file.

### Component props should be typed inline in the component function definition.

For example:

```tsx
export default function MyComponent({ color }: { color: Color }) {
// ...
}
```

This makes component code easy to read. An exception to this may be made when props are especially complex, such as when they are conditional.

There is no need to export the props interface, since consumers can simply use `React.ComponentProps<typeof ComponentName>` to extract the props interface from the component.

### Components must not accept `className` or `style` props.

This ensures that each component is internally responsible for its own styling.

Any variations of the component's appearance must be controlled via props like `variant`, `state`, etc. — not by kitchen-sink props like `className` and `style`, which allow arbitrary changes to be made that could interfere with the internal structure of the component and cause visual inconsistencies between instances of Penumbra UI components.

### Components should not define external margins or absolute/fixed positioning.

Components may only define their internal spacing and layout — never external spacing and layout. This ensures that components can be reused in any context.

External spacing and positioning is the responsibility of parent components, and can be achieved in the parent via, e.g., wrapper `<div />`s that position components appropriately.

(Note that absolute positioning _is_ acceptable for elements who have a higher-level relative-positioned element in the same component, since that means that the absolute-positioned element is still contained within the component's layout. There also a few exceptions to this rule, such as for tooltips and dropdown menus, as well as for page-level components that absolutely position a child component via a wrapper.)

#### Correct

```tsx
// BackgroundAnimation/index.tsx
export default function BackgroundAnimation() {
return <img src='./animation.gif' />;
}

// SplashPage/index.tsx
export default function SplashPage() {
return (
// ✅ CORRECT: position the background animation in the center of the screen
// using a wrapper div in the parent
<div className='absolute top-1/2 left-1/2 -translate-1/2'>
<BackgroundAnimation />
</div>
);
}
```

#### Incorrect

```tsx
// BackgroundAnimation/index.tsx
export default function BackgroundAnimation() {
return (
// ❌ INCORRECT: do not absolute-position elements in a non-page-level
// component
<div className='absolute top-1/2 left-1/2 -translate-1/2'>
<img src='./animation.gif' />;
</div>
);
}

// SplashPage/index.tsx
export default function SplashPage() {
return <BackgroundAnimation />;
}
```

#### Correct

```tsx
// AssetIcon/index.tsx
export default function AssetIcon({ display, src }: { display: string; src: string }) {
return <img src={src} alt={`Icon for the ${display} asset`} />;
}

// ValueComponent/index.tsx
export default function ValueComponent({ value, metadata }: { value: Value; metadata: Metadata }) {
return (
<Pill>
// ✅ CORRECT: define space around components using wrapper divs
<div className='flex gap-2'>
<AssetIcon display={metadata.display} src='./icon.png' />

{metadata.display}
</div>
</Pill>
);
}
```

#### Incorrect

```tsx
// AssetIcon/index.tsx
export default function AssetIcon({ display, src }: { display: string; src: string }) {
return (
<img
src={src}
alt={`Icon for the ${display} asset`}
// ❌ INCORRECT: do not define external margins for a component
className='mr-2'
/>
);
}

// ValueComponent/index.tsx
export default function ValueComponent({ value, metadata }: { value: Value; metadata: Metadata }) {
return (
<Pill>
<AssetIcon display={metadata.display} src='./icon.png' />
{metadata.display}
</Pill>
);
}
```

### Components should include Storybook stories.

[Storybook stories](https://storybook.js.org/docs/react/writing-stories/introduction) are pages in Storybook that show a particular component, usually in a specific state.

Storybook stories should be located next to the component they apply to, and have a file suffix of `.stories.ts(x)`. For example, a component located at `components/v2/Button/index.tsx` should have stories at `src/components/v2/Button/index.stories.ts`.

When writing stories, make sure to tag your stories with [`autodocs`](https://storybook.js.org/docs/react/writing-docs/autodocs). This is a Storybook feature that analyzes your component code to auto-generate documentation for your component, including a code sample, controls for each of the props, etc.

Also, be sure to add [JSDoc](https://jsdoc.app/about-getting-started.html#adding-documentation-comments-to-your-code)-style comments (`/** ... */`) before any props that need to be documented. Storybook will automatically pull these into the Storybook UI as documentation.

0 comments on commit 45c35fd

Please sign in to comment.