Skip to content

Commit

Permalink
#65 Separate components from IntlProvider dependency (#66)
Browse files Browse the repository at this point in the history
* #65 Refactor useIntlContext

* #65 Refactor Pagination to include Intl

* #65 Refactor useLabel to exclude Intl if not present

* #65 Add fallback to useLabel, improve naming

* #65 Refactor Date

* #65 Improve Intl utilities & extend intlDictionary

* #65 Improve IntlProvider independence for date inputs

* #65 Improve IntlProvider independence for remaining components

* #65 Update docs

* #65 Add autocompleteAddItem key to defaultKeyConfig

* #65 Improve NumberInput, useIntlContext and docs

* #65 Improve Date component

---------

Co-authored-by: Felix Beceic <[email protected]>
  • Loading branch information
fbeceic and Felix Beceic authored Apr 26, 2023
1 parent e1b0e4f commit 9da6342
Show file tree
Hide file tree
Showing 54 changed files with 633 additions and 380 deletions.
78 changes: 59 additions & 19 deletions libs/core/src/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,42 @@ import * as React from "react";

import { range, slice } from "lodash";

import { Intl, useIntlContext } from "@tiller-ds/intl";
import { ComponentTokens, cx, useIcon, useTokens } from "@tiller-ds/theme";
import { useViewport } from "@tiller-ds/util";

import ButtonGroups from "./ButtonGroups";

import { useViewport } from "@tiller-ds/util";

