diff --git a/LICENSE b/LICENSE index 1dd7498..4abbe55 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,201 @@ -MIT License + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2025 Asadbek Karimov -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + 1. Definitions. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2012-present SheetJS LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 7df4e68..8117953 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,573 @@ -# react-native-tabeller +

React Native Tabeller

+ +

+ +

+ +This is a table component for react native. + +- [Installation](#installation) +- [Examples](#examples) +- [Properties](#properties) +- [Notice](#notice) +- [License](#license) -React Native table with reanimated and gestures. ## Installation - ```sh npm install react-native-tabeller ``` -## Usage - - -```js -import { multiply } from 'react-native-tabeller'; - -// ... - -const result = await multiply(3, 7); +```tsx +import { Table, TableWrapper, Row, Rows, Col, Cols, Cell } from 'react-native-tabeller'; ``` +## Examples + +### Basic Table + + +```tsx +import { Table, Row, Rows } from 'react-native-tabeller'; +import { View, StyleSheet } from 'react-native'; + +export const BasicExample = () => { + const tableHead: string[] = ['Name', 'Index']; + const tableData: string[][] = [ + ['Bill Clinton', '42'], + ['GeorgeW Bush', '43'], + ['Barack Obama', '44'], + ['Donald Trump', '45'], + ['Joseph Biden', '46'] + ]; + return ( + + + + +
+
+ ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + backgroundColor: '#fff' + }, + head: { + height: 44, + backgroundColor: '#C6F3E0' + }, + text: { + textAlign: 'center', + padding: 5 + }, + headText: { + textAlign: 'center', + fontWeight: 'bold' + } +}); +``` + +### Scrollable Example + + +```tsx +import { Table, Row, Rows } from 'react-native-tabeller'; +import { View, StyleSheet, ScrollView } from 'react-native'; + +export const ScrollableExample = () => { + const tableHead = ['Head', 'Head2', 'Head3', 'Head4', 'Head5', 'Head6', 'Head7', 'Head8']; + const widthArr = [40, 69, 80, 100, 120, 140, 160, 180]; + + // generate a large table data + const tableData = []; + for (let i = 0; i < 30; i += 1) { + const rowData = []; + for (let j = 0; j < 8; j += 1) { + rowData.push(`${i}${j}`); + } + tableData.push(rowData); + } + + return ( + + + + + +
+ + + +
+
+
+
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + height: 400, + backgroundColor: '#fff', + }, + header: { + height: 50, + backgroundColor: '#C6F3E0' + }, + headerText: { + textAlign: 'center', + fontWeight: 'bold' + }, + text: { + textAlign: 'center', + padding: 5 + }, + dataWrapper: { + marginTop: -1 + }, + row: { + height: 40, + backgroundColor: '#fff' + } +}); +``` + +### Example three + + +```tsx +import { Table, TableWrapper, Row, Rows, Col } from 'react-native-tabeller'; +import { View, StyleSheet } from 'react-native'; + +export const ExampleThree = () => { + const tableHead = ['', 'Head1', 'Head2', 'Head3']; + const tableTitle = ['Title1', 'Title2', 'Title3']; + const tableData = [ + ['a', 'b', 'c'], + ['d', 'e', 'f'], + ['g', 'h', 'i'] + ]; + + const onCellPress = (data: any) => { + console.log(`Cell pressed: ${data}`); + }; + + return ( + + + + + + + +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + backgroundColor: '#fff' + }, + head: { + height: 44, + backgroundColor: '#C6F3E0' + }, + text: { + textAlign: 'center', + padding: 5 + }, + headText: { + textAlign: 'center', + fontWeight: 'bold' + }, + row: { + height: 40, + backgroundColor: '#fff' + }, + wrapper: { + flexDirection: 'row' + }, + title: { + flex: 1, + backgroundColor: '#C6F3E0' + }, + titleText: { + textAlign: 'left', + marginLeft: 6, + fontWeight: '600' + } +}); +``` + +### Example Four + + +```tsx +import React, { useState } from 'react'; +import { StyleSheet, View, Text, TouchableOpacity, Alert } from 'react-native'; +import { Table, TableWrapper, Row, Cell } from 'react-native-tabeller'; + +type TableDataType = string[][]; + +export const ExampleFour: React.FC = () => { + const [tableHead] = useState(['Head', 'Head2', 'Head3', 'Head4']); + const [tableData] = useState([ + ['1', '2', '3', '4'], + ['a', 'b', 'c', 'd'], + ['1', '2', '3', '4'], + ['a', 'b', 'c', 'd'] + ]); + + const alertIndex = (index: number): void => { + Alert.alert(`This is row ${index + 1}`); + }; + + const element = (data: any, index: number): React.ReactElement => ( + alertIndex(index)}> + + button + + + ); + + return ( + + + + { + tableData.map((rowData, index) => ( + + { + rowData.map((cellData, cellIndex) => ( + + )) + } + + )) + } +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, paddingTop: 30, backgroundColor: '#fff' }, + head: { height: 40, backgroundColor: '#C6F3E0' }, + text: { margin: 8 }, + row: { flexDirection: 'row', backgroundColor: '#FFF1C1' }, + btn: { width: 58, height: 18, backgroundColor: '#78B7BB', borderRadius: 2 }, + btnText: { textAlign: 'center', color: '#fff' } +}); + +export default ExampleFour; +``` + +### Example Five + + +```tsx +import React, { useState } from 'react'; +import { StyleSheet, View, Text, TouchableOpacity, Alert } from 'react-native'; +import { Table, TableWrapper, Row, Col, Rows } from 'react-native-tabeller'; + +export const ExampleFive: React.FC = () => { + const alertIndex = (value: string): void => { + Alert.alert(`This is column ${value}`); + }; + + const elementButton = (value: string): React.ReactElement => ( + alertIndex(value)}> + + button + + + ); + + const [tableHead] = useState(['', elementButton('1'), elementButton('2'), elementButton('3')]); + const [sideHead] = useState(['H1', 'H2']); + const [rowTitles] = useState(['Title', 'Title2', 'Title3', 'Title4']); + + // Table data - matching the structure in the screenshot + const [tableData] = useState([ + ['a', '1', 'a'], + ['b', '2', 'b'], + ['c', '3', 'c'], + ['d', '4', 'd'] + ]); + + return ( + + + {/* table header row with buttons */} + + + + {/* left column with H1, H2 */} + + + + {/* row titles column */} + + + {/* content cells */} + + + +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + backgroundColor: '#fff' + }, + header: { + height: 40, + backgroundColor: '#fff' + }, + headerText: { + textAlign: 'center', + fontWeight: '500' + }, + wrapper: { + flexDirection: 'row' + }, + sideHeader: { + width: 60, + backgroundColor: '#C6F3E0' + }, + sideHeaderText: { + textAlign: 'center', + fontWeight: '500' + }, + tableContentWrapper: { + flex: 1, + flexDirection: 'row' + }, + title: { + width: 80, + backgroundColor: '#f6f8fa' + }, + titleText: { + textAlign: 'left', + paddingLeft: 5 + }, + row: { + height: 30 + }, + text: { + textAlign: 'center' + }, + btn: { + width: 58, + height: 18, + backgroundColor: '#c8e1ff', + borderRadius: 2, + alignSelf: 'center' + }, + btnText: { + textAlign: 'center', + fontSize: 12 + } +}); + +export default ExampleFive; +``` + +--- + +

