Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/src/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Combobox from '../../packages/combobox/docs/Combobox.mdx';
import Button from '../../packages/button/docs/Button.mdx';
import Slider from '../../packages/slider/docs/Slider.mdx';
import Box from '../../packages/box/docs/Box.mdx';
import Expandable from '../../packages/expandable/docs/Expandable.mdx';

const components = {
PackageInfo,
Expand Down Expand Up @@ -114,6 +115,10 @@ const App = () => {
<Route path="/box">
<Box />
</Route>

<Route path="/expandable">
<Expandable />
</Route>
</Switch>
</main>
<footer className="mt-20 text-right">
Expand Down
1 change: 1 addition & 0 deletions docs/src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function Nav() {

<NavCategory title="Layout">
<StyledLink to="/box">Box</StyledLink>
<StyledLink to="/expandable">Expandable</StyledLink>
</NavCategory>
</div>
</nav>
Expand Down
2 changes: 1 addition & 1 deletion packages/box/stories/Box.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Box } from '../src';

const metadata = { title: 'Other/Box' };
const metadata = { title: 'Layout/Box' };
export default metadata;

export const Default = () => (
Expand Down
Empty file added packages/expandable/README.md
Empty file.
77 changes: 77 additions & 0 deletions packages/expandable/docs/Expandable.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Expandable } from '../src';
import packageData from '../package.json';

# Expandable

Expandable is a layout component used for creating expandable content areas on a
page.

<PackageInfo packageData={packageData} componentNames={['Expandable']} />

## Visual Options

### Default

```jsx example
<Expandable title="I am expandable">
<p>Expandable contents go here.</p>
</Expandable>
```

### Expandable box

```jsx example
<Expandable title="I am expandable" box>
<p>Expandable contents go here.</p>
</Expandable>
```

### Expandable info box

```jsx example
<Expandable title="I am expandable" info box>
<p>Expandable contents go here.</p>
</Expandable>
```

### onChange event

```jsx example
<Expandable title="I am expandable" onChange={(state) => console.log(state)}>
<h1>onChange example</h1>
<p>Expandable contents go here.</p>
</Expandable>
```

### The expanded prop

You can set whether the component is open or collapsed using the `expanded`
prop.

```jsx example
<Expandable title="I am initially..." expanded>
<p>...expanded</p>
</Expandable>
```

### Controlling the component

If you need to take control of expansion, use the `onChange` event in
combination with the `expanded` prop

```jsx example
function Example() {
const [open, setOpen] = React.useState(true);
return (
<Expandable title="Expandable box" box info expanded={open} onChange={setOpen}>
<p>I am expanded</p>
</Expandable>
);
}
```

## Props

```props packages/expandable/src/component.tsx

```
26 changes: 26 additions & 0 deletions packages/expandable/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@finn-no/fabric-react-expandable",
"license": "ISC",
"version": "0.0.1",
"type": "module",
"exports": "./dist/index.js",
"module": "dist/index.js",
"files": [
"dist/"
],
"scripts": {
"build": "microbundle -i src/index.ts -o dist/index.js --jsx React.createElement --format modern --no-pkg-main",
"prepublishOnly": "yarn build"
},
"types": "dist/index.d.ts",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@chbphone55/classnames": "^2.0.0",
"@finn-no/fabric-component-classes": "^0.0.16"
},
"peerDependencies": {
"react": "^17.0.0"
}
}
100 changes: 100 additions & 0 deletions packages/expandable/src/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { classNames } from '@chbphone55/classnames';
import {
buttonReset,
box as boxClasses,
} from '@finn-no/fabric-component-classes';
import type { ExpandableProps } from './props';

const setup = ({
title = '',
info = false,
box = false,
bleed = false,
buttonClass = '',
contentClass = '',
expanded = false,
className,
...attrs
}: any) => ({
...attrs,
wrapperClasses: classNames(className, {
'bg-aqua-50': info,
['py-0 px-0 ' + boxClasses.box]: box,
[boxClasses.bleed]: bleed,
}),
buttonClasses: classNames({
[buttonClass || '']: true,
[buttonReset + ' hover:underline focus:underline']: true,
['w-full text-left relative ' + boxClasses.box]: box,
'hover:text-aqua-700 active:text-aqua-800': info,
}),
chevronClasses: classNames({
'inline-block align-middle transform transition-transform': true,
'-rotate-180': expanded,
'relative left-8': !box,
'box-chevron absolute right-16': box,
}),
contentClasses: classNames({
[contentClass || '']: true,
[boxClasses.box + (title ? ' pt-0' : '')]: box,
}),
title,
});

