Skip to content

Commit

Permalink
feat(components)!: add Menu, MenuItem, MenuTrigger, Header, `…
Browse files Browse the repository at this point in the history
…Keyboard`, `Section`, `Separator`, and `Text` (#1154)

* feat(components): add menu

* feat: add supporting components

* feat: support slots with selection

* feat!: support grid line names

* refactor: update class selectors

* chore: add changeset

* docs: update migration guidelines

* feat: update story

* feat: add tests

* fix: align headers

* fix: add missing styles

* fix: excludeDecorators
  • Loading branch information
Niznikr authored Feb 1, 2024
1 parent 9dd6b14 commit 36d8a2d
Show file tree
Hide file tree
Showing 29 changed files with 590 additions and 94 deletions.
15 changes: 15 additions & 0 deletions .changeset/blue-papayas-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@launchpad-ui/split-button": minor
"@launchpad-ui/inline-edit": minor
"@launchpad-ui/snackbar": minor
"@launchpad-ui/tooltip": minor
"@launchpad-ui/button": minor
"@launchpad-ui/filter": minor
"@launchpad-ui/alert": minor
"@launchpad-ui/form": minor
"@launchpad-ui/menu": minor
"@launchpad-ui/core": minor
"@launchpad-ui/icons": minor
---

Change class pattern to `[hash]_[local]`
5 changes: 5 additions & 0 deletions .changeset/five-tigers-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@launchpad-ui/components": patch
---

Add `Menu`, `MenuItem`, `MenuTrigger`, `Header`, `Keyboard`, `Section`, `Separator`, and `Text`
5 changes: 5 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export const parameters = {
},
},
},
docs: {
source: {
excludeDecorators: true,
},
},
};

export const decorators = [
Expand Down
95 changes: 95 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,100 @@
# Migration @launchpad-ui/core

## 0.49.0

### Component class name pattern changed

Class names have been updated from `[hash]_[local]_` to `[hash]_[local]`. Update selectors targeting LP class names accordingly:

```css
[class*='_Button_'] {
...
}
```

**After**

```css
[class*='_Button'] {
...
}
```

## 0.48.0

### Remove icons

Icons `pulse-active`, `verified`, and `circles` have been removed.

## 0.47.0

### Base 16 font size

The base font size for all tokens and components is now 16. Update `rem` values accordingly. https://nekocalc.com/px-to-rem-converter

## 0.46.0

### Icon refresh

See https://launchpad.launchdarkly.com/?path=/story/components-icon--default for latest set of icons.

## 0.45.0

### Remove icons

Icons `warning-circle` and `arrow-thin-right-circle` have been removed.

## 0.44.0

### Remove bar-chart icon

Icon `bar-chart` has been replaced by `experiment`.

## 0.42.0

### Use sprites for icons

**Before**

```js
import { Add } from '@launchpad-ui/icons';

const MyIcon = () => <Add size="medium" />;
```

**After**

```js
import { Icon } from '@launchpad-ui/icons';

const MyIcon = () => <Icon name="add" size="medium" />;
```

By default, the component expects `@launchpad-ui/icons/dist/sprite.svg` to be available from `APP_ROOT/static/sprite.svg` in your app. A custom path to the sprite can be set via the `IconContext` provider.

For example, if importing a static asset returns a resolved URL you can do the following in your app to load the icons:

```js
import { IconContext } from '@launchpad-ui/icons';
import icons from '@launchpad-ui/icons/sprite.svg';
import { createRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = createRoot(domNode);

root.render(
<IconContext.Provider value={{ path: icons }}>
<App />
</IconContext.Provider>
);
```

## 0.41.0

### Modal size renaming

Modal size `normal` renamed to `medium`.

## 0.40.0

### TagGroup API changes
Expand Down
1 change: 1 addition & 0 deletions apps/remix/app/data.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export async function getComponents() {
{ to: 'components/tooltip', name: 'Tooltip' },
{ to: 'rac/button', name: 'RAC Button', role: 'button' },
{ to: 'rac/link-button', name: 'RAC LinkButton', role: 'link' },
{ to: 'rac/menu', name: 'RAC Menu', role: 'menu' },
{ to: 'rac/modal', name: 'RAC Modal', role: 'dialog' },
{ to: 'rac/popover', name: 'RAC Popover', role: 'dialog' },
{ to: 'rac/progress-bar', name: 'RAC ProgressBar', role: 'progressbar' },
Expand Down
16 changes: 16 additions & 0 deletions apps/remix/app/routes/rac.menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Popover, Menu, MenuItem, MenuTrigger, Button } from '@launchpad-ui/components';

export default function Index() {
return (
<MenuTrigger>
<Button>Trigger</Button>
<Popover isOpen>
<Menu>
<MenuItem>Item one</MenuItem>
<MenuItem>Item two</MenuItem>
<MenuItem>Item three</MenuItem>
</Menu>
</Popover>
</MenuTrigger>
);
}
4 changes: 2 additions & 2 deletions packages/alert/src/styles/Alert.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
line-height: 1.25;
}