+ + +## Properties + +### `Table` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **style** | Style | Container style for the table | `null` | +| **borderStyle** | Object | Table border line width and color | `{ borderWidth: 0, borderColor: '#000' }` | +| **children** | ReactNode | Table content | Required | + +### `TableWrapper` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **style** | Style | Container style | `null` | +| **borderStyle** | Object | Table border line width and color | `{ borderWidth: 0, borderColor: '#000' }` | +| **children** | ReactNode | TableWrapper content | Required | + +### `Cell` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **data** | string \| number \| null | Cell content | `null` | +| **width** | number | Cell width in pixels | `null` | +| **height** | number | Cell height in pixels | `null` | +| **flex** | number | Flex value for the cell | `1` (if no width, height, or style) | +| **style** | Style | Container style | `null` | +| **textStyle** | Style | Text style for cell content | `null` | +| **borderStyle** | Object | Cell border line width and color | `{ borderWidth: 0, borderColor: '#000' }` | +| **cellContainerProps** | ViewProps | Props passed to the cell container | `{}` | +| **onPress** | Function | Callback when cell is pressed | `null` | +| **children** | ReactNode | Children to render inside the cell | `null` | + +### `Row` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **data** | Array | Array of data items for each cell in the row | Required | +| **style** | Style | Container style | `null` | +| **widthArr** | number[] | Array of widths for each cell | `[]` | +| **height** | number | Height for the entire row | `null` | +| **flexArr** | number[] | Array of flex values for each cell in the row | `[]` | +| **textStyle** | Style | Text style applied to all cells in the row | `null` | +| **borderStyle** | Object | Border line width and color | `{ borderWidth: 0, borderColor: '#000' }` | +| **cellTextStyle** | Function | Function to generate custom text styles for individual cells | `null` | +| **onPress** | Function | Callback when a cell is pressed | `null` | + +### `Rows` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **data** | Array> | 2D array of data for rows and cells | Required | +| **style** | Style | Container style | `null` | +| **widthArr** | number[] | Array of widths for each column | `[]` | +| **heightArr** | number[] | Array of heights for each row | `[]` | +| **flexArr** | number[] | Array of flex values for each column | `[]` | +| **textStyle** | Style | Text style applied to all cells | `null` | +| **borderStyle** | Object | Border line width and color | `{ borderWidth: 0, borderColor: '#000' }` | +| **onPress** | Function | Callback when a cell is pressed | `null` | + +### `Col` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **data** | Array | Array of data items for each cell in the column | Required | +| **style** | Style | Container style | `null` | +| **width** | number | Width for the entire column | `null` | +| **heightArr** | number[] | Array of heights for each cell | `[]` | +| **flex** | number | Flex value for the column | `null` | +| **textStyle** | Style | Text style applied to all cells in the column | `null` | +| **borderStyle** | Object | Border line width and color | `{ borderWidth: 0, borderColor: '#000' }` | + +### `Cols` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **data** | Array> | 2D array of data for columns and cells | Required | +| **style** | Style | Container style | `null` | +| **widthArr** | number[] | Array of widths for each column | `[]` | +| **heightArr** | number[] | Array of heights for each cell in a column | `[]` | +| **flexArr** | number[] | Array of flex values for each column | `[]` | +| **textStyle** | Style | Text style applied to all cells | `null` | +| **borderStyle** | Object | Border line width and color | `{ borderWidth: 0, borderColor: '#000' }` | + +### `StickyTable` Component Properties + +| Prop | Type | Description | Default | +|---|---|---|---| +| **data** | Array> | Full table data including first column | Required | +| **stickyColumnWidth** | number | Width of the sticky column | Required | +| **columnWidths** | number[] | Widths for non-sticky columns | `[]` | +| **style** | Style | Style for the container | `null` | +| **cellStyle** | Style | Style for cells | `null` | +| **textStyle** | Style | Text style for cell content | `null` | +| **headerStyle** | Style | Style for header row | `null` | +| **headerTextStyle** | Style | Text style for header cells | `null` | +| **borderStyle** | Object | Border style | `{ borderWidth: 1, borderColor: '#000' }` | +--- + + + +

