npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

p14-ui-kit

v1.7.3

Published

A react component library

Downloads

15

Readme

Overview

p14-ui-kit is a library exporting 4 elements (3 components and 1 hook).
Those 4 elements are related to the completion of the 14th OpenClassRooms project.

Instalation

npm install p14-ui-kit

or

yarn add p14-ui-kit

import

import { useTable, Datepicker, ModalProvider, Modal, Select } from "p14-ui-kit";

useTable

Overview

useTable is a headless table utility not a table component.
As such it doesn't provide any front-end so it perfectly fits into any application.

QuickStart

useTable return a object containing everything you will need to build a table and intereact with it's state.

Getting data

const data = React.useMemo(
  () => [
    {
      score: 0.88037086,
      show: {
        name: "Snow",
        type: "Scripted",
        language: "English",
        genres: ["Comedy", "Family", "Fantasy"],
      },
    },
    {
      score: 0.65440583,
      show: {
        name: "Snow Queen",
        type: "Scripted",
        language: "English",
        genres: ["Adventure", "Family", "Fantasy"],
      },
    },
    {
      score: 0.64544654,
      show: {
        name: "Snow Lotus",
        type: "Scripted",
        language: "Korean",
        genres: ["Drama", "Fantasy", "Romance"],
      },
    },
    {
      score: 0.6404078,
      show: {
        name: "Snow White",
        type: "Scripted",
        language: "Korean",
        genres: ["Drama", "Comedy", "Romance"],
      },
    },
  ],
  []
);

Define Columns

const columns = React.useMemo(
  () => [
    {
      Header: "Score",
      accessor: "score",
    },
    {
      Header: "Name",
      accessor: "show.name",
    },
    {
      Header: "Type",
      accessor: "show.type",
    },
    {
      Header: "Language",
      accessor: "show.language",
    },
    {
      Header: "Genre(s)",
      accessor: "show.genres",
    },
  ],
  []
);

Using the useTable hook

const { headerGroups, rows } = useTable({ columns, data });

Table display :

return (
  <table>
    <thead>
      {headerGroups.map((headerGroup) => (
        <tr {...headerGroup.getHeaderGroupProps()}>
          {headerGroup.headers.map((column) => (
            <th {...column.getHeaderProps()}>{column.header}</th>
          ))}
        </tr>
      ))}
    </thead>
    <tbody>
      {rows.map((row, i) => (
        <tr {...row.getRowProps()}>
          {row.cells.map((cell) => {
            return <td {...cell.getCellProps()}>{cell.value}</td>;
          })}
        </tr>
      ))}
    </tbody>
  </table>
);

Sorted Table

We keep the same data but we will add more definitions on columns and the useTable call.

useTable have build-in fonctions to sort numbers and string. For other type of data we can specify a sort function.
In this exemples we will sort Genres by array length.

We could also specify a get function to change the display of the value for exemple we want "/" instead of the default "," array speration.

const columns = React.useMemo(
  () => [
    {
      Header: "Score",
      accessor: "score",
    },
    {
      Header: "Name",
      accessor: "show.name",
    },
    {
      Header: "Type",
      accessor: "show.type",
    },
    {
      Header: "Language",
      accessor: "show.language",
    },
    {
      Header: "Genre(s)",
      accessor: "show.genres",
      sortFn: (a, b) => a.length - b.length,
      getFn: (array) => array.join("/"),
    },
  ],
  []
);
const { headerGroups, rows, setSortBy } = useTable({ columns, data, useSortBy: true });

Notice we now have acces to setSortBy it can be used to sort one or multiple columns at once.
It's accept an array of object containing the column accesor to be sorted with it's order.

Here is a exemple setting the sortBy property as an initial state for our table.
(The table will be sorted by default).

const { headerGroups, rows, setSortBy } = useTable({
  columns,
  data,
  useSortBy: true,
  initalState: { sortBy: [{ accessor: "show.genres", desc: false }] },
});

Now we can also use more props and data on our columns to display the sorting state and to sort a column on click.

return (
  <table>
    <thead>
      {headerGroups.map((headerGroup) => (
        <tr {...headerGroup.getHeaderGroupProps()}>
          {headerGroup.headers.map((column) => (
            <th {...column.getHeaderProps()} {...column?.getSortByToggleProps()}>
              {column.header} <span>{column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : ""}</span>
            </th>
          ))}
        </tr>
      ))}
    </thead>
    <tbody>
      {rows.map((row, i) => (
        <tr {...row.getRowProps()}>
          {row.cells.map((cell) => {
            return <td {...cell.getCellProps()}>{cell.value}</td>;
          })}
        </tr>
      ))}
    </tbody>
  </table>
);

Table with pagination

We keep the same data but we will add one more definition on the useTable call.