export function Expandable(props: ExpandableProps) {
const { children, expanded = false, onChange, ...rest } = props;
const [stateExpanded, setStateExpanded] = React.useState(expanded);
const {
wrapperClasses,
buttonClasses,
contentClasses,
chevronClasses,
title,
chevron = true,
...attrs
} = setup({ expanded: stateExpanded, ...rest });

const toggleExpandable = (state) => {
setStateExpanded(!state);
if (onChange) onChange(!state);
};

return (
<div {...attrs} className={wrapperClasses}>
<button
aria-expanded={stateExpanded}
className={buttonClasses}
onClick={() => toggleExpandable(stateExpanded)}
>
{title && <span className="h4">{title}</span>}
{chevron && (
<div className={chevronClasses}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 16 16"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M2.5 5.5L8 11l5.5-5.5"
/>
</svg>
</div>
)}
</button>
{stateExpanded && (
<div>
<div className="overflow-hidden">
<div className={contentClasses}>{children}</div>
</div>
</div>
)}
</div>
);
}
2 changes: 2 additions & 0 deletions packages/expandable/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Expandable } from './component';
export type { ExpandableProps } from './props';
65 changes: 65 additions & 0 deletions packages/expandable/src/props.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as React from 'react';

export type ExpandableProps = {
children: React.ReactNode;

/**
* Additional classes to include
*/
className?: string;

/**
* CSS styles to inline on the component
*/
style?: React.CSSProperties;

/**
* Toggles bleed, makes a box full-width on mobile
* @default false
*/
bleed?: boolean;

/**
* Styles the box with light blue color
* @default false
*/
info?: boolean;

/**
* The state of the component, either true for expanded or false for closed.
* @default false
*/
expanded?: boolean;

/**
* Event function to be called any time the component is expanded or closed. Function will be passed a boolean with a value of true if the component is now expanded or false if it is now closed.
*/
onChange?: (state: boolean) => void;

/**
* Component title. Can be a string or component. Used to display the title value which is always present regardless of whether the component is open or closed.
*/
title: React.ReactNode;

/**
* Whether to display the component as a padded box or not.
* @default false
*/
box?: boolean;

/**
* Additional CSS classes to include on the button part of the component
*/
buttonClass?: string;

/**
* Additional CSS classes to include on the content part of the component
*/
contentClass?: string;

/**
* Whether to display the chevron on the button part of the component
* @default true
*/
chevron?: boolean;
};
53 changes: 53 additions & 0 deletions packages/expandable/stories/Expandable.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import { Expandable } from '../src';

const metadata = { title: 'Layout/Expandable' };
export default metadata;

export const Default = () => (
<Expandable title="This is a title">
<h1>I am expandable</h1>
</Expandable>
);

export const Box = () => (
<Expandable title="This is a title" box>
<h1>I am expandable</h1>
</Expandable>
);

export const InfoBox = () => (
<Expandable title="This is a title" box info>
<h1>I am expandable</h1>
</Expandable>
);

export const RedBox = () => (
<Expandable title="This is a title" box className="bg-red-50">
<h1>I am expandable</h1>
</Expandable>
);

export const GreenButton = () => (
<Expandable title="This is a title" box className="bg-green-50" buttonClass="hover:text-green-700">
<h1>I am expandable</h1>
</Expandable>
);

export const Controlled = () => {
const [open, setOpen] = React.useState(false);
return (
<Expandable title={open ? 'Open' : 'Closed'} box info onChange={setOpen}>
<h1>I am expandable</h1>
</Expandable>
);
};

export const NoChevron = () => {
const [open, setOpen] = React.useState(false);
return (
<Expandable title={open ? 'Open' : 'Closed'} box info chevron={false} onChange={setOpen}>
<h1>I am expandable</h1>
</Expandable>
);
};
7 changes: 7 additions & 0 deletions packages/expandable/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"]
}