Initial version of scrollable form panel (#45)
* initial version of a scrollable form component * added missing module parameter
This commit is contained in:
parent
c480f9cd9d
commit
de3d78384e
7 changed files with 138 additions and 2 deletions
20
src/components/scroll-form/FormPanel.tsx
Normal file
20
src/components/scroll-form/FormPanel.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import { Title } from '@patternfly/react-core';
|
||||
|
||||
import style from './form-panel.module.css';
|
||||
|
||||
interface FormPanelProps extends React.HTMLProps<HTMLFormElement> {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const FormPanel = (props: FormPanelProps) => {
|
||||
const { title, children, ...rest } = props;
|
||||
return (
|
||||
<section {...rest} className={style.panel}>
|
||||
<Title headingLevel="h4" size="xl" className={style.title}>
|
||||
{title}
|
||||
</Title>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
};
|
87
src/components/scroll-form/ScrollForm.tsx
Normal file
87
src/components/scroll-form/ScrollForm.tsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
import React, { Children, useEffect, useState } from 'react';
|
||||
import { Form, Grid, GridItem, Title } from '@patternfly/react-core';
|
||||
|
||||
import { FormPanel } from './FormPanel';
|
||||
import style from './scroll-form.module.css';
|
||||
|
||||
type ScrollFormProps = {
|
||||
sections: string[];
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const ScrollForm = ({ sections, children }: ScrollFormProps) => {
|
||||
const getCurrentSection = () => {
|
||||
for (let sectionName of sections) {
|
||||
const section = document.getElementById(sectionName)!;
|
||||
const startAt = section.offsetTop;
|
||||
const endAt = startAt + section.offsetHeight;
|
||||
const currentPosition =
|
||||
document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const isInView = currentPosition >= startAt && currentPosition < endAt;
|
||||
if (isInView) {
|
||||
return sectionName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [active, setActive] = useState(sections[0]);
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', () => {
|
||||
const active = getCurrentSection();
|
||||
if (active) {
|
||||
setActive(active);
|
||||
}
|
||||
});
|
||||
}, [active]);
|
||||
|
||||
const Nav = () => (
|
||||
<div className={style.sticky}>
|
||||
<Title headingLevel="h5" size="lg">
|
||||
Jump to Section
|
||||
</Title>
|
||||
<div className="pf-c-tabs pf-m-vertical">
|
||||
<ul className="pf-c-tabs__list">
|
||||
{sections.map((cat) => (
|
||||
<li
|
||||
className={
|
||||
'pf-c-tabs__item' + (active === cat ? ' pf-m-current' : '')
|
||||
}
|
||||
key={cat}
|
||||
>
|
||||
<button
|
||||
className="pf-c-tabs__link"
|
||||
id={`link-${cat}`}
|
||||
onClick={() =>
|
||||
document
|
||||
.getElementById(cat)
|
||||
?.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
>
|
||||
<span className="pf-c-tabs__item-text">{cat}</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const nodes = Children.toArray(children);
|
||||
return (
|
||||
<>
|
||||
<Grid hasGutter>
|
||||
<GridItem span={8}>
|
||||
<Form>
|
||||
{sections.map((cat, index) => (
|
||||
<FormPanel id={cat} key={cat} title={cat}>
|
||||
{nodes[index]}
|
||||
</FormPanel>
|
||||
))}
|
||||
</Form>
|
||||
</GridItem>
|
||||
<GridItem span={4}>
|
||||
<Nav />
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
11
src/components/scroll-form/form-panel.module.css
Normal file
11
src/components/scroll-form/form-panel.module.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
.panel {
|
||||
padding: 24px;
|
||||
border-style: solid;
|
||||
border-color: var(--pf-global--BorderColor--100);
|
||||
border-width: var(--pf-global--BorderWidth--sm);
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-bottom: 8px;
|
||||
}
|
5
src/components/scroll-form/scroll-form.module.css
Normal file
5
src/components/scroll-form/scroll-form.module.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
.sticky {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
}
|
|
@ -4,7 +4,7 @@ import { storiesOf } from '@storybook/react';
|
|||
import { ClientList } from '../src/clients/ClientList';
|
||||
import clientMock from '../src/clients/mock-clients.json';
|
||||
|
||||
storiesOf('Client list page')
|
||||
storiesOf('Client list page', module)
|
||||
.add('view', () => {
|
||||
return (<ClientList clients={clientMock} baseUrl="http://test.nl"/>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ import { storiesOf } from '@storybook/react';
|
|||
import { AlertPanel } from '../src/components/alert/AlertPanel';
|
||||
import { useAlerts } from '../src/components/alert/Alerts';
|
||||
|
||||
storiesOf('Alert Panel')
|
||||
storiesOf('Alert Panel', module)
|
||||
.add('api', () => <AlertPanel alerts={[{ key: 1, message: 'Hello', variant: AlertVariant.default }]} onCloseAlert={() => { }} />)
|
||||
.add('add alert', () => {
|
||||
const [add, alerts, hide] = useAlerts();
|
||||
|
|
13
stories/7-ScrollSpy.stories.js
Normal file
13
stories/7-ScrollSpy.stories.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import { ScrollForm } from "../src/components/scroll-form/ScrollForm";
|
||||
|
||||
storiesOf("Scroll Spy form", module).add("view", () => {
|
||||
return (
|
||||
<ScrollForm sections={["Revocation", "Clustering", "Fine grain stuff"]}>
|
||||
<div style={{ height: "2400px" }}>One</div>
|
||||
<div style={{ height: "2400px" }}>Two</div>
|
||||
<div style={{ height: "2400px" }}>fine grain</div>
|
||||
</ScrollForm>
|
||||
);
|
||||
});
|
Loading…
Reference in a new issue