const {
  headerGroups,
  rows,
  // Pagination
  page,
  pageSize,
  pageCount,
  canPreviousPage,
  canNextPage,
  goToPage,
  previousPage,
  nextPage,
  setPageSize,
} = useTable({ columns, data: shows, usePagination: true });

Then we display the table with pagination.

return (
  <>
    <table>
      <thead>
        {headerGroups.map((headerGroup) => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map((column) => (
              <th {...column.getHeaderProps()}>{column.header}</th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {rows.map((row, i) => (
          <tr {...row.getRowProps()}>
            {row.cells.map((cell) => {
              return <td {...cell.getCellProps()}>{cell.value}</td>;
            })}
          </tr>
        ))}
      </tbody>
    </table>
    <div className="pagination">
      <button onClick={() => goToPage(0)} disabled={!canPreviousPage}>
        {"<<"}
      </button>
      <button onClick={() => previousPage()} disabled={!canPreviousPage}>
        {"<"}
      </button>
      <button onClick={() => nextPage()} disabled={!canNextPage}>
        {">"}
      </button>
      <button onClick={() => goToPage(pageCount)} disabled={!canNextPage}>
        {">>"}
      </button>
      <span>
        Page{" "}
        <strong>
          {page} of {pageCount}
        </strong>
      </span>
      <span>
        | Go to page:{" "}
        <input
          type="number"
          defaultValue={page}
          onChange={(e) => goToPage(Number(e.target.value))}
          style={{ width: "100px" }}
        />
      </span> <select
        value={pageSize}
        onChange={(e) => {
          setPageSize(Number(e.target.value));
        }}
      >
        {[1, 2, 5, 10, 20].map((pageSize) => (
          <option key={pageSize} value={pageSize}>
            Show {pageSize}
          </option>
        ))}
      </select>
    </div>
  </>
);

We can also specify the inital page and per page number to display.

const {
  headerGroups,
  rows,
  // Pagination
  page,
  pageSize,
  pageCount,
  canPreviousPage,
  canNextPage,
  goToPage,
  previousPage,
  nextPage,
  setPageSize,
} = useTable({ columns, data: shows, usePagination: true, initalState: { page: { number: 1, size: 10 } } });

Multiple headers layers

Columns definition also support nested layers of headers.

const columns = React.useMemo(
  () => [
    {
      Header: "Score / Name",
      columns: [
        {
          Header: "Score",
          accessor: "score",
        },
        {
          Header: "Name",
          accessor: "show.name",
        },
      ],
    },
    {
      Header: "Type",
      accessor: "show.type",
    },
    {
      Header: "Language",
      accessor: "show.language",
    },
    {
      Header: "Genre(s)",
      accessor: "show.genres",
    },
  ],
  []
);

useTable will automaticly calculate colSpan and other props.
So you can still use any of the previously given method of table rendering.

Datepicker

The Datepicker component use by default a read only text input. You can pass any props to this input but some will be erased like value, onFocus and onClick.

return (
  <label htmlFor="date-of-birth">Date of Birth</label>
  <Datepicker inputProps={{ id: "date-of-birth", name: "date-of-birth" }} />
);

In this exemple we can retrive the Datepicker value like any other input inside a form (using formData for exemple).

Controlled

You can also use this datePicker like a controlled input by using onChange and selected prop.

const [dateOfBirth, setDateOfBirth] = React.useState(new Date());
return (
  <label htmlFor="date-of-birth">Date of Birth</label>
  <Datepicker selected={dateOfBirth} onChange={setDateOfBirth} inputProps={{ id: "date-of-birth", name: "date-of-birth" }} />
);

Changing labels

const dayLabels = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
const monthLabels = [
  "Janvier",
  "Février",
  "Mars",
  "Avril",
  "Mai",
  "Juin",
  "Juillet",
  "Août",
  "Septembre",
  "Octobre",
  "Novembre",
  "Décembre",
];
return (
  <label htmlFor="date-of-birth">Date of Birth</label>
  <Datepicker dayLabels={dayLabels} motnhLabels={monthLabels} inputProps={{ id: "date-of-birth", name: "date-of-birth" }} />
);

Specify selectable years

const years = [1998, 1999, 2000];

For big ranges you can also create a function.

const range = (start, end) => {
  const array = [];
  for (let i = start; i <= end; i++) {
    array.push(i);
  }
  return array;
};
const years = range(1998, 2050);
return (
  <label htmlFor="date-of-birth">Date of Birth</label>
  <Datepicker years={years} inputProps={{ id: "date-of-birth", name: "date-of-birth" }} />
);

Custom Input

Custom input can be any html element.
If you want to learn more about forwardRef here is the React Documentation.

const customInput = React.forwardRef(({ value, ...props }, ref) => (
  <button {...props} ref={ref}>
    {value}
  </button>
));
return (
  <label htmlFor="date-of-birth">Date of Birth</label>
  <Datepicker customInput={customInput} inputProps={{ id: "date-of-birth", name: "date-of-birth" }} />
);

Styling

You can style the input and the date picker by using inputProps and datepickerProps respectively.

Input

You can pass the style prop to the input to modify the default style or you can erase it by specifying a className.

return (
  <label htmlFor="date-of-birth">Date of Birth</label>
  <Datepicker inputProps={{ id: "date-of-birth", name: "date-of-birth" , style : {}, className: "Custom input"}} />
);

Datepicker

The recommended way to style the datepciker is by specifying a className, this className is used as a base to create childrens classNames. Here is a list of generated classNames using datepicker as a start.

datepicker
datepicker__header
datepicker__chevron
datepicker__dayLabels
datepicker__dayNumbers
datepicker__selectedDay
datepicker__hoverDay
return (
  <label htmlFor="date-of-birth">Date of Birth</label>
  <Datepicker datepickerProps={{ className: "datepicker" }} inputProps={{ id: "date-of-birth", name: "date-of-birth" }} />
);

Select

The Select component use text input as such id and name props are recommended.

First we create a options composed of values and labels.

const options = [
  {
    label: "Alabama",
    value: "AL",
  },
  {
    label: "Alaska",
    value: "AK",
  },
  {
    label: "American Samoa",
    value: "AS",
  },
  {
    label: "Arizona",
    value: "AZ",
  },
];

The recommended way to use Select is like a controlled input, it help getting the value instead of the label.

const [selectedState, setSelectedState] = useState("");
return (
  <>
    <label htmlFor="state">State</label>
    <Select id="state" name="state" options={states} value={selectedState} onChange={setSelectedState} />
  </>
);

Seachable input

You can make a input searchable by passing isSearchable prop.

const [selectedState, setSelectedState] = useState("");
return (
  <>
    <label htmlFor="state">State</label>
    <Select isSearchable id="state" name="state" options={states} value={selectedState} onChange={setSelectedState} />
  </>
);

Placeholder

By default the placeholder is "Select..." you can change it by using the placeHolder prop.

const [selectedState, setSelectedState] = useState("");
return (
  <>
    <label htmlFor="state">State</label>
    <Select
      placeHolder="Select state..."
      id="state"
      name="state"
      options={states}
      value={selectedState}
      onChange={setSelectedState}
    />
  </>
);

Styling

ClassName

You can pass your className as a prop, this className is used as a base to create childrens classNames. Here is a list of generated classNames using select as a start.

select
select--active
select__input
select__selectOptions
select__selectOption
select__selectOption--hover
const [selectedState, setSelectedState] = useState("");
return (
  <>
    <label htmlFor="state">State</label>
    <Select
      id="state"
      name="state"
      className="select"
      options={states}
      value={selectedState}
      onChange={setSelectedState}
    />
  </>
);

Style object

You can also pass a styles object that will be dispached to coresponding child elements.
Here is the structure of this object:

const styles = {
  container: {},
  input: {},
  options: {},
  option: {},
};

Modal

The modal component need a parent ModalProvider to be used, this provider will reorder elements and put any children modal to the top.

const [isVisible, setIsVisible] = useState(false);
return (
  <ModalProvider>
    <h1>A nice title</h1>
    <button onClick={() => setIsVisible(true)}>Open Modal</button>
    <Modal isVisible={isVisible} setIsVisible={setIsVisible}>
      <h1>Modal Content</h1>
    </Modal>
  </ModalProvider>
);

So the modal and it's content will be first in the dom then the tittle and button will follow.

Close button

The default close button can be hidden by using displayCloseButton prop then you can implement your own button and styles.

const [isVisible, setIsVisible] = useState(false);
return (
  <ModalProvider>
    <h1>A nice title</h1>
    <button onClick={() => setIsVisible(true)}>Open Modal</button>
    <Modal isVisible={isVisible} displayCloseButton={false}>
      <button onClick={() => setIsVisible(false)}>&times;</button>
      <h1>Modal Content</h1>
    </Modal>
  </ModalProvider>
);

You can also keep the default close button and modify is style and behavior by passing the buttonProps prop.

const [isVisible, setIsVisible] = useState(false);
return (
  <ModalProvider>
    <h1>A nice title</h1>
    <button onClick={() => setIsVisible(true)}>Open Modal</button>
    <Modal
      isVisible={isVisible}
      buttonProps={{ onClick: () => console.log("button clicked"), className: "closeModalButton" }}
    >
      <h1>Modal Content</h1>
    </Modal>
  </ModalProvider>
);

Modal container props

The Modal container is a simple div as such it can accept any div props directly.
Passing className to the Modal component will erase is default styling.