diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index cad2f823..d7377c93 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import type { Preview } from '@storybook/react';
-
+import '../src/tailwind.css';
/** @type { import('@storybook/react').Preview } */
-import '../dist/style.css';
+
const preview: Preview = {
parameters: {
controls: {
@@ -20,7 +20,7 @@ const preview: Preview = {
},
decorators: [
(Story) => (
-
+
),
diff --git a/changelog.txt b/changelog.txt
index 62b39fe6..4a314439 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,6 @@
+Version x.x.x - x x, x
+- Fixed - Asterisk missing on required input label.
+
Version 1.2.2 - 4th December, 2024
- Improvement - Removed margin and added new props to the Line Chart component for customizability.
diff --git a/src/components/checkbox/checkbox.tsx b/src/components/checkbox/checkbox.tsx
index b3054fc9..1c6e547e 100644
--- a/src/components/checkbox/checkbox.tsx
+++ b/src/components/checkbox/checkbox.tsx
@@ -145,14 +145,14 @@ export const CheckboxComponent = (
return (
+
);
}, [ label, size, inputId ] );
diff --git a/src/components/label/label.tsx b/src/components/label/label.tsx
index b9fd168e..7a261cad 100644
--- a/src/components/label/label.tsx
+++ b/src/components/label/label.tsx
@@ -17,7 +17,7 @@ export interface LabelProps {
}
const Label = forwardRef(
- (
+
(
{
children = null,
tag: Tag = 'label',
@@ -26,7 +26,7 @@ const Label = forwardRef(
variant = 'neutral', // neutral, help, error, disabled
required = false,
...props
- }: LabelProps,
+ }: LabelProps & T,
ref: React.Ref
) => {
// Base classes. - Mandatory classes.
@@ -78,4 +78,7 @@ const Label = forwardRef(
}
);
-export default Label;
+export default Label as (
+ props: LabelProps & T,
+ ref: React.Ref
+) => React.ReactNode;
diff --git a/src/components/table/index.ts b/src/components/table/index.ts
new file mode 100644
index 00000000..1a876cfc
--- /dev/null
+++ b/src/components/table/index.ts
@@ -0,0 +1 @@
+export { default as Table } from './table';
diff --git a/src/components/table/table.stories.tsx b/src/components/table/table.stories.tsx
new file mode 100644
index 00000000..9723f129
--- /dev/null
+++ b/src/components/table/table.stories.tsx
@@ -0,0 +1,286 @@
+import { useState, type ComponentType } from 'react';
+import { StoryFn, Meta } from '@storybook/react';
+import Table from './table';
+import { Button, Container, Pagination, Tooltip } from '@/components';
+import { Edit, Trash } from 'lucide-react';
+
+const meta = {
+ title: 'Atoms/Table',
+ component: Table,
+ subcomponents: {
+ 'Table.Head': Table.Head,
+ 'Table.HeadCell': Table.HeadCell,
+ 'Table.Body': Table.Body,
+ 'Table.Row': Table.Row,
+ 'Table.Cell': Table.Cell,
+ 'Table.Footer': Table.Footer,
+ } as Record>,
+ tags: [ 'autodocs' ],
+} satisfies Meta;
+export default meta;
+
+type Story = StoryFn;
+
+const data = [
+ {
+ name: 'John Doe',
+ age: 30,
+ email: 'KXk7g@example.com',
+ phone: '1234567890',
+ },
+ {
+ name: 'Jane Doe',
+ age: 25,
+ email: 'oXHsO@example.com',
+ phone: '1234567890',
+ },
+ {
+ name: 'Bob Smith',
+ age: 40,
+ email: 'oXHsO@example.com',
+ phone: '1234567890',
+ },
+ {
+ name: 'Alice Johnson',
+ age: 35,
+ email: 'oXHsO@example.com',
+ phone: '1234567890',
+ },
+];
+
+const Template: Story = ( { checkboxSelection } ) => {
+ const [ selected, setSelected ] = useState( [] );
+
+ const handleCheckboxChange = (
+ checked: boolean,
+ value: ( typeof data )[number]
+ ) => {
+ if ( checked ) {
+ setSelected( [ ...selected, value.name ] );
+ } else {
+ setSelected( selected.filter( ( item: string ) => item !== value.name ) );
+ }
+ };
+
+ const toggleSelectAll = ( checked: boolean ) => {
+ if ( checked ) {
+ setSelected( data.map( ( item ) => item.name ) );
+ } else {
+ setSelected( [] );
+ }
+ };
+
+ return (
+
+ 0 }
+ onChangeSelection={ toggleSelectAll }
+ indeterminate={
+ selected.length > 0 && selected.length < data.length
+ }
+ >
+ Name
+ Age
+ Email
+ Phone
+
+ Actions
+
+
+
+ { data.map( ( item, index ) => (
+
+ { item.name }
+ { item.age }
+ { item.email }
+ { item.phone }
+
+
+
+ }
+ size="xs"
+ className="text-icon-secondary hover:text-icon-primary"
+ aria-label="Delete"
+ />
+
+
+ }
+ size="xs"
+ className="text-icon-secondary hover:text-icon-primary"
+ aria-label="Edit"
+ />
+
+
+
+
+ ) ) }
+
+
+
+
+ Page 1 out of 9
+
+
+
+
+ 1
+ 2
+ 3
+
+ 7
+ 8
+ 9
+
+
+
+
+
+
+ );
+};
+
+export const Default = Template.bind( {} );
+Default.args = {
+ checkboxSelection: false,
+};
+
+export const WithCheckboxSelection = Template.bind( {} );
+WithCheckboxSelection.args = {
+ checkboxSelection: true,
+};
+WithCheckboxSelection.parameters = {
+ docs: {
+ source: {
+ code: `
+import { useState } from 'react';
+import { Table, Button, Pagination, Tooltip } from '@bsf/force-ui';
+import { Edit, Trash } from 'lucide-react';
+
+const data = [
+ {
+ name: 'John Doe',
+ age: 30,
+ email: 'KXk7g@example.com',
+ phone: '1234567890',
+ },
+ {
+ name: 'Jane Doe',
+ age: 25,
+ email: 'oXHsO@example.com',
+ phone: '1234567890',
+ },
+ {
+ name: 'Bob Smith',
+ age: 40,
+ email: 'oXHsO@example.com',
+ phone: '1234567890',
+ },
+ {
+ name: 'Alice Johnson',
+ age: 35,
+ email: 'oXHsO@example.com',
+ phone: '1234567890',
+ },
+];
+
+const App = () => {
+ const [ selected, setSelected ] = useState( [] );
+
+ const handleCheckboxChange = ( checked, value ) => {
+ if ( checked ) {
+ setSelected( [ ...selected, value.name ] );
+ } else {
+ setSelected( selected.filter( ( item ) => item !== value.name ) );
+ }
+ };
+
+ const toggleSelectAll = ( checked ) => {
+ if ( checked ) {
+ setSelected( data.map( ( item ) => item.name ) );
+ } else {
+ setSelected( [] );
+ }
+ };
+
+ return (
+
+ 0 }
+ onChangeSelection={ toggleSelectAll }
+ indeterminate={ selected.length > 0 && selected.length < data.length }
+ >
+ Name
+ Age
+ Email
+ Phone
+
+ Actions
+
+
+
+ { data.map( ( item, index ) => (
+
+ { item.name }
+ { item.age }
+ { item.email }
+ { item.phone }
+
+
+
+ } size="xs" className="text-icon-secondary hover:text-icon-primary" aria-label="Delete" />
+
+
+ } size="xs" className="text-icon-secondary hover:text-icon-primary" aria-label="Edit" />
+
+
+
+
+ ) ) }
+
+
+
+
+ Page 1 out of 9
+
+
+
+
+ 1
+ 2
+ 3
+
+ 7
+ 8
+ 9
+
+
+
+
+
+
+ );
+}
+ `,
+ },
+ },
+};
diff --git a/src/components/table/table.tsx b/src/components/table/table.tsx
new file mode 100644
index 00000000..c37cc33d
--- /dev/null
+++ b/src/components/table/table.tsx
@@ -0,0 +1,421 @@
+import { cn } from '@/utilities/functions';
+import React, {
+ Children,
+ createContext,
+ useContext,
+ type ReactNode,
+} from 'react';
+import { Checkbox } from '@/components';
+
+/**
+ * Common props for all table components.
+ */
+export interface TableCommonProps {
+ /**
+ * Children to render within the component.
+ */
+ children?: ReactNode;
+ /**
+ * Class name to apply to the component.
+ */
+ className?: string;
+}
+
+/**
+ * Interface for table context.
+ */
+export interface TableContextType {
+ /**
+ * Set of indices for selected rows.
+ */
+ selectedRows?: Set;
+ /**
+ * Whether to show checkboxes for row selection.
+ */
+ checkboxSelection?: boolean;
+ /**
+ * On checkbox selection change.
+ */
+ onChangeSelection?: ( checked: boolean, value: T ) => void;
+}
+
+/**
+ * Interface for base table props.
+ */
+export interface BaseTableProps
+ extends TableCommonProps,
+ Omit, 'className' | 'children'> {
+ /**
+ * Child components to render within the table.
+ *
+ * @default undefined
+ */
+ children?: ReactNode;
+ /**
+ * Whether to show checkboxes for row selection.
+ */
+ checkboxSelection?: boolean;
+}
+
+/**
+ * Interface for table head props.
+ */
+export interface TableHeadProps
+ extends TableCommonProps,
+ Omit<
+ React.HTMLAttributes,
+ 'className' | 'children'
+ > {
+ /**
+ * Child components to render within the table head.
+ */
+ children?: ReactNode;
+ /**
+ * Whether any of the rows are selected.
+ */
+ selected?: boolean;
+
+ /**
+ * Whether the checkbox is indeterminate.
+ */
+ indeterminate?: boolean;
+
+ /**
+ * Whether the checkbox is disabled.
+ */
+ disabled?: boolean;
+
+ /**
+ * On checkbox change for bulk selection/deselection.
+ *
+ * @default undefined
+ */
+ onChangeSelection?: ( checked: boolean ) => void;
+}
+
+/**
+ * Interface for table head cell props.
+ */
+export interface TableHeadCellProps
+ extends TableCommonProps,
+ Omit<
+ React.HTMLAttributes,
+ 'className' | 'children'
+ > {
+ /**
+ * Content to display in the header cell.
+ */
+ children?: ReactNode;
+}
+
+/**
+ * Interface for table body props.
+ */
+export interface TableBodyProps
+ extends TableCommonProps,
+ Omit<
+ React.HTMLAttributes,
+ 'className' | 'children'
+ > {
+ /**
+ * Child components to render within the table body.
+ */
+ children?: ReactNode;
+}
+
+/**
+ * Interface for table row props.
+ */
+export interface TableRowProps
+ extends TableCommonProps,
+ Omit<
+ React.HTMLAttributes,
+ 'className' | 'children'
+ > {
+ /**
+ * Child components to render within the table row.
+ */
+ children?: ReactNode;
+ /**
+ * value of the row.
+ */
+ value?: T | undefined;
+ /**
+ * Whether the row is selected.
+ */
+ selected?: boolean;
+
+ /**
+ * On checkbox selection change.
+ */
+ onChangeSelection?: ( checked: boolean, value: T ) => void;
+
+ /**
+ * Whether the row is disabled.
+ */
+ disabled?: boolean;
+}
+
+/**
+ * Interface for table cell props.
+ */
+export interface TableCellProps
+ extends TableCommonProps,
+ Omit<
+ React.HTMLAttributes,
+ 'className' | 'children'
+ > {
+ /**
+ * Content to display in the table cell.
+ */
+ children?: ReactNode;
+}
+
+/**
+ * Interface for table footer props.
+ */
+export interface TableFooterProps
+ extends TableCommonProps,
+ Omit, 'className' | 'children'> {
+ /**
+ * Child components to render within the table footer.
+ */
+ children?: ReactNode;
+}
+
+const TableContext = createContext | undefined>(
+ undefined
+);
+
+const useTableContext = () => {
+ const context = useContext( TableContext );
+ if ( ! context ) {
+ throw new Error( 'Table components must be used within Table component' );
+ }
+ return context;
+};
+
+export const Table = ( {
+ children,
+ className,
+ checkboxSelection = false,
+ ...props
+}: BaseTableProps ) => {
+ const contextValue: TableContextType = {
+ checkboxSelection,
+ };
+
+ // Extract footer from children
+ const footer = Children.toArray( children ).find(
+ ( child ) => React.isValidElement( child ) && child.type === TableFooter
+ );
+ const restChildren = Children.toArray( children ).filter(
+ ( child ) => React.isValidElement( child ) && child.type !== TableFooter
+ );
+ return (
+ }
+ >
+
+
+ );
+};
+
+// Head Components
+export const TableHead: React.FC = ( {
+ children,
+ className,
+ selected,
+ onChangeSelection,
+ indeterminate,
+ disabled,
+ ...props
+} ) => {
+ const { checkboxSelection } = useTableContext();
+
+ const handleCheckboxChange = ( checked: boolean ) => {
+ if ( typeof onChangeSelection !== 'function' ) {
+ return;
+ }
+ onChangeSelection( checked );
+ };
+
+ return (
+
+
+ { checkboxSelection && (
+
+
+ |
+ ) }
+ { children }
+
+
+ );
+};
+
+export const TableHeadCell: React.FC = ( {
+ children,
+ className,
+ ...props
+} ) => {
+ return (
+
+ { children }
+ |
+ );
+};
+
+// Body Components
+export const TableBody: React.FC = ( {
+ children,
+ className,
+ ...props
+} ) => {
+ return (
+
+ { children }
+
+ );
+};
+
+export const TableRow = ( {
+ children,
+ selected,
+ value,
+ className,
+ onChangeSelection,
+ ...props
+}: TableRowProps ) => {
+ const { checkboxSelection } = useTableContext();
+
+ const handleCheckboxChange = ( checked: boolean ) => {
+ if ( typeof onChangeSelection !== 'function' ) {
+ return;
+ }
+ onChangeSelection( checked, value as T );
+ };
+
+ return (
+
+ { checkboxSelection && (
+
+
+ |
+ ) }
+ { children }
+
+ );
+};
+
+export const TableCell: React.FC = ( {
+ children,
+ className,
+ ...props
+} ) => {
+ return (
+
+ { children }
+ |
+ );
+};
+
+// Table Footer
+export const TableFooter: React.FC = ( {
+ children,
+ className,
+ ...props
+} ) => {
+ const { checkboxSelection } = useTableContext();
+ return (
+
+ { children }
+
+ );
+};
+
+// Update display name
+Table.displayName = 'Table';
+TableHead.displayName = 'Table.Head';
+TableHeadCell.displayName = 'Table.HeadCell';
+TableBody.displayName = 'Table.Body';
+TableRow.displayName = 'Table.Row';
+TableCell.displayName = 'Table.Cell';
+TableFooter.displayName = 'Table.Footer';
+
+// Assign compound components
+Table.Head = TableHead;
+Table.HeadCell = TableHeadCell;
+Table.Body = TableBody;
+Table.Row = TableRow;
+Table.Cell = TableCell;
+Table.Footer = TableFooter;
+
+export default Table;
diff --git a/src/theme/default-config.js b/src/theme/default-config.js
index aa5153cd..c797dba2 100644
--- a/src/theme/default-config.js
+++ b/src/theme/default-config.js
@@ -197,6 +197,8 @@ const defaultTheme = {
tiny: '0.625rem',
},
spacing: {
+ 4.5: '1.125rem', // 18px
+ 5.5: '1.375rem', // 22px
120: '30rem', // 480px
95: '23.75rem', // 380px
141.5: '35.375rem', // 566px