From c5a0064571e4b082fcf8b61060f35952d37472fe Mon Sep 17 00:00:00 2001 From: Ivan Melnik Date: Mon, 18 Nov 2024 13:59:04 +0200 Subject: [PATCH] fix: types comments in components and utils (#195) First version of documentation --- README.md | 240 +++++++++++------- src/apollo/navigation/Navigation.tsx | 6 + src/apollo/page/Page.tsx | 6 + src/apollo/page/apolloPageContext.tsx | 6 + src/apollo/pageContent/PageContent.tsx | 3 + .../PageWithTemplateContent.tsx | 6 + src/common/components/container/Container.tsx | 6 + src/common/components/grid/Grid.tsx | 9 + .../components/htmlToReact/HtmlToReact.tsx | 12 + src/common/components/list/List.tsx | 6 + src/common/components/tag/Tag.tsx | 18 ++ src/common/components/text/Text.tsx | 15 ++ src/common/utils/dates.ts | 16 ++ src/common/utils/getIsValidHttpUrl.ts | 5 +- src/common/utils/testImage.ts | 5 + .../archiveSearchPage/ArchivePageMeta.tsx | 6 + .../archiveSearchPage/ArchiveSearchPage.tsx | 9 + .../ArchiveSearchPageContent.tsx | 48 ++++ src/core/card/Card.tsx | 69 +++++ src/core/card/LargeCard.tsx | 45 ++++ src/core/cardsList/CardsList.tsx | 9 + src/core/carousel/Carousel.tsx | 30 +++ src/core/carousel/utils/utils.ts | 21 ++ src/core/collection/Collection.tsx | 30 +++ src/core/collection/utils.ts | 28 ++ src/core/configProvider/ConfigProvider.tsx | 6 + src/core/configProvider/configContext.ts | 49 ++++ .../contentContainer/ContentContainer.tsx | 6 + src/core/hero/Hero.tsx | 21 ++ src/core/image/BackgroundImage.tsx | 12 + src/core/image/Image.tsx | 15 ++ src/core/imageGallery/ImageGallery.tsx | 15 ++ src/core/imageGallery/ImageGalleryContext.tsx | 21 ++ .../imageGallery/ImageGalleryProvider.tsx | 3 + src/core/imageGallery/ImageItem.tsx | 15 ++ src/core/imageGallery/ImagesGrid.tsx | 15 ++ src/core/imageGallery/Lightbox.tsx | 6 + src/core/link/Link.tsx | 30 +++ src/core/linkBox/LinkBox.tsx | 9 + src/core/notification/Notification.tsx | 3 + src/core/pageSection/PageSection.tsx | 21 ++ 41 files changed, 812 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index b4990e40..cfdf2641 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,104 @@ # React Helsinki Headless CMS -Demo: +## Introduction -## Quick Start +React Helsinki Headless CMS - is a highly customized component library based on [HDS](https://github.com/City-of-Helsinki/helsinki-design-system). It is designed for Helsinki City Web applications which are using preconfigured Wordpress Headless CMS environments (compatible with the library). This library is a set of unified visual components for Pages, Artciles, Artcicle Archives which provide: + +1. Unified designs for pages, layouts, and custom components across multiple applications. +2. Ability to pass app-specific configurations, such as translations and themes. +3. A set of components for visually presenting data from WordPress content modules and features. +4. A set of utilities, type and constants required for components implementation. +5. Support for required Apollo providers, such as linked events and venue search. **Note:** This library does not inject the Helsinki Grotesk font for you--you must add it yourself. **Note:** This library uses HDS design tokens through the SCSS interface so that mitch matching design token versions does not lead to unexpected results. -### 1. Install +### The known clients that are using this library + +- The city of Helsinki Events: https://tapahtumat.hel.fi | https://github.com/City-of-Helsinki/events-helsinki-monorepo +- The city of Helsinki Hobbies: https://harrastukset.hel.fi | https://github.com/City-of-Helsinki/events-helsinki-monorepo +- The city of Helsinki Sports: https://liikunta.hel.fi | https://github.com/City-of-Helsinki/events-helsinki-monorepo +- The city of Helsinki Kultus: https://kultus.fi | https://github.com/City-of-Helsinki/palvelutarjotin-ui +- The city of Helsinki Culture Kids: https://kummilapset.hel.fi/ | https://github.com/City-of-Helsinki/kukkuu-ui + +## Installation ```bash yarn add react-helsinki-headless-cms ``` -### 2. Import +## Development -`App.tsx` +**NOTE: The library is for general use and should not be developed for a single application environment only!** Check [the known clients](#the-known-clients-that-are-using-this-library) -```tsx -// ... -import { - Page, - PageContent, - ConfigProvider, - defaultConfig, -} from "react-helsinki-headless-cms"; +### When to develop -function App() { - const page = ...; +The general requirements for new Component development: - return ( - - ...} - getUrlForLanguage={(language, currentLanguage) => new URL(...) - } - /> - } - content={} - footer={...} - /> - - ); -} -``` +1. The new Component must be connected to an instance of the WordPress Headless CMS. +2. The Wordpress Headless CMS environment has new Component compatible architecture, features and data structure (component library is heavily dependent on the GraphQL schemas). +3. The new Component is not presented in [HDS](https://github.com/City-of-Helsinki/helsinki-design-system) or HDS cannot fully fulfill the specifications. +4. The new Component exists in HDS backlog, however, still it is not released by HDS team. In that case, HCRC new Component can be implemented and later must be replaced with HDS component when one is available. +5. New Component can be reused accross multiple applications. -## Use provided queries +### Available scripts -This can handle data queries for you if you are using a supported library to fetch your data. + +| Name | Purpose | Useful Options | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `yarn dev` | Starts storybook environment that can be used for developing components. | | +| `yarn typecheck` | Runs the ts type check in the project components. | | +| `yarn lint` | Lints the application to be according to quality standards (eslint) and formatting standards (prettier). | `--fix`: fix fixable problems | +| `yarn test` | Runs tests with jest. | `--watch`: enable watch mode | +| `yarn test-storybook` | Runs storybook accessibility tests jest. | | +| `yarn build` | Builds application with rollup. | | +| `yarn docker:dev` | Runs the application with docker with Development target environment. | | +| `yarn docker:prod` | Runs the application with docker with Production target environment. | | +| `yarn docker:down` | Shuts down the docker environment. | | +| `yarn publish-canary` | Publishes a canary tagged version of the application. CD is configured to run this script on additions to the main branch. | | +| `yarn publish-stable` | Publishes a stable tagged version of the application. CD is configured to run this script on additions to the main branch. | | +| `yarn generate:graphql` | Generates / updates GraphQL schema for the project. | | + +**NOTE: To manually publish a new version to the NPM, you will need the credentials that can be found from the City of Helsinki Culture and Leisure's Vault-service.** + +### Module structure + +This library consists of three modules. + +- Core module that includes data naive components. +- Apollo module that wraps core module components with logic that is able to fetch data with the help of an `ApolloClient` instance. +- Nextjs module that provides utilities when working with `Nextjs` and `Apollo`. + +### CI + +Checks + +- Tests pass +- Lint pass +- Build completes +- Type check pass + +### CD + +On additions to main, a canary version gets published to npm. + +On a new release, a new version is released to npm. + +### Storybook + +Storybook is a frontend workshop for building UI components and pages in isolation https://storybook.js.org/. The Storybook can be used to develop and to test the components, but also to document the components and their features. + +The project is using the Storybook 8. + +**NOTE: Storybook version 8 may require the `playwright-chromium` installation.** + +The `yarn dev` command will start `storybook` in port `6006`. When you make changes in `src`, they'll be automatically updated to `storybook`. ### Apollo +This can handle data queries for you if you are using a supported library to fetch your data. + By importing data dependent components from `react-helsinki-headless-cms/apollo`, this library will request the data for you. **Note:** An Apollo client linked to a graphql endpoint with a supported schema (headless CMS) must be provided in the `apolloClient` field of the `config` object. @@ -86,49 +129,6 @@ import { Navigation } from 'react-helsinki-headless-cms/apollo'; We provide utilities for fetching headless data for NextJs in `react-helsinki-headless-cms/nextjs`. These can be used when generating static pages. -## For Developers of Library - -**NOTE: The library is for general use and should not be developed for a single application environment only!** Check [the known clients](#the-known-clients-that-are-using-this-library) - -| Name | Purpose | Useful Options | -| --------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | -| `yarn dev` | Starts storybook environment that can be used for developing components. | | -| `yarn lint` | Lints the application to be according to quality standards (eslint) and formatting standards (prettier). | `--fix`: fix fixable problems | -| `yarn test` | Runs tests with jest. | `--watch`: enable watch mode | -| `yarn build` | Builds application with rollup. | | -| `yarn publish-canary` | Publishes a canary tagged version of the application. CD is configured to run this script on additions to the main branch. | | -| `yarn publish-stable` | Publishes a stable tagged version of the application. CD is configured to run this script on additions to the main branch. | | - -**NOTE: To manually publish a new version to the NPM, you will need the credentials that can be found from the City of Helsinki Culture and Leisure's Vault-service.** - -### Module structure - -This library consists of three modules. - -- Core module that includes data naive components. -- Apollo module that wraps core module components with logic that is able to fetch data with the help of an `ApolloClient` instance. -- Nextjs module that provides utilities when working with Nextjs and Apollo. - -### CI - -Checks - -- Tests pass -- Lint pass -- Build completes - -### CD - -On additions to main, a canary version gets published to npm. - -On a new release, a new version is released to npm. - -### Storybook - -Storybook is a frontend workshop for building UI components and pages in isolation https://storybook.js.org/. The Storybook can be used to develop and to test the components, but also to document the components and their features. - -The `yarn dev` command will start `storybook` in port `6006`. When you make changes in `src`, they'll be automatically updated to `storybook`. - ### Use as a application dependency The easiest way to test the React Helsinki Headless CMS -library is to install it as a dependency of an application by using a local relative path: https://docs.npmjs.com/cli/v9/configuring-npm/package-json#local-paths. @@ -158,16 +158,80 @@ This project uses `rollup` for its final bundle. A new version of the `npm` package is automatically released when a new release is created in GitHub. Additionally, a new canary release is created after each new push into master. -### Known issues +## Testing + +With new features introduced in Storybook version 7, this library is configured with `@storybook/addon-a11y` Axe Accessibility Plugin. + +The test could be run from the Storybook UI (Accessibility tab of the Story) or using the script. + +### Testing in IDE terminal + +```bash +yarn test-storybook +``` + +After executing the script, you will get the Axe Accessibility testing report in the terminal window. +The number of tests are dynamic per component and decided by Axe plugin logic. + +## Usage + +`App.tsx` + +```tsx +// ... +import { + Page, + PageContent, + ConfigProvider, + defaultConfig, +} from "react-helsinki-headless-cms"; + +function App() { + const page = ...; + + return ( + + ...} + getUrlForLanguage={(language, currentLanguage) => new URL(...) + } + /> + } + content={} + footer={...} + /> + + ); +} + + +``` + +## Publishing new versions + +There are 2 scripts for publishing of new verions of npm pagage: + + +To publish a stable version use + +```bash +yarn publish-stable +``` + +To publish a canary version use + +```bash +yarn publish-canary +``` + +**Note:** There is an a known issue with publishing using Windows environment. If you have a Windows machine use Docker container to publish the package.An Apollo client linked to a graphql endpoint with a supported schema (headless CMS) must be provided in the `apolloClient` field of the `config` object. + +## Known issues - Jest has difficulties loading this library. When this library is required in a test file, it's possible that some imports are cjs and some are esm. These two variants do not share a react context which can result in `useConfig` calls that return an empty config object even though `` is declared correctly. I.e. `` sets values for `context1` and `useConfig` reads `context2`. - `yarn generate:graphql` does not work with Node.js v16 or greater - Some of the built packages created with `yarn build` does some issues with some types. This leads to a situation where the application that uses the library cannot read all the exported types. Especially the exported enums inside a built package might be handled incorrectly (https://github.com/rollup/rollup/issues/4291), but there are other type related issues also, but not on every built package. - -### The known clients that are using this library - -- The city of Helsinki Events: https://tapahtumat.hel.fi | https://github.com/City-of-Helsinki/events-helsinki-monorepo -- The city of Helsinki Hobbies: https://harrastukset.hel.fi | https://github.com/City-of-Helsinki/events-helsinki-monorepo -- The city of Helsinki Sports: https://liikunta.hel.fi | https://github.com/City-of-Helsinki/events-helsinki-monorepo -- The city of Helsinki Kultus: https://kultus.fi | https://github.com/City-of-Helsinki/palvelutarjotin-ui -- The city of Helsinki Culture Kids: https://kummilapset.hel.fi/ | https://github.com/City-of-Helsinki/kukkuu-ui diff --git a/src/apollo/navigation/Navigation.tsx b/src/apollo/navigation/Navigation.tsx index 153a4e6b..6e22ada7 100644 --- a/src/apollo/navigation/Navigation.tsx +++ b/src/apollo/navigation/Navigation.tsx @@ -10,7 +10,13 @@ export type NavigationProps = Omit< NavigationPropsWithoutData, 'menu' | 'languages' | 'getPathnameForLanguage' > & { + /** + * The name of the menu in Wordpress headless CMS. + */ menuName: string; + /** + * Gets language specific path for navigation item. + */ getPathnameForLanguage: NavigationPropsWithoutData['getPathnameForLanguage']; }; diff --git a/src/apollo/page/Page.tsx b/src/apollo/page/Page.tsx index 5b770a60..d7aa1c88 100644 --- a/src/apollo/page/Page.tsx +++ b/src/apollo/page/Page.tsx @@ -6,7 +6,13 @@ import { Page as PageWithoutData } from '../../core/page/Page'; import ApolloPageContextProvider from './ApolloPageContextProvider'; export type PageProps = PagePropsWithoutData & { + /** + * Uri of the page. + */ uri?: string; + /** + * Page template value. + */ pageTemplate?: TemplateEnum; }; diff --git a/src/apollo/page/apolloPageContext.tsx b/src/apollo/page/apolloPageContext.tsx index c81195aa..b268c3fb 100644 --- a/src/apollo/page/apolloPageContext.tsx +++ b/src/apollo/page/apolloPageContext.tsx @@ -3,7 +3,13 @@ import { createContext } from 'react'; import type { TemplateEnum } from '../../core'; type ApolloPageContext = { + /** + * Uri of the page. + */ uri?: string; + /** + * Page tyoe template. + */ template?: TemplateEnum; }; diff --git a/src/apollo/pageContent/PageContent.tsx b/src/apollo/pageContent/PageContent.tsx index fbd842cf..389e401f 100644 --- a/src/apollo/pageContent/PageContent.tsx +++ b/src/apollo/pageContent/PageContent.tsx @@ -9,6 +9,9 @@ import useApolloPageContext from '../page/useApolloPageContext'; import { MAIN_CONTENT_ID } from '../../common/constants'; export type PageProps = Omit & { + /** + * Page content if data not found or missing from cms. + */ notFoundPageContent?: JSX.Element; // All other props // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/apollo/pageWithTemplateContent/PageWithTemplateContent.tsx b/src/apollo/pageWithTemplateContent/PageWithTemplateContent.tsx index 5b3e5bf7..bdd5b022 100644 --- a/src/apollo/pageWithTemplateContent/PageWithTemplateContent.tsx +++ b/src/apollo/pageWithTemplateContent/PageWithTemplateContent.tsx @@ -9,7 +9,13 @@ import useApolloPageContext from '../page/useApolloPageContext'; import type { LanguageCodeEnum } from '../../core'; export type PageProps = Omit & { + /** + * The language of the page. + */ language: LanguageCodeEnum; + /** + * Page content if data not found or missing from cms. + */ notFoundPageContent?: JSX.Element; // All other props // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/common/components/container/Container.tsx b/src/common/components/container/Container.tsx index 5125fd4b..fc5616bc 100644 --- a/src/common/components/container/Container.tsx +++ b/src/common/components/container/Container.tsx @@ -1,7 +1,13 @@ import * as React from 'react'; type ContainerProps = { + /** + * Additional children to render inside the container. + */ children: JSX.Element | string; + /** + * Optional wrapper element for the container. + */ wrapper?: JSX.Element; }; diff --git a/src/common/components/grid/Grid.tsx b/src/common/components/grid/Grid.tsx index f9f86086..ad6879bf 100644 --- a/src/common/components/grid/Grid.tsx +++ b/src/common/components/grid/Grid.tsx @@ -5,8 +5,17 @@ import classNames from 'classnames'; import styles from './grid.module.scss'; export interface GridProps { + /** + * Additional children to render inside the grid. + */ children: React.ReactNode; + /** + * Number of grid columns. + */ colsCount?: number; + /** + * Additional classname for grid container. + */ className?: string; } diff --git a/src/common/components/htmlToReact/HtmlToReact.tsx b/src/common/components/htmlToReact/HtmlToReact.tsx index 66440068..078fdf55 100644 --- a/src/common/components/htmlToReact/HtmlToReact.tsx +++ b/src/common/components/htmlToReact/HtmlToReact.tsx @@ -120,9 +120,21 @@ type Components = { }; export type HtmlToReactProps = { + /** + * Additional children to render inside the grid. + */ children: string; + /** + * React components for custom html elements transformation. + */ components?: Components; + /** + * Option to allow unsafe tags, otherwise cleared and not rendered. + */ allowedUnsafeTags?: Config['ALLOWED_TAGS']; + /** + * Trusted origins for url properties and attributes, for elements such as iframes and scripts. + */ trustedOrigins?: string[]; }; diff --git a/src/common/components/list/List.tsx b/src/common/components/list/List.tsx index e2824e75..7305216b 100644 --- a/src/common/components/list/List.tsx +++ b/src/common/components/list/List.tsx @@ -9,7 +9,13 @@ function getKey(...elements: JSX.Element[]): string { } type ListProps = { + /** + * List item elements. + */ items?: (JSX.Element | JSX.Element[] | null | false)[]; + /** + * List item variant options. + */ variant?: | 'spacing-4-xs' | 'spacing-3-xs' diff --git a/src/common/components/tag/Tag.tsx b/src/common/components/tag/Tag.tsx index c9abb123..0680837f 100644 --- a/src/common/components/tag/Tag.tsx +++ b/src/common/components/tag/Tag.tsx @@ -8,11 +8,29 @@ import styles from './tag.module.scss'; import { theme1, theme2 } from './tagThemes'; type Props = { + /** + * Additional children to render inside the tag. + */ children: React.ReactNode; + /** + * Additional classname for the tag. + */ className?: string; + /** + * Boolean indicating whether the tag has a featured style variant. + */ featured?: boolean; + /** + * Boolean indicating whether the tag has a white-only style variant. + */ whiteOnly?: boolean; + /** + * Boolean indicating whether the tag has a selected style variant. + */ selected?: boolean; + /** + * onClick handler. + */ onClick?: () => void; } & Pick; diff --git a/src/common/components/text/Text.tsx b/src/common/components/text/Text.tsx index 87b19020..907043b4 100644 --- a/src/common/components/text/Text.tsx +++ b/src/common/components/text/Text.tsx @@ -14,10 +14,25 @@ type TextVariant = | 'body-xl'; type TextVariantProps = { + /** + * The render html element type for the text. + */ as?: string | React.ComponentType>; + /** + * Text variant option. + */ variant?: TextVariant; + /** + * Additional classname for the text. + */ children: ReactNode; + /** + * Additional classname for the text. + */ className?: string; + /** + * Deprecated + */ role?: string; }; diff --git a/src/common/utils/dates.ts b/src/common/utils/dates.ts index dab54419..5599ca02 100644 --- a/src/common/utils/dates.ts +++ b/src/common/utils/dates.ts @@ -1,13 +1,29 @@ import { format, parseISO } from 'date-fns'; +/** + * @param {dateTime} dateTime - the date to format to 'd.M.yyyy, HH:mm'. + * @return {string} - Formatted date. + */ export const formatDateTime = (dateTime: Date): string => format(dateTime, 'd.M.yyyy, HH:mm'); +/** + * @param {dateTime} dateTime -the date to format to 'd.M.yyyy'. + * @return {string} - Formatted date. + */ export const formatDate = (dateTime: Date): string => format(dateTime, 'd.M.yyyy'); +/** + * @param {dateTime} dateTime - date as string to format to 'd.M.yyyy'. + * @return {string} - Formatted date. + */ export const formatDateFromString = (dateTime: string): string => format(parseISO(dateTime), 'd.M.yyyy'); +/** + * @param {dateTime} dateTime - date as string to format to 'd.M.yyyy, HH:mm'. + * @return {string} - Formatted date. + */ export const formatDateTimeFromString = (dateTime: string): string => format(parseISO(dateTime), 'd.M.yyyy, HH:mm'); diff --git a/src/common/utils/getIsValidHttpUrl.ts b/src/common/utils/getIsValidHttpUrl.ts index 305dc010..fa0c6711 100644 --- a/src/common/utils/getIsValidHttpUrl.ts +++ b/src/common/utils/getIsValidHttpUrl.ts @@ -1,7 +1,10 @@ +/** + * @param {possibleUrl} possibleUrl - url for protocol validity check. + * @return {boolean} - Return true if valid http url. + */ export default function getIsValidHttpUrl(possibleUrl: string) { try { const url = new URL(possibleUrl); - return url.protocol === 'http:' || url.protocol === 'https:'; // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_) { diff --git a/src/common/utils/testImage.ts b/src/common/utils/testImage.ts index 2ee08ad0..ea3d21c6 100644 --- a/src/common/utils/testImage.ts +++ b/src/common/utils/testImage.ts @@ -1,6 +1,11 @@ /** * Test that loading image is successful */ + +/** + * @param {url} url - url of the image to test. + * @return {boolean} - Returns promise. + */ const testImage = (url?: string): Promise => { if (!url) { return Promise.reject(new Error('No image URL given')); diff --git a/src/core/archiveSearchPage/ArchivePageMeta.tsx b/src/core/archiveSearchPage/ArchivePageMeta.tsx index 866f93b3..7295800d 100644 --- a/src/core/archiveSearchPage/ArchivePageMeta.tsx +++ b/src/core/archiveSearchPage/ArchivePageMeta.tsx @@ -3,7 +3,13 @@ import React from 'react'; import { useConfig } from '../configProvider/useConfig'; type BasicMetaProps = { + /** + * Archive page meta title tag contents. + */ title?: string; + /** + * Archive page meta description tag contents. + */ description?: string; }; diff --git a/src/core/archiveSearchPage/ArchiveSearchPage.tsx b/src/core/archiveSearchPage/ArchiveSearchPage.tsx index 1d2625ac..65774dbd 100644 --- a/src/core/archiveSearchPage/ArchiveSearchPage.tsx +++ b/src/core/archiveSearchPage/ArchiveSearchPage.tsx @@ -3,8 +3,17 @@ import React from 'react'; import styles from './archiveSearchPage.module.scss'; export type ArchiveSearchPageProps = { + /** + * Navigation component for the archive search page. + */ navigation: React.ReactNode; + /** + *Content component for the archive search page. + */ content: React.ReactNode; + /** + * Footer component for the archive search page. + */ footer: React.ReactNode; }; diff --git a/src/core/archiveSearchPageContent/ArchiveSearchPageContent.tsx b/src/core/archiveSearchPageContent/ArchiveSearchPageContent.tsx index 88e4753e..87d0a352 100644 --- a/src/core/archiveSearchPageContent/ArchiveSearchPageContent.tsx +++ b/src/core/archiveSearchPageContent/ArchiveSearchPageContent.tsx @@ -114,23 +114,71 @@ export function SearchTags({ } export interface SearchPageContentProps { + /** + * Page object from wordpress headless cms. + */ page?: PageType | ArticleType; + /** + * Breadcrumbs object from wordpress headless cms. + */ breadcrumbs?: | BreadcrumbUnionType | ((page?: PageType | ArticleType) => BreadcrumbUnionType); + /** + * Custom content react components to be dispayed on the page. + */ customContent?: string | JSX.Element; + /** + * Archive collection items.. + */ items?: CollectionItemType[]; + /** + * Boolean indicating whether the page is loading. + */ isLoading?: boolean; + /** + * Boolean indicating whether the collection has more items. + */ hasMore?: boolean; + /** + * Boolean indicating whether no results found for the collection items. + */ noResults?: boolean; + /** + * Additional classname for the SearchPageContent. + */ className?: string; + /** + * All tags collection for the page content. + */ tags?: SearchTag[]; + /** + * Applied search tags collection for the page content. + */ currentTags?: SearchTag[]; + /** + * Boolean indicating whether the tag should update on change. + */ withQuery?: boolean; + /** + * If true then current search text is updated on change. + */ currentText?: string; + /** + * Boolean indicating whether the first search item should be rendered as a large card. + */ largeFirstItem?: boolean; + /** + * Search button click handler. + */ onSearch?: (freeSearch: string, tags: SearchTag[]) => void; + /** + * Load more button click handler. + */ onLoadMore?: () => void; + /** + * Custom renderer for the large card. + */ createLargeCard?: ( item: CollectionItemType, ) => React.ReactElement; diff --git a/src/core/card/Card.tsx b/src/core/card/Card.tsx index 3fe22c69..f7f2157f 100644 --- a/src/core/card/Card.tsx +++ b/src/core/card/Card.tsx @@ -28,28 +28,97 @@ export type CardAlignment = | 'delimited-right'; export type CardProps = { + /** + * Card id. + */ id?: string; + /** + * Card container aria-label. + */ ariaLabel?: string; + /** + * Additional classname for the card. + */ className?: string; + /** + * Card image url. + */ imageUrl?: string | null; + /** + * Card image label. + */ imageLabel?: string; + /** + * Card title. + */ title?: string; + /** + * Boolean indicating whether the styling applied for the title with icon layout. + */ withTitleIcon?: boolean; + /** + * Card title icon. + */ titleIcon?: React.ReactNode | string; + /** + * Card subtitle. + */ subTitle?: string; + /** + * Card main text contents. + */ text?: string; + /** + * Card custom content (component) below the main text. + */ customContent?: React.ReactNode | string; + /** + * Boolean indicating whether the link arrow icon is shown. + */ hasLink?: boolean; + /** + * If true, show link arrow icon label. + */ linkArrowLabel?: string; + /** + * Card link url. + */ url?: string; + /** + * Boolean indicating whether the border styles should be applied to the Card. + */ withBorder?: boolean; + /** + * Boolean indicating whether the shadow styles should be applied to the Card. + */ withShadow?: boolean; + /** + * Boolean which defines the Card content direction. + */ direction?: CardDirection; + /** + * Defines the Card text and custon content alignement inside the Card. + */ alignment?: CardAlignment; + /** + * Boolean indicating whether the text content is clamped in the Card. + */ clampText?: boolean; + /** + * Boolean indicating whether the Card link opens in new tab. + */ openLinkInNewTab?: boolean; + /** + * Custom css properties for the Card container + */ style?: React.CSSProperties; + /** + * Backgroud color of the Card + */ backgroundColor?: string; + /** + * Defines the width ratio for the text and image element in the card. Primary is wider. + */ primaryContent?: 'image' | 'text'; }; diff --git a/src/core/card/LargeCard.tsx b/src/core/card/LargeCard.tsx index e535764c..0ba84566 100644 --- a/src/core/card/LargeCard.tsx +++ b/src/core/card/LargeCard.tsx @@ -9,20 +9,65 @@ import { Link } from '../link/Link'; import { BackgroundImage } from '../image/BackgroundImage'; export type LargeCardProps = { + /** + * Card id. + */ id?: string; + /** + * Card container aria-label. + */ ariaLabel?: string; + /** + * Card image label. + */ imageLabel?: string; + /** + * Card image url. + */ imageUrl?: string | null; + /** + * Card image image position. + */ imagePosition?: 'image-right' | 'image-left'; + /** + * Card subtitle. + */ subTitle?: string; + /** + * Card main text contents. + */ text?: string; + /** + * Card title. + */ title?: string; + /** + * Card link url. + */ url?: string; + /** + * Additional classname for the card. + */ className?: string; + /** + * Card custom content (component) below the main text. + */ customContent?: React.ReactNode; + /** + * Boolean indicating whether the link arrow icon is shown. + */ hasLink?: boolean; + /** + * Boolean indicating whether the text content is clamped in the Card. + */ clampText?: boolean; + /** + * Boolean indicating whether the Card link opens in new tab. + */ openInNewTab?: boolean; + /** + * Boolean indicating whether the border styles should be applied to the Card. + */ withBorder?: boolean; }; diff --git a/src/core/cardsList/CardsList.tsx b/src/core/cardsList/CardsList.tsx index b14c99af..3a248baa 100644 --- a/src/core/cardsList/CardsList.tsx +++ b/src/core/cardsList/CardsList.tsx @@ -3,8 +3,17 @@ import React from 'react'; import Grid from '../../common/components/grid/Grid'; export type CardListProps = { + /** + * Additional children to render inside the card list. + */ children: React.ReactNode; + /** + * Additional classname for the card list. + */ className?: string; + /** + * Card list columns number. + */ colsCount?: number; }; diff --git a/src/core/carousel/Carousel.tsx b/src/core/carousel/Carousel.tsx index f65d228e..a8eceefc 100644 --- a/src/core/carousel/Carousel.tsx +++ b/src/core/carousel/Carousel.tsx @@ -14,15 +14,45 @@ import { import { useConfig } from '../configProvider/useConfig'; export type CarouselProps = { + /** + * Additional children to render inside the Carousel. + */ children: React.ReactElement[]; + /** + * Number of items to show per one slide on desktop device. + */ itemsDesktop?: 1 | 2 | 3 | 4 | 5; + /** + * Number of items to show per one slide on mobile device. + */ itemsMobile?: 1 | 2; + /** + * Additional classname for the card list. + */ className?: string; + /** + * Boolean indicating whether the Carousel is shown with dots (number of sliders). + */ withDots?: boolean; + /** + * onLoadMore event handler. + */ onLoadMore?: () => void; + /** + * Boolean indicating whether the data collection has more items. + */ hasMore?: boolean; + /** + * Boolean indicating whether the Carousel data is loading. + */ loading?: boolean; + /** + * Text of load more button. + */ loadMoreButtonLabelText?: string; + /** + * Title of the carousel component. + */ title?: string; }; diff --git a/src/core/carousel/utils/utils.ts b/src/core/carousel/utils/utils.ts index dd44a948..b71156ff 100644 --- a/src/core/carousel/utils/utils.ts +++ b/src/core/carousel/utils/utils.ts @@ -1,3 +1,8 @@ +/** + * @param {arr} arr - any array. + * @param {len} len - the array length. + * @return {Array} - Returns the chunks array of initial array (grouped elements). + */ export function splitArrayIntoChunksOfLen(arr, len) { const chunks = []; let i = 0; @@ -8,10 +13,21 @@ export function splitArrayIntoChunksOfLen(arr, len) { return chunks; } +/** + * @param {item} item - item set (element of the array). + * @param {index} index - item set index. + * @return {string} - Returns the unique key based on input data. + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export const getItemSetKey = (item: any, index: number) => `itemSet-${item.id ?? Math.random()}-${index}`; +/** + * @param {item} item - item (element of the array). + * @param {itemSetPrefix} itemSetPrefix - item prefix (element of the array). + * @param {index} index - item index. + * @return {string} - Returns the unique key based on input data. + */ export const getItemSetItemKey = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any item: any, @@ -20,6 +36,11 @@ export const getItemSetItemKey = ( index: number, ) => `itemSet-item-${item.id ?? Math.random()}-${index}`; +/** + * @param {item} item - item (element of the array). + * @param {index} index - item index. + * @return {string} - Returns the unique key for the dot (slide counter) based on input data. + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export const getSlideDotKey = (item: any, index: number) => `slide-dot-${item?.id ?? Math.random()}-${index}`; diff --git a/src/core/collection/Collection.tsx b/src/core/collection/Collection.tsx index 9c69b897..bd4a6573 100644 --- a/src/core/collection/Collection.tsx +++ b/src/core/collection/Collection.tsx @@ -41,17 +41,47 @@ import { DEFAULT_LOCALE } from '../../constants'; import { isPageType, isArticleType } from '../../common/headlessService/utils'; export type CollectionProps = { + /** + * Collection title. + */ title?: string; + /** + * Collection description. + */ description?: string; + /** + * Collection cards data. + */ cards: React.ReactElement[]; + /** + * Additinal classname applied to the Collection container. + */ className?: string; + /** + * Collection additional props. + */ collectionContainerProps?: | Partial> | Partial; + /** + * Collection type. + */ type: 'carousel' | 'grid'; + /** + * Boolean indicating whether the collection data is loading. + */ loading?: boolean; + /** + * Boolean indicating whether the collection data has next page. + */ hasNext?: boolean; + /** + * Show all link url. + */ showAllUrl?: string; + /** + * onLoadMore event handler. + */ onLoadMore?: () => void; }; diff --git a/src/core/collection/utils.ts b/src/core/collection/utils.ts index 48d7beb2..9c214895 100644 --- a/src/core/collection/utils.ts +++ b/src/core/collection/utils.ts @@ -4,13 +4,25 @@ import type { EventType } from '../../common/eventsService/types'; import normalizeKeys from '../../linkedEvents/utils/normalizeKeys'; import { LINKEDEVENTS_DATE_NOW } from './constants'; +/** + * @param {ids} ids - the venue ids in number format. + * @return {Array} - Returns array of tprek formatted string ids. + */ export function getVenueIds(ids: number[]): string[] { return ids.map((id) => `tprek:${id}`); } +/** + * @param {event} - event. + * @return {boolean} - Returns true if events' end time in past. + */ export const isEventEndTimeInPast = (event: EventType): boolean => !!event?.endTime && isPast(new Date(event.endTime)); +/** + * @param {event} - event. + * @return {boolean} - Returns true if events' end time in null and start time in past. + */ export const isEventEndTimeNullAndStartTimeInPast = ( event: EventType, ): boolean => @@ -18,9 +30,17 @@ export const isEventEndTimeNullAndStartTimeInPast = ( event?.endTime === null && isPast(new Date(event.startTime)); +/** + * @param {event} - event. + * @return {boolean} - Returns true if event is closed. + */ export const isEventClosed = (event: EventType): boolean => isEventEndTimeInPast(event) || isEventEndTimeNullAndStartTimeInPast(event); +/** + * @param {params} - Get params. + * @return {object} - Returns normalized values. + */ export const normalizeParamsValues = (params: Record) => { const normalizedParams = { ...normalizeKeys(params) }; @@ -43,10 +63,18 @@ export const normalizeParamsValues = (params: Record) => { return normalizedParams; }; +/** + * @param {dateString} - Get date string. + * @return {boolean} - Returns true if the date is valid + */ export function isValidDate(dateString: string) { return !Number.isNaN(Date.parse(dateString)); } +/** + * @param {start} start - Get start date. + * @return {boolean} - Returns current date as string if the initial was in past. + */ export const convertDateStringInPastToNow = (start: string): string => { const startDate = new Date(start); const now = new Date(); diff --git a/src/core/configProvider/ConfigProvider.tsx b/src/core/configProvider/ConfigProvider.tsx index b408670d..5693dac3 100644 --- a/src/core/configProvider/ConfigProvider.tsx +++ b/src/core/configProvider/ConfigProvider.tsx @@ -5,7 +5,13 @@ import { configContext } from './configContext'; import { defaultConfig } from './defaultConfig'; export type ConfigProviderProps = { + /** + * Global library configuration. + */ config: Config; + /** + * Additional children to render inside the Config Provider. + */ children: React.ReactNode; }; diff --git a/src/core/configProvider/configContext.ts b/src/core/configProvider/configContext.ts index 5efe2ea8..4e4d2bec 100644 --- a/src/core/configProvider/configContext.ts +++ b/src/core/configProvider/configContext.ts @@ -19,13 +19,37 @@ import type { } from '../translation/types'; export type Config = { + /** + * Site or App name. + */ siteName: string; + /** + * Id of the html element where SkipTo link will point. + */ mainContentId?: string; + /** + * Internal origins for generating correct urls for app routing. + */ internalHrefOrigins: string[]; + /** + * List of organizations for additional UI highlights (f.e. check icon after title if its Helsinki specific item). + */ organisationPrefixes: string[]; + /** + * Current language code of the app. + */ currentLanguageCode: LanguageCodeEnum; + /** + * Fallback image urls. Used if image is not defined, but imaage is still required in the component. + */ fallbackImageUrls: string[]; + /** + * Fallback translations. + */ fallbackTranslations: FallbackTranslations; + /** + * Translated texts which are needed to render components properly. + */ copy: { breadcrumbNavigationLabel: string; breadcrumbListLabel: string; @@ -49,10 +73,17 @@ export type Config = { clearAll: string; }; } & OptionalTranslationsWithFallbacks; + /** + * Custom translated texts which are needed to render components properly. + * Used when for technical reasons the use of the copy property is not possible. + */ customCopy?: { loadMoreButtonVariant?: Exclude; loadMoreButtonTheme?: ButtonTheme; }; + /** + * React component replacements for defined html elements. + */ components: { A: (props: React.AnchorHTMLAttributes) => JSX.Element; Img: (props: React.ImgHTMLAttributes) => JSX.Element; @@ -65,9 +96,21 @@ export type Config = { ArticleCardContent?: React.FC>; HelsinkiCityOwnedIcon?: React.FC>; }; + /** + * Apollo client to make gql calls to headless cms. + */ apolloClient?: ApolloClient; + /** + * Apollo client to make gql calls to linked events. + */ eventsApolloClient?: ApolloClient | 'disabled'; + /** + * Apollo client to make gql calls to venues search. + */ venuesApolloClient?: ApolloClient | 'disabled'; + /** + * Utilities custom implementation provided by apps (app specific). + */ utils: { getArticlePageCardProps: (item: ArticleType | PageType) => CardProps; getEventCardProps: ( @@ -84,12 +127,18 @@ export type Config = { redirectToUrl: (url: string) => void; redirectToArticlesSearch?: (tag: string) => void; }; + /** + * Meta data for the Head components. + */ meta?: { appleTouchIconUrl?: string; favIconUrl?: string; favIconSvgUrl?: string; manifestUrl?: string; }; + /** + * App specific html sanitising configs for html editors. + */ htmlSanitizer: { allowedUnsafeTags: HtmlToReactProps['allowedUnsafeTags']; trustedOrigins: HtmlToReactProps['trustedOrigins']; diff --git a/src/core/contentContainer/ContentContainer.tsx b/src/core/contentContainer/ContentContainer.tsx index ee82e30d..e81e534a 100644 --- a/src/core/contentContainer/ContentContainer.tsx +++ b/src/core/contentContainer/ContentContainer.tsx @@ -5,7 +5,13 @@ import classNames from 'classnames'; import styles from './contentContainer.module.scss'; export type ContentContainerProps = { + /** + * Additional children to render inside the Content container. + */ children: React.ReactNode; + /** + * Additinal classname applied to the Content container. + */ className?: string; }; diff --git a/src/core/hero/Hero.tsx b/src/core/hero/Hero.tsx index 21256761..211c903c 100644 --- a/src/core/hero/Hero.tsx +++ b/src/core/hero/Hero.tsx @@ -15,12 +15,33 @@ import { getColor, isWhiteText } from '../utils/string'; import { useConfig } from '../configProvider/useConfig'; export type HeroComponentProps = { + /** + * Hero container id. + */ id: string; + /** + * Additinal classname applied to the Content container. + */ className?: string; + /** + * Hero back button url. + */ backUrl?: string; + /** + * Hero iamge url. + */ imageUrl?: string; + /** + * Hero alt image text. + */ imageAlt?: string; + /** + * Hero iamge label. + */ imageLabel?: string; + /** + * Hero container wrapper component. + */ container?: JSX.Element; } & HeroProps; diff --git a/src/core/image/BackgroundImage.tsx b/src/core/image/BackgroundImage.tsx index c87bba2c..08d00813 100644 --- a/src/core/image/BackgroundImage.tsx +++ b/src/core/image/BackgroundImage.tsx @@ -5,9 +5,21 @@ import { Tag } from '../../common/components/tag/Tag'; import { useResolveImageUrl } from '../hooks/useResolveImageUrl'; export type BackgroundImageProps = { + /** + * Background image id. + */ id: string; + /** + * Background image url. + */ url?: string | null; + /** + * Background fallback image url. + */ customFallbackUrl?: string; + /** + * Background image label tag. + */ labelTag?: string; } & React.HTMLAttributes; diff --git a/src/core/image/Image.tsx b/src/core/image/Image.tsx index 58344416..b305f843 100644 --- a/src/core/image/Image.tsx +++ b/src/core/image/Image.tsx @@ -3,10 +3,25 @@ import React from 'react'; import { useResolveImageUrl } from '../hooks/useResolveImageUrl'; export type ImageProps = { + /** + * Image id. + */ id: string; + /** + * Image src attribute value. + */ src: string; + /** + * Alt image text. + */ alt: string; + /** + * Image fallback url. + */ customFallbackSrc?: string; + /** + * Image caption. + */ caption?: string; } & Omit, 'src' | 'alt'>; diff --git a/src/core/imageGallery/ImageGallery.tsx b/src/core/imageGallery/ImageGallery.tsx index eb626eca..4001b7ca 100644 --- a/src/core/imageGallery/ImageGallery.tsx +++ b/src/core/imageGallery/ImageGallery.tsx @@ -6,10 +6,25 @@ import { Lightbox } from './Lightbox'; import type { ImageItem as ImageItemType } from './types'; export type ImageGalleryProps = { + /** + * Image gallery items. + */ images: ImageItemType[]; + /** + * Image gallery lightbox component uid. + */ lightboxUid: string; + /** + * Boolean indicating whether the border styles should be applied to the Image. + */ withBorder?: boolean; + /** + * Boolean indicating whether the lightbox feature is enabled. + */ withLightbox?: boolean; + /** + * Number of columns in Image gallery. + */ columns?: number; }; diff --git a/src/core/imageGallery/ImageGalleryContext.tsx b/src/core/imageGallery/ImageGalleryContext.tsx index ff31821f..cf6e5870 100644 --- a/src/core/imageGallery/ImageGalleryContext.tsx +++ b/src/core/imageGallery/ImageGalleryContext.tsx @@ -2,12 +2,33 @@ import type { Dispatch, SetStateAction } from 'react'; import { createContext } from 'react'; export interface ImageGalleryContextProps { + /** + * Image index. + */ imageIndex: number; + /** + * Set Image index. + */ setImageIndex: Dispatch>; + /** + * Selected Image index. + */ selectedImageIndex: number; + /** + * Set selected Image index. + */ setSelectedImageIndex: Dispatch>; + /** + * Boolean indicating whether the lightbox is visible. + */ isLightboxVisible: boolean; + /** + * Set is Iamge lightbox visible. + */ setIsLightboxVisible: Dispatch>; + /** + * Toggle lightbox method. + */ toggleLightbox: () => void; } diff --git a/src/core/imageGallery/ImageGalleryProvider.tsx b/src/core/imageGallery/ImageGalleryProvider.tsx index 157e5571..c994ffd6 100644 --- a/src/core/imageGallery/ImageGalleryProvider.tsx +++ b/src/core/imageGallery/ImageGalleryProvider.tsx @@ -4,6 +4,9 @@ import React, { useCallback, useState } from 'react'; import { ImageGalleryContext } from './ImageGalleryContext'; interface ImageGalleryProviderProps { + /** + * Additional children to render inside the Image Gallery. + */ children: ReactNode; } diff --git a/src/core/imageGallery/ImageItem.tsx b/src/core/imageGallery/ImageItem.tsx index c03d46d7..5a1f4dad 100644 --- a/src/core/imageGallery/ImageItem.tsx +++ b/src/core/imageGallery/ImageItem.tsx @@ -10,10 +10,25 @@ import styles from './imageGallery.module.scss'; import useImageGalleryContext from './useImageGalleryContext'; interface ImageItemProps { + /** + * Image object. + */ image: ImageItemType; + /** + * Image id. + */ imageId: number; + /** + * Image lightbox uid. + */ lightboxUid: string; + /** + * Boolean indicating whether the border styles should be applied to the Image. + */ withBorder: boolean; + /** + * Boolean indicating whether the lightbox feature is enabled. + */ withLightbox: boolean; } diff --git a/src/core/imageGallery/ImagesGrid.tsx b/src/core/imageGallery/ImagesGrid.tsx index 911326a4..da53b156 100644 --- a/src/core/imageGallery/ImagesGrid.tsx +++ b/src/core/imageGallery/ImagesGrid.tsx @@ -6,10 +6,25 @@ import { ImageItem } from './ImageItem'; import type { ImageItem as ImageItemType } from './types'; interface ImagesGridProps { + /** + * Array of Image items. + */ images: ImageItemType[]; + /** + * Number of columns in Image grid. + */ columns: number; + /** + * Image lightbox uid. + */ lightboxUid: string; + /** + * Boolean indicating whether the border styles should be applied to the Image. + */ withBorder: boolean; + /** + * Boolean indicating whether the lightbox feature is enabled. + */ withLightbox: boolean; } diff --git a/src/core/imageGallery/Lightbox.tsx b/src/core/imageGallery/Lightbox.tsx index fb0dc60b..53dea82c 100644 --- a/src/core/imageGallery/Lightbox.tsx +++ b/src/core/imageGallery/Lightbox.tsx @@ -13,7 +13,13 @@ import { useConfig } from '../configProvider/useConfig'; import useImageGalleryContext from './useImageGalleryContext'; interface LightboxProps { + /** + * Image items. + */ images: ImageItem[]; + /** + * Image lightbox uid. + */ lightboxUid: string; } diff --git a/src/core/link/Link.tsx b/src/core/link/Link.tsx index 272b9d1f..f1214ae4 100644 --- a/src/core/link/Link.tsx +++ b/src/core/link/Link.tsx @@ -13,15 +13,45 @@ export type LinkProps = Omit< React.ComponentPropsWithoutRef<'a'>, 'target' | 'href' | 'onPointerEnterCapture' | 'onPointerLeaveCapture' > & { + /** + * Link href. + */ href?: string; + /** + * Link left icon component. + */ iconLeft?: React.ReactNode; + /** + * Link right icon component. + */ iconRight?: React.ReactNode; + /** + * Boolean indicating whether the external icon is shown. + */ showExternalIcon?: boolean; + /** + * Boolean indicating whether the link should be opened in a new tab. + */ openInNewTab?: boolean; + /** + * Link text font size. + */ size?: 'S' | 'M' | 'L'; + /** + * Link variant. + */ variant?: 'default' | 'arrowRight'; + /** + * Additional children to render inside the Link. + */ children?: React.ReactNode; + /** + * Additinal classname applied to the Link. + */ className?: string; + /** + * Boolean indicating whether the icons should be displayed with inline styling. + */ inlineIcons?: boolean; }; diff --git a/src/core/linkBox/LinkBox.tsx b/src/core/linkBox/LinkBox.tsx index bdbb55a5..deef46f5 100644 --- a/src/core/linkBox/LinkBox.tsx +++ b/src/core/linkBox/LinkBox.tsx @@ -6,8 +6,17 @@ import { Link } from '../link/Link'; import styles from './LinkBox.module.scss'; export type LinkProps = Omit, 'target'> & { + /** + * Link left icon component. + */ iconLeft?: React.ReactNode; + /** + * Link right icon component. + */ iconRight?: React.ReactNode; + /** + * Boolean indicating whether the link should be opened in a new tab. + */ openInNewTab?: boolean; }; diff --git a/src/core/notification/Notification.tsx b/src/core/notification/Notification.tsx index c2c7bc9e..28f8a561 100644 --- a/src/core/notification/Notification.tsx +++ b/src/core/notification/Notification.tsx @@ -54,6 +54,9 @@ const notificationTypeMap: Record = }; export type NotificationProps = { + /** + * Notification object. + */ notification?: NotificationType | null; }; diff --git a/src/core/pageSection/PageSection.tsx b/src/core/pageSection/PageSection.tsx index 5f70375e..ef7ab02e 100644 --- a/src/core/pageSection/PageSection.tsx +++ b/src/core/pageSection/PageSection.tsx @@ -7,12 +7,33 @@ import styles from './pageSection.module.scss'; import { BackgroundImage } from '../image/BackgroundImage'; export type PageSectionProps = { + /** + * Additional children to render inside the Page section. + */ children: React.ReactNode; + /** + * Additional classname for the Page section. + */ className?: string; + /** + * Boolean indicating whether the koros top shouyld be displayed. + */ korosTop?: boolean; + /** + * Custom top koros classname. + */ korosTopClassName?: string; + /** + * Boolean indicating whether the koros bottom shouyld be displayed. + */ korosBottom?: boolean; + /** + * Custom bottom koros classname. + */ korosBottomClassName?: string; + /** + * Backgroud image url. + */ backgroundImageUrl?: string; };