+ +## Notice ++ `Col` and `Cols` components do not support automatic height adjustment ++ Use the `textStyle` property to set margins - avoid using padding ++ If the parent element is Not `Table` component, specify the `borderStyle` + +```tsx + + {/* add borderStyle if the parent is not a Table component */} + + + + +``` ## Contributing @@ -26,8 +575,4 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the ## License -MIT - ---- - -Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob) +Apache License, Version 2.0 [(ALv2)](LICENSE) diff --git a/example/src/App.tsx b/example/src/App.tsx index 9d05b24..0548ef5 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,20 +1,78 @@ -import { multiply } from 'react-native-tabeller'; -import { Text, View, StyleSheet } from 'react-native'; - -const result = multiply(3, 7); +import { Text, View, StyleSheet, ScrollView } from 'react-native'; +import { BasicExample } from './BasicExample'; +import { ScrollableExample } from './ScrollableExample'; +import { ExampleThree } from './ExampleThree'; +import { StickyTableExample } from './StickyColumnExample'; +import { ExampleFour } from './ExampleFour'; +import { ExampleFive } from './ExampleFive'; export default function App() { return ( - - Result: {result} - + + React Native Tabeller + + + Basic Table + + + + + Scrollable Table + + + + + + Scrollable Sticky 1st Column Table + + + + + + Example Three + + + + + Example Four + + + + + Example Five + + + ); } const styles = StyleSheet.create({ container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', + flexGrow: 1, + paddingHorizontal: 20, + paddingTop: 60, + backgroundColor: '#f5f5f5', + }, + heading: { + fontSize: 24, + fontWeight: 'bold', + textAlign: 'center', + marginBottom: 20, + }, + section: { + marginBottom: 30, + padding: 15, + backgroundColor: '#fff', + borderRadius: 8, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }, + subheading: { + fontSize: 18, + fontWeight: '600', + marginBottom: 10, }, }); diff --git a/example/src/BasicExample.tsx b/example/src/BasicExample.tsx new file mode 100644 index 0000000..663ef4f --- /dev/null +++ b/example/src/BasicExample.tsx @@ -0,0 +1,42 @@ +import { Table, Row, Rows } from 'react-native-tabeller'; +import { View, StyleSheet } from 'react-native'; + +export const BasicExample = () => { + const tableHead: string[] = ['Name', 'Index']; + const tableData: string[][] = [ + ['Bill Clinton', '42'], + ['GeorgeW Bush', '43'], + ['Barack Obama', '44'], + ['Donald Trump', '45'], + ['Joseph Biden', '46'], + ]; + return ( + + + + +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + backgroundColor: '#fff', + }, + head: { + height: 44, + backgroundColor: '#C6F3E0', + }, + text: { + textAlign: 'center', + padding: 5, + }, + headText: { + textAlign: 'center', + fontWeight: 'bold', + }, +}); diff --git a/example/src/ExampleFive.tsx b/example/src/ExampleFive.tsx new file mode 100644 index 0000000..b33eebc --- /dev/null +++ b/example/src/ExampleFive.tsx @@ -0,0 +1,136 @@ +import React, { useState } from 'react'; +import { StyleSheet, View, Text, TouchableOpacity, Alert } from 'react-native'; +import { Table, TableWrapper, Row, Col, Rows } from 'react-native-tabeller'; + +export const ExampleFive: React.FC = () => { + const alertIndex = (value: string): void => { + Alert.alert(`This is column ${value}`); + }; + const elementButton = (value: string): React.ReactElement => ( + alertIndex(value)}> + + button + + + ); + + const [tableHead] = useState([ + '', + elementButton('1'), + elementButton('2'), + elementButton('3'), + ]); + const [sideHead] = useState(['H1', 'H2']); + const [rowTitles] = useState([ + 'Title', + 'Title2', + 'Title3', + 'Title4', + ]); + + // Table data - matching the structure in the screenshot + const [tableData] = useState([ + ['a', '1', 'a'], + ['b', '2', 'b'], + ['c', '3', 'c'], + ['d', '4', 'd'], + ]); + + return ( + + + {/* table header row with buttons */} + + + {/* left column with H1, H2 */} + + + {/* row titles column */} + + {/* content cells */} + + + +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + backgroundColor: '#fff', + }, + header: { + height: 40, + backgroundColor: '#fff', + }, + headerText: { + textAlign: 'center', + fontWeight: '500', + }, + wrapper: { + flexDirection: 'row', + }, + sideHeader: { + width: 60, + backgroundColor: '#C6F3E0', + }, + sideHeaderText: { + textAlign: 'center', + fontWeight: '500', + }, + tableContentWrapper: { + flex: 1, + flexDirection: 'row', + }, + title: { + width: 80, + backgroundColor: '#f6f8fa', + }, + titleText: { + textAlign: 'left', + paddingLeft: 5, + }, + row: { + height: 30, + }, + text: { + textAlign: 'center', + }, + btn: { + width: 58, + height: 18, + backgroundColor: '#c8e1ff', + borderRadius: 2, + alignSelf: 'center', + }, + btnText: { + textAlign: 'center', + fontSize: 12, + }, +}); + +export default ExampleFive; diff --git a/example/src/ExampleFour.tsx b/example/src/ExampleFour.tsx new file mode 100644 index 0000000..f2e296b --- /dev/null +++ b/example/src/ExampleFour.tsx @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; +import { StyleSheet, View, Text, TouchableOpacity, Alert } from 'react-native'; +import { Table, TableWrapper, Row, Cell } from 'react-native-tabeller'; + +type TableDataType = string[][]; + +export const ExampleFour: React.FC = () => { + const [tableHead] = useState(['Head', 'Head2', 'Head3', 'Head4']); + const [tableData] = useState([ + ['1', '2', '3', '4'], + ['a', 'b', 'c', 'd'], + ['1', '2', '3', '4'], + ['a', 'b', 'c', 'd'], + ]); + + const alertIndex = (index: number): void => { + Alert.alert(`This is row ${index + 1}`); + }; + + const element = (data: any, index: number): React.ReactElement => ( + alertIndex(index)}> + + button + + + ); + + return ( + + + + {tableData.map((rowData, index) => ( + + {rowData.map((cellData, cellIndex) => ( + + ))} + + ))} +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { flex: 1, padding: 16, paddingTop: 30, backgroundColor: '#fff' }, + head: { height: 40, backgroundColor: '#C6F3E0' }, + text: { margin: 8 }, + row: { flexDirection: 'row', backgroundColor: '#FFF1C1' }, + btn: { width: 58, height: 18, backgroundColor: '#78B7BB', borderRadius: 2 }, + btnText: { textAlign: 'center', color: '#fff' }, +}); + +export default ExampleFour; diff --git a/example/src/ExampleThree.tsx b/example/src/ExampleThree.tsx new file mode 100644 index 0000000..42f2e30 --- /dev/null +++ b/example/src/ExampleThree.tsx @@ -0,0 +1,76 @@ +import { Table, TableWrapper, Row, Rows, Col } from 'react-native-tabeller'; +import { View, StyleSheet } from 'react-native'; + +export const ExampleThree = () => { + const tableHead = ['', 'Head1', 'Head2', 'Head3']; + const tableTitle = ['Title1', 'Title2', 'Title3']; + const tableData = [ + ['a', 'b', 'c'], + ['d', 'e', 'f'], + ['g', 'h', 'i'], + ]; + + const onCellPress = (data: any) => { + console.log(`Cell pressed: ${data}`); + }; + + return ( + + + + + + + +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + backgroundColor: '#fff', + }, + head: { + height: 44, + backgroundColor: '#C6F3E0', + }, + text: { + textAlign: 'center', + padding: 5, + }, + headText: { + textAlign: 'center', + fontWeight: 'bold', + }, + row: { + height: 40, + backgroundColor: '#fff', + }, + wrapper: { + flexDirection: 'row', + }, + title: { + flex: 1, + backgroundColor: '#C6F3E0', + }, + titleText: { + textAlign: 'left', + marginLeft: 6, + fontWeight: '600', + }, +}); diff --git a/example/src/ScrollableExample.tsx b/example/src/ScrollableExample.tsx new file mode 100644 index 0000000..93e158a --- /dev/null +++ b/example/src/ScrollableExample.tsx @@ -0,0 +1,82 @@ +import { Table, Row, Rows } from 'react-native-tabeller'; +import { View, StyleSheet, ScrollView } from 'react-native'; + +export const ScrollableExample = () => { + const tableHead = [ + 'Head', + 'Head2', + 'Head3', + 'Head4', + 'Head5', + 'Head6', + 'Head7', + 'Head8', + ]; + const widthArr = [40, 69, 80, 100, 120, 140, 160, 180]; + + // generates large table data + const tableData = []; + for (let i = 0; i < 30; i += 1) { + const rowData = []; + for (let j = 0; j < 8; j += 1) { + rowData.push(`${i}${j}`); + } + tableData.push(rowData); + } + + return ( + + + + + +
+ + + +
+
+
+
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + paddingTop: 30, + height: 400, + backgroundColor: '#fff', + }, + header: { + height: 50, + backgroundColor: '#C6F3E0', + }, + headerText: { + textAlign: 'center', + fontWeight: 'bold', + }, + text: { + textAlign: 'center', + padding: 5, + }, + dataWrapper: { + marginTop: -1, + }, + row: { + height: 40, + backgroundColor: '#fff', + }, +}); diff --git a/example/src/StickyColumnExample.tsx b/example/src/StickyColumnExample.tsx new file mode 100644 index 0000000..8393918 --- /dev/null +++ b/example/src/StickyColumnExample.tsx @@ -0,0 +1,41 @@ +import { StyleSheet } from 'react-native'; +import { StickyTable } from 'react-native-tabeller'; + +export const StickyTableExample = () => { + // first column will be sticky + const tableData = [ + ['Header', 'Col 1', 'Col 2', 'Col 3', 'Col 4'], + ['Row 1', 'Data 1-1', 'Data 1-2', 'Data 1-3', 'Data 1-4'], + ['Row 2', 'Data 2-1', 'Data 2-2', 'Data 2-3', 'Data 2-4'], + ['Row 3', 'Data 3-1', 'Data 3-2', 'Data 3-3', 'Data 3-4'], + ]; + + const stickyColumnWidth: number = 120; + const columnWidths: number[] = [120, 120, 120, 120]; + + return ( + + ); +}; + +const styles = StyleSheet.create({ + header: { + backgroundColor: '#C6F3E0', + }, + headerText: { + fontWeight: 'bold', + textAlign: 'center', + }, + text: { + textAlign: 'center', + padding: 5, + }, +}); diff --git a/package.json b/package.json index b5b5f46..7169917 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-tabeller", "version": "0.1.0", - "description": "React Native table with reanimated and gestures.�", + "description": "React Native table with reanimated and gestures.", "source": "./src/index.tsx", "main": "./lib/commonjs/index.js", "module": "./lib/module/index.js", @@ -48,14 +48,16 @@ "keywords": [ "react-native", "ios", - "android" + "android", + "react-native-table", + "tabeller" ], "repository": { "type": "git", "url": "git+https://git.sheetjs.com/asadbek064/react-native-tabeller.git.git" }, "author": "Asadbek Karimov (https://asadk.dev)", - "license": "MIT", + "license": "ALv2", "bugs": { "url": "https://git.sheetjs.com/asadbek064/react-native-tabeller.git/issues" }, diff --git a/react-native-tabeller-0.1.0.tgz b/react-native-tabeller-0.1.0.tgz new file mode 100644 index 0000000..fab8eaa Binary files /dev/null and b/react-native-tabeller-0.1.0.tgz differ diff --git a/src/components/cell.tsx b/src/components/cell.tsx new file mode 100644 index 0000000..25aba5d --- /dev/null +++ b/src/components/cell.tsx @@ -0,0 +1,81 @@ +import { useMemo } from 'react'; +import type { FC, PropsWithChildren } from 'react'; +import { View, Text, StyleSheet, TouchableWithoutFeedback } from 'react-native'; +import type { ViewStyle } from 'react-native'; + +import type { CellProps } from '../types'; + +/** + * cell component - the basic building block of the table + */ +export const Cell: FC> = ({ + data, + width, + height, + flex, + style, + textStyle, + borderStyle, + children, + onPress, + cellContainerProps = {}, + ...props +}) => { + const textDom = children ?? ( + + {data} + + ); + + const borderTopWidth = borderStyle?.borderWidth ?? 0; + const borderRightWidth = borderTopWidth; + const borderColor = borderStyle?.borderColor ?? '#000'; + + const composedStyles = useMemo(() => { + const styles: ViewStyle = {}; + if (width) styles.width = width; + if (height) styles.height = height; + if (flex) styles.flex = flex; + if (!width && !flex && !height && !style) styles.flex = 1; + + return styles; + }, [width, height, flex, style]); + + return ( + + onPress(data) : undefined} + disabled={!onPress} + > + {textDom} + + + ); +}; + +const styles = StyleSheet.create({ + cell: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + touchableContainer: { + flex: 1, + width: '100%', + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/src/components/cols.tsx b/src/components/cols.tsx new file mode 100644 index 0000000..4a6e40d --- /dev/null +++ b/src/components/cols.tsx @@ -0,0 +1,83 @@ +import type { FC } from 'react'; +import { View, StyleSheet } from 'react-native'; +import { Cell } from './cell'; +import type { ColProps, ColsProps } from '../types'; +import { sum } from '../util'; + +export const Col: FC = ({ + data, + style, + width, + heightArr, + flex, + textStyle, + borderStyle, + ...props +}) => { + if (!data) return null; + + return ( + + {data.map((item, i) => { + const height = heightArr?.[i]; + return ( + + ); + })} + + ); +}; + +export const Cols: FC = ({ + data, + style, + widthArr, + heightArr, + flexArr, + textStyle, + borderStyle, + ...props +}) => { + if (!data) return null; + + const width = widthArr ? sum(widthArr) : 0; + + return ( + + {data.map((item, i) => { + const flex = flexArr?.[i]; + const wth = widthArr?.[i]; + return ( + + ); + })} + + ); +}; + +const styles = StyleSheet.create({ + cols: { flexDirection: 'row' }, +}); diff --git a/src/components/rows.tsx b/src/components/rows.tsx new file mode 100644 index 0000000..700298f --- /dev/null +++ b/src/components/rows.tsx @@ -0,0 +1,116 @@ +import { useMemo } from 'react'; +import type { FC } from 'react'; +import { View, StyleSheet } from 'react-native'; +import type { ViewStyle } from 'react-native'; + +import { Cell } from './cell'; +import type { RowProps, RowsProps } from '../types'; +import { sum } from '../util'; + +/** Row component - Renders a single row of cells*/ +export const Row: FC = ({ + data, + style, + widthArr, + height, + flexArr, + textStyle, + borderStyle, + onPress, + cellTextStyle, + ...props +}) => { + // calc total width based on width array + const totalWidth = widthArr ? sum(widthArr) : 0; + + // calc row styles based on props + const rowStyle = useMemo((): ViewStyle => { + const styles: ViewStyle = {}; + if (totalWidth) styles.width = totalWidth; + if (height) styles.height = height; + return styles; + }, [totalWidth, height]); + + if (!data || !data.length) return null; + + return ( + + {data.map((item, index) => { + const cellFlex = flexArr?.[index]; + const cellWidth = widthArr?.[index]; + const customTextStyle = cellTextStyle + ? cellTextStyle(item, index) + : undefined; + + return ( + + ); + })} + + ); +}; + +/** Rows component - Renders multiple rows*/ +export const Rows: FC = ({ + data, + style, + widthArr, + heightArr, + flexArr, + textStyle, + borderStyle, + ...props +}) => { + // calc total flex and width + const totalFlex = flexArr ? sum(flexArr) : 0; + const totalWidth = widthArr ? sum(widthArr) : 0; + + // calc container styles + const containerStyle = useMemo((): ViewStyle => { + const styles: ViewStyle = {}; + if (totalFlex) styles.flex = totalFlex; + if (totalWidth) styles.width = totalWidth; + return styles; + }, [totalFlex, totalWidth]); + + if (!data || !data.length) return null; + + return ( + + {data.map((rowData, index) => { + const rowHeight = heightArr?.[index]; + + return ( + + ); + })} + + ); +}; + +const styles = StyleSheet.create({ + row: { + flexDirection: 'row', + overflow: 'hidden', + }, +}); diff --git a/src/components/sticky-table.tsx b/src/components/sticky-table.tsx new file mode 100644 index 0000000..d185c09 --- /dev/null +++ b/src/components/sticky-table.tsx @@ -0,0 +1,129 @@ +import React from 'react'; +import { View, ScrollView, StyleSheet } from 'react-native'; +import { Cell } from './cell'; +import type { StickyTableProps } from '../types'; + +/** StickyTable component - Table with sticky first column */ +export const StickyTable: React.FC = ({ + data, + stickyColumnWidth, + columnWidths = [], + style, + cellStyle, + textStyle, + headerStyle, + headerTextStyle, + borderStyle = { borderWidth: 1, borderColor: '#000' }, +}) => { + if (!data || data.length === 0) return null; + + // calc content width + const contentWidth = columnWidths.reduce((sum, width) => sum + width, 0); + + return ( + + {/* sticky Column */} + + {data.map((row, rowIndex) => { + const isHeader = rowIndex === 0; + return ( + + + + ); + })} + + + {/* scrollable Content */} + + + {data.map((row, rowIndex) => { + const isHeader = rowIndex === 0; + // skip first column as it's already in the sticky part + const rowData = row.slice(1); + + return ( + + {rowData.map((cellData, cellIndex) => { + const colWidth = columnWidths[cellIndex]; + + return ( + + + + ); + })} + + ); + })} + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + }, + stickyColumn: { + zIndex: 1, + backgroundColor: 'white', + elevation: 3, + }, + scrollView: { + flex: 1, + }, + row: { + flexDirection: 'row', + }, + cell: { + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'white', + overflow: 'hidden', + }, +}); diff --git a/src/components/table.tsx b/src/components/table.tsx new file mode 100644 index 0000000..cf02f7a --- /dev/null +++ b/src/components/table.tsx @@ -0,0 +1,82 @@ +import type { FC } from 'react'; +import React from 'react'; +import { View, StyleSheet } from 'react-native'; + +import type { TableProps, TableWrapperProps } from '../types'; + +export const Table: FC = ({ style, borderStyle, children }) => { + const borderLeftWidth = borderStyle?.borderWidth ?? 0; + const borderBottomWidth = borderLeftWidth; + const borderColor = borderStyle?.borderColor ?? '#000'; + + const renderChildren = () => + React.Children.map(children, (child) => { + if (!React.isValidElement(child)) return child; + + // check if we should add the border style | skip known type names + const elementType = child.type as any; + const isScrollView = + elementType?.displayName === 'ScrollView' || + elementType?.name === 'ScrollView'; + + if (borderStyle && !isScrollView) { + return React.cloneElement(child, { + borderStyle, + ...child.props, + }); + } + + return child; + }); + + return ( + + {renderChildren()} + + ); +}; + +export const TableWrapper: FC = ({ + style, + borderStyle, + children, +}) => { + const renderChildren = () => + React.Children.map(children, (child) => { + if (!React.isValidElement(child)) return child; + + if (borderStyle) { + return React.cloneElement(child, { + borderStyle, + ...child.props, + }); + } + + return child; + }); + + return ( + + {renderChildren()} + + ); +}; + +const styles = StyleSheet.create({ + table: { + overflow: 'hidden', + }, + wrapper: { + flex: 1, + }, +}); diff --git a/src/index.tsx b/src/index.tsx index 14289fe..13843e6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,26 @@ -export function multiply(a: number, b: number): number { - return a * b; -} +/** + * Tabeller - Table Components for React Native + * A set of lightweight and customizable components to build tables + * in React Native apps. Includes rows, columns, cells, and table wrappers. + */ + +// components +export { Cell } from './components/cell'; +export { Row, Rows } from './components/rows'; +export { Col, Cols } from './components/cols'; +export { Table, TableWrapper } from './components/table'; +export { StickyTable } from './components/sticky-table'; + +// types +export type { + BorderStyle, + BaseTableProps, + CellProps, + RowProps, + RowsProps, + ColProps, + ColsProps, + TableProps, + TableWrapperProps, + StickyTableProps, +} from './types'; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a54c33f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,130 @@ +import type { ReactNode } from 'react'; +import type { ViewStyle, TextStyle, StyleProp, ViewProps } from 'react-native'; + +/** border style configuration for table elements */ +export interface BorderStyle { + borderColor?: string; // defaults to #000 + borderWidth?: number; // defaults to 0 +} + +/** base props shared across table components */ +export interface BaseTableProps { + /** custom style for the component */ + style?: StyleProp; + /** border style configuration */ + borderStyle?: BorderStyle; +} + +/** props for the Cell component */ +export interface CellProps extends BaseTableProps { + /** cell content */ + data?: string | number | null; + /** cell width (pixels) */ + width?: number; + /** cell height (pixels) */ + height?: number; + /** flex value for the cell */ + flex?: number; + /** text style for cell content */ + textStyle?: StyleProp; + /** props passed to the cell container */ + cellContainerProps?: ViewProps; + /** callback when cell is pressed */ + onPress?: (data: any) => void; + /** children to render inside the cell */ + children?: ReactNode; +} + +/** props for the Row component */ +export interface RowProps extends BaseTableProps { + /** array of data items for each cell in the row */ + data: Array; + /** array of widths for each cell */ + widthArr?: number[]; + /** height for the entire row */ + height?: number; + /** array of flex values for each cell in the row */ + flexArr?: number[]; + /** text style applied to all cell in the row */ + textStyle?: StyleProp; + /** function to generate custom text styles for individual cell */ + cellTextStyle?: (item: any, index: number) => StyleProp; + /** callback when a cell is pressed */ + onPress?: (item: any) => void; +} + +/** props for the Rows componen */ +export interface RowsProps extends BaseTableProps { + /** 2D array of data for rows and cells */ + data: Array>; + /** array of widths for each column */ + widthArr?: number[]; + /** array of heights for each cell in a column */ + heightArr?: number[]; + /** array of flex values for each column */ + flexArr?: number[]; + /** text style applied to all cells */ + textStyle?: StyleProp; + /** callback when a cell is pressed */ + onPress?: (item: any) => void; +} + +/** props for the Col component */ +export interface ColProps extends BaseTableProps { + /** array of data items for each cell in the column */ + data: Array; + /** width for the entire column */ + width?: number; + /** array of heights for each cell */ + heightArr?: number[]; + /** flex value for the column */ + flex?: number; + /** text style applied to all cells in the column */ + textStyle?: StyleProp; +} + +/** props for the Cols component */ +export interface ColsProps extends BaseTableProps { + /** 2D array of data for columns and cells */ + data: Array>; + /** array of widths for each column */ + widthArr?: number[]; + /** array of heights for each cell in a column */ + heightArr?: number[]; + /** array of flex values for each column */ + flexArr?: number[]; + /** text style applied to all cells */ + textStyle?: StyleProp; +} + +/** props for the Table component */ +export interface TableProps extends BaseTableProps { + children: ReactNode; // Table content +} + +/** props for the TableWrapper component */ +export interface TableWrapperProps extends BaseTableProps { + children: ReactNode; // TableWrapper content +} + +/** props for the StickyTable component */ +export interface StickyTableProps { + /** full table data including first column */ + data: (string | number | null)[][]; + /** width of the sticky column */ + stickyColumnWidth: number; + /** widths for non-sticky columns */ + columnWidths?: number[]; + /** style for the container */ + style?: StyleProp; + /** style for cells */ + cellStyle?: StyleProp; + /** text style for cell content */ + textStyle?: StyleProp; + /** style for header row */ + headerStyle?: StyleProp; + /** text style for header cells */ + headerTextStyle?: StyleProp; + /** border style */ + borderStyle?: BorderStyle; +} diff --git a/src/util/index.ts b/src/util/index.ts new file mode 100644 index 0000000..b87d83d --- /dev/null +++ b/src/util/index.ts @@ -0,0 +1,9 @@ +/** + * sum all numbers in an array + * @param arr - arr of numbers + * @returns sum of all numbers + */ +export const sum = (arr: number[]): number => { + if (!arr || !arr.length) return 0; + return arr.reduce((acc, n) => acc + n, 0); +};