.Alert :global([class*='_ButtonGroup_']) {
.Alert :global([class*='_ButtonGroup']) {
margin-top: 0.75rem;
}

Expand Down Expand Up @@ -197,7 +197,7 @@
margin-left: auto;
}

.Alert-content :global(a:not([class*='_Button_'])) {
.Alert-content :global(a:not([class*='_Button'])) {
color: var(--lp-color-text-interactive-base);

&:hover {
Expand Down
4 changes: 2 additions & 2 deletions packages/button/__tests__/ButtonGroup.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('ButtonGroup', () => {
</ButtonGroup>
);
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
expect(container.querySelector('[class*="_ButtonGroup--compact_"]')).not.toBeNull();
expect(container.querySelector('[class*="_ButtonGroup--compact"]')).not.toBeNull();
expect(screen.getByRole('button', { name: 'One' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Two' })).toBeInTheDocument();
});
Expand All @@ -36,7 +36,7 @@ describe('ButtonGroup', () => {
</ButtonGroup>
);
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
expect(container.querySelector('[class*="_ButtonGroup--large_"]')).not.toBeNull();
expect(container.querySelector('[class*="_ButtonGroup--large"]')).not.toBeNull();
expect(screen.getByRole('button', { name: 'One' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Two' })).toBeInTheDocument();
});
Expand Down
20 changes: 10 additions & 10 deletions packages/button/src/styles/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@
border-color: var(--Button-color-border-disabled);
}

.Button[disabled] [class*='_icon_'] {
.Button[disabled] [class*='_icon'] {
fill: var(--Button-icon-color-fill-disabled);
}

Expand Down Expand Up @@ -784,30 +784,30 @@
}

.ButtonGroup--compact > .Button + .Button,
.ButtonGroup--compact > [class*='_Popover-target_'] + .Button,
.ButtonGroup--compact > .Button + [class*='_Popover-target_'],
.ButtonGroup--compact > [class*='_Popover-target_'] + [class*='_Popover-target_'] {
.ButtonGroup--compact > [class*='_Popover-target'] + .Button,
.ButtonGroup--compact > .Button + [class*='_Popover-target'],
.ButtonGroup--compact > [class*='_Popover-target'] + [class*='_Popover-target'] {
margin-left: -1px;
}

.ButtonGroup--compact > .Button + [class*='_Popover-target_'],
.ButtonGroup--compact > [class*='_Popover-target_'] + [class*='_Popover-target_'] {
.ButtonGroup--compact > .Button + [class*='_Popover-target'],
.ButtonGroup--compact > [class*='_Popover-target'] + [class*='_Popover-target'] {
line-height: 1;
}

.ButtonGroup--compact > .Button:not(:first-child),
.ButtonGroup--compact > .Button:not(:last-child),
.ButtonGroup--compact > [class*='_Popover-target_']:not(:first-child),
.ButtonGroup--compact > [class*='_Popover-target_']:not(:last-child) {
.ButtonGroup--compact > [class*='_Popover-target']:not(:first-child),
.ButtonGroup--compact > [class*='_Popover-target']:not(:last-child) {
border-radius: 0;
}

.ButtonGroup--compact > .Button:first-child,
.ButtonGroup--compact > [class*='_Popover-target_']:first-child button {
.ButtonGroup--compact > [class*='_Popover-target']:first-child button {
border-radius: var(--lp-border-radius-regular) 0 0 var(--lp-border-radius-regular);
}

.ButtonGroup--compact > .Button:last-child,
.ButtonGroup--compact > [class*='_Popover-target_']:last-child button {
.ButtonGroup--compact > [class*='_Popover-target']:last-child button {
border-radius: 0 var(--lp-border-radius-regular) var(--lp-border-radius-regular) 0;
}
25 changes: 25 additions & 0 deletions packages/components/__tests__/Menu.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { it, expect, describe } from 'vitest';

import { render, screen, userEvent } from '../../../test/utils';
import { Menu, MenuItem, MenuTrigger, Button, Popover } from '../src';

describe('Menu', () => {
it('renders', async () => {
const user = userEvent.setup();
render(
<MenuTrigger>
<Button>Trigger</Button>
<Popover>
<Menu>
<MenuItem>Item one</MenuItem>
<MenuItem>Item two</MenuItem>
<MenuItem>Item three</MenuItem>
</Menu>
</Popover>
</MenuTrigger>
);

await user.click(screen.getByRole('button'));
expect(await screen.findByRole('menu')).toBeVisible();
});
});
3 changes: 3 additions & 0 deletions packages/components/src/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Header } from 'react-aria-components';

export { Header };
3 changes: 3 additions & 0 deletions packages/components/src/Keyboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Keyboard } from 'react-aria-components';

export { Keyboard };
69 changes: 69 additions & 0 deletions packages/components/src/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { ForwardedRef } from 'react';
import type {
MenuProps as AriaMenuProps,
MenuItemProps,
MenuTriggerProps,
} from 'react-aria-components';

import { Icon } from '@launchpad-ui/icons';
import { cva } from 'class-variance-authority';
import { forwardRef } from 'react';
import {
Menu as AriaMenu,
MenuItem as AriaMenuItem,
MenuTrigger,
composeRenderProps,
} from 'react-aria-components';

import styles from './styles/Menu.module.css';

type MenuProps<T> = AriaMenuProps<T>;

const menu = cva(styles.menu);
const item = cva(styles.item);

const _Menu = <T extends object>(
{ className, ...props }: MenuProps<T>,
ref: ForwardedRef<HTMLDivElement>
) => {
return <AriaMenu {...props} ref={ref} className={menu({ className })} />;
};

/**
* A menu displays a list of actions or options that a user can choose.
*
* https://react-spectrum.adobe.com/react-aria/Menu.html
*/
const Menu = forwardRef(_Menu);

const _MenuItem = <T extends object>(
props: MenuItemProps<T>,
ref: ForwardedRef<HTMLDivElement>
) => {
return (
<AriaMenuItem
{...props}
ref={ref}
className={composeRenderProps(props.className, (className, renderProps) =>
item({ ...renderProps, className })
)}
>
{composeRenderProps(props.children, (children, { selectionMode, isSelected }) => (
<>
{selectionMode !== 'none' && (
<span className={styles.check}>{isSelected && <Icon name="check" size="small" />}</span>
)}
{children}
</>
))}
</AriaMenuItem>
);
};

/**
* A MenuItem represents an individual action in a Menu.
*/
const MenuItem = forwardRef(_MenuItem);

export { Menu, MenuItem, MenuTrigger };
export type { MenuProps, MenuItemProps, MenuTriggerProps };
6 changes: 6 additions & 0 deletions packages/components/src/Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { SectionProps } from 'react-aria-components';

import { Section } from 'react-aria-components';

export { Section };
export type { SectionProps };
6 changes: 6 additions & 0 deletions packages/components/src/Separator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { SeparatorProps } from 'react-aria-components';

import { Separator } from 'react-aria-components';

export { Separator };
export type { SeparatorProps };
6 changes: 6 additions & 0 deletions packages/components/src/Text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { TextProps } from 'react-aria-components';

import { Text } from 'react-aria-components';

export { Text };
export type { TextProps };
Loading

0 comments on commit 36d8a2d

Please sign in to comment.