1.7.3 • Published 1 year ago

p14-ui-kit v1.7.3

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

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.

1.7.3

1 year ago

1.7.2

1 year ago

1.7.1

1 year ago

1.7.0

1 year ago

1.6.4

2 years ago

1.3.7

2 years ago

1.6.3

2 years ago

1.3.6

2 years ago

1.6.2

2 years ago

1.6.1

2 years ago

1.6.0

2 years ago

1.5.0

2 years ago

1.4.1

2 years ago

1.4.0

2 years ago

1.3.10

2 years ago

1.3.9

2 years ago

1.3.8

2 years ago

1.3.5

2 years ago

1.3.4

2 years ago

1.3.3

2 years ago

1.3.2

2 years ago

1.3.1

2 years ago

1.3.0

2 years ago

1.2.2

2 years ago

1.2.1

2 years ago

1.2.0

2 years ago

1.1.0

2 years ago

1.0.0

2 years ago