{"id":36418487,"url":"https://github.com/ryinex/compose-table","last_synced_at":"2026-01-11T17:01:27.956Z","repository":{"id":285148411,"uuid":"951105531","full_name":"ryinex/compose-table","owner":"ryinex","description":"Data table implementation for Compose Multiplatform ( Kotlin multiplatform ).","archived":false,"fork":false,"pushed_at":"2025-05-18T10:39:14.000Z","size":16031,"stargazers_count":47,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"dev","last_synced_at":"2025-05-18T11:15:38.652Z","etag":null,"topics":["android","desktop","ios","web"],"latest_commit_sha":null,"homepage":"https://csvviewer.ryinex.com/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryinex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-19T07:00:42.000Z","updated_at":"2025-05-18T10:38:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"a2088248-70bc-4bdf-87b3-518063ac6a29","html_url":"https://github.com/ryinex/compose-table","commit_stats":null,"previous_names":["ryinex/compose-table"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/ryinex/compose-table","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryinex%2Fcompose-table","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryinex%2Fcompose-table/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryinex%2Fcompose-table/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryinex%2Fcompose-table/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryinex","download_url":"https://codeload.github.com/ryinex/compose-table/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryinex%2Fcompose-table/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28314259,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T14:58:17.114Z","status":"ssl_error","status_checked_at":"2026-01-11T14:55:53.580Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","desktop","ios","web"],"created_at":"2026-01-11T17:01:22.405Z","updated_at":"2026-01-11T17:01:27.923Z","avatar_url":"https://github.com/ryinex.png","language":"Kotlin","readme":"# Compose Table\n\nA powerful and flexible data table implementation for Compose Multiplatform. Built to handle basic data visualization and manipulation needs across desktop, web, and mobile platforms (android \u0026 ios).\n![Table](images/table.gif)\n\n## Features\n\n- **Responsive Layout**\n  - Resizable columns with drag handles\n  - Multiple column layout modes (Fixed, Scrollable, Weighted)\n  - Sticky headers\n  - Horizontal and vertical scrolling\n\n- **Data Manipulation**\n  - In-cell editing with validation and confirmation\n  - Mobile-friendly edit dialog\n  - Sort functionality on columns\n  - Custom cell presentations\n  - Comparable, text and composable column types\n  - The ability to load more items when reaching the bottom of the table\n\n- **Customization**\n  - Keyboard navigation (Arrows and Tab keys)\n  - Custom cell rendering\n  - Theming support\n  - Column alignment options\n  - Configurable spacing and borders\n  - Header customization\n  - Ability to lock all interactions except for vertical scrolling\n\n- **Performance**\n  - Efficient list updates\n  - Lazy loading support\n  - Key-based optimization\n\n## Installation\n\nAdd the dependency to your project:\n\n```kotlin\n// Kotlin multiplatform\nkotlin {\n    sourceSets {\n        commonMain.dependencies {\n            implementation(\"com.ryinex.kotlin:compose-data-table:\u003clatest-version\u003e\")\n        }\n    }\n}\n```\n\n## Tl;dr\n\n### Playground\nYou can try the library in actual application: [CSV Viewer](https://csvviewer.ryinex.com)\n\n### Basic Table\n\n```kotlin\n@Composable\nprivate fun Table() {\n    val scope = rememberCoroutineScope()\n    val lazyState = rememberLazyListState()\n    val config = DataTableConfig.default()\n    val datatable = remember {\n        val table = DataTable(config = config, scope = scope, lazyState = lazyState)\n\n        table    \n            .text(name = \"Name\", value = { index, person -\u003e person.name })\n            .text(name = \"Age\", value = { index, person -\u003e person.age })\n            .setList(list = Samples.persons(), key = { index, person -\u003e person.id })\n    }\n    \n    DataTableView(table = datatable)\n}\n```\n\n### Embedded Table\nThis library provides a table that can be embedded into a LazyColumn but you must provide a horizontal scroll state yourself.\n```kotlin\nval scope = rememberCoroutineScope()\nval lazyState = rememberLazyListState()\nval config = DataTableConfig.default()\nval horizontalScrollState = rememberScrollState()\nval datatable = remember {\n    val table = DataTable(config = config, scope = scope, lazyState = lazyState)\n\n    table\n        .text(name = \"Name\", value = { index, person -\u003e person.name })\n        .text(name = \"Age\", value = { index, person -\u003e person.age })\n        .setList(list = Samples.persons(), key = { index, person -\u003e person.id })\n}\n\nColumn {\n    LazyColumn {\n        item { /* item */ }\n        item { /* item */ }\n        \n        EmbeddedDataTableView(horizontalScrollState, table)\n        \n        item { /* item */ }\n    }\n    \n    // Optional horizontal scrollbar but it would make a better experience\n    DataTableHorizontalScrollbar(state = horizontalScrollState)\n}\n\n```\n\n## Screenshots\n\n### Keyboard Navigation\n![Keyboard Navigation](images/table_keyboard_navigation.gif)\n\n### Sorting\n![Sorting](images/table_sorting.gif)\n\n### Resizing\n![Resizing](images/table_resizing.gif)\n\n### Editing\n![Editing](images/table_text_edit.gif)\n\n### Custom Cell Rendering\n![Custom Cell Rendering](images/table_custom_composables.gif)\n\n### Loading More Items\n![Loading More Items](images/table_load_more.gif)\n\n## Advanced\n\n### Comparable Columns\nComparable columns are columns that can be sorted by clicking on the column header.                    \nThe `value` must return be of type `Comparable\u003cT\u003e` interface, most primitive types are comparable like string, boolean, numbers but this interface can be applied to any custom class as well.                        \nComparable columns takes their own composable views for rendering the cell content.             \nIn the below example, double clicking the header will sort the column ascending or descending depending if the value of `Boolean` type is `true` or `false`.\n\n```kotlin\ntable\n    .comparable(\n        name = \"Insurance\",\n        value = { index, data -\u003e data.insurance },\n        content = { index, value /* Boolean */ -\u003e Checkbox(checked = value, onCheckedChange = null) }\n    )\n```\n\n### Text Columns\nBy default text columns are comparable, so double click header to sort ascending or descending will work.\n\n```kotlin\ntable\n    .text(\n        name = \"Vote ability\",\n        textMapper = { value /* Boolean */ -\u003e if (value == true) \"Can vote\" else \"Can't vote\" },\n        value = { index, data -\u003e data.age \u003e= 18 }\n    )\n    .text(\n        name = \"Name\",\n        value = { index, data -\u003e data.name } // textMapper returns { value.toString() } by default\n    )\n```\n\nText columns takes an DataTableEditTextConfig object to configure the text field.        \n```kotlin\ndata class DataTableEditTextConfig\u003cVALUE, DATA : Any\u003e(\n    val isEditable: Boolean,\n    val saveTrigger: List\u003cDataTableSaveTrigger\u003e,\n    val saveConfirm: DataTableSaveConfirm,\n    val mobileEditConfig: DataTableMobileTextEdit,\n    val keyboardOptions: KeyboardOptions,\n    val inputTransformation: InputTransformation?,\n    val outputTransformation: OutputTransformation?,\n    val lineLimits: TextFieldLineLimits,\n    val maxLines: Int,\n    val onConfirmEdit: suspend (data: DATA, old: VALUE, text: String) -\u003e VALUE?\n)\n\nenum class DataTableSaveTrigger {\n    LEAVE_FOCUS,\n    ENTER_PRESS\n}\n\nsealed interface DataTableSaveConfirm {\n    data object Auto : DataTableSaveConfirm\n\n    data class Dialog\u003cVALUE, DATA : Any\u003e(\n        val content: @Composable (data: DATA, oldValue: VALUE, text: String, onCancel: () -\u003e Unit, onConfirm: () -\u003e Unit) -\u003e Unit\n    ) : DataTableSaveConfirm\n}\n\nsealed interface DataTableMobileTextEdit {\n    data object InPlace : DataTableMobileTextEdit\n\n    data class Dialog(\n        val textFieldHint: @Composable () -\u003e String,\n        val content: @Composable (textField: @Composable () -\u003e Unit, onCancel: () -\u003e Unit, onConfirm: () -\u003e Unit) -\u003e Unit\n    ) : DataTableMobileTextEdit\n}\n```\nMost these data classes comes with `default` opinionated implementations that can be modified to config the edit properties of the column.                    \nthe `onConfirmEdit` lambda is called when the `DataTableSaveConfirm` is triggered it gives you the old value and the current text in the cell and it's up to you to confirm that the current text in the cell is valid so you return the text casted as `VALUE` or return `null` to cancel the edit.           \nOn mobile devices or mobile browsers, editing cell in place can be inconvenient so we provide a popup dialog you provide to edit the text more.              \nTriggering saving the edit can be done in two ways `Leaving the focus of the cell` or `Pressing enter / Action button on softkeyboard on mobile devices`.                    \nTo confirm saving the edit this can be done in two ways `Auto` or popup `Dialog` you provide to confirm intention.\n```kotlin\nval editConfig = remember {\n    DataTableEditTextConfig.default\u003cAny, Any\u003e(\n        isEditable = true,\n        saveConfirm = DataTableSaveConfirm.Dialog\u003cAny, Any\u003e { _, _, _, onCancel, onConfirm -\u003e SaveChangesDialog(onCancel, onConfirm) },\n        mobileBrowserEditConfig = DataTableMobileTextEdit.Dialog(\n            textFieldHint = { \"Your input\" },\n            content = { field, cancel, confirm -\u003e MobileChangesDialog(textField = field, onCancel = cancel, onSave = confirm) }\n        )\n    )\n}\n\ntable\n    .text(\n        name = \"Salary\",\n        editTextConfig = editConfig.copy(onConfirmEdit = { index, old, text -\u003e if (!text.isValidPositiveSalary()) null else text.asSalary() /* Double */ }),\n        value = { index, data -\u003e data.salary /* Double */ }\n    )\n\n@Composable\ninternal fun MobileChangesDialog(textField: @Composable () -\u003e Unit, onCancel: () -\u003e Unit, onSave: () -\u003e Unit) {\n    Card {\n        /* \n        Custom content\n         */\n        textField() // Important\n        Button(onClick = onCancel) { /* Button or whatever composable you choose to cancel the edit */ }\n        Button(onClick = onSave) { /* Button or whatever composable you choose to save the edit */ }\n    }\n}\n\n@Composable\nprivate fun SaveChangesDialog(onCancel: () -\u003e Unit, onConfirm: () -\u003e Unit) {\n    Card {\n        /* \n        Custom content\n         */\n        Button(onClick = onCancel) { /* Button or whatever composable you choose to cancel the edit */ }\n        Button(onClick = onConfirm) { /* Button or whatever composable you choose to confirm the edit */ }\n    }\n}\n```\n\n### Composable Columns\n\nComposable columns are columns that are rendered as a composable with no sorting or editing functionality.\n\n```kotlin\ntable\n    .composable(\n        name = \"Pet\",\n        content = { index, data -\u003e Image(painter = painterResource(data.pet), contentDescription = null) }\n    )\n```\n\n## Configuration Classes\n\n### DataTableConfig\nCore configuration for the data table:\n```kotlin\nDataTableConfig.default(\n    verticalSpacing = 4,              // Spacing between rows\n    horizontalSpacing = 8,            // Spacing between columns\n    isIndexed = true,                 // Show row indices\n    isHeadered = true,                // Show column headers\n    isHeaderSticky = true,            // Keep headers visible while scrolling\n    column = { it }                   // Column configuration\n)\n```\n\n### DataTableColumnLayout\nControls how columns are sized and arranged:\n```kotlin\nDataTableColumnLayout.FixedEquals        // Equal width columns\nDataTableColumnLayout.FixedWighted       // Width based on weights\nDataTableColumnLayout.ScrollableKeepInitial    // Scrollable with keeping initial largest width fixed between header and first cell\nDataTableColumnLayout.ScrollableKeepLargest    // Scrollable with keeping largest width based on the largest cell width in the column\n```\n\n### DataTableColumnConfig\nConfigures individual column behavior:\n```kotlin\nDataTableColumnConfig(\n    layout = DataTableColumnLayout.ScrollableKeepInitial,\n    weight = 1f,                      // Column weight for FixedWighted layout\n    isResizable = true,               // Enable column resizing\n    resizeHandleColor = Color.Gray,   // Color of resize handle\n    cell = DataTableCellConfig()      // Cell configuration\n)\n```\n\n### DataTableCellConfig\nControls cell appearance and behavior:\n```kotlin\nDataTableCellConfig(\n    modifier = Modifier,\n    color = Color.Black,              // Text color\n    backgroundColor = Color.White,     // Cell background\n    shape = RectangleShape,           // Cell shape\n    padding = PaddingValues(8.dp),    // Cell padding\n    textStyle = TextStyle(),          // Text styling\n    isForceLtr = false,              // Force left-to-right text\n    enterFocusChild = true,          // Focus behavior\n    alignment = Alignment.CenterStart, // Content alignment\n    textAlign = TextAlign.Start       // Text alignment\n)\n```\n\n\n## License\n\nApache License 2.0 - See [LICENSE](LICENSE) for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryinex%2Fcompose-table","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryinex%2Fcompose-table","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryinex%2Fcompose-table/lists"}