diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index c9376a1e3..919a4c67e 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -116,52 +116,6 @@ import '@arc-web/components/src/components/button/arc-button'; -### React - -React can render web components, however, makes assumptions about HTML elements that don't always hold for custom elements, while also treating lower-case tag names differently from upper-cased. This makes working with web components harder than necessary to use. React is working on fixes to these issues, but in the meantime, the `@arc-web/react` provides a wrapper that takes care of setting properties and listening to events for you. Read more about why we need this wrapper [here](https://lit.dev/docs/frameworks/react/#why-are-wrappers-needed) - -Install both the `@arc-web/components` and `@arc-web/react` packages from npm: - -```sh -npm install @arc-web/components@latest @arc-web/react@latest -``` - -Setup the `@arc-web/components` package as described above, however, import components from the `@arc-web/react` package instead: - -```tsx -import { ArcButton } from '@arc-web/react'; - -export const App = () => { - return Click Me; -}; -``` - -#### React with (ARC & Material UI Components) - -``` -npm install @arc-web/components@latest @arc-web/react@latest @arc-wb/material@latest @mui/material@latest @mui/icons-material@latest -``` - -```tsx -import React from 'react'; -import { ArcContainer, ArcButton } from '@arc-web/react'; -import { ThemeProvider } from '@arc-web/material'; -import { Button } from '@mui/material'; - -export function App() { - return ( - - -
- Click me - -
-
-
- ); -} -``` - ### Customization **ARC** components can be customized at a high level through design tokens. This gives you control over theme colours and general styling. For more advanced customizations, web-components can expose something called CSS `parts`. To ensure that each application looks and feels the same, these `parts` are not being exposed from the **ARC** components. diff --git a/MIGRATION_GUIDES.md b/MIGRATION_GUIDES.md index f5890ed71..454c94cea 100644 --- a/MIGRATION_GUIDES.md +++ b/MIGRATION_GUIDES.md @@ -1,5 +1,33 @@ # Migration Guides +## v3 to v4 + +v4 introdcues serval breaking chanages to the library. Please take care to read the following migration guide before upgrading. + +#### Breaking Changes + +- `ArcTable` component has been removed in favour of the **gridjs** theme. + +#### Upgrade Steps + +- Replace all instances of `ArcTable` with the new **gridjs** theme: + +```diff +- import '@arc-web/components/src/components/table/arc-table'; +- + ++ ++
++ + +``` + +Please refer to the [official gridjs documentation](https://gridjs.io/) for more information. + ## v2 to v3 In version 3 we have taken the opportunity to make several important breaking changes to the library. Please take care to read the following migration guide before upgrading. diff --git a/lib.nix b/lib.nix index f0df3132a..989ee4e3e 100644 --- a/lib.nix +++ b/lib.nix @@ -33,7 +33,8 @@ in # this workspace is a monorepo and all dependencies # are resolved via the workspace root package.json src = cleanSource ./.; - npmDepsHash = "sha256-fFtpvA2IpYxDHlPJuvgkgx/sQDBdCWvIv9vGwSM6Q7o="; + npmDepsHash = "sha256-vaLGyLd24ethltsLfF+4aXkz5CpbQg8JaWhgEqvD5gE="; + # dont run the build scripts when rebuilding # npm dependencies as node-keytar will fail diff --git a/package-lock.json b/package-lock.json index 888a58075..4db1aae7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,11 @@ "@storybook/web-components-vite": "8.1.11", "@sveltejs/kit": "2.7.1", "@sveltejs/vite-plugin-svelte": "3.1.2", + "@tanstack/angular-table": "8.20.5", + "@tanstack/lit-table": "8.20.5", + "@tanstack/react-table": "8.20.6", + "@tanstack/table-core": "8.20.5", + "@tanstack/vue-table": "8.20.5", "@types/koa": "2.13.9", "@types/koa-router": "7.4.5", "@types/koa-static": "4.0.2", @@ -87,7 +92,7 @@ "eslint-plugin-html": "7.1.0", "eslint-plugin-storybook": "0.6.12", "eslint-plugin-vue": "9.17.0", - "gridjs": "6.0.6", + "gridjs": "6.2.0", "jsonc-eslint-parser": "2.3.0", "koa": "2.14.2", "koa-node-resolve": "1.0.0-pre.9", @@ -17578,6 +17583,109 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, + "node_modules/@tanstack/angular-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/angular-table/-/angular-table-8.20.5.tgz", + "integrity": "sha512-VjfUVDS5qUSr3fo5y13QObNmM4w4fwUXzgmS3Q7l7iHtaemCCie4T3w0b4n5jRpskOZrWOHIF5HBszo9NMAycA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@angular/core": ">=17" + } + }, + "node_modules/@tanstack/angular-table/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@tanstack/lit-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/lit-table/-/lit-table-8.20.5.tgz", + "integrity": "sha512-5Quam5W8A7VNw32NvV6Up/oylyRg3EGdcH3/vaFeKQHVxI/KiX0W+XWoNw36cDkfRjKEIgOMe+ymis+inobX8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "lit": "^3.1.3" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.20.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz", + "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.20.5.tgz", + "integrity": "sha512-2xixT3BEgSDw+jOSqPt6ylO/eutDI107t2WdFMVYIZZ45UmTHLySqNriNs0+dMaKR56K5z3t+97P6VuVnI2L+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": ">=3.2" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -27069,9 +27177,11 @@ } }, "node_modules/gridjs": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/gridjs/-/gridjs-6.0.6.tgz", - "integrity": "sha512-TZ20nY+weE/wlyXOd3A9FJyJlsJ/MrHr6frMgUHFN29RmWZCYtnyfF0zuspXC81oePwSJeSZ8HY651aeyX8+rQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/gridjs/-/gridjs-6.2.0.tgz", + "integrity": "sha512-EAGGfHjyEXWh12Txs6DjTGnWTo226wbowtMrLI+yNZQaJpvs0m7yDcyM7r+D4RA7rZQVj/cfmwEaRz/rlxg8LA==", + "dev": true, + "license": "MIT", "dependencies": { "preact": "^10.11.3" } @@ -36407,6 +36517,7 @@ "version": "10.19.6", "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.6.tgz", "integrity": "sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==", + "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -43713,7 +43824,6 @@ "@azure/msal-browser": "2.28.1", "@floating-ui/dom": "0.3.1", "date-fns": "2.29.1", - "gridjs": "6.0.6", "lit": "3.1.2", "tabbable": "5.3.3", "tslib": "2.3.1" diff --git a/package.json b/package.json index 5e77855a7..b32a03ee4 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,11 @@ "@storybook/web-components-vite": "8.1.11", "@sveltejs/kit": "2.7.1", "@sveltejs/vite-plugin-svelte": "3.1.2", + "@tanstack/angular-table": "8.20.5", + "@tanstack/lit-table": "8.20.5", + "@tanstack/react-table": "8.20.6", + "@tanstack/table-core": "8.20.5", + "@tanstack/vue-table": "8.20.5", "@types/koa": "2.13.9", "@types/koa-router": "7.4.5", "@types/koa-static": "4.0.2", @@ -83,7 +88,7 @@ "eslint-plugin-html": "7.1.0", "eslint-plugin-storybook": "0.6.12", "eslint-plugin-vue": "9.17.0", - "gridjs": "6.0.6", + "gridjs": "6.2.0", "jsonc-eslint-parser": "2.3.0", "koa": "2.14.2", "koa-node-resolve": "1.0.0-pre.9", diff --git a/packages/components/.storybook/preview.tsx b/packages/components/.storybook/preview.tsx index cce960b3f..f3f76abfa 100644 --- a/packages/components/.storybook/preview.tsx +++ b/packages/components/.storybook/preview.tsx @@ -1,6 +1,5 @@ import { html } from 'lit'; import { setCustomElementsManifest } from '@storybook/web-components'; -import { Preview } from '@story/types'; import DocumentationTemplate from './documentation-template.mdx'; import '../themes/index.css'; import '../src/index'; @@ -10,7 +9,7 @@ import CUSTOM_ELEMENTS from '../../../dist/packages/components/custom-elements.j setCustomElementsManifest(CUSTOM_ELEMENTS); -const PREVIEW: Preview = { +const PREVIEW = { decorators: [ (story, { parameters }) => { const { noContainer } = parameters; diff --git a/packages/components/package.json b/packages/components/package.json index b11b1a3f3..bf3974f5a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -14,7 +14,6 @@ "@azure/msal-browser": "2.28.1", "@floating-ui/dom": "0.3.1", "date-fns": "2.29.1", - "gridjs": "6.0.6", "lit": "3.1.2", "tabbable": "5.3.3", "tslib": "2.3.1" diff --git a/packages/components/src/components/table/ArcTable.ts b/packages/components/src/components/table/ArcTable.ts deleted file mode 100644 index 26ba0fd11..000000000 --- a/packages/components/src/components/table/ArcTable.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { html, LitElement } from 'lit'; -import { property, query } from 'lit/decorators.js'; -import { Grid, Row, createElement, Config } from 'gridjs'; -import { TCell, TColumn } from 'gridjs/dist/src/types'; -import { Language } from 'gridjs/dist/src/i18n/language'; -import { ComponentChildren, ComponentType, Attributes } from 'preact'; -import { emit } from '../../internal/event.js'; -import { - ARC_EVENTS, - ArcEvent, -} from '../../internal/constants/eventConstants.js'; -import styles from './arc-table.styles.js'; - -const enum TABLE_EVENTS { - CELL_CLICK = 'cellClick', - ROW_CLICK = 'rowClick', - TABLE_READY = 'tableReady', -} - -/** - * @event arc-cell-click - Emitted when the user clicks on a cell. - * @event arc-row-click - Emitted when the user clicks on a row. - * @event arc-table-ready - Emitted when the table is ready. - * - * @ssr - True - */ -export default class ArcTable extends LitElement { - /** @internal */ - static tag = 'arc-table'; - - static styles = styles; - - /** @internal - Reference to the GridJS table. */ - private _grid: Grid; - - /** @internal */ - @query('#main') table: HTMLElement; - - /** Define the columns of the table. */ - @property() columns: string[] | TColumn[]; - - /** Define the rows and columns of the table. */ - @property() data: TCell[][] | { [key: string]: TCell }[]; - - /** Puts the header in a fixed state. */ - @property({ type: Boolean, attribute: 'fixed-header' }) fixedHeader: boolean = - false; - - /** Set the height of the table. This is useful when setting a fixed header. */ - @property() height: string; - - /** Localize and update the messages used in the table. */ - @property() language: Language; - - /** Show the pagination. */ - @property({ type: Boolean }) pagination: boolean = false; - - /** Set the pagination limit. */ - @property({ type: Number, attribute: 'pagination-limit' }) - paginationLimit: number = 10; - - /** Show the pagination summary. */ - @property({ type: Boolean, attribute: 'pagination-summary' }) - paginationSummary: boolean = false; - - /** Allow resizing of columns. */ - @property({ type: Boolean }) resizable: boolean = false; - - /** Allow sorting. */ - @property({ type: Boolean }) sort: boolean = false; - - /** Support global search on all rows and columns. */ - @property({ type: Boolean }) search: boolean = false; - - /* Create a new GridJS instance. */ - firstUpdated() { - this._grid = new Grid({ - columns: this.columns || [], - data: this.data || [], - height: this.height, - fixedHeader: this.fixedHeader, - language: { - pagination: { - previous: '˂', - next: '˃', - }, - loading: 'Retrieving your data, please wait...', - noRecordsFound: 'No matching records found.', - error: 'An error occurred while fetching your data.', - ...this.language, - }, - pagination: this.pagination - ? { - limit: this.paginationLimit, - summary: this.paginationSummary, - } - : false, - resizable: this.resizable, - sort: this.sort, - search: this.search, - }).render(this.table); - - /* Add listeners to the grid */ - this._addTableListeners(); - } - - /* Emit the GridJS events, so the user can listen to arc-specific events instead. */ - private _emitTableEvent( - type: ArcEvent, - args?: - | [e: MouseEvent, row: Row] - | [e: MouseEvent, cell: TCell, column: TColumn, row: Row], - ) { - emit(this, ARC_EVENTS[type], { - detail: args, - }); - } - - /* Add specific listeners to the table instance. */ - private _addTableListeners() { - this._grid.on('rowClick', (...args) => - this._emitTableEvent(TABLE_EVENTS.ROW_CLICK, args), - ); - this._grid.on('cellClick', (...args) => - this._emitTableEvent(TABLE_EVENTS.CELL_CLICK, args), - ); - this._grid.config.store.subscribe((state, prevState) => { - const status: number = (state?.status as number) ?? 0; - const prevStatus: number = (prevState?.status as number) ?? 0; - if (prevStatus < status) { - if (prevStatus === 2 && status === 3) { - this._emitTableEvent(TABLE_EVENTS.TABLE_READY); - } - } - }); - } - - /* Method used to format a table cell. */ - format( - type: ComponentType, - props: (Attributes & string) | null, - ...children: ComponentChildren[] - ) { - return createElement(type, props, ...children); - } - - /** - * Method to update the GridJS configuration and set the required arc-table properties. - * GridJS provides support for more advanced features than the arc-table requires. - * To allow the flexibility that GridJS provides, the given userConfig needs to be checked. - * */ - updateConfig(userConfig: Partial) { - const keys = Object.keys(userConfig); - - /* Make sure there are actual properties given. */ - if (keys.length === 0) { - throw new Error( - 'Missing property: Please provide at least one property to update the configuration.', - ); - } - - /* Update the GridJS config */ - this._grid.updateConfig({ ...userConfig }); - this._grid.forceRender(); - - /* Each property of the component itself will also require an update, but only if they exist in the ArcTable API */ - keys.forEach((key: keyof Config) => { - if (!(key in this)) return; // Make sure that the given key exists on the ArcTable (this) class. - (this as any)[key] = userConfig[key]; // Update the value of a given key. - }); - } - - protected render() { - return html`
`; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'arc-table': ArcTable; - } -} diff --git a/packages/components/src/components/table/arc-table.documentation.mdx b/packages/components/src/components/table/arc-table.documentation.mdx deleted file mode 100644 index b46536534..000000000 --- a/packages/components/src/components/table/arc-table.documentation.mdx +++ /dev/null @@ -1,252 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# `ArcTable` - -**ARC** provides support for data visualization by providing an `ArcTable` wapper component around [Grid.js](https://gridjs.io/). - -## Usage - -You don't need any build tools to use the **ARC** table component. -Simply include and add the component in your project, like so: - -```html - -
- - - -``` - -### Columns - -The `columns` property can be defined in one of the following ways: - -```bash -#Array of strings -columns: ["Name", "Email", "Number"]; - -#or - -#Array of TColumn objects -columns: [ - { name: 'Name', sort: true }, - { name: 'Email', sort: false }, - { name: 'Number', sort: false, width: '50%' }, -] -``` - -The `TColumn` object has the following available properties: - -```bash -id?: string, -data?: function or TCell, -name: string, -width?: string, -sort?: boolean, -hidden?: boolean, -formatter?: function, -attributes?: HTMLAttributes or function -``` - -### Data - -The `data` property can be defined in one of the following ways: - -```bash -#Array of TCell arrays -data: [ - ['John', 'john@example.com', '(353) 01 222 3333'], - ['Mark', 'mark@gmail.com', '(01) 22 888 4444'], - ['Eoin', 'eo3n@yahoo.com', '(05) 10 878 5554'], - ['Nisen', 'nis900@gmail.com', '313 333 1923'] -]; - -#or - -#Array of TCell objects -data: [ - { name: 'John', email: 'john@example.com' }, - { name: 'Mark', email: 'mark@gmail.com' }, - { name: 'Eoin', email: 'eo3n@yahoo.com' }, - { name: 'Nisen', email: 'nis900@gmail.com' } -] -``` - -Data can also be fetched by using an async function for example. -The below code adds a setTimeout function to imitate a loading state. - -```js -{ - data: () => { - return new Promise((resolve) => { - setTimeout( - () => - resolve([ - ['John', 'john@example.com', '(353) 01 222 3333'], - ['Mark', 'mark@gmail.com', '(01) 22 888 4444'], - ]), - 2000, - ); - }); - }; -} -``` - -## Customization - -**ARC** provides support for customizing cells at runtime by using the `format` method in the formatter property. - -```js -const app = document.getElementById('app'); -const table = document.createElement('arc-table'); // Create a reference to the arc-table to get access to the built-in methods. -app.appendChild( - Object.assign(table, { - columns: [ - 'Name', - 'LastName', - { - name: 'Email', - formatter: (cell) => - table.format( - 'div', - { - id: 'myDiv', - className: 'my-div', - style: 'display: flex; justify-content: center;', - }, - cell, - ), - }, - ], - data: [ - ['John', 'Doe', 'john.doe@arup.com'], - ['Jane', 'Doe', 'jane.doe@arup.com'], - ['Joe', 'Doakes', 'joe.doakes@arup.com'], - ['Juan', 'Perez', 'juan.perez@arup.com'], - ['Fred', 'Nerk', 'fred.nerk@arup.com'], - ], - }), -); -``` - -The first property of the format method is a string, which can be any HTMLElement (i.e. div, span, strong etc.). -The second property of the format method can be used to add HTMLAttributes (i.e. className, style etc.). -The last property of the format method can be used to render children, in case of the example the cell itself is returned. - -### Update - -It is also possible to create an empty arc-table instance and update the configuration later on. -**ARC** provides a method to update the configuration and `re-render` the table. - -```js -const app = document.getElementById('app'); -const table = document.createElement('arc-table'); // Create a reference to the arc-table to get access to the built-in methods. -app.appendChild( - Object.assign(table, { - columns: ['Name', 'LastName', 'Email'], - data: [['John', 'Doe', 'john.doe@arup.com']], - }), -); - -setTimeout(() => { - table.updateConfig({ pagination: true }); -}, 0); -``` - -Calling the updateConfig method of the arc-table component, -will automatically update all associated properties of the arc-table component. - -## Serialization - -**ARC** provides support for various different languages: - -- en_US -- es_ES -- fr_FR -- it_IT -- tr_TR - -### Installing a locale - -Import your language file first; - -```js -import { frFR } from 'gridjs/l10n'; -``` - -then pass it to the `language` setting of the arc-table component. - -```js -Object.assign(document.createElement('arc-table'), { - ...allProps, - language: frFR, -}); -``` - -### Custom serialization - -**ARC** also provides a way to easily customize the messages and add your own language. -Simply extend the `language` config to replace the strings: - -```js -Object.assign(document.createElement('arc-table'), { - ...allProps, - language: { - search: { - placeholder: '🔍 Search...', - }, - pagination: { - previous: '⬅️', - next: '➡️', - showing: '😃 Displaying', - results: 'Records', - }, - }, -}); -``` - -### Properties - -Below, a list of the default language configurations that can be overwritten: - -```js -{ - search: { - placeholder: 'Type a keyword...', - }, - sort: { - sortAsc: 'Sort column ascending', - sortDesc: 'Sort column descending', - }, - pagination: { - previous: '˂', - next: '˃', - navigate: (page, pages) => `Page ${page} of ${pages}`, - page: (page) => `Page ${page}`, - showing: 'Showing', - of: 'of', - to: 'to', - results: 'results', - }, - loading: 'Retrieving your data, please wait...', - noRecordsFound: 'No matching records found.', - error: 'An error occurred while fetching your data.', -} -``` - -> **Note**: For more advanced features of the arc-table component, -> check the official [GridJS](https://gridjs.io/docs/examples/cell-formatting) docs. diff --git a/packages/components/src/components/table/arc-table.stories.tsx b/packages/components/src/components/table/arc-table.stories.tsx deleted file mode 100644 index 692c3abdd..000000000 --- a/packages/components/src/components/table/arc-table.stories.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Meta, Story } from '@storybook/web-components'; -import ArcTableDocumentation from './arc-table.documentation.mdx'; -import ArcTable from './ArcTable.js'; -import './arc-table.js'; - -export default { - title: 'Components/ArcTable', - component: 'arc-table', - parameters: { - docs: { - page: ArcTableDocumentation, - }, - }, -} as Meta; - -const Template: Story = (args: any) => - Object.assign(document.createElement(ArcTable.tag), args); - -const defaultArgs = { - columns: ['Name', 'Lastname', 'Email'], - data: [ - ['John', 'Doe', 'john.doe@arup.com'], - ['Jane', 'Doe', 'jane.doe@arup.com'], - ['Joe', 'Doakes', 'joe.doakes@arup.com'], - ['Juan', 'Perez', 'juan.perez@arup.com'], - ['Fred', 'Nerk', 'fred.nerk@arup.com'], - ], - fixedHeader: false, - height: '', - language: undefined, - pagination: false, - paginationLimit: 10, - paginationSummary: true, - resizable: false, - sort: false, - search: false, -}; - -export const BasicTable = Template.bind({}); -BasicTable.args = { ...defaultArgs }; - -export const FixedHeader = Template.bind({}); -FixedHeader.args = { ...defaultArgs, fixedHeader: true, height: '250px' }; - -export const Pagination = Template.bind({}); -Pagination.args = { ...defaultArgs, pagination: true }; - -export const Resizable = Template.bind({}); -Resizable.args = { ...defaultArgs, resizable: true }; - -export const Sorting = Template.bind({}); -Sorting.args = { ...defaultArgs, sort: true }; - -export const Search = Template.bind({}); -Search.args = { ...defaultArgs, search: true }; - -export const Localization = Template.bind({}); -Localization.args = { - ...defaultArgs, - search: true, - pagination: true, - language: { - search: { - placeholder: '🔍 Search...', - }, - pagination: { - previous: '⬅️', - next: '➡️', - showing: '😃 Displaying', - results: 'Records', - }, - }, -}; diff --git a/packages/components/src/components/table/arc-table.styles.ts b/packages/components/src/components/table/arc-table.styles.ts deleted file mode 100644 index 82c145160..000000000 --- a/packages/components/src/components/table/arc-table.styles.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { css } from 'lit'; -import componentStyles from '../../styles/component.styles.js'; - -export default [ - componentStyles, - css` - #main { - height: 100%; - } - - /* Overwrite the scrollbar-tracks to display a border that looks good with the table */ - ::-webkit-scrollbar-track { - border-left: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - border-radius: 0; - } - ::-webkit-scrollbar-track:horizontal { - border-top: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - border-left: none; - } - - /* Container */ - .gridjs-container { - display: inline-flex; - flex-direction: column; - height: inherit; - overflow: hidden; - color: rgb(var(--arc-font-color)); - position: relative; - z-index: 0; - } - - /* Header (contains the search input plugin) */ - .gridjs-head { - width: 100%; - padding-bottom: var(--arc-spacing-small); - } - .gridjs-head:empty, - .gridjs-footer:empty { - padding: 0; - border: none; - } - - /* Wrapper */ - .gridjs-wrapper { - display: flex; - width: 100%; - position: relative; - overflow: auto; - border: var(--arc-border-style) var(--arc-border-width) - rgb(var(--arc-color-default)); - z-index: 1; - -webkit-font-smoothing: antialiased; - } - - /* Table */ - table.gridjs-table { - flex: 1 1 100%; - border-collapse: collapse; - text-align: left; - display: table; - table-layout: fixed; - overflow: auto; - } - - /* Rows */ - .gridjs-tr { - border-top: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - border-bottom: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - } - .gridjs-tr:hover, - .gridjs-tr-selected { - background-color: rgba(var(--arc-font-color), 5%); - } - .gridjs-tr:first-of-type { - border-top: none; - } - .gridjs-tr:last-of-type { - border-bottom: none; - } - - /* Headers */ - th.gridjs-th { - position: relative; - padding: var(--arc-spacing-small) var(--arc-spacing-medium); - background-color: rgb(var(--arc-container-color)); - border: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - border-top: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - white-space: nowrap; - outline: none; - vertical-align: middle; - } - th.gridjs-th .gridjs-th-content { - text-overflow: ellipsis; - overflow: hidden; - width: 100%; - float: left; - } - th.gridjs-th-fixed { - position: sticky; - z-index: 1; - } - th.gridjs-th:first-of-type { - border-left: none; - } - th.gridjs-th:last-of-type { - border-right: none; - } - th.gridjs-th-sort { - cursor: pointer; - } - th.gridjs-th-sort .gridjs-th-content { - width: calc(100% - 15px); - } - th.gridjs-th-sort:hover, - th.gridjs-th-sort:focus { - background-color: currentcolor; - background-image: linear-gradient(var(--arc-hover-lighter) 0 0); - } - - /* Data */ - td.gridjs-td { - border-left: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - border-right: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - padding: var(--arc-spacing-small) var(--arc-spacing-medium); - background-color: transparent; - box-sizing: content-box; - } - td.gridjs-td:first-of-type { - border-left: none; - } - td.gridjs-td:last-of-type { - border-right: none; - } - - /* Footer */ - .gridjs-footer { - display: block; - position: relative; - width: 100%; - z-index: 5; - padding: var(--arc-spacing-small) var(--arc-spacing-medium); - border: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - border-top: none; - } - - /* Error message */ - td.gridjs-message { - text-align: center; - } - - /* Loading */ - .gridjs-loading-bar { - z-index: 10; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - background-color: transparent; - opacity: 0.5; - } - .gridjs-loading-bar::after { - position: absolute; - content: ''; - top: 0; - right: 0; - bottom: 0; - left: 0; - transform: translateX(-100%); - background-image: linear-gradient(var(--arc-hover-dark) 0 0); - background-repeat: no-repeat; - display: inline-block; - - -webkit-animation-duration: 2s; - -webkit-animation-fill-mode: forwards; - -webkit-animation-iteration-count: infinite; - -webkit-animation-name: shimmer; - -webkit-animation-timing-function: linear; - } - - @-webkit-keyframes shimmer { - 100% { - transform: translateX(100%); - } - } - - @keyframes shimmer { - 100% { - transform: translateX(100%); - } - } - - /* PLUGINS */ - /* Search filter */ - .gridjs-search { - float: left; - } - .gridjs-search-input { - width: 18.5rem; - } - input.gridjs-input { - outline: none; - color: inherit; - background-color: transparent; - border: var(--arc-border-width) var(--arc-border-style) - rgb(var(--arc-color-default)); - padding: var(--arc-spacing-x-small) var(--arc-spacing-normal); - line-height: inherit; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - } - input.gridjs-input:focus { - box-shadow: var(--arc-box-shadow); - border-color: rgb(var(--arc-color-info)); - } - - /* Sorting icon */ - button.gridjs-sort { - float: right; - height: 24px; - width: 13px; - background-color: rgb(var(--arc-font-color)); - background-repeat: no-repeat; - background-position-x: center; - background-size: contain; - cursor: pointer; - border: none; - outline: none; - } - button.gridjs-sort-neutral { - opacity: 0.3; - -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M12 0l8 10h-16l8-10zm8 14h-16l8 10 8-10z'/%3E%3C/svg%3E"); - mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M12 0l8 10h-16l8-10zm8 14h-16l8 10 8-10z'/%3E%3C/svg%3E"); - } - button.gridjs-sort-asc { - -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M0 21l12-18 12 18z'/%3E%3C/svg%3E") - no-repeat 50% 35%; - mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M0 21l12-18 12 18z'/%3E%3C/svg%3E") - no-repeat 50% 35%; - } - button.gridjs-sort-desc { - -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M24 3l-12 18-12-18z'/%3E%3C/svg%3E") - no-repeat 50% 65%; - mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M24 3l-12 18-12-18z'/%3E%3C/svg%3E") - no-repeat 50% 65%; - } - button.gridjs-sort:focus { - outline: none; - } - - /* Resizable */ - .gridjs-resizable { - position: absolute; - top: 0; - bottom: 0; - right: 0; - width: 3px; - } - .gridjs-resizable:hover { - cursor: ew-resize; - background-color: rgb(var(--arc-color-info)); - } - - /* Row selection */ - .gridjs-td .gridjs-checkbox { - display: block; - margin: auto; - cursor: pointer; - } - - /* Pagination */ - .gridjs-pagination, - .gridjs-pagination .gridjs-pages { - display: flex; - align-items: center; - } - .gridjs-pagination { - justify-content: space-between; - } - .gridjs-pagination .gridjs-pages { - margin-left: auto; - } - .gridjs-pagination .gridjs-pages button { - cursor: pointer; - padding: var(--arc-spacing-x-small) var(--arc-spacing-small); - border: none; - color: inherit; - background-color: transparent; - outline: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - } - .gridjs-pagination .gridjs-pages button.gridjs-currentPage { - border-bottom: calc(var(--arc-border-width) * 2) var(--arc-border-style) - currentColor; - } - .gridjs-pagination .gridjs-pages button:hover, - .gridjs-pagination .gridjs-pages button:focus-visible { - background-color: rgba(var(--arc-font-color), 10%); - } - .gridjs-pagination .gridjs-pages button:disabled, - .gridjs-pagination .gridjs-pages button[disabled], - .gridjs-pagination .gridjs-pages button:hover:disabled { - cursor: default; - background-color: transparent; - color: rgb(var(--arc-grey-050)); - } - .gridjs-pagination .gridjs-pages button.gridjs-spread { - cursor: default; - box-shadow: none; - background-color: transparent; - } - `, -]; diff --git a/packages/components/src/components/table/arc-table.test.ts b/packages/components/src/components/table/arc-table.test.ts deleted file mode 100644 index 3ac4c8957..000000000 --- a/packages/components/src/components/table/arc-table.test.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { html } from 'lit'; -import { expect, fixture, elementUpdated, waitUntil } from '@open-wc/testing'; -import { ComponentType } from 'preact'; -import sinon, { SinonSpy } from 'sinon'; -import { ARC_EVENTS } from '../../internal/constants/eventConstants.js'; - -import type ArcTable from './ArcTable.js'; -import './arc-table.js'; - -describe('ArcTable', () => { - /* Test the rendering of the component */ - describe('rendering', () => { - let element: ArcTable; - - beforeEach(async () => { - element = await fixture(html``); - }); - - /* Test default properties that reflect to the DOM */ - it('renders the element with default properties in the dom', () => { - expect(element).dom.to.equal(``); - }); - - /* Test the accessibility */ - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(); - }); - }); - - /* Test the setters/getters */ - describe('setters/getters', () => { - it('renders the element with a custom columns property', async () => { - const columns = ['Name', 'LastName', 'Email']; - const element: ArcTable = await fixture( - html``, - ); - expect(element.columns.length).to.equal(columns.length); - expect(element.columns[0]).to.equal(columns[0]); - }); - - it('renders the element with a custom data property', async () => { - const data = ['John', 'Doe', 'john.doe@johndoe.com']; - const element: ArcTable = await fixture( - html``, - ); - expect(element.data.length).to.equal(data.length); - expect(element.data[0]).to.equal(data[0]); - }); - - it('renders the element with a custom height property', async () => { - const element: ArcTable = await fixture( - html``, - ); - - expect(element.height).to.equal('200px'); - expect(element.getAttribute('height')).to.equal('200px'); - }); - - it('renders the element with a custom language property', async () => { - const language = { - search: { - placeholder: 'Search...', - }, - }; - const element: ArcTable = await fixture( - html``, - ); - // @ts-ignore - const { placeholder } = element.language.search; - expect(placeholder).to.equal(language.search.placeholder); - }); - - it('renders the element with a custom pagination-limit property', async () => { - const element: ArcTable = await fixture( - html``, - ); - - expect(element.paginationLimit).to.equal(10); - expect(element.getAttribute('pagination-limit')).to.equal('10'); - }); - }); - - /* Test different component states (active, disabled, loading etc.) */ - describe('states', () => { - let element: ArcTable; - - beforeEach(async () => { - element = await fixture(html``); - }); - - it('renders the component in a fixed-header state', async () => { - expect(element.fixedHeader).to.be.false; - element.fixedHeader = true; - await elementUpdated(element); - expect(element.fixedHeader).to.be.true; - }); - - it('renders the component in a state that shows pagination', async () => { - expect(element.pagination).to.be.false; - element.pagination = true; - await elementUpdated(element); - expect(element.pagination).to.be.true; - }); - - it('renders the component in a state that shows the paginationSummary', async () => { - expect(element.paginationSummary).to.be.false; - element.paginationSummary = true; - await elementUpdated(element); - expect(element.paginationSummary).to.be.true; - }); - - it('renders the component in a resizable state', async () => { - expect(element.resizable).to.be.false; - element.resizable = true; - await elementUpdated(element); - expect(element.resizable).to.be.true; - }); - - it('renders the component in a sortable state', async () => { - expect(element.sort).to.be.false; - element.sort = true; - await elementUpdated(element); - expect(element.sort).to.be.true; - }); - - it('renders the component in a search state', async () => { - expect(element.search).to.be.false; - element.search = true; - await elementUpdated(element); - expect(element.search).to.be.true; - }); - }); - - /* Test specific methods */ - describe('methods', () => { - let element: ArcTable; - let tableReadySpy: SinonSpy; - - /* Due to GridJS creating the table, the reference to any DOM elements is lost after each update. */ - const getTableBody = (): HTMLTableSectionElement => { - const gridTable: HTMLTableElement = - element.shadowRoot?.querySelector('table.gridjs-table')!; - return gridTable.querySelector('tbody')!; - }; - - beforeEach(async () => { - tableReadySpy = sinon.spy(); - element = await fixture(html``); - element.addEventListener(ARC_EVENTS.tableReady, tableReadySpy); - }); - - afterEach(() => { - tableReadySpy.resetHistory(); - }); - - it('should update the configuration after initializing the table', async () => { - /* Wait for the underlying gridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledOnce); - - /* When there is no data, there's a single row with an alert. */ - expect(getTableBody().children.length).to.equal(1); - expect(element.pagination).to.be.false; - - /* Update the configuration */ - element.updateConfig({ - data: [ - ['one', 'two', 'three'], - ['four', 'five', 'six'], - ], - pagination: true, - search: true, - }); - - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledThrice); - - expect(getTableBody().children.length).to.equal(2); - expect(element.pagination).to.be.true; - expect(element.search).to.be.true; - }); - - it('throws an error when updating the configuration without properties', async () => { - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledOnce); - - expect(() => element.updateConfig({})).to.throw( - 'Missing property: Please provide at least one property to update the configuration.', - ); - }); - - it('should prevent updating or setting arc-table properties that are not part of the API', async () => { - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledOnce); - - // @ts-ignore It is known that the property does not exist on the arc-table. - expect(element.autoWidth).to.be.undefined; - - /* Update the GridJS configuration */ - element.updateConfig({ - autoWidth: true, - }); - - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledOnce); - - // @ts-ignore It is known that the property does not exist on the arc-table. - expect(element.autoWidth).to.be.undefined; - }); - - it('should format a specific column through the format method', async () => { - const firstColumn = () => { - const rows = getTableBody().children; - const firstRow = rows[0] as HTMLTableRowElement; - const columns = firstRow.children; - return columns[0] as HTMLTableCellElement; - }; - - await waitUntil(() => tableReadySpy.calledOnce); - - /* Update the GridJS configuration */ - element.updateConfig({ - columns: ['Name', 'LastName'], - data: [ - ['John', 'Doe'], - ['Jane', 'Doe'], - ], - }); - - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil( - () => tableReadySpy.calledTwice, - 'Table did not update with data', - ); - - expect(firstColumn()).dom.to.equal('John'); - - /* Format the first column */ - element.updateConfig({ - columns: [ - { - name: 'Name', - formatter: (cell) => - element.format( - 'strong' as unknown as ComponentType, - '', - cell, - ), - }, - 'LastName', - ], - }); - - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil( - () => tableReadySpy.calledThrice, - 'Table did not update with formatter', - ); - - expect(firstColumn()).dom.to.equal('John'); - }); - }); - - /* Test the events (click, focus, blur etc.) */ - describe('events', () => { - let element: ArcTable; - let rowClickSpy: SinonSpy; - let cellClickSpy: SinonSpy; - let tableReadySpy: SinonSpy; - - /* Due to GridJS creating the table, the reference to any DOM elements is lost after each update. */ - const getTableBody = (): HTMLTableSectionElement => { - const gridTable: HTMLTableElement = - element.shadowRoot?.querySelector('table.gridjs-table')!; - return gridTable.querySelector('tbody')!; - }; - - beforeEach(async () => { - rowClickSpy = sinon.spy(); - cellClickSpy = sinon.spy(); - tableReadySpy = sinon.spy(); - const data = ['John', 'Doe', 'john.doe@johndoe.com']; - element = await fixture(html``); - element.addEventListener(ARC_EVENTS.tableReady, tableReadySpy); - }); - - afterEach(async () => { - rowClickSpy.resetHistory(); - cellClickSpy.resetHistory(); - tableReadySpy.resetHistory(); - }); - - it('simulates a click on the table row', async () => { - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledOnce); - - const rows = getTableBody().children; - const firstRow = rows[0] as HTMLTableRowElement; - firstRow.addEventListener('click', rowClickSpy); - - firstRow.click(); - await waitUntil(() => rowClickSpy.calledOnce); - - expect(rowClickSpy).to.have.been.calledOnce; - }); - - it('simulates a click on the table cell', async () => { - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledOnce); - - const rows = getTableBody().children; - const firstRow = rows[0] as HTMLTableRowElement; - const columns = firstRow.children; - const firstColumn = columns[0] as HTMLTableCellElement; - - firstColumn.addEventListener('click', cellClickSpy); - - firstColumn.click(); - await waitUntil(() => cellClickSpy.calledOnce); - - expect(cellClickSpy).to.have.been.calledOnce; - }); - - it('emits when the table is ready', async () => { - /* Wait for the underlying GridJS instance to finish rendering. */ - await waitUntil(() => tableReadySpy.calledOnce); - expect(tableReadySpy).to.have.been.calledOnce; - }); - }); -}); diff --git a/packages/components/src/components/table/arc-table.ts b/packages/components/src/components/table/arc-table.ts deleted file mode 100644 index 4371de579..000000000 --- a/packages/components/src/components/table/arc-table.ts +++ /dev/null @@ -1,3 +0,0 @@ -import ArcTable from './ArcTable.js'; - -customElements.define('arc-table', ArcTable); diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 42c2fc1bd..76be5cf8b 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -22,7 +22,6 @@ import './components/switch/arc-switch.js'; import './components/avatar/arc-avatar.js'; import './components/chip/arc-chip.js'; import './components/tooltip/arc-tooltip.js'; -import './components/table/arc-table.js'; /* Types */ export { default as ArcAccessibility } from './components/accessibility/ArcAccessibility.js'; @@ -48,7 +47,6 @@ export { default as ArcSwitch } from './components/switch/ArcSwitch.js'; export { default as ArcAvatar } from './components/avatar/ArcAvatar.js'; export { default as ArcChip } from './components/chip/ArcChip.js'; export { default as ArcTooltip } from './components/tooltip/ArcTooltip.js'; -export { default as ArcTable } from './components/table/ArcTable.js'; /* Utilities */ export * from './utilities/form-utils.js'; diff --git a/packages/components/stories/docusaurus.mdx b/packages/components/stories/docusaurus.mdx new file mode 100644 index 000000000..bf52a29f6 --- /dev/null +++ b/packages/components/stories/docusaurus.mdx @@ -0,0 +1,36 @@ +import { Meta } from '@storybook/blocks'; + + + +# Docusaurus + +## Installation + +```bash +npm install -E @arc-web/components@latest +``` + +## Usage + +```ts +/* docusaurus.config.ts */ +import type { Config } from '@docusaurus/types'; +import type * as Preset from '@docusaurus/preset-classic'; + +const config: Config = { + ... + presets: [ + ... + { + docs: { ... }, + theme: { + ... + customCss: + require.resolve('/@arc-web/components/themes/docusaurus.css'), + } + } satisfies Preset.Options + ] +}; + +export default config; +``` diff --git a/packages/components/stories/gridjs.mdx b/packages/components/stories/gridjs.mdx new file mode 100644 index 000000000..8f14cad98 --- /dev/null +++ b/packages/components/stories/gridjs.mdx @@ -0,0 +1,53 @@ +import { Meta } from '@storybook/blocks'; + + + +# Grid.js + +Grid.js is a Free and open-source JavaScript table plugin. + +## Installation + +```bash +npm i -E @arc-web/components@latest gridjs@latest +``` + +## Basic Usage + +```html + + + + ... + + + + +
+
+ + + + +``` + +Please refer to the [official gridjs documentation](https://gridjs.io/) for more information. diff --git a/packages/components/stories/react-mui-material.mdx b/packages/components/stories/react-mui-material.mdx new file mode 100644 index 000000000..a00f12f11 --- /dev/null +++ b/packages/components/stories/react-mui-material.mdx @@ -0,0 +1,29 @@ +import { Meta } from '@storybook/blocks'; + + + +# React and MUI Material + +``` +npm install @arc-web/components@latest @arc-web/react@latest @arc-wb/material@latest @mui/material@latest @mui/icons-material@latest +``` + +```tsx +import React from 'react'; +import { ArcContainer, ArcButton } from '@arc-web/react'; +import { ThemeProvider } from '@arc-web/material'; +import { Button } from '@mui/material'; + +export function App() { + return ( + + +
+ Click me + +
+
+
+ ); +} +``` diff --git a/packages/components/stories/react.mdx b/packages/components/stories/react.mdx new file mode 100644 index 000000000..46444abb6 --- /dev/null +++ b/packages/components/stories/react.mdx @@ -0,0 +1,23 @@ +import { Meta } from '@storybook/blocks'; + + + +# React + +React can render web components, however, makes assumptions about HTML elements that don't always hold for custom elements, while also treating lower-case tag names differently from upper-cased. This makes working with web components harder than necessary to use. React is working on fixes to these issues, but in the meantime, the `@arc-web/react` provides a wrapper that takes care of setting properties and listening to events for you. Read more about why we need this wrapper [here](https://lit.dev/docs/frameworks/react/#why-are-wrappers-needed) + +Install both the `@arc-web/components` and `@arc-web/react` packages from npm: + +```sh +npm install @arc-web/components@latest @arc-web/react@latest +``` + +Setup the `@arc-web/components` package as normal, however, import React components from the `@arc-web/react` package: + +```tsx +import { ArcContainer } from '@arc-web/react'; + +export const App = () => { + return ; +}; +``` diff --git a/packages/components/stories/tanstack-table.mdx b/packages/components/stories/tanstack-table.mdx new file mode 100644 index 000000000..b7941a081 --- /dev/null +++ b/packages/components/stories/tanstack-table.mdx @@ -0,0 +1,106 @@ +import { Meta } from '@storybook/blocks'; + + + +# TanStack Tables + +TanStack table is a headless UI for building powerful tables & datagrids. + +## Installation + +```bash +npm install -E @arc-web/components@latest @tanstack/table-core@latest +``` + +## Usage + +```html + + + + ... + + + + + + + + +``` + +Please refer to the [official TanStack table documentation](https://tanstack.com/) for more information. diff --git a/packages/components/themes/gridjs.css b/packages/components/themes/gridjs.css new file mode 100644 index 000000000..eb234da7c --- /dev/null +++ b/packages/components/themes/gridjs.css @@ -0,0 +1,282 @@ +::-webkit-scrollbar-track { + border-left: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-radius: 0; +} +::-webkit-scrollbar-track:horizontal { + border-top: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-left: none; +} +.gridjs-container { + display: inline-flex; + flex-direction: column; + height: inherit; + overflow: hidden; + color: rgb(var(--arc-font-color)); + position: relative; + z-index: 0; +} +.gridjs-head { + width: 100%; + padding-bottom: var(--arc-spacing-small); +} +.gridjs-head:empty, +.gridjs-footer:empty { + padding: 0; + border: none; +} +.gridjs-wrapper { + display: flex; + width: 100%; + position: relative; + overflow: auto; + border: var(--arc-border-style) var(--arc-border-width) + rgb(var(--arc-color-default)); + z-index: 1; + -webkit-font-smoothing: antialiased; +} +table.gridjs-table { + flex: 1 1 100%; + border-collapse: collapse; + text-align: left; + display: table; + table-layout: fixed; + overflow: auto; +} +.gridjs-tr { + border-top: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-bottom: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); +} +.gridjs-tr:hover, +.gridjs-tr-selected { + background-color: rgba(var(--arc-font-color), 5%); +} +.gridjs-tr:first-of-type { + border-top: none; +} +.gridjs-tr:last-of-type { + border-bottom: none; +} +th.gridjs-th { + position: relative; + padding: var(--arc-spacing-small) var(--arc-spacing-medium); + background-color: rgb(var(--arc-container-color)); + border: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-top: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + white-space: nowrap; + outline: none; + vertical-align: middle; +} +th.gridjs-th .gridjs-th-content { + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + float: left; +} +th.gridjs-th-fixed { + position: sticky; + z-index: 1; +} +th.gridjs-th:first-of-type { + border-left: none; +} +th.gridjs-th:last-of-type { + border-right: none; +} +th.gridjs-th-sort { + cursor: pointer; +} +th.gridjs-th-sort .gridjs-th-content { + width: calc(100% - 15px); +} +th.gridjs-th-sort:hover, +th.gridjs-th-sort:focus { + background-color: currentcolor; + background-image: linear-gradient(var(--arc-hover-lighter) 0 0); +} +td.gridjs-td { + border-left: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-right: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + padding: var(--arc-spacing-small) var(--arc-spacing-medium); + background-color: transparent; + box-sizing: content-box; +} +td.gridjs-td:first-of-type { + border-left: none; +} +td.gridjs-td:last-of-type { + border-right: none; +} +.gridjs-footer { + display: block; + position: relative; + width: 100%; + z-index: 5; + padding: var(--arc-spacing-small) var(--arc-spacing-medium); + border: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-top: none; +} +td.gridjs-message { + text-align: center; +} +.gridjs-loading-bar { + z-index: 10; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: transparent; + opacity: 0.5; +} +.gridjs-loading-bar::after { + position: absolute; + content: ''; + top: 0; + right: 0; + bottom: 0; + left: 0; + transform: translateX(-100%); + background-image: linear-gradient(var(--arc-hover-dark) 0 0); + background-repeat: no-repeat; + display: inline-block; + -webkit-animation-duration: 2s; + -webkit-animation-fill-mode: forwards; + -webkit-animation-iteration-count: infinite; + -webkit-animation-name: shimmer; + -webkit-animation-timing-function: linear; +} +@-webkit-keyframes shimmer { + 100% { + transform: translateX(100%); + } +} +@keyframes shimmer { + 100% { + transform: translateX(100%); + } +} +.gridjs-search { + float: left; +} +.gridjs-search-input { + width: 18.5rem; +} +input.gridjs-input { + outline: none; + color: inherit; + background-color: transparent; + border: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + padding: var(--arc-spacing-x-small) var(--arc-spacing-normal); + line-height: inherit; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +input.gridjs-input:focus { + box-shadow: var(--arc-box-shadow); + border-color: rgb(var(--arc-color-info)); +} +button.gridjs-sort { + float: right; + height: 24px; + width: 13px; + background-color: rgb(var(--arc-font-color)); + background-repeat: no-repeat; + background-position-x: center; + background-size: contain; + cursor: pointer; + border: none; + outline: none; +} +button.gridjs-sort-neutral { + opacity: 0.3; + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M12 0l8 10h-16l8-10zm8 14h-16l8 10 8-10z'/%3E%3C/svg%3E"); + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M12 0l8 10h-16l8-10zm8 14h-16l8 10 8-10z'/%3E%3C/svg%3E"); +} +button.gridjs-sort-asc { + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M0 21l12-18 12 18z'/%3E%3C/svg%3E") + no-repeat 50% 35%; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M0 21l12-18 12 18z'/%3E%3C/svg%3E") + no-repeat 50% 35%; +} +button.gridjs-sort-desc { + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M24 3l-12 18-12-18z'/%3E%3C/svg%3E") + no-repeat 50% 65%; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='8' fill='currentColor' stroke='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M24 3l-12 18-12-18z'/%3E%3C/svg%3E") + no-repeat 50% 65%; +} +button.gridjs-sort:focus { + outline: none; +} +.gridjs-resizable { + position: absolute; + top: 0; + bottom: 0; + right: 0; + width: 3px; +} +.gridjs-resizable:hover { + cursor: ew-resize; + background-color: rgb(var(--arc-color-info)); +} +.gridjs-td .gridjs-checkbox { + display: block; + margin: auto; + cursor: pointer; +} +.gridjs-pagination, +.gridjs-pagination .gridjs-pages { + display: flex; + align-items: center; +} +.gridjs-pagination { + justify-content: space-between; +} +.gridjs-pagination .gridjs-pages { + margin-left: auto; +} +.gridjs-pagination .gridjs-pages button { + cursor: pointer; + padding: var(--arc-spacing-x-small) var(--arc-spacing-small); + border: none; + color: inherit; + background-color: transparent; + outline: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.gridjs-pagination .gridjs-pages button.gridjs-currentPage { + border-bottom: calc(var(--arc-border-width) * 2) var(--arc-border-style) + currentColor; +} +.gridjs-pagination .gridjs-pages button:hover, +.gridjs-pagination .gridjs-pages button:focus-visible { + background-color: rgba(var(--arc-font-color), 10%); +} +.gridjs-pagination .gridjs-pages button:disabled, +.gridjs-pagination .gridjs-pages button[disabled], +.gridjs-pagination .gridjs-pages button:hover:disabled { + cursor: default; + background-color: transparent; + color: rgb(var(--arc-grey-050)); +} +.gridjs-pagination .gridjs-pages button.gridjs-spread { + cursor: default; + box-shadow: none; + background-color: transparent; +} diff --git a/packages/components/themes/tanstack-table.css b/packages/components/themes/tanstack-table.css new file mode 100644 index 000000000..5d553ba26 --- /dev/null +++ b/packages/components/themes/tanstack-table.css @@ -0,0 +1,92 @@ +table[arc-table] { + flex: 1 1 100%; + border-collapse: collapse; + text-align: left; + display: table; + table-layout: fixed; + overflow: auto; + width: 100%; + border: var(--arc-border-style) var(--arc-border-width) + rgb(var(--arc-color-default)); +} +table[arc-table]::-webkit-scrollbar-track { + border-left: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-radius: 0; +} +table[arc-table]::-webkit-scrollbar-track:horizontal { + border-top: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-left: none; +} + +table[arc-table] tr { + border-top: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-bottom: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); +} +table[arc-table] tr:hover { + background-color: rgba(var(--arc-font-color), 5%); +} +table[arc-table] tr:first-of-type, +table[arc-table] tr:first-child { + border-top: none; +} +table[arc-table] tr:last-of-type, +table[arc-table] tr:last-child { + border-bottom: none; +} + +table[arc-table] th { + position: relative; + padding: var(--arc-spacing-small) var(--arc-spacing-medium); + background-color: rgb(var(--arc-container-color)); + border: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-top: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + white-space: nowrap; + outline: none; + vertical-align: middle; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; +} +table[arc-table] th[arc-fixed] { + position: sticky; +} +table[arc-table] th[arc-fixed]::after { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: var(--arc-border-width); + background-color: rgb(var(--arc-color-default)); +} +table[arc-table] th:first-of-type { + border-left: none; +} +table[arc-table] th:last-of-type { + border-right: none; +} + +table[arc-table] td { + border-left: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + border-right: var(--arc-border-width) var(--arc-border-style) + rgb(var(--arc-color-default)); + padding: var(--arc-spacing-small) var(--arc-spacing-medium); + background-color: transparent; + box-sizing: content-box; +} +table[arc-table] td:first-of-type { + border-left: none; +} +table[arc-table] td:last-of-type { + border-right: none; +} diff --git a/packages/react/src/components/arc-table.ts b/packages/react/src/components/arc-table.ts deleted file mode 100644 index 158d58442..000000000 --- a/packages/react/src/components/arc-table.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createComponent, EventName } from '@lit-labs/react'; -import React from 'react'; -import type { Row } from 'gridjs'; -import type { TCell, TColumn } from 'gridjs/dist/src/types'; -import { ARC_EVENTS } from '@arc-web/components/src/internal/constants/eventConstants.js'; -import ArcTableWC from '@arc-web/components/src/components/table/ArcTable.js'; -import '@arc-web/components/src/components/table/arc-table.js'; - -export const ArcTable = createComponent({ - tagName: 'arc-table', - elementClass: ArcTableWC, - react: React, - events: { - onArcRowClick: ARC_EVENTS.rowClick as EventName< - CustomEvent<[e: MouseEvent, row: Row]> - >, - onArcCellClick: ARC_EVENTS.cellClick as EventName< - CustomEvent<[e: MouseEvent, cell: TCell, column: TColumn, row: Row]> - >, - onArcTableReady: ARC_EVENTS.tableReady as EventName>, - }, -}); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2d6d85543..e17d2ece6 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -20,7 +20,6 @@ export * from './components/arc-sidebar.js'; export * from './components/arc-spinner.js'; export * from './components/arc-sso.js'; export * from './components/arc-switch.js'; -export * from './components/arc-table.js'; export * from './components/arc-tooltip.js'; export * from '@arc-web/components/src/utilities/form-utils.js'; diff --git a/playgrounds/angular/src/main.ts b/playgrounds/angular/src/main.ts index 2e708cd85..30cf9b7dd 100644 --- a/playgrounds/angular/src/main.ts +++ b/playgrounds/angular/src/main.ts @@ -1,13 +1,183 @@ import { bootstrapApplication } from '@angular/platform-browser'; -import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { + Component, + CUSTOM_ELEMENTS_SCHEMA, + ElementRef, + ViewChild, + AfterViewInit, + signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; import '@arc-web/components/src/components/container/arc-container'; +import { Grid as ArcGrid } from 'gridjs'; +import { + ColumnDef, + createAngularTable, + FlexRenderDirective, + getCoreRowModel, +} from '@tanstack/angular-table'; + +type Person = { + firstName: string; + lastName: string; + age: number; + visits: number; + status: string; + progress: number; +}; + +const defaultData: Person[] = [ + { + firstName: 'tanner', + lastName: 'linsley', + age: 24, + visits: 100, + status: 'In Relationship', + progress: 50, + }, + { + firstName: 'tandy', + lastName: 'miller', + age: 40, + visits: 40, + status: 'Single', + progress: 80, + }, + { + firstName: 'joe', + lastName: 'dirte', + age: 45, + visits: 20, + status: 'Complicated', + progress: 10, + }, +]; + +const defaultColumns: ColumnDef[] = [ + { + accessorKey: 'firstName', + cell: (info) => info.getValue(), + footer: (info) => info.column.id, + }, + { + accessorFn: (row) => row.lastName, + id: 'lastName', + cell: (info) => `${info.getValue()}`, + header: () => `Last Name`, + footer: (info) => info.column.id, + }, + { + accessorKey: 'age', + header: () => 'Age', + footer: (info) => info.column.id, + }, + { + accessorKey: 'visits', + header: () => `Visits`, + footer: (info) => info.column.id, + }, + { + accessorKey: 'status', + header: 'Status', + footer: (info) => info.column.id, + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + footer: (info) => info.column.id, + }, +]; @Component({ standalone: true, selector: 'app-root', schemas: [CUSTOM_ELEMENTS_SCHEMA], - template: ``, + imports: [CommonModule, FlexRenderDirective], + template: ` + +

