├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── components.json ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── src ├── app.d.ts ├── app.html ├── app.pcss ├── lib │ ├── assets │ │ ├── ss.jpg │ │ ├── tzezar-logo-black.png │ │ └── tzezar-logo-white.png │ ├── components │ │ ├── table │ │ │ ├── Table.svelte │ │ │ ├── cells │ │ │ │ ├── HeaderSelectCheckbox.svelte │ │ │ │ ├── InlineRowEditingToggleCell.svelte │ │ │ │ ├── LinkDetailsCell.svelte │ │ │ │ ├── NumberInputEditableCell.svelte │ │ │ │ ├── RowExpandToggle.svelte │ │ │ │ ├── RowSelectCheckbox.svelte │ │ │ │ └── TextInputEditableCell.svelte │ │ │ ├── components │ │ │ │ ├── CellEditableTemplate.svelte │ │ │ │ ├── CellTemplate.svelte │ │ │ │ ├── ColumnOrderDraggableChanger.svelte │ │ │ │ ├── ColumnVisibilitySelect.svelte │ │ │ │ ├── FullscreenModeToggle.svelte │ │ │ │ ├── Pagination.svelte │ │ │ │ └── SortingToggle.svelte │ │ │ ├── index.ts │ │ │ ├── stores │ │ │ │ ├── columnSizesStore.ts │ │ │ │ ├── expandedRowsStore.ts │ │ │ │ ├── fullscreenModeStore.ts │ │ │ │ ├── hiddenColumnsStore.ts │ │ │ │ ├── inlineEditingStore.ts │ │ │ │ ├── selectedRowsStore.ts │ │ │ │ ├── sortingStore.ts │ │ │ │ └── visibleRowsStore.ts │ │ │ ├── table │ │ │ │ ├── index.ts │ │ │ │ ├── table-body.svelte │ │ │ │ ├── table-caption.svelte │ │ │ │ ├── table-cell.svelte │ │ │ │ ├── table-footer.svelte │ │ │ │ ├── table-head.svelte │ │ │ │ ├── table-header.svelte │ │ │ │ ├── table-row.svelte │ │ │ │ └── table.svelte │ │ │ └── utils │ │ │ │ ├── clientMode.ts │ │ │ │ ├── getNestedValue.ts │ │ │ │ └── throttle.ts │ │ └── ui │ │ │ ├── button │ │ │ ├── button.svelte │ │ │ └── index.ts │ │ │ ├── checkbox │ │ │ ├── checkbox.svelte │ │ │ └── index.ts │ │ │ ├── collapsible │ │ │ ├── collapsible-content.svelte │ │ │ └── index.ts │ │ │ ├── dropdown-menu │ │ │ ├── dropdown-menu-checkbox-item.svelte │ │ │ ├── dropdown-menu-content.svelte │ │ │ ├── dropdown-menu-item.svelte │ │ │ ├── dropdown-menu-label.svelte │ │ │ ├── dropdown-menu-radio-group.svelte │ │ │ ├── dropdown-menu-radio-item.svelte │ │ │ ├── dropdown-menu-separator.svelte │ │ │ ├── dropdown-menu-shortcut.svelte │ │ │ ├── dropdown-menu-sub-content.svelte │ │ │ ├── dropdown-menu-sub-trigger.svelte │ │ │ └── index.ts │ │ │ ├── input │ │ │ ├── index.ts │ │ │ └── input.svelte │ │ │ ├── pagination │ │ │ ├── index.ts │ │ │ ├── pagination-content.svelte │ │ │ ├── pagination-ellipsis.svelte │ │ │ ├── pagination-item.svelte │ │ │ ├── pagination-link.svelte │ │ │ ├── pagination-next-button.svelte │ │ │ ├── pagination-prev-button.svelte │ │ │ └── pagination.svelte │ │ │ ├── popover │ │ │ ├── index.ts │ │ │ └── popover-content.svelte │ │ │ ├── select │ │ │ ├── index.ts │ │ │ ├── select-content.svelte │ │ │ ├── select-item.svelte │ │ │ ├── select-label.svelte │ │ │ ├── select-separator.svelte │ │ │ └── select-trigger.svelte │ │ │ ├── separator │ │ │ ├── index.ts │ │ │ └── separator.svelte │ │ │ ├── sheet │ │ │ ├── index.ts │ │ │ ├── sheet-content.svelte │ │ │ ├── sheet-description.svelte │ │ │ ├── sheet-footer.svelte │ │ │ ├── sheet-header.svelte │ │ │ ├── sheet-overlay.svelte │ │ │ ├── sheet-portal.svelte │ │ │ └── sheet-title.svelte │ │ │ ├── sonner │ │ │ ├── index.ts │ │ │ └── sonner.svelte │ │ │ └── tabs │ │ │ ├── index.ts │ │ │ ├── tabs-content.svelte │ │ │ ├── tabs-list.svelte │ │ │ └── tabs-trigger.svelte │ ├── index.ts │ └── utils.ts └── routes │ ├── (examples) │ ├── +layout.svelte │ ├── basic │ │ └── +page.svelte │ ├── client-mode │ │ ├── +page.svelte │ │ └── data.ts │ ├── column-filtering │ │ ├── +page.svelte │ │ └── _components │ │ │ ├── ExpandableRow.svelte │ │ │ ├── RatingCell.svelte │ │ │ └── TitleHeaderCell.svelte │ ├── column-hiding │ │ └── +page.svelte │ ├── column-ordering │ │ └── +page.svelte │ ├── column-resizing │ │ └── +page.svelte │ ├── conditional-styling │ │ └── +page.svelte │ ├── content-align │ │ └── +page.svelte │ ├── custom-cells │ │ ├── +page.svelte │ │ ├── BrandCell.svelte │ │ ├── DescriptionCell.svelte │ │ ├── IdCellHeader.svelte │ │ └── RatingCell.svelte │ ├── pagination │ │ └── +page.svelte │ ├── row-expanding │ │ ├── +page.svelte │ │ └── ExpandableRow.svelte │ ├── row-inline-editing-save-on-click │ │ ├── +page.svelte │ │ └── _components │ │ │ ├── InlineEditing.svelte │ │ │ ├── SelectBrand.svelte │ │ │ └── ValueCellEdit.svelte │ ├── row-inline-editing │ │ └── +page.svelte │ ├── row-modal-editing │ │ ├── +page.svelte │ │ └── EditModal.svelte │ ├── row-selection │ │ └── +page.svelte │ └── sorting │ │ └── +page.svelte │ ├── +layout.svelte │ ├── +page.svelte │ ├── _components │ ├── RatingCell.svelte │ ├── Sidebar.svelte │ ├── TitleHeaderCell.svelte │ └── custom-row-expand │ │ └── CustomRowExpandToggle.svelte │ ├── installation │ └── +page.svelte │ └── planned │ └── +page.svelte ├── static └── favicon.png ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json └── vite.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type { import("eslint").Linter.Config } */ 2 | module.exports = { 3 | root: true, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:svelte/recommended', 8 | 'prettier' 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['@typescript-eslint'], 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaVersion: 2020, 15 | extraFileExtensions: ['.svelte'] 16 | }, 17 | env: { 18 | browser: true, 19 | es2017: true, 20 | node: true 21 | }, 22 | overrides: [ 23 | { 24 | files: ['*.svelte'], 25 | parser: 'svelte-eslint-parser', 26 | parserOptions: { 27 | parser: '@typescript-eslint/parser' 28 | } 29 | } 30 | ] 31 | }; 32 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .svelte-kit 2 | .dist 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional stylelint cache 61 | .stylelintcache 62 | 63 | # Microbundle cache 64 | .rpt2_cache/ 65 | .rts2_cache_cjs/ 66 | .rts2_cache_es/ 67 | .rts2_cache_umd/ 68 | 69 | # Optional REPL history 70 | .node_repl_history 71 | 72 | # Output of 'npm pack' 73 | *.tgz 74 | 75 | # Yarn Integrity file 76 | .yarn-integrity 77 | 78 | # dotenv environment variable files 79 | .env 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | .env.local 84 | 85 | # parcel-bundler cache (https://parceljs.org/) 86 | .cache 87 | .parcel-cache 88 | 89 | # Next.js build output 90 | .next 91 | out 92 | 93 | # Nuxt.js build / generate output 94 | .nuxt 95 | dist 96 | 97 | # Gatsby files 98 | .cache/ 99 | # Comment in the public line in if your project uses Gatsby and not Next.js 100 | # https://nextjs.org/blog/next-9-1#public-directory-support 101 | # public 102 | 103 | # vuepress build output 104 | .vuepress/dist 105 | 106 | # vuepress v2.x temp and cache directory 107 | .temp 108 | .cache 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": [ 7 | "prettier-plugin-svelte", 8 | "prettier-plugin-tailwindcss" 9 | ], 10 | "overrides": [ 11 | { 12 | "files": "*.svelte", 13 | "options": { 14 | "parser": "svelte" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Sebastian Drozd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TZEZAR's TABLE 2 | 3 | > **Archived and No Longer Maintained** 4 | 5 | This project has been archived and is no longer actively maintained. It is provided as-is, and no further updates, bug fixes, or new features will be added. 6 | 7 | ## Status 8 | 9 | - **Archived**: The repository is in an archived state and is no longer actively developed or supported. 10 | - **No active support**: No further issues will be addressed, and pull requests will not be merged. 11 | 12 | ## Usage 13 | 14 | You are welcome to fork or clone the repository if you wish to continue using or contributing to it. However, please be aware that there will be no official updates or support from the maintainers. 15 | 16 | ## Alternatives 17 | 18 | If you're looking for actively maintained alternatives, you might consider [Tzezar's Datagrid](https://datagrid.tzezar.pl/) built with svelte 5. 19 | 20 | \ 21 | \ 22 | \ 23 | \ 24 | \ 25 | \ 26 | \ 27 | \ 28 | \ 29 | \ 30 | \ 31 | \ 32 | \ 33 | \ 34 | \ 35 | \ 36 | \ 37 | \ 38 | ...\ 39 | --- 40 | 41 | 42 | 43 | Table component built in svelte based on shadcn-svelte components. 44 | 45 | [documentation, examples and live demo](https://tzezar-table.vercel.app/) 46 | 47 | ## What is this? 48 | Table component for easy display of tables based on remote data (planned support for client-side functionality). 49 | Based on the excellent [shadcn-svelte](https://github.com/huntabyte/shadcn-svelte) component library. 50 | 51 | ## For whom? 52 | You copy the [component](https://github.com/tzezar/table/tree/main/src/lib/components/table) directly into your project and can edit it as you wish. It is something between a ready-made data-grid and a headless table. 53 | 54 | ## Supported Functions 55 | - Virtualization 56 | - Column hiding 57 | - Column reordering 58 | - Column resizing 59 | - Conditional styling 60 | - Content alignment 61 | - Custom content 62 | - Pagination 63 | - Inline row editing 64 | - Modal row editing 65 | - Row editing 66 | - Row expanding 67 | - Single column sorting 68 | 69 | ## Shortcuts 70 | [live examples](https://tzezar-table.vercel.app/) 71 | [installation](https://tzezar-table.vercel.app/installation) 72 | [component code](https://github.com/tzezar/table/tree/main/src/lib/components/table) 73 | [examples code](https://github.com/tzezar/table/tree/main/src/routes/(examples)) 74 | 75 | ## MIT License 76 | 77 | Copyright (c) 2024 Sebastian Drozd 78 | 79 | Permission is hereby granted, free of charge, to any person obtaining a copy 80 | of this software and associated documentation files (the "Software"), to deal 81 | in the Software without restriction, including without limitation the rights 82 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 83 | copies of the Software, and to permit persons to whom the Software is 84 | furnished to do so, subject to the following conditions: 85 | 86 | The above copyright notice and this permission notice shall be included in all 87 | copies or substantial portions of the Software. 88 | 89 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 90 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 91 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 92 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 93 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 94 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 95 | SOFTWARE. 96 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-svelte.com/schema.json", 3 | "style": "default", 4 | "tailwind": { 5 | "config": "tailwind.config.js", 6 | "css": "src/app.pcss", 7 | "baseColor": "stone" 8 | }, 9 | "aliases": { 10 | "components": "$lib/components", 11 | "utils": "$lib/utils" 12 | }, 13 | "typescript": true 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "530rge-table", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "vite dev", 6 | "build": "vite build && npm run package", 7 | "preview": "vite preview", 8 | "package": "svelte-kit sync && svelte-package && publint", 9 | "prepublishOnly": "npm run package", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "lint": "prettier --check . && eslint .", 13 | "format": "prettier --write ." 14 | }, 15 | "exports": { 16 | ".": { 17 | "types": "./dist/index.d.ts", 18 | "svelte": "./dist/index.js" 19 | } 20 | }, 21 | "files": [ 22 | "dist", 23 | "!dist/**/*.test.*", 24 | "!dist/**/*.spec.*" 25 | ], 26 | "peerDependencies": { 27 | "svelte": "^4.0.0" 28 | }, 29 | "devDependencies": { 30 | "@sveltejs/adapter-auto": "^3.0.0", 31 | "@sveltejs/adapter-vercel": "^5.1.1", 32 | "@sveltejs/kit": "^2.0.0", 33 | "@sveltejs/package": "^2.0.0", 34 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 35 | "@tailwindcss/typography": "^0.5.10", 36 | "@types/eslint": "^8.56.0", 37 | "@typescript-eslint/eslint-plugin": "^7.0.0", 38 | "@typescript-eslint/parser": "^7.0.0", 39 | "autoprefixer": "^10.4.16", 40 | "eslint": "^8.56.0", 41 | "eslint-config-prettier": "^9.1.0", 42 | "eslint-plugin-svelte": "^2.35.1", 43 | "postcss": "^8.4.32", 44 | "postcss-load-config": "^5.0.2", 45 | "prettier": "^3.1.1", 46 | "prettier-plugin-svelte": "^3.1.2", 47 | "prettier-plugin-tailwindcss": "^0.5.9", 48 | "publint": "^0.1.9", 49 | "svelte": "^4.2.7", 50 | "svelte-check": "^3.6.0", 51 | "tailwindcss": "^3.3.6", 52 | "tslib": "^2.4.1", 53 | "typescript": "^5.0.0", 54 | "vite": "^5.0.11" 55 | }, 56 | "svelte": "./dist/index.js", 57 | "types": "./dist/index.d.ts", 58 | "type": "module", 59 | "dependencies": { 60 | "@tanstack/svelte-query": "^5.28.4", 61 | "@tanstack/svelte-query-devtools": "^5.28.4", 62 | "@tanstack/svelte-virtual": "^3.2.0", 63 | "bits-ui": "^0.19.7", 64 | "clsx": "^2.1.0", 65 | "lucide-svelte": "^0.358.0", 66 | "mode-watcher": "^0.3.0", 67 | "svelte-dnd-action": "^0.9.40", 68 | "svelte-legos": "^0.2.2", 69 | "svelte-sonner": "^0.3.19", 70 | "tailwind-merge": "^2.2.2", 71 | "tailwind-variants": "^0.2.1" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require("tailwindcss"); 2 | const autoprefixer = require("autoprefixer"); 3 | 4 | const config = { 5 | plugins: [ 6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 7 | tailwindcss(), 8 | //But others, like autoprefixer, need to run after, 9 | autoprefixer 10 | ] 11 | }; 12 | 13 | module.exports = config; -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app.pcss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | 7 | 8 | @layer base { 9 | :root { 10 | --background: 0 0% 100%; 11 | --foreground: 20 14.3% 4.1%; 12 | 13 | --muted: 60 4.8% 95.9%; 14 | --muted-foreground: 25 5.3% 44.7%; 15 | 16 | --popover: 0 0% 100%; 17 | --popover-foreground: 20 14.3% 4.1%; 18 | 19 | --card: 0 0% 100%; 20 | --card-foreground: 20 14.3% 4.1%; 21 | 22 | --border: 20 5.9% 90%; 23 | --input: 20 5.9% 90%; 24 | 25 | --primary: 24 9.8% 10%; 26 | --primary-foreground: 60 9.1% 97.8%; 27 | 28 | --secondary: 60 4.8% 95.9%; 29 | --secondary-foreground: 24 9.8% 10%; 30 | 31 | --accent: 60 4.8% 95.9%; 32 | --accent-foreground: 24 9.8% 10%; 33 | 34 | --destructive: 0 72.2% 50.6%; 35 | --destructive-foreground: 60 9.1% 97.8%; 36 | 37 | --ring: 20 14.3% 4.1%; 38 | 39 | --radius: 0.5rem; 40 | } 41 | 42 | .dark { 43 | --background: 20 14.3% 4.1%; 44 | --foreground: 60 9.1% 97.8%; 45 | 46 | --muted: 12 6.5% 15.1%; 47 | --muted-foreground: 24 5.4% 63.9%; 48 | 49 | --popover: 20 14.3% 4.1%; 50 | --popover-foreground: 60 9.1% 97.8%; 51 | 52 | --card: 20 14.3% 4.1%; 53 | --card-foreground: 60 9.1% 97.8%; 54 | 55 | --border: 12 6.5% 15.1%; 56 | --input: 12 6.5% 15.1%; 57 | 58 | --primary: 60 9.1% 97.8%; 59 | --primary-foreground: 24 9.8% 10%; 60 | 61 | --secondary: 12 6.5% 15.1%; 62 | --secondary-foreground: 60 9.1% 97.8%; 63 | 64 | --accent: 12 6.5% 15.1%; 65 | --accent-foreground: 60 9.1% 97.8%; 66 | 67 | --destructive: 0 62.8% 30.6%; 68 | --destructive-foreground: 60 9.1% 97.8%; 69 | 70 | --ring: 24 5.7% 82.9%; 71 | } 72 | } 73 | 74 | 75 | 76 | 77 | @layer base { 78 | * { 79 | @apply border-border; 80 | } 81 | body { 82 | @apply bg-background text-foreground; 83 | } 84 | } 85 | 86 | 87 | 88 | 89 | /* width */ 90 | ::-webkit-scrollbar { 91 | width: 10px; 92 | height: 10px; 93 | } 94 | 95 | /* Track */ 96 | ::-webkit-scrollbar-track { 97 | background: #f1f1f1; 98 | } 99 | 100 | /* Handle */ 101 | ::-webkit-scrollbar-thumb { 102 | background: #888; 103 | } 104 | 105 | /* Handle on hover */ 106 | ::-webkit-scrollbar-thumb:hover { 107 | background: #555; 108 | } 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/lib/assets/ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzezar/table/67a908b769c7c7cf05f1adf4b102e0b69d9ce0e7/src/lib/assets/ss.jpg -------------------------------------------------------------------------------- /src/lib/assets/tzezar-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzezar/table/67a908b769c7c7cf05f1adf4b102e0b69d9ce0e7/src/lib/assets/tzezar-logo-black.png -------------------------------------------------------------------------------- /src/lib/assets/tzezar-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzezar/table/67a908b769c7c7cf05f1adf4b102e0b69d9ce0e7/src/lib/assets/tzezar-logo-white.png -------------------------------------------------------------------------------- /src/lib/components/table/Table.svelte: -------------------------------------------------------------------------------- 1 | 190 | 191 | 192 | 193 |
194 |
201 |
202 |
203 |
204 |
205 | 206 |
207 |
208 | {#if enableColumnReordering} 209 | 210 | {/if} 211 | {#if enableColumnVisiblitySelect} 212 |
218 |
219 |
220 | 221 |
222 |
223 |
224 | 230 | 231 | 234 | {#each $columnsStore.filter((c) => !$hiddenColumns.includes(c.accessor)) as column, columnIndex} 235 | 239 | {#if typeof column?.head?.component === 'string' || typeof column.head?.component === 'number'} 240 | 241 | {column.head.component} 242 | 243 | {:else if column.head?.component} 244 | 250 | {:else if column.header} 251 | 252 | {column.header} 253 | 254 | {/if} 255 |
256 | {#if column && column?.config?.sortable !== false && enableSorting} 257 | 258 | {/if} 259 | {#if column.config?.resizable != false && enableResizing} 260 | 266 | 267 | {/each} 268 | 269 | 270 | {#if enableVirtualization} 271 |
272 | 273 |
278 | {#each items as row, idx (row.index)} 279 | {@const dataRow = data[row.index]} 280 | {@const dataIndex = row.index} 281 |
282 | 290 | {#each $columnsStore.filter((c) => !$hiddenColumns.includes(c.accessor)) as column, columnIndex} 291 | 305 | {#if $inlineEditing.includes(dataRow.id)} 306 | 307 | {:else} 308 | 309 | {/if} 310 | 311 | {/each} 312 | 313 | {#if $expandedRows.includes(dataRow.id)} 314 | 315 | 322 | 323 | {/if} 324 |
325 | {/each} 326 |
327 |
328 |
329 | {:else if !enableVirtualization} 330 | 331 | {#if !loading && data.length < 1} 332 | 333 | 334 |
335 | 336 |

No data found

337 |
338 |
339 |
340 | {/if} 341 | 342 | {#each $visibleRows as row, rowIndex} 343 | 351 | {#each $columnsStore.filter((c) => !$hiddenColumns.includes(c.accessor)) as column, columnIndex} 352 | 365 | {#if $inlineEditing.includes(row.id)} 366 | 367 | {:else} 368 | 369 | {/if} 370 | 371 | {/each} 372 | 373 | {#if $expandedRows.includes(row.id)} 374 | 375 | 382 | 383 | {/if} 384 | {/each} 385 |
386 | {/if} 387 | 388 |
389 | {#if enablePagination} 390 | 391 | {/if} 392 |
393 |
394 |
395 | -------------------------------------------------------------------------------- /src/lib/components/table/cells/HeaderSelectCheckbox.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Rows 35 | 36 | selectedRows.selectAllOnPage($visibleRows)} 37 | >Select all on page 39 | selectedRows.deselectAllOnPage($visibleRows)} 40 | >Deselect all on page 42 | selectedRows.deselectAll()} 43 | >Deselec all 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/lib/components/table/cells/InlineRowEditingToggleCell.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | {#if $inlineEditing.includes(rowId)} 16 | 22 | {:else} 23 | 29 | {/if} 30 |
31 | -------------------------------------------------------------------------------- /src/lib/components/table/cells/LinkDetailsCell.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | {props.value} 8 | -------------------------------------------------------------------------------- /src/lib/components/table/cells/NumberInputEditableCell.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | props.column.extra.onChange({ 14 | value: +e.target.value, 15 | id: props.row.id, 16 | accessor: props.column.accessor 17 | })} 18 | /> 19 | -------------------------------------------------------------------------------- /src/lib/components/table/cells/RowExpandToggle.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | {#if $expandedRows.includes(rowId)} 18 | 21 | {:else} 22 | 25 | {/if} 26 |
27 | -------------------------------------------------------------------------------- /src/lib/components/table/cells/RowSelectCheckbox.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 | selectRow({ rowId })} class="" /> 22 |
23 | -------------------------------------------------------------------------------- /src/lib/components/table/cells/TextInputEditableCell.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | props.column.extra.onChange({ 13 | value: e.target.value, 14 | id: props.row.id, 15 | accessor: props.column.accessor 16 | })} 17 | /> 18 | -------------------------------------------------------------------------------- /src/lib/components/table/components/CellEditableTemplate.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | {#if !column.cell?.componentEditable} 21 | {#if getNestedValue(row, column.accessor) !== undefined} 22 | {getNestedValue(row, column.accessor)} 23 | {:else} 24 | {row[column.accessor]} 25 | {/if} 26 | {:else if column.cell?.componentEditable} 27 | 36 | {:else if column?.cell?.simplified} 37 | {@html column.cell.simplified({ column, row, rowIndex, value })} 38 | {/if} 39 | -------------------------------------------------------------------------------- /src/lib/components/table/components/CellTemplate.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | {#if !column.cell?.component} 21 | {#if column?.cell?.simplified} 22 | {@html column.cell.simplified({ column, row, rowIndex, value })} 23 | 24 | {:else if getNestedValue(row, column.accessor) !== undefined} 25 | {getNestedValue(row, column.accessor)} 26 | {:else} 27 | {row[column.accessor]} 28 | {/if} 29 | 30 | {:else if column.cell?.component} 31 | 40 | {/if} 41 | -------------------------------------------------------------------------------- /src/lib/components/table/components/ColumnOrderDraggableChanger.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
39 | {#each $columns as item (item.id)} 40 | 41 |
45 | {#if item?.config?.moveable != false} 46 | {item.header} 47 | 48 | {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} 49 |
50 | {item.header} 51 |
52 | {/if} 53 | {/if} 54 |
55 | {/each} 56 |

57 | To change column order, click and hold one. Then move to the desired location and release. 58 |

59 |
60 |
61 |
62 |
63 | -------------------------------------------------------------------------------- /src/lib/components/table/components/ColumnVisibilitySelect.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | {#each columns 21 | .filter((c) => c.header != '') 22 | .map( (c) => ({ accessor: c.accessor, header: c.header, disabled: c.config?.disableHiding }) ) as col} 23 | { 27 | if (checked == false) { 28 | let newHidden = [...$hidden, col.accessor]; 29 | hidden.set(newHidden); 30 | } else if (checked == true) { 31 | let newHidden = $hidden.filter((h) => h !== col.accessor); 32 | hidden.set(newHidden); 33 | } 34 | }} 35 | > 36 | 37 | {col.header} 38 | 39 | {/each} 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/lib/components/table/components/FullscreenModeToggle.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /src/lib/components/table/components/Pagination.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {#each pages as page (page.key)} 37 | {#if page.type === 'ellipsis'} 38 | 39 | 40 | 41 | {:else} 42 | 43 | 44 | {page.value} 45 | 46 | 47 | {/if} 48 | {/each} 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 |
60 | { 63 | if (!v) { 64 | throw new Error(); 65 | } 66 | perPage = v?.value; 67 | if (page * perPage - perPage >= count) { 68 | page = Math.ceil(count / perPage); 69 | } 70 | }} 71 | selected={limits.find((p) => p.value == perPage)} 72 | > 73 | 74 | 75 | 76 | 77 | {#each limits as limit} 78 | {limit.label} 79 | {/each} 80 | 81 | 82 |
83 |
84 | -------------------------------------------------------------------------------- /src/lib/components/table/components/SortingToggle.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | {#if $sorting.accessor == column.accessor} 12 | {#if $sorting.direction == 'asc'} 13 | 23 | {:else if $sorting.direction == 'desc'} 24 | 34 | {/if} 35 | {:else} 36 | 46 | {/if} 47 | -------------------------------------------------------------------------------- /src/lib/components/table/index.ts: -------------------------------------------------------------------------------- 1 | type ClassFunction = (props: { row: any; column: Column }) => string; 2 | 3 | 4 | export type Column = { 5 | accessor: string; 6 | header?: string, 7 | head?: { 8 | component?: any, 9 | class?: string 10 | }, 11 | cell?: { 12 | component?: any, 13 | componentEditable?: any, 14 | simplified?: (props: { row: any; column: Column, value: string | number | any, rowIndex: number }) => string, 15 | class?: string | ClassFunction 16 | }, 17 | config?: { 18 | visible?: boolean, 19 | sortable?: boolean, 20 | disableHiding?: boolean, 21 | resizable?: boolean, 22 | moveable?: boolean, 23 | size?: { 24 | w?: number, 25 | minW?: number, 26 | maxW?: number, 27 | }, 28 | align?: "left" | "right" | "center", 29 | 30 | }, 31 | extra?: any 32 | }; 33 | 34 | export type Columns = Column[]; 35 | 36 | export type CellProps = { 37 | value: unknown, 38 | column: Column, 39 | row: any, 40 | rowIndex: number 41 | } 42 | 43 | export type HeaderCellProps = { 44 | column: Column 45 | } 46 | 47 | 48 | 49 | export type SortingElement = { 50 | accessor: string, 51 | direction: 'ASC' | 'DESC' | "" 52 | } 53 | 54 | 55 | export type ColumnClassNames = { 56 | table: () => string; 57 | th: () => string; 58 | row: (row: any) => string; 59 | cell: (row: any, column: Column) => string; 60 | }; 61 | 62 | 63 | 64 | 65 | export type Filter = { 66 | column: string, 67 | operator: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' | 'notBetween' | 'like' | 'ilike' | 'notIlike' | 'not' | 'inArray' | "", 68 | value: string | number 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/components/table/stores/columnSizesStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export function createColumnSizesStore(arr: Array<{ accessor: string; w: number; minW: number; maxW: number }> = []) { 4 | const { set, subscribe, update } = writable(arr); 5 | 6 | return { 7 | subscribe, 8 | set, 9 | update, 10 | }; 11 | } 12 | 13 | export const columnSizes = createColumnSizesStore(); 14 | export type ColumnSizes = typeof columnSizes -------------------------------------------------------------------------------- /src/lib/components/table/stores/expandedRowsStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export function createExpandedRowsStore(arr: Array = []) { 4 | const { set, subscribe, update } = writable(arr); 5 | 6 | return { 7 | subscribe, 8 | set, 9 | update, 10 | expand: (rowId: number) => 11 | update((state) => { 12 | if (!state.includes(rowId)) { 13 | return [...state, rowId]; 14 | } 15 | return state; 16 | }), 17 | collapse: (rowId: number) => 18 | update((state) => { 19 | let newState = state.filter((e) => e != rowId); 20 | return newState; 21 | }) 22 | }; 23 | } 24 | 25 | export const expandedRows = createExpandedRowsStore(); 26 | 27 | export type ExpandedRows = typeof expandedRows -------------------------------------------------------------------------------- /src/lib/components/table/stores/fullscreenModeStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export function createFullscreenModeStore(open: boolean = false) { 4 | const { set, subscribe, update } = writable(open); 5 | 6 | return { 7 | subscribe, 8 | set, 9 | update, 10 | on: () => 11 | update(() => { 12 | return true; 13 | }), 14 | off: () => 15 | update(() => { 16 | return false; 17 | }), 18 | toggle: () => { 19 | update((store) => { 20 | return !store 21 | }) 22 | } 23 | }; 24 | } 25 | 26 | export const fullscreenMode = createFullscreenModeStore(); 27 | export type FullscreenMode = typeof fullscreenMode -------------------------------------------------------------------------------- /src/lib/components/table/stores/hiddenColumnsStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export function createHiddenColumnsStore(arr: Array = []) { 4 | const { set, subscribe, update } = writable(arr); 5 | 6 | return { 7 | subscribe, 8 | set, 9 | update, 10 | }; 11 | } 12 | 13 | export const hiddenColumns = createHiddenColumnsStore(); 14 | export type HiddenColumns = typeof hiddenColumns -------------------------------------------------------------------------------- /src/lib/components/table/stores/inlineEditingStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export function createInlineEditingStore(arr: Array = []) { 4 | const { set, subscribe, update } = writable(arr); 5 | 6 | return { 7 | subscribe, 8 | set, 9 | update, 10 | }; 11 | } 12 | 13 | export const inlineEditing = createInlineEditingStore(); 14 | export type InlineEditing = typeof inlineEditing -------------------------------------------------------------------------------- /src/lib/components/table/stores/selectedRowsStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | 4 | 5 | 6 | type Rows = { id: number }[] 7 | 8 | 9 | export function createSelectedRowsStore(arr: Array = []) { 10 | const { set, subscribe, update } = writable(arr); 11 | 12 | const toggle = (rowId: number) => { 13 | update(state => { 14 | if (state.includes(rowId)) { 15 | return state.filter(id => id !== rowId); 16 | } else { 17 | return [...state, rowId]; 18 | } 19 | }); 20 | }; 21 | 22 | const select = (rowId: number) => { 23 | update(state => { 24 | if (!state.includes(rowId)) { 25 | return [...state, rowId]; 26 | } 27 | return state; 28 | }); 29 | }; 30 | 31 | const deselect = (rowId: number) => { 32 | update(state => state.filter(id => id !== rowId)); 33 | }; 34 | 35 | const deselectAll = () => { 36 | set([]); 37 | }; 38 | 39 | const deselectAllOnPage = (rows: any) => { 40 | update(state => { 41 | const rowIds = rows.map(row => row.id); 42 | return state.filter(selectedRowId => !rowIds.includes(selectedRowId)); 43 | }); 44 | }; 45 | 46 | const selectAllOnPage = (rows: any) => { 47 | update(state => { 48 | const rowIds = rows.map(row => row.id); 49 | return [...new Set([...state, ...rowIds])]; 50 | }); 51 | }; 52 | 53 | 54 | return { 55 | subscribe, 56 | set, 57 | update, 58 | toggle, 59 | select, 60 | deselect, 61 | deselectAll, 62 | deselectAllOnPage, 63 | selectAllOnPage 64 | }; 65 | } 66 | 67 | export const selectedRows = createSelectedRowsStore(); 68 | 69 | export type SelectedRows = typeof selectedRows -------------------------------------------------------------------------------- /src/lib/components/table/stores/sortingStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | import type { SortingElement } from '..'; 3 | 4 | export function createSortingStore(sorting: SortingElement) { 5 | const { set, subscribe, update } = writable(sorting); 6 | 7 | return { 8 | subscribe, 9 | set, 10 | update, 11 | }; 12 | } 13 | 14 | export const sorting = createSortingStore({ accessor: "", direction: "" }); 15 | export type Sorting = typeof sorting -------------------------------------------------------------------------------- /src/lib/components/table/stores/visibleRowsStore.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export function createVisibleRowsStore(arr: Array = []) { 4 | const { set, subscribe, update } = writable(arr); 5 | 6 | return { 7 | subscribe, 8 | set, 9 | update, 10 | }; 11 | } 12 | 13 | export const visibleRows = createVisibleRowsStore(); 14 | export type VisibleRows = typeof visibleRows -------------------------------------------------------------------------------- /src/lib/components/table/table/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./table.svelte"; 2 | import Body from "./table-body.svelte"; 3 | import Caption from "./table-caption.svelte"; 4 | import Cell from "./table-cell.svelte"; 5 | import Footer from "./table-footer.svelte"; 6 | import Head from "./table-head.svelte"; 7 | import Header from "./table-header.svelte"; 8 | import Row from "./table-row.svelte"; 9 | 10 | export { 11 | Root, 12 | Body, 13 | Caption, 14 | Cell, 15 | Footer, 16 | Head, 17 | Header, 18 | Row, 19 | // 20 | Root as Table, 21 | Body as TableBody, 22 | Caption as TableCaption, 23 | Cell as TableCell, 24 | Footer as TableFooter, 25 | Head as TableHead, 26 | Header as TableHeader, 27 | Row as TableRow, 28 | }; 29 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table-body.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table-caption.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table-cell.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table-head.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table-row.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 |
23 | 24 |
25 | -------------------------------------------------------------------------------- /src/lib/components/table/table/table.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/lib/components/table/utils/clientMode.ts: -------------------------------------------------------------------------------- 1 | import type { SortingElement } from "../index.js"; 2 | 3 | export function getPropertyValue(obj: any, path: string): any { 4 | if (!obj || !path) return ''; // Add this check 5 | 6 | const properties = path.split('.'); 7 | return properties.reduce((prev, curr) => prev && prev[curr], obj) || ''; 8 | } 9 | 10 | export function sortByPropertyName(array: any[], propertyName: string, sortingElement: SortingElement): any[] { 11 | if (propertyName === '' || sortingElement.direction === '') { 12 | return array; 13 | } 14 | 15 | return array.slice().sort((a, b) => { 16 | const valueA = getPropertyValue(a, propertyName); 17 | const valueB = getPropertyValue(b, propertyName); 18 | 19 | if (typeof valueA === 'number' && typeof valueB === 'number') { 20 | // Compare numbers directly 21 | return sortingElement.direction === 'ASC' ? valueA - valueB : valueB - valueA; 22 | } 23 | 24 | // If not numbers, treat them as strings 25 | const stringA = String(valueA).toUpperCase(); 26 | const stringB = String(valueB).toUpperCase(); 27 | 28 | return sortingElement.direction === 'ASC' 29 | ? stringA.localeCompare(stringB) 30 | : stringB.localeCompare(stringA); 31 | }); 32 | } -------------------------------------------------------------------------------- /src/lib/components/table/utils/getNestedValue.ts: -------------------------------------------------------------------------------- 1 | export function getNestedValue(obj, path) { 2 | const properties = path.split('.'); 3 | return properties.reduce((accumulator, currentProperty) => { 4 | if (accumulator && accumulator[currentProperty]) { 5 | return accumulator[currentProperty]; 6 | } 7 | return undefined; 8 | }, obj); 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/components/table/utils/throttle.ts: -------------------------------------------------------------------------------- 1 | 2 | export function throttle(func: Function, limit: number) { 3 | let inThrottle: boolean; 4 | return function (...args: any[]) { 5 | if (!inThrottle) { 6 | func(...args); 7 | inThrottle = true; 8 | setTimeout(() => (inThrottle = false), limit); 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/components/ui/button/button.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/lib/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./button.svelte"; 2 | import { tv, type VariantProps } from "tailwind-variants"; 3 | import type { Button as ButtonPrimitive } from "bits-ui"; 4 | 5 | const buttonVariants = tv({ 6 | base: "inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 7 | variants: { 8 | variant: { 9 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 10 | destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", 11 | outline: 12 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 13 | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", 14 | ghost: "hover:bg-accent hover:text-accent-foreground", 15 | link: "text-primary underline-offset-4 hover:underline", 16 | }, 17 | size: { 18 | default: "h-10 px-4 py-2", 19 | sm: "h-9 rounded-md px-3", 20 | lg: "h-11 rounded-md px-8", 21 | icon: "h-10 w-10", 22 | }, 23 | }, 24 | defaultVariants: { 25 | variant: "default", 26 | size: "default", 27 | }, 28 | }); 29 | 30 | type Variant = VariantProps["variant"]; 31 | type Size = VariantProps["size"]; 32 | 33 | type Props = ButtonPrimitive.Props & { 34 | variant?: Variant; 35 | size?: Size; 36 | }; 37 | 38 | type Events = ButtonPrimitive.Events; 39 | 40 | export { 41 | Root, 42 | type Props, 43 | type Events, 44 | // 45 | Root as Button, 46 | type Props as ButtonProps, 47 | type Events as ButtonEvents, 48 | buttonVariants, 49 | }; 50 | -------------------------------------------------------------------------------- /src/lib/components/ui/checkbox/checkbox.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 29 | {#if isChecked} 30 | 31 | {:else if isIndeterminate} 32 | 33 | {/if} 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/lib/components/ui/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./checkbox.svelte"; 2 | export { 3 | Root, 4 | // 5 | Root as Checkbox, 6 | }; 7 | -------------------------------------------------------------------------------- /src/lib/components/ui/collapsible/collapsible-content.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/components/ui/collapsible/index.ts: -------------------------------------------------------------------------------- 1 | import { Collapsible as CollapsiblePrimitive } from "bits-ui"; 2 | import Content from "./collapsible-content.svelte"; 3 | 4 | const Root = CollapsiblePrimitive.Root; 5 | const Trigger = CollapsiblePrimitive.Trigger; 6 | 7 | export { 8 | Root, 9 | Content, 10 | Trigger, 11 | // 12 | Root as Collapsible, 13 | Content as CollapsibleContent, 14 | Trigger as CollapsibleTrigger, 15 | }; 16 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/lib/components/ui/dropdown-menu/index.ts: -------------------------------------------------------------------------------- 1 | import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; 2 | import Item from "./dropdown-menu-item.svelte"; 3 | import Label from "./dropdown-menu-label.svelte"; 4 | import Content from "./dropdown-menu-content.svelte"; 5 | import Shortcut from "./dropdown-menu-shortcut.svelte"; 6 | import RadioItem from "./dropdown-menu-radio-item.svelte"; 7 | import Separator from "./dropdown-menu-separator.svelte"; 8 | import RadioGroup from "./dropdown-menu-radio-group.svelte"; 9 | import SubContent from "./dropdown-menu-sub-content.svelte"; 10 | import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; 11 | import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; 12 | 13 | const Sub = DropdownMenuPrimitive.Sub; 14 | const Root = DropdownMenuPrimitive.Root; 15 | const Trigger = DropdownMenuPrimitive.Trigger; 16 | const Group = DropdownMenuPrimitive.Group; 17 | 18 | export { 19 | Sub, 20 | Root, 21 | Item, 22 | Label, 23 | Group, 24 | Trigger, 25 | Content, 26 | Shortcut, 27 | Separator, 28 | RadioItem, 29 | SubContent, 30 | SubTrigger, 31 | RadioGroup, 32 | CheckboxItem, 33 | // 34 | Root as DropdownMenu, 35 | Sub as DropdownMenuSub, 36 | Item as DropdownMenuItem, 37 | Label as DropdownMenuLabel, 38 | Group as DropdownMenuGroup, 39 | Content as DropdownMenuContent, 40 | Trigger as DropdownMenuTrigger, 41 | Shortcut as DropdownMenuShortcut, 42 | RadioItem as DropdownMenuRadioItem, 43 | Separator as DropdownMenuSeparator, 44 | RadioGroup as DropdownMenuRadioGroup, 45 | SubContent as DropdownMenuSubContent, 46 | SubTrigger as DropdownMenuSubTrigger, 47 | CheckboxItem as DropdownMenuCheckboxItem, 48 | }; 49 | -------------------------------------------------------------------------------- /src/lib/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./input.svelte"; 2 | 3 | export type FormInputEvent = T & { 4 | currentTarget: EventTarget & HTMLInputElement; 5 | }; 6 | export type InputEvents = { 7 | blur: FormInputEvent; 8 | change: FormInputEvent; 9 | click: FormInputEvent; 10 | focus: FormInputEvent; 11 | focusin: FormInputEvent; 12 | focusout: FormInputEvent; 13 | keydown: FormInputEvent; 14 | keypress: FormInputEvent; 15 | keyup: FormInputEvent; 16 | mouseover: FormInputEvent; 17 | mouseenter: FormInputEvent; 18 | mouseleave: FormInputEvent; 19 | paste: FormInputEvent; 20 | input: FormInputEvent; 21 | }; 22 | 23 | export { 24 | Root, 25 | // 26 | Root as Input, 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/components/ui/input/input.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./pagination.svelte"; 2 | import Content from "./pagination-content.svelte"; 3 | import Item from "./pagination-item.svelte"; 4 | import Link from "./pagination-link.svelte"; 5 | import PrevButton from "./pagination-prev-button.svelte"; 6 | import NextButton from "./pagination-next-button.svelte"; 7 | import Ellipsis from "./pagination-ellipsis.svelte"; 8 | export { 9 | Root, 10 | Content, 11 | Item, 12 | Link, 13 | PrevButton, 14 | NextButton, 15 | Ellipsis, 16 | // 17 | Root as Pagination, 18 | Content as PaginationContent, 19 | Item as PaginationItem, 20 | Link as PaginationLink, 21 | PrevButton as PaginationPrevButton, 22 | NextButton as PaginationNextButton, 23 | Ellipsis as PaginationEllipsis, 24 | }; 25 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/pagination-content.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
    12 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/pagination-ellipsis.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | More pages 19 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/pagination-item.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
  • 12 | 13 |
  • 14 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/pagination-link.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 33 | {page.value} 34 | 35 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/pagination-next-button.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 27 | 28 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/pagination-prev-button.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 27 | 28 | -------------------------------------------------------------------------------- /src/lib/components/ui/pagination/pagination.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /src/lib/components/ui/popover/index.ts: -------------------------------------------------------------------------------- 1 | import { Popover as PopoverPrimitive } from "bits-ui"; 2 | import Content from "./popover-content.svelte"; 3 | const Root = PopoverPrimitive.Root; 4 | const Trigger = PopoverPrimitive.Trigger; 5 | const Close = PopoverPrimitive.Close; 6 | 7 | export { 8 | Root, 9 | Content, 10 | Trigger, 11 | Close, 12 | // 13 | Root as Popover, 14 | Content as PopoverContent, 15 | Trigger as PopoverTrigger, 16 | Close as PopoverClose, 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/components/ui/popover/popover-content.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/index.ts: -------------------------------------------------------------------------------- 1 | import { Select as SelectPrimitive } from "bits-ui"; 2 | 3 | import Label from "./select-label.svelte"; 4 | import Item from "./select-item.svelte"; 5 | import Content from "./select-content.svelte"; 6 | import Trigger from "./select-trigger.svelte"; 7 | import Separator from "./select-separator.svelte"; 8 | 9 | const Root = SelectPrimitive.Root; 10 | const Group = SelectPrimitive.Group; 11 | const Input = SelectPrimitive.Input; 12 | const Value = SelectPrimitive.Value; 13 | 14 | export { 15 | Root, 16 | Group, 17 | Input, 18 | Label, 19 | Item, 20 | Value, 21 | Content, 22 | Trigger, 23 | Separator, 24 | // 25 | Root as Select, 26 | Group as SelectGroup, 27 | Input as SelectInput, 28 | Label as SelectLabel, 29 | Item as SelectItem, 30 | Value as SelectValue, 31 | Content as SelectContent, 32 | Trigger as SelectTrigger, 33 | Separator as SelectSeparator, 34 | }; 35 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-content.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 36 |
    37 | 38 |
    39 |
    40 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-item.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {label ? label : value} 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-label.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-separator.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/lib/components/ui/select/select-trigger.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | span]:line-clamp-1", 16 | className 17 | )} 18 | {...$$restProps} 19 | let:builder 20 | on:click 21 | on:keydown 22 | > 23 | 24 |
    25 | 26 |
    27 |
    28 | -------------------------------------------------------------------------------- /src/lib/components/ui/separator/index.ts: -------------------------------------------------------------------------------- 1 | import Root from "./separator.svelte"; 2 | 3 | export { 4 | Root, 5 | // 6 | Root as Separator, 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/components/ui/separator/separator.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/index.ts: -------------------------------------------------------------------------------- 1 | import { Dialog as SheetPrimitive } from "bits-ui"; 2 | import { tv, type VariantProps } from "tailwind-variants"; 3 | 4 | import Portal from "./sheet-portal.svelte"; 5 | import Overlay from "./sheet-overlay.svelte"; 6 | import Content from "./sheet-content.svelte"; 7 | import Header from "./sheet-header.svelte"; 8 | import Footer from "./sheet-footer.svelte"; 9 | import Title from "./sheet-title.svelte"; 10 | import Description from "./sheet-description.svelte"; 11 | 12 | const Root = SheetPrimitive.Root; 13 | const Close = SheetPrimitive.Close; 14 | const Trigger = SheetPrimitive.Trigger; 15 | 16 | export { 17 | Root, 18 | Close, 19 | Trigger, 20 | Portal, 21 | Overlay, 22 | Content, 23 | Header, 24 | Footer, 25 | Title, 26 | Description, 27 | // 28 | Root as Sheet, 29 | Close as SheetClose, 30 | Trigger as SheetTrigger, 31 | Portal as SheetPortal, 32 | Overlay as SheetOverlay, 33 | Content as SheetContent, 34 | Header as SheetHeader, 35 | Footer as SheetFooter, 36 | Title as SheetTitle, 37 | Description as SheetDescription, 38 | }; 39 | 40 | export const sheetVariants = tv({ 41 | base: "fixed z-50 gap-4 bg-background p-6 shadow-lg", 42 | variants: { 43 | side: { 44 | top: "inset-x-0 top-0 border-b", 45 | bottom: "inset-x-0 bottom-0 border-t", 46 | left: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", 47 | right: "inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", 48 | }, 49 | }, 50 | defaultVariants: { 51 | side: "right", 52 | }, 53 | }); 54 | 55 | export const sheetTransitions = { 56 | top: { 57 | in: { 58 | y: "-100%", 59 | duration: 500, 60 | opacity: 1, 61 | }, 62 | out: { 63 | y: "-100%", 64 | duration: 300, 65 | opacity: 1, 66 | }, 67 | }, 68 | bottom: { 69 | in: { 70 | y: "100%", 71 | duration: 500, 72 | opacity: 1, 73 | }, 74 | out: { 75 | y: "100%", 76 | duration: 300, 77 | opacity: 1, 78 | }, 79 | }, 80 | left: { 81 | in: { 82 | x: "-100%", 83 | duration: 500, 84 | opacity: 1, 85 | }, 86 | out: { 87 | x: "-100%", 88 | duration: 300, 89 | opacity: 1, 90 | }, 91 | }, 92 | right: { 93 | in: { 94 | x: "100%", 95 | duration: 500, 96 | opacity: 1, 97 | }, 98 | out: { 99 | x: "100%", 100 | duration: 300, 101 | opacity: 1, 102 | }, 103 | }, 104 | }; 105 | 106 | export type Side = VariantProps["side"]; 107 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/sheet-content.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | 31 | 39 | 40 | 43 | 44 | Close 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/sheet-description.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/sheet-footer.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
    15 | 16 |
    17 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/sheet-header.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |
    12 | 13 |
    14 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/sheet-overlay.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/sheet-portal.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/lib/components/ui/sheet/sheet-title.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/components/ui/sonner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from "./sonner.svelte"; 2 | -------------------------------------------------------------------------------- /src/lib/components/ui/sonner/sonner.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/index.ts: -------------------------------------------------------------------------------- 1 | import { Tabs as TabsPrimitive } from "bits-ui"; 2 | import Content from "./tabs-content.svelte"; 3 | import List from "./tabs-list.svelte"; 4 | import Trigger from "./tabs-trigger.svelte"; 5 | 6 | const Root = TabsPrimitive.Root; 7 | 8 | export { 9 | Root, 10 | Content, 11 | List, 12 | Trigger, 13 | // 14 | Root as Tabs, 15 | Content as TabsContent, 16 | List as TabsList, 17 | Trigger as TabsTrigger, 18 | }; 19 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/tabs-content.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/tabs-list.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/lib/components/ui/tabs/tabs-trigger.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // Reexport your entry components here 2 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | import { cubicOut } from "svelte/easing"; 4 | import type { TransitionConfig } from "svelte/transition"; 5 | 6 | export function cn(...inputs: ClassValue[]) { 7 | return twMerge(clsx(inputs)); 8 | } 9 | 10 | type FlyAndScaleParams = { 11 | y?: number; 12 | x?: number; 13 | start?: number; 14 | duration?: number; 15 | }; 16 | 17 | export const flyAndScale = ( 18 | node: Element, 19 | params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } 20 | ): TransitionConfig => { 21 | const style = getComputedStyle(node); 22 | const transform = style.transform === "none" ? "" : style.transform; 23 | 24 | const scaleConversion = ( 25 | valueA: number, 26 | scaleA: [number, number], 27 | scaleB: [number, number] 28 | ) => { 29 | const [minA, maxA] = scaleA; 30 | const [minB, maxB] = scaleB; 31 | 32 | const percentage = (valueA - minA) / (maxA - minA); 33 | const valueB = percentage * (maxB - minB) + minB; 34 | 35 | return valueB; 36 | }; 37 | 38 | const styleToString = ( 39 | style: Record 40 | ): string => { 41 | return Object.keys(style).reduce((str, key) => { 42 | if (style[key] === undefined) return str; 43 | return str + `${key}:${style[key]};`; 44 | }, ""); 45 | }; 46 | 47 | return { 48 | duration: params.duration ?? 200, 49 | delay: 0, 50 | css: (t) => { 51 | const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); 52 | const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); 53 | const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); 54 | 55 | return styleToString({ 56 | transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, 57 | opacity: t 58 | }); 59 | }, 60 | easing: cubicOut 61 | }; 62 | }; -------------------------------------------------------------------------------- /src/routes/(examples)/+layout.svelte: -------------------------------------------------------------------------------- 1 |

    Code from example

    2 |

    3 | The code from the example as well as the annotations are available at GITHUB 7 |

    8 | 9 | 10 | -------------------------------------------------------------------------------- /src/routes/(examples)/basic/+page.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/routes/(examples)/client-mode/+page.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | 51 |
    62 | 63 |
    64 | 71 | 72 | 79 | 86 | 93 | 100 | 107 |
    108 |
    109 |

    EXPERIMENTAL PREVIEW

    110 |
    Not tested enough, I would not use it in production
    111 |

    Pagination does not work with virtualization

    112 |
    -------------------------------------------------------------------------------- /src/routes/(examples)/column-filtering/+page.svelte: -------------------------------------------------------------------------------- 1 | 82 | 83 |
    91 | 92 |

    Note

    93 |

    94 | In response to a question, I add an example of how to build column filtering. The component as 95 | such does not have a built-in way to add column filtering with a single value like 96 | "enableColumnFilter: number". You need to add a custom header cell with input and handle the 97 | change of value. You could make a store and bind values to it and then refetch the data by 98 | detecting the change. 99 |

    100 | -------------------------------------------------------------------------------- /src/routes/(examples)/column-filtering/_components/ExpandableRow.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 |
    'bg-foreground/5 max-h-[200px] overflow-auto' 34 | }} 35 | /> 36 | -------------------------------------------------------------------------------- /src/routes/(examples)/column-filtering/_components/RatingCell.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 4.6 && 'text-green-500')}>{props.value} -------------------------------------------------------------------------------- /src/routes/(examples)/column-filtering/_components/TitleHeaderCell.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
    10 | {props.column.header} 11 | 12 | 13 |
    14 | -------------------------------------------------------------------------------- /src/routes/(examples)/column-hiding/+page.svelte: -------------------------------------------------------------------------------- 1 | 49 | 50 | 51 |
    52 | -------------------------------------------------------------------------------- /src/routes/(examples)/column-ordering/+page.svelte: -------------------------------------------------------------------------------- 1 | 43 | 44 | 45 |
    46 | -------------------------------------------------------------------------------- /src/routes/(examples)/column-resizing/+page.svelte: -------------------------------------------------------------------------------- 1 | 49 | 50 | 51 | 52 |
    60 | -------------------------------------------------------------------------------- /src/routes/(examples)/conditional-styling/+page.svelte: -------------------------------------------------------------------------------- 1 | 44 | 45 |
    (row.title.startsWith('S') ? 'bg-red-400 hover:bg-red-400/80' : ''), 50 | cell: ({row, column}) => { 51 | return column.accessor == 'brand' ? 'bg-blue-400 hover:bg-blue-400/80' : ''; 52 | } 53 | }} 54 | /> 55 | -------------------------------------------------------------------------------- /src/routes/(examples)/content-align/+page.svelte: -------------------------------------------------------------------------------- 1 | 64 | 65 |
    66 | -------------------------------------------------------------------------------- /src/routes/(examples)/custom-cells/+page.svelte: -------------------------------------------------------------------------------- 1 | 52 | 53 |
    54 | -------------------------------------------------------------------------------- /src/routes/(examples)/custom-cells/BrandCell.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | {props.value} 8 | -------------------------------------------------------------------------------- /src/routes/(examples)/custom-cells/DescriptionCell.svelte: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Custom cell -------------------------------------------------------------------------------- /src/routes/(examples)/custom-cells/IdCellHeader.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Custom header cell -------------------------------------------------------------------------------- /src/routes/(examples)/custom-cells/RatingCell.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 4.6 && 'text-green-500')}>{props.value} -------------------------------------------------------------------------------- /src/routes/(examples)/pagination/+page.svelte: -------------------------------------------------------------------------------- 1 | 53 | 54 |
    62 | 63 | 64 |
    65 |

    Use these values to apply pagination to the server

    66 |

    perPage: {perPage}

    67 |

    page: {page}

    68 |

    offset: {page * perPage - perPage}

    69 |
    70 | 71 | 72 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-expanding/+page.svelte: -------------------------------------------------------------------------------- 1 | 67 | 68 |
    69 |
    70 | 71 |
    72 |
    73 | 74 |

    Expanded rows:

    75 |
    76 | 	 {JSON.stringify($expandedRows, '', 2)}
    77 | 
    78 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-expanding/ExpandableRow.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
     8 |     {JSON.stringify(props,"",2)}
     9 | 
    10 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-inline-editing-save-on-click/+page.svelte: -------------------------------------------------------------------------------- 1 | 150 | 151 | 152 | 153 |

    Inline editing rows:

    154 |
    155 | 	 {JSON.stringify(
    156 | 		$rows.map((e) => {
    157 | 			return { brand: e.brand, price: e.price };
    158 | 		}),
    159 | 		'',
    160 | 		2
    161 | 	)}
    162 | 
    163 | 164 |

    Note

    165 |

    166 | It is more common practice to save a row when clicking the save button than to edit cells directly 167 | as in the previous example. The nature of the component is very flexible, in this example I will 168 | show how I usually do it. You can add e.g. cancel button etc. it is up to you. Check out the code 169 | on github for more tips. 170 |

    171 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-inline-editing-save-on-click/_components/InlineEditing.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
    22 | {#if $inlineEditing.includes(rowId)} 23 | 38 | {:else} 39 | 48 | {/if} 49 |
    50 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-inline-editing-save-on-click/_components/SelectBrand.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 33 | 34 | { 37 | let newRows = $rows.map((row) => { 38 | if (row.id === props.row.id) { 39 | return { ...row, brand: v?.value }; 40 | } 41 | return row; 42 | }); 43 | $rows = newRows; 44 | }} 45 | > 46 | 47 | 48 | 49 | 50 | {#each options as option} 51 | {option.label} 52 | {/each} 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-inline-editing-save-on-click/_components/ValueCellEdit.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | { 13 | props.column.extra.onChange({ 14 | value: +e.target.value, 15 | id: props.row.id, 16 | accessor: props.column.accessor 17 | }); 18 | }} 19 | /> 20 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-inline-editing/+page.svelte: -------------------------------------------------------------------------------- 1 | 70 | 71 |
    72 | 73 |

    Inline editing rows:

    74 |
    75 | 	 {JSON.stringify($inlineEditing, '', 2)}
    76 | 
    77 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-modal-editing/+page.svelte: -------------------------------------------------------------------------------- 1 | 55 | 56 | 57 |
    58 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-modal-editing/EditModal.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Edit profile 23 | 24 | Make changes to your profile here. Click save when you're done. 25 | 26 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/routes/(examples)/row-selection/+page.svelte: -------------------------------------------------------------------------------- 1 | 75 | 76 |
    77 | 78 |

    Selected rows:

    79 |
    80 | 	 {JSON.stringify($selectedRows, '', 2)}
    81 | 
    82 | -------------------------------------------------------------------------------- /src/routes/(examples)/sorting/+page.svelte: -------------------------------------------------------------------------------- 1 | 67 | 68 |
    69 |

    Sorting is not working in the example because dummyjson.com does not support it

    70 | 71 | {$sorting.accessor} 72 | {$sorting.direction} 73 | 74 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 36 | 37 |
    38 |
    41 |
    42 | 43 | {#if $mode == 'dark'} 44 | 45 | {:else} 46 | 47 | 48 | {/if} 49 | 50 |

    51 | TZEZAR's 52 |

    53 |
    54 | 55 |
    56 |
    57 | 58 | 59 | 60 | 61 | Menu 62 | 63 | 64 |
    65 | 66 |
    67 |
    68 | 69 |
    70 | 80 |
    81 |
    82 |
    83 |
    84 | 89 |
    90 |
    91 | 92 |
    93 |
    94 | 95 |
    96 | DISCORD | 97 | GITHUB 98 |
    99 |

    100 | Sebastian "Tzezar" Drozd {new Date().getFullYear()} 101 |

    102 |
    103 |
    104 |
    105 |
    106 | 107 |
    108 | 109 | 123 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 156 | 157 |

    TZEZAR's Table

    158 |

    161 | Table component built in svelte based on shadcn-svelte components. 162 |

    163 | 164 |
    165 |
    180 |
    181 | {#if enableActions} 182 |
    183 | 184 |
    185 | 186 | 187 | 188 | 189 | 190 | 191 | Create product form 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 |
    200 | 201 |
    202 | {/if} 203 |
    204 | 205 |
    206 |
    207 | {#if $enableExpandableRow} 208 |
    custom expanded row
    209 | {/if} 210 |
    211 |
    212 |
    213 | 214 |

    215 | Sorting is not working in the example because dummyjson.com does not support it 216 |

    217 | 218 |
    219 | 226 | 227 | 235 | 242 | 249 | 256 | 263 | 270 | 277 | 285 |
    286 | 287 |

    What is this?

    288 |

    289 | I entrust to you a modest component, crafted atop the robust shadcn-svelte library, utilizing 290 | built-in svelte functions such as stores, among others. Every aspect of the table - its 291 | aesthetics, functionality, and beyond - can be customized to your heart's content. This serves as 292 | an excellent foundation to tailor a solution precisely to your requirements. If a component or 293 | basic solution doesn't quite align with your vision, fear not - mold it to your liking! 294 |

    295 | 296 |

    For whom?

    297 |

    298 | The component efficiently manages server-side data (with client-side capability coming soon). It's 299 | designed for seamless extension with extra features, and styling integration should pose no major 300 | challenges. If you're already leveraging shadcn-svelte and tailwind, incorporating this component 301 | into your workflow is a natural fit. 302 |

    303 |

    Genesis

    304 |

    305 | This project started as a way for me to make warehouse operations smoother with dynamic table 306 | displays. Despite trying options like ag-grid and headless solutions, none quite fit the bill. 307 | 'Full' solutions were tough to customize, and headless ones like tanstack-table had tricky 308 | documentation. Dealing with remote data was also a hassle. So, TZEZAR's TABLE (pronounced like 309 | 'Caesar') was born to bridge the gap, offering flexibility. Feel free to tweak this component to 310 | suit your needs! 311 |

    312 | 313 |

    Currently Supported Functions

    314 | 328 |

    Planned Functions

    329 | 345 |

    Not Planned for Immediate Addition

    346 | 350 |

    351 | If you spot any missing features, don't worry! Adding them should be a breeze. Feel free to 352 | suggest ideas or improvements, and let's work together to make our component even better. Your 353 | input is always welcome, and together, we can create something truly outstanding! 354 |

    355 | 356 |

    357 | If you find the project interesting feel free to give a star on github. Thanks! ❤️ 360 |

    361 | -------------------------------------------------------------------------------- /src/routes/_components/RatingCell.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 4.6 && 'text-green-500')}>{props.value} -------------------------------------------------------------------------------- /src/routes/_components/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
    40 | 53 |
    54 | -------------------------------------------------------------------------------- /src/routes/_components/TitleHeaderCell.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
    10 | {props.column.header} 11 | 12 | 13 |
    14 | -------------------------------------------------------------------------------- /src/routes/_components/custom-row-expand/CustomRowExpandToggle.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
    17 | {#if $expandedRows.includes(rowId)} 18 | 21 | {:else} 22 | 25 | {/if} 26 |
    27 | -------------------------------------------------------------------------------- /src/routes/installation/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |

    Installation

    7 | 8 |

    shadcn-svelte

    9 |

    10 | I assume you already have shadcn-svelte installed, if not here is a tutorial: https://www.shadcn-svelte.com/docs/installation 15 |

    16 | 17 |

    Component

    18 |

    19 | Copy content of 20 | https://github.com/530RGE/table/tree/main/src/lib/components/table 25 | into your project. 26 |

    27 |

    28 | You also need cn helper function from 29 | https://github.com/530RGE/table/blob/main/src/lib/utils.ts. 32 |

    33 |

    34 | flyAndScale util is used for draggable column reordering (optional 35 | if you make custom component) 36 |

    37 | 38 |

    39 | Table out of the box use those shadcn-svelte components 40 |

    41 | 42 |
    43 |         npx shadcn-svelte@latest add button
    44 |         npx shadcn-svelte@latest add select
    45 |         npx shadcn-svelte@latest add checkbox
    46 |         npx shadcn-svelte@latest add pagination
    47 |         npx shadcn-svelte@latest add dropdown-menu
    48 |         npx shadcn-svelte@latest add popover
    49 |         npx shadcn-svelte@latest add sheet
    50 |         npx shadcn-svelte@latest add input
    51 |     
    52 |
    53 | 54 |

    55 | Draggable column reordering uses svelte-dnd-action 56 |

    57 | 58 |
    59 |         npm i svelte-dnd-action
    60 |         npm i svelte-legos
    61 |     
    62 |
    63 | 64 |

    Optional

    65 | 66 |
    67 |         npm install mode-watcher
    68 |         npm i @tanstack/svelte-query
    69 |         npm i @tanstack/svelte-query-devtools
    70 |     
    71 |
    72 | 73 |

    74 | mode-watches is easy way to implement dark mode, and tanstack-query is good for data fetching 75 |

    76 | 77 |

    04/04/2024 update

    78 |

    79 | I rebuilt the shadcn table to use divs (easier styling, ability to change width, no weird 80 | behaviour) and used them in the component. It made component cleaner and easier to read. You don't 81 | have to overwrite it, you can have both the 82 | official shadcn version and the one for the component built with divs. 83 | 84 | 85 | 86 | 87 |

    88 | 89 |

    Code from examples

    90 |

    91 | The code from the example as well as the annotations are available at GITHUB 95 |

    96 | -------------------------------------------------------------------------------- /src/routes/planned/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
    6 |

    What's planned?

    7 | 8 |
    9 |

    Virtualisation

    10 |

    Added 21/03/2024

    11 |

    12 | Virtualisation is not easy especially with something as complex as tables. In a private 13 | project I use it with the table component in several places, but I am aware of one bug and 14 | until I fix it it will not be implemented. I need to spend some time on this and it will 15 | certainly be added. 16 |

    17 |
    18 |

    Client-side functionalities

    19 |

    20 | I actually have this already implemented in a private project too, but it's not pretty code and 21 | needs to be rewritten before it sees the light of day. 22 |

    23 |

    More examples

    24 |

    25 | There are things such as filter slots or additional actions or global search that are not 26 | described. More examples will be added when I have time. Unfortunately I have a lot of other 27 | work right now. 28 |

    29 |

    Advanced filter functionality

    30 |

    31 | I don't quite know if this fits in well with the theme of the table, on the one hand it is 32 | geared towards server data, but on the other hand such a component is something else entirely. 33 | Maybe it will be as an additional component. 34 |

    35 | 36 | 37 |

    Async loading indicator

    38 |

    39 | In theory this is implemented and you can pass `loading` to a component, but the loading 40 | indicator is a simple loader, not some beauty. Definitely to be improved as I find time. 41 |

    42 | 43 |

    Column filtering

    44 |

    45 | I'm not a big fan of this solution but I know some people will need it. To be done in the 46 | future. 47 |

    48 |

    Density toggle

    49 |

    This is actually implemented in my private project, I will soon add it for sure.

    50 | 51 |
    52 |

    Fullscreen

    53 |

    Added 21/03/2024

    54 |

    55 | As above 56 |

    57 |
    58 |
    59 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzezar/table/67a908b769c7c7cf05f1adf4b102e0b69d9ce0e7/static/favicon.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: [vitePreprocess({})], 9 | 10 | kit: { 11 | alias: { 12 | "@/*": "./path/to/lib/*", 13 | }, 14 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 15 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 16 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 17 | adapter: adapter() 18 | } 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import { fontFamily } from "tailwindcss/defaultTheme"; 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | const config = { 5 | darkMode: ["class"], 6 | content: ["./src/**/*.{html,js,svelte,ts}"], 7 | safelist: ["dark"], 8 | theme: { 9 | container: { 10 | center: true, 11 | padding: "2rem", 12 | screens: { 13 | "2xl": "1400px" 14 | } 15 | }, 16 | extend: { 17 | colors: { 18 | border: "hsl(var(--border) / )", 19 | input: "hsl(var(--input) / )", 20 | ring: "hsl(var(--ring) / )", 21 | background: "hsl(var(--background) / )", 22 | foreground: "hsl(var(--foreground) / )", 23 | primary: { 24 | DEFAULT: "hsl(var(--primary) / )", 25 | foreground: "hsl(var(--primary-foreground) / )" 26 | }, 27 | secondary: { 28 | DEFAULT: "hsl(var(--secondary) / )", 29 | foreground: "hsl(var(--secondary-foreground) / )" 30 | }, 31 | destructive: { 32 | DEFAULT: "hsl(var(--destructive) / )", 33 | foreground: "hsl(var(--destructive-foreground) / )" 34 | }, 35 | muted: { 36 | DEFAULT: "hsl(var(--muted) / )", 37 | foreground: "hsl(var(--muted-foreground) / )" 38 | }, 39 | accent: { 40 | DEFAULT: "hsl(var(--accent) / )", 41 | foreground: "hsl(var(--accent-foreground) / )" 42 | }, 43 | popover: { 44 | DEFAULT: "hsl(var(--popover) / )", 45 | foreground: "hsl(var(--popover-foreground) / )" 46 | }, 47 | card: { 48 | DEFAULT: "hsl(var(--card) / )", 49 | foreground: "hsl(var(--card-foreground) / )" 50 | } 51 | }, 52 | borderRadius: { 53 | lg: "var(--radius)", 54 | md: "calc(var(--radius) - 2px)", 55 | sm: "calc(var(--radius) - 4px)" 56 | }, 57 | fontFamily: { 58 | sans: [...fontFamily.sans] 59 | }, 60 | 61 | 62 | } 63 | }, 64 | plugins: [ 65 | require('@tailwindcss/typography'), 66 | // ... 67 | ], 68 | }; 69 | 70 | export default config; 71 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "module": "NodeNext", 13 | "moduleResolution": "NodeNext" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | }); 7 | --------------------------------------------------------------------------------