diff --git a/.vscode/settings.json b/.vscode/settings.json index e4ea8aee..ac8e05f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "files.eol": "\r\n", "editor.formatOnSave": true, - "tailwindCSS.experimental.configFile": "libs/flowbite-angular/styles/flowbite-angular.css", + "tailwindCSS.experimental.configFile": "apps/docs/public/css/styles.css", "tailwindCSS.classAttributes": ["class", "className", "ngClass", "customStyle"], "tailwindCSS.classFunctions": ["twMerge", "createTheme"], "typescript.tsdk": "node_modules/typescript/lib", diff --git a/apps/docs/docs/components/table/_default.component.html b/apps/docs/docs/components/table/_default.component.html new file mode 100644 index 00000000..d0662fc7 --- /dev/null +++ b/apps/docs/docs/components/table/_default.component.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ Product name + + Qty + + Price +
+ {{ data.name }} + + {{ data.qty }} + + {{ data.price }} +
+ Total + 1030
diff --git a/apps/docs/docs/components/table/_default.component.ts b/apps/docs/docs/components/table/_default.component.ts new file mode 100644 index 00000000..87261cfe --- /dev/null +++ b/apps/docs/docs/components/table/_default.component.ts @@ -0,0 +1,18 @@ +import { Table, TableBody, TableFoot, TableHead } from 'flowbite-angular/table'; + +import { Component } from '@angular/core'; + +@Component({ + imports: [Table, TableBody, TableFoot, TableHead], + templateUrl: './_default.component.html', + host: { + class: 'flex flex-wrap flex-row gap-3 p-6', + }, +}) +export class FlowbiteDefaultComponent { + readonly data = Array.from({ length: 5 }, (_, i) => i++).map((x) => ({ + name: `Product ${x}`, + qty: x, + price: x * x, + })); +} diff --git a/apps/docs/docs/components/table/index.md b/apps/docs/docs/components/table/index.md new file mode 100644 index 00000000..8fbead47 --- /dev/null +++ b/apps/docs/docs/components/table/index.md @@ -0,0 +1,18 @@ +--- +keyword: TablePage +--- + +## Default Table + +{{ NgDocActions.demo('flowbiteDefaultComponent', {container: false}) }} + +```angular-html file="./_default.component.html" group="default" name="html" + +``` + +```angular-ts file="./_default.component.ts" group="default" name="typescript" + +``` + +{% import "../../shared/theme-macro.md" as themeMacro %} +{{ themeMacro.display(NgDocPage.data.themes) }} diff --git a/apps/docs/docs/components/table/ng-doc.page.ts b/apps/docs/docs/components/table/ng-doc.page.ts new file mode 100644 index 00000000..44feb99e --- /dev/null +++ b/apps/docs/docs/components/table/ng-doc.page.ts @@ -0,0 +1,35 @@ +import type { DocThemes } from '../../doc-theme.model'; +import { toIndentedJson } from '../../doc-theme.model'; +import ComponentCategory from '../ng-doc.category'; +import { FlowbiteDefaultComponent } from './_default.component'; + +import { + flowbiteTableBodyTheme, + flowbiteTableFootTheme, + flowbiteTableHeadTheme, + flowbiteTableTheme, +} from 'flowbite-angular/table'; + +import type { NgDocPage } from '@ng-doc/core'; + +/** + * + */ +const Table: NgDocPage = { + title: 'Table', + mdFile: './index.md', + category: ComponentCategory, + demos: { + flowbiteDefaultComponent: FlowbiteDefaultComponent, + }, + data: { + themes: [ + { title: 'Table', content: toIndentedJson(flowbiteTableTheme) }, + { title: 'Table Head', content: toIndentedJson(flowbiteTableHeadTheme) }, + { title: 'Table Body', content: toIndentedJson(flowbiteTableBodyTheme) }, + { title: 'Table Foot', content: toIndentedJson(flowbiteTableFootTheme) }, + ] satisfies DocThemes, + }, +}; + +export default Table; diff --git a/apps/docs/docs/ng-doc.api.ts b/apps/docs/docs/ng-doc.api.ts index d07abf0e..312d1223 100644 --- a/apps/docs/docs/ng-doc.api.ts +++ b/apps/docs/docs/ng-doc.api.ts @@ -31,6 +31,7 @@ const api: NgDocApi = { 'libs/flowbite-angular/sidebar/src/index.ts', 'libs/flowbite-angular/tab/src/index.ts', 'libs/flowbite-angular/theme-toggle/src/index.ts', + 'libs/flowbite-angular/table/src/index.ts', 'libs/flowbite-angular/tooltip/src/index.ts', ], }, diff --git a/apps/storybook/src/table.component.stories.ts b/apps/storybook/src/table.component.stories.ts new file mode 100644 index 00000000..6b8f20fb --- /dev/null +++ b/apps/storybook/src/table.component.stories.ts @@ -0,0 +1,125 @@ +import { + defaultFlowbiteTableConfig, + Table, + TableBody, + TableFoot, + TableHead, +} from 'flowbite-angular/table'; + +import type { Meta, StoryObj } from '@storybook/angular'; +import { argsToTemplate, moduleMetadata } from '@storybook/angular'; + +type StoryType = Table; + +export default { + title: 'Component/Table', + component: Table, + decorators: [ + moduleMetadata({ + imports: [TableHead, TableBody, TableFoot], + }), + ], + argTypes: { + color: { + control: 'select', + type: 'string', + options: ['default', 'info', 'failure', 'success', 'warning', 'primary'], + table: { + category: 'Input', + defaultValue: { + summary: JSON.stringify(defaultFlowbiteTableConfig.color), + }, + }, + }, + striped: { + control: 'boolean', + type: 'boolean', + table: { + category: 'Input', + defaultValue: { + summary: JSON.stringify(defaultFlowbiteTableConfig.striped), + }, + }, + }, + data: { + control: 'object', + type: 'symbol', + table: { + category: 'Input', + defaultValue: { + summary: JSON.stringify([]), + }, + }, + }, + customTheme: { + control: 'object', + type: 'symbol', + table: { + category: 'Input', + defaultValue: { + summary: JSON.stringify(defaultFlowbiteTableConfig.customTheme), + }, + }, + }, + }, +} as Meta; + +export const Default: StoryObj = { + name: 'Default', + args: { + color: defaultFlowbiteTableConfig.color, + striped: defaultFlowbiteTableConfig.striped, + data: Array.from({ length: 5 }, (_, i) => i++).map((x) => ({ + name: `Product ${x}`, + qty: x, + price: x * x, + })), + customTheme: defaultFlowbiteTableConfig.customTheme, + }, + render: (args) => ({ + props: args, + template: ` + + + + + + + + + + + + + + + + + + + + + + +
+ Product name + + Qty + + Price +
+ {{ data.name }} + + {{ data.qty }} + + {{ data.price }} +
+ Total + + 10 + + 30 +
+ `, + }), +}; diff --git a/libs/flowbite-angular/table/README.md b/libs/flowbite-angular/table/README.md new file mode 100644 index 00000000..b6d036db --- /dev/null +++ b/libs/flowbite-angular/table/README.md @@ -0,0 +1,4 @@ +# flowbite-angular/table + +Secondary entry point of `flowbite-angular`. It can be used by importing from +`flowbite-angular/table`. diff --git a/libs/flowbite-angular/table/ng-package.json b/libs/flowbite-angular/table/ng-package.json new file mode 100644 index 00000000..c781f0df --- /dev/null +++ b/libs/flowbite-angular/table/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/libs/flowbite-angular/table/src/config/table-body-config.ts b/libs/flowbite-angular/table/src/config/table-body-config.ts new file mode 100644 index 00000000..a43c3260 --- /dev/null +++ b/libs/flowbite-angular/table/src/config/table-body-config.ts @@ -0,0 +1,49 @@ +import { flowbiteTableBodyTheme, type FlowbiteTableBodyTheme } from '../table-body/theme'; + +import type { DeepPartial } from 'flowbite-angular'; + +import type { Provider } from '@angular/core'; +import { inject, InjectionToken } from '@angular/core'; + +export interface FlowbiteTableBodyConfig { + /** + * The default theme of table-body + */ + baseTheme: FlowbiteTableBodyTheme; + + /** + * The custom theme of table-body + */ + customTheme: DeepPartial; +} + +export const defaultFlowbiteTableBodyConfig: FlowbiteTableBodyConfig = { + baseTheme: flowbiteTableBodyTheme, + customTheme: {}, +}; + +export const FlowbiteTableBodyConfigToken = new InjectionToken( + 'FlowbiteTableBodyConfigToken' +); + +/** + * Provide the default TableBody configuration + * @param config The TableBody configuration + * @returns The provider + */ +export const provideFlowbiteTableBodyConfig = ( + config: Partial +): Provider[] => [ + { + provide: FlowbiteTableBodyConfigToken, + useValue: { ...defaultFlowbiteTableBodyConfig, ...config }, + }, +]; + +/** + * Inject the TableBody configuration + * @see {@link defaultFlowbiteTableBodyConfig} + * @returns The configuration + */ +export const injectFlowbiteTableBodyConfig = (): FlowbiteTableBodyConfig => + inject(FlowbiteTableBodyConfigToken, { optional: true }) ?? defaultFlowbiteTableBodyConfig; diff --git a/libs/flowbite-angular/table/src/config/table-config.ts b/libs/flowbite-angular/table/src/config/table-config.ts new file mode 100644 index 00000000..5ad1274d --- /dev/null +++ b/libs/flowbite-angular/table/src/config/table-config.ts @@ -0,0 +1,57 @@ +import type { FlowbiteTableColors } from '../table/theme'; +import { flowbiteTableTheme, type FlowbiteTableTheme } from '../table/theme'; + +import type { DeepPartial } from 'flowbite-angular'; + +import type { Provider } from '@angular/core'; +import { inject, InjectionToken } from '@angular/core'; + +export interface FlowbiteTableConfig { + /** + * The default theme of table + */ + baseTheme: FlowbiteTableTheme; + /** + * The default color of table. + */ + color: keyof FlowbiteTableColors; + /** + * Whether the table is striped. + */ + striped: boolean; + /** + * The custom theme of table + */ + customTheme: DeepPartial; +} + +export const defaultFlowbiteTableConfig: FlowbiteTableConfig = { + baseTheme: flowbiteTableTheme, + color: 'default', + striped: false, + customTheme: {}, +}; + +export const FlowbiteTableConfigToken = new InjectionToken( + 'FlowbiteTableConfigToken' +); + +/** + * Provide the default Table configuration + * @param config The Table configuration + * @returns The provider + */ +export const provideFlowbiteTableConfig = (config: Partial): Provider[] => [ + { + provide: FlowbiteTableConfigToken, + useValue: { ...defaultFlowbiteTableConfig, ...config }, + }, +]; + +/** + * Inject the Table configuration + * @see {@link defaultFlowbiteTableConfig} + * @returns The configuration + */ +export const injectFlowbiteTableConfig = (): FlowbiteTableConfig => + inject(FlowbiteTableConfigToken, { optional: true }) ?? defaultFlowbiteTableConfig; diff --git a/libs/flowbite-angular/table/src/config/table-foot-config.ts b/libs/flowbite-angular/table/src/config/table-foot-config.ts new file mode 100644 index 00000000..49f314dc --- /dev/null +++ b/libs/flowbite-angular/table/src/config/table-foot-config.ts @@ -0,0 +1,49 @@ +import { flowbiteTableFootTheme, type FlowbiteTableFootTheme } from '../table-foot/theme'; + +import type { DeepPartial } from 'flowbite-angular'; + +import type { Provider } from '@angular/core'; +import { inject, InjectionToken } from '@angular/core'; + +export interface FlowbiteTableFootConfig { + /** + * The default theme of table-foot + */ + baseTheme: FlowbiteTableFootTheme; + + /** + * The custom theme of table-foot + */ + customTheme: DeepPartial; +} + +export const defaultFlowbiteTableFootConfig: FlowbiteTableFootConfig = { + baseTheme: flowbiteTableFootTheme, + customTheme: {}, +}; + +export const FlowbiteTableFootConfigToken = new InjectionToken( + 'FlowbiteTableFootConfigToken' +); + +/** + * Provide the default TableFoot configuration + * @param config The TableFoot configuration + * @returns The provider + */ +export const provideFlowbiteTableFootConfig = ( + config: Partial +): Provider[] => [ + { + provide: FlowbiteTableFootConfigToken, + useValue: { ...defaultFlowbiteTableFootConfig, ...config }, + }, +]; + +/** + * Inject the TableFoot configuration + * @see {@link defaultFlowbiteTableFootConfig} + * @returns The configuration + */ +export const injectFlowbiteTableFootConfig = (): FlowbiteTableFootConfig => + inject(FlowbiteTableFootConfigToken, { optional: true }) ?? defaultFlowbiteTableFootConfig; diff --git a/libs/flowbite-angular/table/src/config/table-head-config.ts b/libs/flowbite-angular/table/src/config/table-head-config.ts new file mode 100644 index 00000000..2866fb4f --- /dev/null +++ b/libs/flowbite-angular/table/src/config/table-head-config.ts @@ -0,0 +1,49 @@ +import { flowbiteTableHeadTheme, type FlowbiteTableHeadTheme } from '../table-head/theme'; + +import type { DeepPartial } from 'flowbite-angular'; + +import type { Provider } from '@angular/core'; +import { inject, InjectionToken } from '@angular/core'; + +export interface FlowbiteTableHeadConfig { + /** + * The default theme of table-head + */ + baseTheme: FlowbiteTableHeadTheme; + + /** + * The custom theme of table-head + */ + customTheme: DeepPartial; +} + +export const defaultFlowbiteTableHeadConfig: FlowbiteTableHeadConfig = { + baseTheme: flowbiteTableHeadTheme, + customTheme: {}, +}; + +export const FlowbiteTableHeadConfigToken = new InjectionToken( + 'FlowbiteTableHeadConfigToken' +); + +/** + * Provide the default TableHead configuration + * @param config The TableHead configuration + * @returns The provider + */ +export const provideFlowbiteTableHeadConfig = ( + config: Partial +): Provider[] => [ + { + provide: FlowbiteTableHeadConfigToken, + useValue: { ...defaultFlowbiteTableHeadConfig, ...config }, + }, +]; + +/** + * Inject the TableHead configuration + * @see {@link defaultFlowbiteTableHeadConfig} + * @returns The configuration + */ +export const injectFlowbiteTableHeadConfig = (): FlowbiteTableHeadConfig => + inject(FlowbiteTableHeadConfigToken, { optional: true }) ?? defaultFlowbiteTableHeadConfig; diff --git a/libs/flowbite-angular/table/src/index.ts b/libs/flowbite-angular/table/src/index.ts new file mode 100644 index 00000000..a055263f --- /dev/null +++ b/libs/flowbite-angular/table/src/index.ts @@ -0,0 +1,27 @@ +/* Table */ +export * from './table/table.component'; +export * from './table/table-state'; +export * from './table/theme'; +/* Config */ +export * from './config/table-config'; + +/* TableHead */ +export * from './table-head/table-head.directive'; +export * from './table-head/table-head-state'; +export * from './table-head/theme'; +/* Config */ +export * from './config/table-head-config'; + +/* TableBody */ +export * from './table-body/table-body.directive'; +export * from './table-body/table-body-state'; +export * from './table-body/theme'; +/* Config */ +export * from './config/table-body-config'; + +/* TableFoot */ +export * from './table-foot/table-foot.directive'; +export * from './table-foot/table-foot-state'; +export * from './table-foot/theme'; +/* Config */ +export * from './config/table-foot-config'; diff --git a/libs/flowbite-angular/table/src/table-body/table-body-state.ts b/libs/flowbite-angular/table/src/table-body/table-body-state.ts new file mode 100644 index 00000000..82bd1c4d --- /dev/null +++ b/libs/flowbite-angular/table/src/table-body/table-body-state.ts @@ -0,0 +1,13 @@ +import type { TableBody } from './table-body.directive'; + +import { + createState, + createStateInjector, + createStateProvider, + createStateToken, +} from 'ng-primitives/state'; + +export const FlowbiteTableBodyStateToken = createStateToken('Flowbite TableBody'); +export const provideFlowbiteTableBodyState = createStateProvider(FlowbiteTableBodyStateToken); +export const injectFlowbiteTableBodyState = createStateInjector(FlowbiteTableBodyStateToken); +export const flowbiteTableBodyState = createState(FlowbiteTableBodyStateToken); diff --git a/libs/flowbite-angular/table/src/table-body/table-body.directive.ts b/libs/flowbite-angular/table/src/table-body/table-body.directive.ts new file mode 100644 index 00000000..0bda2caa --- /dev/null +++ b/libs/flowbite-angular/table/src/table-body/table-body.directive.ts @@ -0,0 +1,49 @@ +import { injectFlowbiteTableBodyConfig } from '../config/table-body-config'; +import { injectFlowbiteTableState } from '../table/table-state'; +import { flowbiteTableBodyState, provideFlowbiteTableBodyState } from './table-body-state'; + +import { colorToTheme, mergeDeep } from 'flowbite-angular'; + +import { computed, Directive, input } from '@angular/core'; +import { twMerge } from 'tailwind-merge'; + +@Directive({ + standalone: true, + selector: ` + tr[flowbiteTableBody] + `, + exportAs: 'flowbiteTableBody', + hostDirectives: [], + providers: [provideFlowbiteTableBodyState()], + host: { + '[class]': `theme().host.root`, + '[attr.data-striped]': 'tableState().striped() || undefined', + }, +}) +export class TableBody { + readonly config = injectFlowbiteTableBodyConfig(); + readonly tableState = injectFlowbiteTableState(); + + /** + * @see {@link injectFlowbiteTableBodyConfig} + */ + readonly customTheme = input(this.config.customTheme); + + readonly theme = computed(() => { + const mergedTheme = mergeDeep(this.config.baseTheme, this.state.customTheme()); + + return { + host: { + root: twMerge( + mergedTheme.host.base, + colorToTheme(mergedTheme.host.color, this.tableState().color()) + ), + }, + }; + }); + + /** + * @internal + */ + readonly state = flowbiteTableBodyState(this); +} diff --git a/libs/flowbite-angular/table/src/table-body/theme.ts b/libs/flowbite-angular/table/src/table-body/theme.ts new file mode 100644 index 00000000..7d4d0409 --- /dev/null +++ b/libs/flowbite-angular/table/src/table-body/theme.ts @@ -0,0 +1,44 @@ +import type { FlowbiteTableColors } from '../table/theme'; + +import { createTheme } from 'flowbite-angular'; + +export interface FlowbiteTableBodyTheme { + host: FlowbiteTableBodyHostTheme; +} + +export interface FlowbiteTableBodyHostTheme { + base: string; + color: FlowbiteTableColors; +} + +export const flowbiteTableBodyTheme: FlowbiteTableBodyTheme = createTheme({ + host: { + base: 'border-b', + color: { + default: { + light: 'border-gray-200 data-striped:odd:bg-white data-striped:even:bg-gray-50', + dark: 'dark:border-gray-700 data-striped:odd:dark:bg-gray-900 data-striped:even:dark:bg-gray-800', + }, + info: { + light: 'border-blue-200 data-striped:odd:bg-white data-striped:even:bg-blue-50', + dark: 'dark:border-blue-900 data-striped:odd:dark:bg-blue-950 data-striped:even:dark:bg-blue-900', + }, + failure: { + light: 'border-red-200 data-striped:odd:bg-white data-striped:even:bg-red-50', + dark: 'dark:border-red-900 data-striped:odd:dark:bg-red-950 data-striped:even:dark:bg-red-900', + }, + success: { + light: 'border-green-200 data-striped:odd:bg-white data-striped:even:bg-green-50', + dark: 'dark:border-green-900 data-striped:odd:dark:bg-green-950 data-striped:even:dark:bg-green-900', + }, + warning: { + light: 'border-yellow-200 data-striped:odd:bg-white data-striped:even:bg-yellow-50', + dark: 'dark:border-yellow-900 data-striped:odd:dark:bg-yellow-950 data-striped:even:dark:bg-yellow-900', + }, + primary: { + light: 'border-primary-200 data-striped:even:bg-primary-50 data-striped:odd:bg-white', + dark: 'dark:border-primary-900 data-striped:odd:dark:bg-primary-950 data-striped:even:dark:bg-primary-900', + }, + }, + }, +}); diff --git a/libs/flowbite-angular/table/src/table-foot/table-foot-state.ts b/libs/flowbite-angular/table/src/table-foot/table-foot-state.ts new file mode 100644 index 00000000..1b4ce09e --- /dev/null +++ b/libs/flowbite-angular/table/src/table-foot/table-foot-state.ts @@ -0,0 +1,13 @@ +import type { TableFoot } from './table-foot.directive'; + +import { + createState, + createStateInjector, + createStateProvider, + createStateToken, +} from 'ng-primitives/state'; + +export const FlowbiteTableFootStateToken = createStateToken('Flowbite TableFoot'); +export const provideFlowbiteTableFootState = createStateProvider(FlowbiteTableFootStateToken); +export const injectFlowbiteTableFootState = createStateInjector(FlowbiteTableFootStateToken); +export const flowbiteTableFootState = createState(FlowbiteTableFootStateToken); diff --git a/libs/flowbite-angular/table/src/table-foot/table-foot.directive.ts b/libs/flowbite-angular/table/src/table-foot/table-foot.directive.ts new file mode 100644 index 00000000..006375e2 --- /dev/null +++ b/libs/flowbite-angular/table/src/table-foot/table-foot.directive.ts @@ -0,0 +1,46 @@ +import { injectFlowbiteTableFootConfig } from '../config/table-foot-config'; +import { injectFlowbiteTableState } from '../table/table-state'; +import { flowbiteTableFootState, provideFlowbiteTableFootState } from './table-foot-state'; + +import { colorToTheme, mergeDeep } from 'flowbite-angular'; + +import { computed, Directive, input } from '@angular/core'; +import { twMerge } from 'tailwind-merge'; + +@Directive({ + standalone: true, + selector: ` + tr[flowbiteTableFoot] + `, + exportAs: 'flowbiteTableFoot', + hostDirectives: [], + providers: [provideFlowbiteTableFootState()], + host: { '[class]': `theme().host.root` }, +}) +export class TableFoot { + readonly config = injectFlowbiteTableFootConfig(); + readonly tableState = injectFlowbiteTableState(); + + /** + * @see {@link injectFlowbiteTableFootConfig} + */ + readonly customTheme = input(this.config.customTheme); + + readonly theme = computed(() => { + const mergedTheme = mergeDeep(this.config.baseTheme, this.state.customTheme()); + + return { + host: { + root: twMerge( + mergedTheme.host.base, + colorToTheme(mergedTheme.host.color, this.tableState().color()) + ), + }, + }; + }); + + /** + * @internal + */ + readonly state = flowbiteTableFootState(this); +} diff --git a/libs/flowbite-angular/table/src/table-foot/theme.ts b/libs/flowbite-angular/table/src/table-foot/theme.ts new file mode 100644 index 00000000..a254aea4 --- /dev/null +++ b/libs/flowbite-angular/table/src/table-foot/theme.ts @@ -0,0 +1,44 @@ +import type { FlowbiteTableColors } from '../table/theme'; + +import { createTheme } from 'flowbite-angular'; + +export interface FlowbiteTableFootTheme { + host: FlowbiteTableFootHostTheme; +} + +export interface FlowbiteTableFootHostTheme { + base: string; + color: FlowbiteTableColors; +} + +export const flowbiteTableFootTheme: FlowbiteTableFootTheme = createTheme({ + host: { + base: 'font-semibold', + color: { + default: { + light: 'text-gray-900', + dark: 'dark:text-white', + }, + info: { + light: 'text-gray-900', + dark: 'dark:text-white', + }, + failure: { + light: 'text-gray-900', + dark: 'dark:text-white', + }, + success: { + light: 'text-gray-900', + dark: 'dark:text-white', + }, + warning: { + light: 'text-gray-900', + dark: 'dark:text-white', + }, + primary: { + light: 'text-gray-900', + dark: 'dark:text-white', + }, + }, + }, +}); diff --git a/libs/flowbite-angular/table/src/table-head/table-head-state.ts b/libs/flowbite-angular/table/src/table-head/table-head-state.ts new file mode 100644 index 00000000..a36f9287 --- /dev/null +++ b/libs/flowbite-angular/table/src/table-head/table-head-state.ts @@ -0,0 +1,13 @@ +import type { TableHead } from './table-head.directive'; + +import { + createState, + createStateInjector, + createStateProvider, + createStateToken, +} from 'ng-primitives/state'; + +export const FlowbiteTableHeadStateToken = createStateToken('Flowbite TableHead'); +export const provideFlowbiteTableHeadState = createStateProvider(FlowbiteTableHeadStateToken); +export const injectFlowbiteTableHeadState = createStateInjector(FlowbiteTableHeadStateToken); +export const flowbiteTableHeadState = createState(FlowbiteTableHeadStateToken); diff --git a/libs/flowbite-angular/table/src/table-head/table-head.directive.ts b/libs/flowbite-angular/table/src/table-head/table-head.directive.ts new file mode 100644 index 00000000..308890b7 --- /dev/null +++ b/libs/flowbite-angular/table/src/table-head/table-head.directive.ts @@ -0,0 +1,46 @@ +import { injectFlowbiteTableHeadConfig } from '../config/table-head-config'; +import { injectFlowbiteTableState } from '../table/table-state'; +import { flowbiteTableHeadState, provideFlowbiteTableHeadState } from './table-head-state'; + +import { colorToTheme, mergeDeep } from 'flowbite-angular'; + +import { computed, Directive, input } from '@angular/core'; +import { twMerge } from 'tailwind-merge'; + +@Directive({ + standalone: true, + selector: ` + tr[flowbiteTableHead] + `, + exportAs: 'flowbiteTableHead', + hostDirectives: [], + providers: [provideFlowbiteTableHeadState()], + host: { '[class]': `theme().host.root` }, +}) +export class TableHead { + readonly config = injectFlowbiteTableHeadConfig(); + readonly tableState = injectFlowbiteTableState(); + + /** + * @see {@link injectFlowbiteTableHeadConfig} + */ + readonly customTheme = input(this.config.customTheme); + + readonly theme = computed(() => { + const mergedTheme = mergeDeep(this.config.baseTheme, this.state.customTheme()); + + return { + host: { + root: twMerge( + mergedTheme.host.base, + colorToTheme(mergedTheme.host.color, this.tableState().color()) + ), + }, + }; + }); + + /** + * @internal + */ + readonly state = flowbiteTableHeadState(this); +} diff --git a/libs/flowbite-angular/table/src/table-head/theme.ts b/libs/flowbite-angular/table/src/table-head/theme.ts new file mode 100644 index 00000000..2a6890b9 --- /dev/null +++ b/libs/flowbite-angular/table/src/table-head/theme.ts @@ -0,0 +1,44 @@ +import type { FlowbiteTableColors } from '../table/theme'; + +import { createTheme } from 'flowbite-angular'; + +export interface FlowbiteTableHeadTheme { + host: FlowbiteTableHeadHostTheme; +} + +export interface FlowbiteTableHeadHostTheme { + base: string; + color: FlowbiteTableColors; +} + +export const flowbiteTableHeadTheme: FlowbiteTableHeadTheme = createTheme({ + host: { + base: 'text-xs uppercase', + color: { + default: { + light: 'bg-gray-50 text-gray-700', + dark: 'dark:bg-gray-700 dark:text-gray-400', + }, + info: { + light: 'bg-blue-50 text-blue-700', + dark: 'dark:bg-blue-700 dark:text-blue-400', + }, + failure: { + light: 'bg-red-50 text-red-700', + dark: 'dark:bg-red-700 dark:text-red-300', + }, + success: { + light: 'bg-green-50 text-green-700', + dark: 'dark:bg-green-700 dark:text-green-400', + }, + warning: { + light: 'bg-yellow-50 text-yellow-700', + dark: 'dark:bg-yellow-700 dark:text-yellow-400', + }, + primary: { + light: 'bg-primary-50 text-primary-700', + dark: 'dark:bg-primary-700 dark:text-primary-300', + }, + }, + }, +}); diff --git a/libs/flowbite-angular/table/src/table/table-state.ts b/libs/flowbite-angular/table/src/table/table-state.ts new file mode 100644 index 00000000..e93f6e56 --- /dev/null +++ b/libs/flowbite-angular/table/src/table/table-state.ts @@ -0,0 +1,13 @@ +import type { Table } from './table.component'; + +import { + createState, + createStateInjector, + createStateProvider, + createStateToken, +} from 'ng-primitives/state'; + +export const FlowbiteTableStateToken = createStateToken('Flowbite Table'); +export const provideFlowbiteTableState = createStateProvider(FlowbiteTableStateToken); +export const injectFlowbiteTableState = createStateInjector(FlowbiteTableStateToken); +export const flowbiteTableState = createState(FlowbiteTableStateToken); diff --git a/libs/flowbite-angular/table/src/table/table.component.ts b/libs/flowbite-angular/table/src/table/table.component.ts new file mode 100644 index 00000000..2c213058 --- /dev/null +++ b/libs/flowbite-angular/table/src/table/table.component.ts @@ -0,0 +1,84 @@ +import { injectFlowbiteTableConfig } from '../config/table-config'; +import { flowbiteTableState, provideFlowbiteTableState } from './table-state'; + +import { colorToTheme, mergeDeep } from 'flowbite-angular'; + +import { NgTemplateOutlet } from '@angular/common'; +import type { TemplateRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + input, + ViewEncapsulation, +} from '@angular/core'; +import { twMerge } from 'tailwind-merge'; + +@Component({ + standalone: true, + selector: ` + table[flowbiteTable] + `, + exportAs: 'flowbiteTable', + hostDirectives: [], + imports: [NgTemplateOutlet], + providers: [provideFlowbiteTableState()], + host: { '[class]': `theme().host.root` }, + template: ` + + + + + @for (record of state.data(); track $index) { + + } + + + + + `, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Table { + readonly config = injectFlowbiteTableConfig(); + + readonly tableHead = input>(); + readonly tableBody = input>(); + readonly data = input(); + readonly tableFoot = input>(); + + /** + * @see {@link injectFlowbiteTableConfig} + */ + readonly color = input(this.config.color); + /** + * @see {@link injectFlowbiteTableConfig} + */ + readonly striped = input(this.config.striped); + + /** + * @see {@link injectFlowbiteTableConfig} + */ + readonly customTheme = input(this.config.customTheme); + + readonly theme = computed(() => { + const mergedTheme = mergeDeep(this.config.baseTheme, this.state.customTheme()); + + return { + host: { + root: twMerge( + mergedTheme.host.base, + colorToTheme(mergedTheme.host.color, this.state.color()) + ), + }, + }; + }); + + /** + * @internal + */ + readonly state = flowbiteTableState
(this); +} diff --git a/libs/flowbite-angular/table/src/table/theme.ts b/libs/flowbite-angular/table/src/table/theme.ts new file mode 100644 index 00000000..ab6378db --- /dev/null +++ b/libs/flowbite-angular/table/src/table/theme.ts @@ -0,0 +1,48 @@ +import type { ColorToTheme, FlowbiteColors } from 'flowbite-angular'; +import { createTheme } from 'flowbite-angular'; + +export interface FlowbiteTableColors + extends Pick { + [key: string]: ColorToTheme; +} + +export interface FlowbiteTableTheme { + host: FlowbiteTableHostTheme; +} + +export interface FlowbiteTableHostTheme { + base: string; + color: FlowbiteTableColors; +} + +export const flowbiteTableTheme: FlowbiteTableTheme = createTheme({ + host: { + base: 'w-full text-left text-sm', + color: { + default: { + light: 'text-gray-500', + dark: 'dark:text-gray-400', + }, + info: { + light: 'text-gray-500', + dark: 'dark:text-gray-400', + }, + failure: { + light: 'text-gray-500', + dark: 'dark:text-gray-400', + }, + success: { + light: 'text-gray-500', + dark: 'dark:text-gray-400', + }, + warning: { + light: 'text-gray-500', + dark: 'dark:text-gray-400', + }, + primary: { + light: 'text-gray-500', + dark: 'dark:text-gray-400', + }, + }, + }, +}); diff --git a/libs/tools/src/generators/component/files/component/__directoryName__/src/__fileName__/__fileName__-state.ts.template b/libs/tools/src/generators/component/files/component/__directoryName__/src/__fileName__/__fileName__-state.ts.template index c6e1e21c..56b361ec 100644 --- a/libs/tools/src/generators/component/files/component/__directoryName__/src/__fileName__/__fileName__-state.ts.template +++ b/libs/tools/src/generators/component/files/component/__directoryName__/src/__fileName__/__fileName__-state.ts.template @@ -8,6 +8,6 @@ import { } from 'ng-primitives/state'; export const Flowbite<%= className %>StateToken = createStateToken<<%= className %>>('Flowbite <%= className %>'); -export const provideFlowbite<%= className %>State = createStateProvider(<%= className %>StateToken); -export const injectFlowbite<%= className %>State = createStateInjector(<%= className %>StateToken); -export const flowbite<%= className %>State = createState(<%= className %>StateToken); +export const provideFlowbite<%= className %>State = createStateProvider(Flowbite<%= className %>StateToken); +export const injectFlowbite<%= className %>State = createStateInjector(Flowbite<%= className %>StateToken); +export const flowbite<%= className %>State = createState(Flowbite<%= className %>StateToken); diff --git a/tsconfig.base.json b/tsconfig.base.json index d47b1cb9..1e19f5dd 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -88,6 +88,7 @@ "flowbite-angular/pagination": ["libs/flowbite-angular/pagination/src/index.ts"], "flowbite-angular/sidebar": ["libs/flowbite-angular/sidebar/src/index.ts"], "flowbite-angular/tab": ["libs/flowbite-angular/tab/src/index.ts"], + "flowbite-angular/table": ["libs/flowbite-angular/table/src/index.ts"], "flowbite-angular/theme-toggle": ["libs/flowbite-angular/theme-toggle/src/index.ts"], "flowbite-angular/tooltip": ["libs/flowbite-angular/tooltip/src/index.ts"] }