Grid.js Grid

+
+

Tanstack Table

+
+ + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+ `, }) -export class AppComponent { } +export class AppComponent implements AfterViewInit { + @ViewChild('grid') grid!: ElementRef; + + public tableData = signal(defaultData); + public table = createAngularTable(() => ({ + data: this.tableData(), + columns: defaultColumns, + getCoreRowModel: getCoreRowModel(), + debugTable: true, + })); + + ngAfterViewInit() { + new ArcGrid({ + columns: [ + { name: 'firstName' }, + { name: 'lastName' }, + { name: 'age' }, + { name: 'visits' }, + { name: 'status' }, + { name: 'progress' }, + ], + data: this.tableData(), + }).render(this.grid.nativeElement); + } +} bootstrapApplication(AppComponent).catch(console.error); diff --git a/playgrounds/angular/tsconfig.json b/playgrounds/angular/tsconfig.json index 397748122..0e1a9a0f2 100644 --- a/playgrounds/angular/tsconfig.json +++ b/playgrounds/angular/tsconfig.json @@ -5,11 +5,5 @@ "../../packages/components/src/**/*.test.ts", "../../packages/components/src/**/*.stories.ts" ], - "files": ["src/main.ts"], - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } + "files": ["src/main.ts"] } diff --git a/playgrounds/svelte-kit/project.json b/playgrounds/svelte-kit/project.json index 4ca38c6e7..56006ee25 100644 --- a/playgrounds/svelte-kit/project.json +++ b/playgrounds/svelte-kit/project.json @@ -9,13 +9,10 @@ "serve": { "executor": "nx:run-commands", "options": { - "commands": [ - "rm -rf .svelte-kit", - "npx vite dev", - ], + "commands": ["rm -rf .svelte-kit", "npx vite dev"], "cwd": "playgrounds/svelte-kit", "parallel": false } - }, + } } } diff --git a/playgrounds/svelte-kit/src/app.d.ts b/playgrounds/svelte-kit/src/app.d.ts index 628556684..a6911e55f 100644 --- a/playgrounds/svelte-kit/src/app.d.ts +++ b/playgrounds/svelte-kit/src/app.d.ts @@ -1,5 +1,5 @@ declare global { - namespace App {} + namespace App {} } export {}; diff --git a/playgrounds/svelte-kit/src/app.html b/playgrounds/svelte-kit/src/app.html index 1ca512349..df3ed1f56 100644 --- a/playgrounds/svelte-kit/src/app.html +++ b/playgrounds/svelte-kit/src/app.html @@ -1,6 +1,6 @@ - + ARC Playground @@ -14,9 +14,9 @@ - %sveltekit.head% - - -
%sveltekit.body%
- + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/playgrounds/svelte-kit/svelte.config.mjs b/playgrounds/svelte-kit/svelte.config.mjs index c6c2a2d2e..5646980ae 100644 --- a/playgrounds/svelte-kit/svelte.config.mjs +++ b/playgrounds/svelte-kit/svelte.config.mjs @@ -3,8 +3,8 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { - preprocess: vitePreprocess(), - kit: { adapter: adapter() }, + preprocess: vitePreprocess(), + kit: { adapter: adapter() }, }; export default config; diff --git a/playgrounds/svelte-kit/tsconfig.json b/playgrounds/svelte-kit/tsconfig.json index 0f47472f7..5c56cee33 100644 --- a/playgrounds/svelte-kit/tsconfig.json +++ b/playgrounds/svelte-kit/tsconfig.json @@ -1,13 +1,13 @@ { - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true - } + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true + } } diff --git a/playgrounds/svelte-kit/vite.config.mts b/playgrounds/svelte-kit/vite.config.mts index 28d2e2de7..76cc7ad0e 100644 --- a/playgrounds/svelte-kit/vite.config.mts +++ b/playgrounds/svelte-kit/vite.config.mts @@ -9,9 +9,13 @@ export default defineConfig({ port: 4200, host: 'localhost', }, - ssr: { noExternal: ['@sveltejs/site-kit', '@sveltejs/kit', 'svelte', '@arc-web/components'] }, - plugins: [ - viteTsConfigPaths({ root: '../../' }), - sveltekit(), - ], + ssr: { + noExternal: [ + '@sveltejs/site-kit', + '@sveltejs/kit', + 'svelte', + '@arc-web/components', + ], + }, + plugins: [viteTsConfigPaths({ root: '../../' }), sveltekit()], }); diff --git a/playgrounds/vanilla/src/main.ts b/playgrounds/vanilla/src/main.ts index 2158b1e69..d2c1240da 100644 --- a/playgrounds/vanilla/src/main.ts +++ b/playgrounds/vanilla/src/main.ts @@ -1,7 +1,6 @@ import '@arc-web/components/themes/index.css'; -import '@arc-web/components/src/components/container/arc-container'; +import '@arc-web/components'; const app = document.querySelector('app-root')!; -const container = document.createElement('arc-container'); - -app.appendChild(container); +const arcContainer = document.createElement('arc-container'); +app.appendChild(arcContainer);