export type PaginationProps = {
/**
* Complex values of types PageInfo and Components for formatting purposes.
* Enables you to handle the page summary text next to the pagination by using the provided values
* of types:
* - **PageInfo** (number values _from_, _to_ and _totalElements_) and/or
* - **Components** (React.ReactNode values _from_, _to_ and _totalElements_ with 'font-medium' class name applied).
*
* @example
* <Pagination {...paginationState} {...paginationHook}>
* {(_pageInfo, components) => (
* <span>
* {components.from}-{components.to} from {components.totalElements}
* </span>
* )}
* </Pagination>
*/
children?: (pageInfo: PageInfo, components: Components) => React.ReactNode;

/**
* Custom function activated when the page changes (takes the current page as a parameter)
* Custom additional class name for the main component.
*/
className?: string;

/**
* Custom icon for going to the next page.
*/
nextIcon?: React.ReactElement;

/**
* Custom function activated when the page changes (takes the current page as a parameter).
*/
onPageChange?: (page: number) => void;

Expand All @@ -42,28 +64,29 @@ export type PaginationProps = {
pageNumber: number;

/**
* Defines the size of each page (number of shown elements on each page).
* Defines a calculator for displaying page numbers.
*/
pageSize: number;
pagerCalculator?: (pageNumber: number, pageCount: number) => PagerPage[];

/**
* Defines the total number of elements on a source that the component is hooked up to.
* Enables or disables the page summary text next to the pagination.
*/
totalElements: number;
pageSummary?: boolean;

/**
* Defines a calculator for displaying page numbers.
* Defines the size of each page (number of shown elements on each page).
*/
pagerCalculator?: (pageNumber: number, pageCount: number) => PagerPage[];
pageSize: number;

/**
* Custom icon for going to the previous page.
*/
previousIcon?: React.ReactElement;

nextIcon?: React.ReactElement;

/**
* Custom additional class name for the main component.
* Defines the total number of elements on a source that the component is hooked up to.
*/
className?: string;
totalElements: number;
} & PaginationTokensProps;

type PaginationTokensProps = {
Expand Down Expand Up @@ -130,7 +153,7 @@ export function useLocalPagination<T>(data: T[], pageSize = 10): [PaginationStat
}

export default function Pagination(props: PaginationProps) {
const { pageNumber, pageSize, totalElements } = props;
const { pageNumber, pageSize, totalElements, pageSummary = true } = props;
const tokens = useTokens("Pagination", props.tokens);

const calculatedProps: CalculatedProps = {
Expand All @@ -140,15 +163,17 @@ export default function Pagination(props: PaginationProps) {

return (
<section className={tokens.master}>
<PageSummary {...props} />
{pageSummary && <PageSummary {...props} />}
<Pager {...props} {...calculatedProps} />
</section>
);
}

function PageSummary({ pageNumber, pageSize, totalElements, children, ...props }: PaginationProps) {
const calculatedFrom = pageNumber * pageSize;
const intl = useIntlContext();
const tokens = useTokens("Pagination", props.tokens);

const calculatedFrom = pageNumber * pageSize;
// user counting starts from 1
const from = totalElements === 0 ? calculatedFrom : calculatedFrom + 1;
const to = Math.min(totalElements, (pageNumber + 1) * pageSize);
Expand All @@ -162,13 +187,28 @@ function PageSummary({ pageNumber, pageSize, totalElements, children, ...props }
if (children) {
return children(
{ pageNumber, pageSize, totalElements, from, to },
{ from: fromElement, to: toElement, totalElements: totalElement }
{ from: fromElement, to: toElement, totalElements: totalElement },
) as React.ReactElement;
}

return (
<p className={pageSummaryClassName}>
Showing {fromElement} to {toElement} of {totalElement} results
{intl ? (
<Intl
name={intl.commonKeys["paginationSummary"] as string}
params={{ from: fromElement, to: toElement, total: totalElement }}
>
{{
from: () => fromElement,
to: () => toElement,
total: () => totalElement,
}}
</Intl>
) : (
<>
Showing {fromElement} to {toElement} of {totalElement} results
</>
)}
</p>
);
}
Expand Down
28 changes: 27 additions & 1 deletion libs/date/src/Date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,42 @@

import * as React from "react";

import * as dateFns from "date-fns";
import { FormattedDate } from "react-intl";

import { useIntlContext } from "@tiller-ds/intl";

export type DateProps = {
/**
* Data handed to the component in a valid date format.
*
* If using the component **with** IntlProvider you don't need to pass the _format_ prop, the
* format will be inferred from the locale.
*
* If using the component **without** IntlProvider, you must also pass the _format_ prop.
*
* Other props come from Intl.DateTimeFormatOptions (https://bit.ly/3urm8s5)
*/
children: Date;

/**
* Format of formatted date (e.g. 'dd. MM. yyyy.' or 'MM/dd/yyyy').
*
* If used, the component will format the date in this format, regardless of the locale inferred from IntlProvider (if it exists).
*/
format?: string;
} & Intl.DateTimeFormatOptions;

export default function Date({ children, ...props }: DateProps) {
export default function Date({ children, format, ...props }: DateProps) {
const intl = useIntlContext();

if (!intl && !format) {
throw new Error("You must pass the format prop if you are using the Date component without IntlProvider.");
}

if (format) {
return <>{dateFns.format(children, format)}</>;
}

return <FormattedDate value={children} {...props} />;
}
5 changes: 3 additions & 2 deletions libs/date/src/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export default function DateInput({
}: DateInputProps) {
const inputRef = React.useRef<HTMLInputElement>(null);
const datePickerRef = React.useRef<HTMLDivElement>(null);
const { lang } = useIntlContext();
const lang = useIntlContext()?.lang || "en";

const finalDateFormat = dateFormat?.replace(/m/g, "M") || getDateFormatByLang(lang);
const formattedValue = value ? dateFns.format(value, finalDateFormat) : "";
Expand Down Expand Up @@ -314,7 +314,8 @@ function DateInputInput({
dateFormat,
...props
}: DateInputInputProps) {
const { lang } = useIntlContext();
const lang = useIntlContext()?.lang || "en";

const tokens = useTokens("DateInput", props.tokens);

const dateIconClassName = cx({ [tokens.DatePicker.range.iconColor]: !(props.disabled || props.readOnly) });
Expand Down
16 changes: 10 additions & 6 deletions libs/date/src/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@

import * as React from "react";

import * as dateFns from "date-fns";
import { useDatepicker, useDay, useMonth, UseMonthProps } from "@datepicker-react/hooks";
import { useIntl } from "react-intl";

import { useIntlContext } from "@tiller-ds/intl";
import { cx, TokenProps, useIcon, useTokens } from "@tiller-ds/theme";

type DatePickerProps = {
Expand Down Expand Up @@ -354,13 +355,14 @@ function YearsPickerContainer({ ...props }) {

function MonthPickerLabels({ month, year, ...props }: MonthPickerLabelsProps) {
const tokens = useTokens("DateInput", props.tokens);
const intl = useIntl();
const intlContext = useIntlContext();
const { onDayPickerToggle, dayPicker, onActiveMonthToggle } = useDatePickerContext();

const { monthLabel } = useMonth({
year,
month,
monthLabelFormat: (date: Date) => intl.formatDate(date, { month: "long" }),
monthLabelFormat: (date: Date) =>
intlContext?.intl.formatDate(date, { month: "long" }) || dateFns.format(date, "MMMM"),
});

const onChevronDownClick = () => {
Expand Down Expand Up @@ -396,14 +398,16 @@ function MonthPickerLabels({ month, year, ...props }: MonthPickerLabelsProps) {
}

function MonthPicker({ year, month, firstDayOfWeek }: MonthPickerProps) {
const intl = useIntl();
const intlContext = useIntlContext();

const { days, weekdayLabels } = useMonth({
year,
month,
firstDayOfWeek,
monthLabelFormat: (date: Date) => intl.formatDate(date, { month: "long" }),
weekdayLabelFormat: (date: Date) => intl.formatDate(date, { weekday: "short" }),
monthLabelFormat: (date: Date) =>
intlContext?.intl.formatDate(date, { month: "long" }) || dateFns.format(date, "MMMM"),
weekdayLabelFormat: (date: Date) =>
intlContext?.intl.formatDate(date, { weekday: "short" }) || dateFns.format(date, "EEEEEE"),
});

return (
Expand Down
4 changes: 2 additions & 2 deletions libs/date/src/DateRangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export default function DateRangeInput({
highlightToday,
...props
}: DateRangeInputProps) {
const { lang } = useIntlContext();
const lang = useIntlContext()?.lang || "en";

const finalDateFormat = dateFormat?.replace(/m/g, "M") || getDateFormatByLang(lang);
const formattedStart = start ? dateFns.format(start, finalDateFormat) : "";
Expand Down Expand Up @@ -427,7 +427,7 @@ function DateRangeInputInput({
dateFormat,
...props
}: DateRangeInputInputProps) {
const { lang } = useIntlContext();
const lang = useIntlContext()?.lang || "en";

const tokens = useTokens("DateInput", props.tokens);
const dateIconClassName = cx({ [tokens.DatePicker.range.iconColor]: !(props.disabled || props.readOnly) });
Expand Down
2 changes: 1 addition & 1 deletion libs/date/src/DateTimeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export default function DateTimeInput({
highlightToday,
...props
}: DateTimeInputProps & DateTimeInputTokens) {
const { lang } = useIntlContext();
const lang = useIntlContext()?.lang || "en";

const dateTimePickerTokens = useTokens("DateTimePicker", props.dateTimePickerTokens);
const dateTimeInputTokens = useTokens("DateTimeInput", props.dateTimeInputTokens);
Expand Down
3 changes: 0 additions & 3 deletions libs/date/src/TimeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import Popover, { positionMatchWidth } from "@reach/popover";

import { IconButton } from "@tiller-ds/core";
import { defaultPlaceholderChar, InputProps, MaskedInput } from "@tiller-ds/form-elements";
import { useIntlContext } from "@tiller-ds/intl";
import { ComponentTokens, cx, useIcon, useTokens } from "@tiller-ds/theme";

import TimePicker, { ClockType, TimePickerProps } from "./TimePicker";
Expand Down Expand Up @@ -148,8 +147,6 @@ export default function TimeInput({
showMaskOnEmpty,
...props
}: TimeInputProps & TimeInputTokens) {
const { lang } = useIntlContext();

const isTwelveHours = type === "use12Hours";
const timeInputTokens = useTokens("TimeInput", props.timeInputTokens);
const inputTokens = useTokens("Input", props.inputTokens);
Expand Down
2 changes: 1 addition & 1 deletion libs/form-elements/src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export function FieldLabel({ id, label, required, tooltip, ...props }: FieldLabe
const inputTokens = useTokens("Input", props.inputTokens);
const fieldTokens = useTokens("Field", props.fieldTokens);

const requiredLabelText = useLabel("required");
const requiredLabelText = useLabel("required", "Required field");

return (
<Label id={id}>
Expand Down
4 changes: 2 additions & 2 deletions libs/form-elements/src/FieldGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function FieldGroup({
const fieldGroupTokens = useTokens("FieldGroup", props.fieldGroupTokens);
const inputTokens = useTokens("Input", props.inputTokens);

const requiredLabelText = useLabel("required");
const requiredLabelText = useLabel("required", "Required field");

let errorMessage = error;

Expand All @@ -102,7 +102,7 @@ function FieldGroup({
className,
fieldGroupTokens.Group.content.master,
{ [fieldGroupTokens.Group.content.horizontal]: !vertical },
{ [fieldGroupTokens.Group.content.vertical]: vertical }
{ [fieldGroupTokens.Group.content.vertical]: vertical },
);

return (
Expand Down
Loading

0 comments on commit 9da6342

Please sign in to comment.