@central-design-system/skills 4.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +6 -0
- package/README.md +218 -0
- package/bin/cds-skills.mjs +124 -0
- package/package.json +33 -0
- package/skills/cds-builder/GENERATION.md +44 -0
- package/skills/cds-builder/SKILL.md +308 -0
- package/skills/cds-builder/references/component-decision-tree.md +310 -0
- package/skills/cds-builder/references/components/cds-app/api.md +19 -0
- package/skills/cds-builder/references/components/cds-app/tokens.md +12 -0
- package/skills/cds-builder/references/components/cds-autocomplete/api.md +155 -0
- package/skills/cds-builder/references/components/cds-autocomplete/examples.md +447 -0
- package/skills/cds-builder/references/components/cds-autocomplete/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-autocomplete/tokens.md +19 -0
- package/skills/cds-builder/references/components/cds-avatar/api.md +35 -0
- package/skills/cds-builder/references/components/cds-avatar/examples.md +240 -0
- package/skills/cds-builder/references/components/cds-avatar/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-avatar/tokens.md +32 -0
- package/skills/cds-builder/references/components/cds-badge/api.md +44 -0
- package/skills/cds-builder/references/components/cds-badge/examples.md +332 -0
- package/skills/cds-builder/references/components/cds-badge/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-badge/tokens.md +32 -0
- package/skills/cds-builder/references/components/cds-breadcrumbs/api.md +83 -0
- package/skills/cds-builder/references/components/cds-breadcrumbs/examples.md +219 -0
- package/skills/cds-builder/references/components/cds-breadcrumbs/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-breadcrumbs/tokens.md +24 -0
- package/skills/cds-builder/references/components/cds-button/api.md +66 -0
- package/skills/cds-builder/references/components/cds-button/examples.md +324 -0
- package/skills/cds-builder/references/components/cds-button/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-button/tokens.md +190 -0
- package/skills/cds-builder/references/components/cds-button-group/api.md +27 -0
- package/skills/cds-builder/references/components/cds-button-group/examples.md +33 -0
- package/skills/cds-builder/references/components/cds-button-group/tokens.md +17 -0
- package/skills/cds-builder/references/components/cds-button-toggle/api.md +51 -0
- package/skills/cds-builder/references/components/cds-button-toggle/examples.md +278 -0
- package/skills/cds-builder/references/components/cds-button-toggle/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-button-toggle/tokens.md +13 -0
- package/skills/cds-builder/references/components/cds-card/api.md +221 -0
- package/skills/cds-builder/references/components/cds-card/examples.md +652 -0
- package/skills/cds-builder/references/components/cds-card/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-card/tokens.md +46 -0
- package/skills/cds-builder/references/components/cds-carousel/api.md +107 -0
- package/skills/cds-builder/references/components/cds-carousel/examples.md +444 -0
- package/skills/cds-builder/references/components/cds-carousel/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-carousel/tokens.md +17 -0
- package/skills/cds-builder/references/components/cds-checkbox/api.md +93 -0
- package/skills/cds-builder/references/components/cds-checkbox/examples.md +319 -0
- package/skills/cds-builder/references/components/cds-checkbox/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-checkbox/tokens.md +18 -0
- package/skills/cds-builder/references/components/cds-checkbox-button/api.md +56 -0
- package/skills/cds-builder/references/components/cds-checkbox-button/examples.md +29 -0
- package/skills/cds-builder/references/components/cds-checkbox-button/tokens.md +56 -0
- package/skills/cds-builder/references/components/cds-combobox/api.md +158 -0
- package/skills/cds-builder/references/components/cds-combobox/examples.md +264 -0
- package/skills/cds-builder/references/components/cds-combobox/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-combobox/tokens.md +19 -0
- package/skills/cds-builder/references/components/cds-counter/api.md +24 -0
- package/skills/cds-builder/references/components/cds-counter/examples.md +93 -0
- package/skills/cds-builder/references/components/cds-counter/tokens.md +11 -0
- package/skills/cds-builder/references/components/cds-date-input/api.md +151 -0
- package/skills/cds-builder/references/components/cds-date-input/examples.md +479 -0
- package/skills/cds-builder/references/components/cds-date-input/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-date-input/tokens.md +16 -0
- package/skills/cds-builder/references/components/cds-date-picker/api.md +63 -0
- package/skills/cds-builder/references/components/cds-date-picker/examples.md +394 -0
- package/skills/cds-builder/references/components/cds-date-picker/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-date-picker/tokens.md +66 -0
- package/skills/cds-builder/references/components/cds-defaults-provider/api.md +21 -0
- package/skills/cds-builder/references/components/cds-description/api.md +22 -0
- package/skills/cds-builder/references/components/cds-description/examples.md +69 -0
- package/skills/cds-builder/references/components/cds-description/tokens.md +11 -0
- package/skills/cds-builder/references/components/cds-divider/api.md +29 -0
- package/skills/cds-builder/references/components/cds-divider/examples.md +374 -0
- package/skills/cds-builder/references/components/cds-divider/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-divider/tokens.md +38 -0
- package/skills/cds-builder/references/components/cds-drawer/api.md +53 -0
- package/skills/cds-builder/references/components/cds-drawer/examples.md +432 -0
- package/skills/cds-builder/references/components/cds-drawer/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-drawer/tokens.md +27 -0
- package/skills/cds-builder/references/components/cds-empty-state/api.md +55 -0
- package/skills/cds-builder/references/components/cds-empty-state/examples.md +330 -0
- package/skills/cds-builder/references/components/cds-empty-state/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-empty-state/tokens.md +25 -0
- package/skills/cds-builder/references/components/cds-expandable-panel/api.md +157 -0
- package/skills/cds-builder/references/components/cds-expandable-panel/examples.md +569 -0
- package/skills/cds-builder/references/components/cds-expandable-panel/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-expandable-panel/tokens.md +34 -0
- package/skills/cds-builder/references/components/cds-field/api.md +62 -0
- package/skills/cds-builder/references/components/cds-field/examples.md +202 -0
- package/skills/cds-builder/references/components/cds-field/tokens.md +94 -0
- package/skills/cds-builder/references/components/cds-file-input/api.md +109 -0
- package/skills/cds-builder/references/components/cds-file-input/examples.md +368 -0
- package/skills/cds-builder/references/components/cds-file-input/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-file-input/tokens.md +15 -0
- package/skills/cds-builder/references/components/cds-file-upload/api.md +296 -0
- package/skills/cds-builder/references/components/cds-file-upload/examples.md +411 -0
- package/skills/cds-builder/references/components/cds-file-upload/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-file-upload/tokens.md +50 -0
- package/skills/cds-builder/references/components/cds-footer/api.md +35 -0
- package/skills/cds-builder/references/components/cds-footer/examples.md +219 -0
- package/skills/cds-builder/references/components/cds-footer/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-footer/tokens.md +14 -0
- package/skills/cds-builder/references/components/cds-form/api.md +48 -0
- package/skills/cds-builder/references/components/cds-form/examples.md +552 -0
- package/skills/cds-builder/references/components/cds-form/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-form/tokens.md +15 -0
- package/skills/cds-builder/references/components/cds-functional-button/api.md +34 -0
- package/skills/cds-builder/references/components/cds-functional-button/examples.md +254 -0
- package/skills/cds-builder/references/components/cds-functional-button/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-functional-button/tokens.md +35 -0
- package/skills/cds-builder/references/components/cds-grid/api.md +66 -0
- package/skills/cds-builder/references/components/cds-grid/examples.md +687 -0
- package/skills/cds-builder/references/components/cds-grid/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-header/api.md +35 -0
- package/skills/cds-builder/references/components/cds-header/examples.md +345 -0
- package/skills/cds-builder/references/components/cds-header/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-header/tokens.md +13 -0
- package/skills/cds-builder/references/components/cds-highlight/api.md +22 -0
- package/skills/cds-builder/references/components/cds-highlight/examples.md +313 -0
- package/skills/cds-builder/references/components/cds-highlight/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-highlight/tokens.md +18 -0
- package/skills/cds-builder/references/components/cds-icon/api.md +33 -0
- package/skills/cds-builder/references/components/cds-icon/examples.md +272 -0
- package/skills/cds-builder/references/components/cds-icon/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-icon/tokens.md +30 -0
- package/skills/cds-builder/references/components/cds-image/api.md +67 -0
- package/skills/cds-builder/references/components/cds-image/examples.md +437 -0
- package/skills/cds-builder/references/components/cds-image/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-image/tokens.md +14 -0
- package/skills/cds-builder/references/components/cds-input/api.md +80 -0
- package/skills/cds-builder/references/components/cds-input/examples.md +205 -0
- package/skills/cds-builder/references/components/cds-input/tokens.md +108 -0
- package/skills/cds-builder/references/components/cds-label/api.md +25 -0
- package/skills/cds-builder/references/components/cds-label/examples.md +72 -0
- package/skills/cds-builder/references/components/cds-label/tokens.md +12 -0
- package/skills/cds-builder/references/components/cds-layout/api.md +50 -0
- package/skills/cds-builder/references/components/cds-layout/examples.md +431 -0
- package/skills/cds-builder/references/components/cds-layout/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-layout/tokens.md +12 -0
- package/skills/cds-builder/references/components/cds-link/api.md +33 -0
- package/skills/cds-builder/references/components/cds-link/examples.md +195 -0
- package/skills/cds-builder/references/components/cds-link/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-link/tokens.md +16 -0
- package/skills/cds-builder/references/components/cds-list/api.md +329 -0
- package/skills/cds-builder/references/components/cds-list/examples.md +653 -0
- package/skills/cds-builder/references/components/cds-list/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-list/tokens.md +125 -0
- package/skills/cds-builder/references/components/cds-loader/api.md +18 -0
- package/skills/cds-builder/references/components/cds-loader/examples.md +59 -0
- package/skills/cds-builder/references/components/cds-loader/tokens.md +50 -0
- package/skills/cds-builder/references/components/cds-main/api.md +14 -0
- package/skills/cds-builder/references/components/cds-main/examples.md +60 -0
- package/skills/cds-builder/references/components/cds-menu/api.md +84 -0
- package/skills/cds-builder/references/components/cds-menu/examples.md +595 -0
- package/skills/cds-builder/references/components/cds-menu/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-menu/tokens.md +19 -0
- package/skills/cds-builder/references/components/cds-messages/api.md +29 -0
- package/skills/cds-builder/references/components/cds-messages/examples.md +119 -0
- package/skills/cds-builder/references/components/cds-messages/tokens.md +15 -0
- package/skills/cds-builder/references/components/cds-modal/api.md +86 -0
- package/skills/cds-builder/references/components/cds-modal/examples.md +516 -0
- package/skills/cds-builder/references/components/cds-modal/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-modal/tokens.md +22 -0
- package/skills/cds-builder/references/components/cds-notification/api.md +46 -0
- package/skills/cds-builder/references/components/cds-notification/examples.md +314 -0
- package/skills/cds-builder/references/components/cds-notification/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-notification/tokens.md +23 -0
- package/skills/cds-builder/references/components/cds-notification-alert/api.md +93 -0
- package/skills/cds-builder/references/components/cds-notification-alert/examples.md +202 -0
- package/skills/cds-builder/references/components/cds-notification-alert/tokens.md +15 -0
- package/skills/cds-builder/references/components/cds-otp-input/api.md +106 -0
- package/skills/cds-builder/references/components/cds-otp-input/examples.md +329 -0
- package/skills/cds-builder/references/components/cds-otp-input/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-otp-input/tokens.md +41 -0
- package/skills/cds-builder/references/components/cds-overlay/api.md +85 -0
- package/skills/cds-builder/references/components/cds-overlay/examples.md +409 -0
- package/skills/cds-builder/references/components/cds-overlay/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-overlay/tokens.md +12 -0
- package/skills/cds-builder/references/components/cds-page/api.md +21 -0
- package/skills/cds-builder/references/components/cds-page/examples.md +123 -0
- package/skills/cds-builder/references/components/cds-pagination/api.md +67 -0
- package/skills/cds-builder/references/components/cds-pagination/examples.md +320 -0
- package/skills/cds-builder/references/components/cds-pagination/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-pagination/tokens.md +20 -0
- package/skills/cds-builder/references/components/cds-progress-bar/api.md +53 -0
- package/skills/cds-builder/references/components/cds-progress-bar/examples.md +393 -0
- package/skills/cds-builder/references/components/cds-progress-bar/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-progress-bar/tokens.md +33 -0
- package/skills/cds-builder/references/components/cds-progress-circle/api.md +30 -0
- package/skills/cds-builder/references/components/cds-progress-circle/examples.md +326 -0
- package/skills/cds-builder/references/components/cds-progress-circle/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-progress-circle/tokens.md +31 -0
- package/skills/cds-builder/references/components/cds-radio/api.md +47 -0
- package/skills/cds-builder/references/components/cds-radio/tokens.md +36 -0
- package/skills/cds-builder/references/components/cds-radio-group/api.md +87 -0
- package/skills/cds-builder/references/components/cds-radio-group/examples.md +320 -0
- package/skills/cds-builder/references/components/cds-radio-group/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-radio-group/tokens.md +14 -0
- package/skills/cds-builder/references/components/cds-responsive/api.md +29 -0
- package/skills/cds-builder/references/components/cds-responsive/examples.md +271 -0
- package/skills/cds-builder/references/components/cds-responsive/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-responsive/tokens.md +13 -0
- package/skills/cds-builder/references/components/cds-select/api.md +155 -0
- package/skills/cds-builder/references/components/cds-select/examples.md +794 -0
- package/skills/cds-builder/references/components/cds-select/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-select/tokens.md +17 -0
- package/skills/cds-builder/references/components/cds-selection-control/api.md +60 -0
- package/skills/cds-builder/references/components/cds-selection-control/examples.md +468 -0
- package/skills/cds-builder/references/components/cds-selection-control-group/api.md +45 -0
- package/skills/cds-builder/references/components/cds-selection-control-group/examples.md +349 -0
- package/skills/cds-builder/references/components/cds-skeleton-loader/api.md +30 -0
- package/skills/cds-builder/references/components/cds-skeleton-loader/examples.md +297 -0
- package/skills/cds-builder/references/components/cds-skeleton-loader/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-skeleton-loader/tokens.md +62 -0
- package/skills/cds-builder/references/components/cds-slide-group/api.md +76 -0
- package/skills/cds-builder/references/components/cds-slide-group/examples.md +304 -0
- package/skills/cds-builder/references/components/cds-slide-group/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-slide-group/tokens.md +11 -0
- package/skills/cds-builder/references/components/cds-slide-view/api.md +88 -0
- package/skills/cds-builder/references/components/cds-slide-view/examples.md +311 -0
- package/skills/cds-builder/references/components/cds-slide-view/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-slide-view/tokens.md +14 -0
- package/skills/cds-builder/references/components/cds-stages/api.md +31 -0
- package/skills/cds-builder/references/components/cds-stages/examples.md +230 -0
- package/skills/cds-builder/references/components/cds-stages/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-stages/tokens.md +27 -0
- package/skills/cds-builder/references/components/cds-status/api.md +24 -0
- package/skills/cds-builder/references/components/cds-status/examples.md +170 -0
- package/skills/cds-builder/references/components/cds-status/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-status/tokens.md +17 -0
- package/skills/cds-builder/references/components/cds-steps/api.md +176 -0
- package/skills/cds-builder/references/components/cds-steps/examples.md +320 -0
- package/skills/cds-builder/references/components/cds-steps/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-steps/tokens.md +52 -0
- package/skills/cds-builder/references/components/cds-surface/api.md +30 -0
- package/skills/cds-builder/references/components/cds-surface/examples.md +106 -0
- package/skills/cds-builder/references/components/cds-table/api.md +120 -0
- package/skills/cds-builder/references/components/cds-table/examples.md +3049 -0
- package/skills/cds-builder/references/components/cds-table/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-table/tokens.md +92 -0
- package/skills/cds-builder/references/components/cds-tabs/api.md +179 -0
- package/skills/cds-builder/references/components/cds-tabs/examples.md +610 -0
- package/skills/cds-builder/references/components/cds-tabs/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-tabs/tokens.md +40 -0
- package/skills/cds-builder/references/components/cds-tag/api.md +64 -0
- package/skills/cds-builder/references/components/cds-tag/examples.md +278 -0
- package/skills/cds-builder/references/components/cds-tag/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-tag/tokens.md +67 -0
- package/skills/cds-builder/references/components/cds-tag-group/api.md +58 -0
- package/skills/cds-builder/references/components/cds-tag-group/examples.md +346 -0
- package/skills/cds-builder/references/components/cds-tag-group/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-tag-group/tokens.md +18 -0
- package/skills/cds-builder/references/components/cds-text-area/api.md +113 -0
- package/skills/cds-builder/references/components/cds-text-area/examples.md +327 -0
- package/skills/cds-builder/references/components/cds-text-area/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-text-area/tokens.md +16 -0
- package/skills/cds-builder/references/components/cds-text-input/api.md +115 -0
- package/skills/cds-builder/references/components/cds-text-input/examples.md +546 -0
- package/skills/cds-builder/references/components/cds-text-input/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-text-input/tokens.md +17 -0
- package/skills/cds-builder/references/components/cds-time-picker/api.md +44 -0
- package/skills/cds-builder/references/components/cds-time-picker/examples.md +407 -0
- package/skills/cds-builder/references/components/cds-time-picker/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-time-picker/tokens.md +23 -0
- package/skills/cds-builder/references/components/cds-toast/api.md +97 -0
- package/skills/cds-builder/references/components/cds-toast/examples.md +296 -0
- package/skills/cds-builder/references/components/cds-toast/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-toast/tokens.md +26 -0
- package/skills/cds-builder/references/components/cds-toast-queue/api.md +86 -0
- package/skills/cds-builder/references/components/cds-toast-queue/examples.md +227 -0
- package/skills/cds-builder/references/components/cds-toggle/api.md +97 -0
- package/skills/cds-builder/references/components/cds-toggle/examples.md +335 -0
- package/skills/cds-builder/references/components/cds-toggle/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-toggle/tokens.md +91 -0
- package/skills/cds-builder/references/components/cds-toolbar/api.md +44 -0
- package/skills/cds-builder/references/components/cds-toolbar/examples.md +186 -0
- package/skills/cds-builder/references/components/cds-toolbar/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-toolbar/tokens.md +13 -0
- package/skills/cds-builder/references/components/cds-tooltip/api.md +83 -0
- package/skills/cds-builder/references/components/cds-tooltip/examples.md +321 -0
- package/skills/cds-builder/references/components/cds-tooltip/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-tooltip/tokens.md +25 -0
- package/skills/cds-builder/references/components/cds-tree/api.md +169 -0
- package/skills/cds-builder/references/components/cds-tree/examples.md +785 -0
- package/skills/cds-builder/references/components/cds-tree/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-tree/tokens.md +39 -0
- package/skills/cds-builder/references/components/cds-uploader/api.md +105 -0
- package/skills/cds-builder/references/components/cds-uploader/examples.md +553 -0
- package/skills/cds-builder/references/components/cds-uploader/guidelines.md +10 -0
- package/skills/cds-builder/references/components/cds-uploader/tokens.md +26 -0
- package/skills/cds-builder/references/components/cds-virtual-scroll/api.md +60 -0
- package/skills/cds-builder/references/components/cds-virtual-scroll/examples.md +272 -0
- package/skills/cds-builder/references/components/cds-virtual-scroll/guidelines.md +9 -0
- package/skills/cds-builder/references/components/cds-virtual-scroll/tokens.md +13 -0
- package/skills/cds-builder/references/guides/conventions.md +48 -0
- package/skills/cds-builder/references/guides/date-utils.md +301 -0
- package/skills/cds-builder/references/guides/display-and-platform.md +263 -0
- package/skills/cds-builder/references/guides/forms.md +77 -0
- package/skills/cds-builder/references/guides/global-configuration.md +354 -0
- package/skills/cds-builder/references/guides/icons.md +172 -0
- package/skills/cds-builder/references/guides/internationalization.md +470 -0
- package/skills/cds-builder/references/guides/migration-cds3-to-cds4.md +49 -0
- package/skills/cds-builder/references/guides/sass-variables.md +238 -0
- package/skills/cds-builder/references/guides/setup.md +81 -0
- package/skills/cds-builder/references/guides/tailwindcss.md +93 -0
- package/skills/cds-builder/references/guides/theme.md +343 -0
- package/skills/cds-builder/references/guides/treeshaking.md +237 -0
- package/skills/cds-builder/references/patterns/dialogs.md +89 -0
- package/skills/cds-builder/references/patterns/forms.md +101 -0
- package/skills/cds-builder/references/patterns/page-layout.md +78 -0
- package/skills/cds-builder/references/patterns/tables.md +87 -0
- package/skills/cds-builder/references/tokens/colors.md +507 -0
- package/skills/cds-builder/references/tokens/themes.md +981 -0
- package/skills/cds-builder/scripts/check-contracts.mjs +264 -0
- package/skills/cds-migrate/GENERATION.md +44 -0
- package/skills/cds-migrate/SKILL.md +160 -0
- package/skills/cds-migrate/references/migration-full.md +2225 -0
- package/skills/cds-refactor/GENERATION.md +44 -0
- package/skills/cds-refactor/SKILL.md +187 -0
- package/skills/cds-refactor/references/html-to-cds.md +80 -0
- package/skills/cds-refactor/references/review-checklist.md +73 -0
- package/skills/cds-refactor/references/tailwind-to-cds.md +65 -0
|
@@ -0,0 +1,3049 @@
|
|
|
1
|
+
# CdsTable — usage examples
|
|
2
|
+
|
|
3
|
+
> Extracted from Storybook stories (`docs/*.stories.ts`) by `bun run generate:skill`. Do not edit manually.
|
|
4
|
+
> The full component contract is in [api.md](api.md). `v-bind="args"` in templates refers to the props from the **Args** block of the corresponding example.
|
|
5
|
+
|
|
6
|
+
A table shows large sets of records in columns and takes the data work off your hands: sorting, filters, pagination, row selection and expansion, a pinned header and pinned columns. Columns are described by the `columns` prop, the records by the `data` prop.
|
|
7
|
+
|
|
8
|
+
## Перетаскивание
|
|
9
|
+
|
|
10
|
+
### Dragging rows
|
|
11
|
+
|
|
12
|
+
The `row-draggable` prop shows a grip icon to the left of the row. The move is reported by the `row-reorder` event — change the order in `data` yourself. A row can only be dropped between neighboring rows.
|
|
13
|
+
|
|
14
|
+
**Example (SFC):**
|
|
15
|
+
|
|
16
|
+
```vue
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import type { NTable } from '@central-design-system/components';
|
|
19
|
+
import { CdsTable } from '@central-design-system/components';
|
|
20
|
+
import { ref } from 'vue';
|
|
21
|
+
|
|
22
|
+
type TRow = { key: string; name: string; category: string; price: string };
|
|
23
|
+
|
|
24
|
+
const columns: NTable.NColumn.TColumns<TRow> = [
|
|
25
|
+
{ title: 'Наименование', dataIndex: 'name', key: 'name' },
|
|
26
|
+
{ title: 'Категория', dataIndex: 'category', key: 'category' },
|
|
27
|
+
{ title: 'Цена', dataIndex: 'price', key: 'price', align: 'right' }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const data = ref<TRow[]>([
|
|
31
|
+
{ key: '1', name: 'iPhone 17 Pro Max', category: 'Смартфоны', price: '$1700' },
|
|
32
|
+
{ key: '2', name: 'MacBook Air M5', category: 'Ноутбуки', price: '$2000' },
|
|
33
|
+
{ key: '3', name: 'Apple Vision Pro', category: 'Гарнитуры', price: '$3500' },
|
|
34
|
+
{ key: '4', name: 'iPad Air', category: 'Планшеты', price: '$800' },
|
|
35
|
+
{ key: '5', name: 'AirPods Pro', category: 'Наушники', price: '$250' }
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const onRowReorder = (info: NTable.NDraggable.IRowReorderInfo<TRow>) => {
|
|
39
|
+
const next = [...data.value];
|
|
40
|
+
const dragIndex = next.findIndex((item) => item.key === info.dragKey);
|
|
41
|
+
if (dragIndex === -1) return;
|
|
42
|
+
const [moved] = next.splice(dragIndex, 1);
|
|
43
|
+
const dropIndex = next.findIndex((item) => item.key === info.dropKey);
|
|
44
|
+
next.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
|
|
45
|
+
data.value = next;
|
|
46
|
+
};
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<CdsTable :columns="columns" :data="data" row-draggable :pagination="false" @row-reorder="onRowReorder" />
|
|
51
|
+
</template>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Dragging nested rows
|
|
55
|
+
|
|
56
|
+
In a tree, a row is only reordered among the siblings of its own parent: it cannot be dropped onto another level — the cursor shows "not allowed" and no event arrives.
|
|
57
|
+
|
|
58
|
+
**Example (SFC):**
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<script setup lang="ts">
|
|
62
|
+
import type { NTable } from '@central-design-system/components';
|
|
63
|
+
import { CdsTable } from '@central-design-system/components';
|
|
64
|
+
import { ref } from 'vue';
|
|
65
|
+
|
|
66
|
+
type TRow = { key: number; name: string; price: number; children?: TRow[] };
|
|
67
|
+
|
|
68
|
+
const columns: NTable.NColumn.TColumns<TRow> = [
|
|
69
|
+
{ title: 'Название', dataIndex: 'name', key: 'name' },
|
|
70
|
+
{ title: 'Цена, $', dataIndex: 'price', key: 'price', align: 'right', width: '20%' }
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const data = ref<TRow[]>([
|
|
74
|
+
{
|
|
75
|
+
key: 1,
|
|
76
|
+
name: 'Apple iPhone',
|
|
77
|
+
price: 3250,
|
|
78
|
+
children: [
|
|
79
|
+
{ key: 11, name: 'iPhone 14 Pro', price: 2000 },
|
|
80
|
+
{ key: 12, name: 'Power Bank', price: 50 },
|
|
81
|
+
{ key: 13, name: 'Headphones', price: 1200 }
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 2,
|
|
86
|
+
name: 'Xiaomi Mi',
|
|
87
|
+
price: 200,
|
|
88
|
+
children: [
|
|
89
|
+
{ key: 21, name: 'Mi Band', price: 40 },
|
|
90
|
+
{ key: 22, name: 'Mi Buds', price: 60 }
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
// Находит массив-родитель, содержащий строку с заданным ключом.
|
|
96
|
+
const findParentList = (list: TRow[], key: NTable.TKey): TRow[] | null => {
|
|
97
|
+
for (const item of list) {
|
|
98
|
+
if (item.key === key) return list;
|
|
99
|
+
if (item.children) {
|
|
100
|
+
const found = findParentList(item.children, key);
|
|
101
|
+
if (found) return found;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const onRowReorder = (info: NTable.NDraggable.IRowReorderInfo<TRow>) => {
|
|
108
|
+
const next = [...data.value];
|
|
109
|
+
// drag и drop гарантированно в одном родителе (reorder только между соседями).
|
|
110
|
+
const parentList = findParentList(next, info.dragKey);
|
|
111
|
+
if (!parentList) return;
|
|
112
|
+
const dragIndex = parentList.findIndex((item) => item.key === info.dragKey);
|
|
113
|
+
const [moved] = parentList.splice(dragIndex, 1);
|
|
114
|
+
const dropIndex = parentList.findIndex((item) => item.key === info.dropKey);
|
|
115
|
+
parentList.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
|
|
116
|
+
data.value = next;
|
|
117
|
+
};
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<CdsTable
|
|
122
|
+
:columns="columns"
|
|
123
|
+
:data="data"
|
|
124
|
+
:default-expand-all-rows="true"
|
|
125
|
+
row-draggable
|
|
126
|
+
:pagination="false"
|
|
127
|
+
@row-reorder="onRowReorder"
|
|
128
|
+
/>
|
|
129
|
+
</template>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Dragging columns
|
|
133
|
+
|
|
134
|
+
The `column-draggable` prop adds a grip icon to the header. The `column-reorder` event reports the new order — update the `columns` array.
|
|
135
|
+
|
|
136
|
+
**Example (SFC):**
|
|
137
|
+
|
|
138
|
+
```vue
|
|
139
|
+
<script setup lang="ts">
|
|
140
|
+
import type { NTable } from '@central-design-system/components';
|
|
141
|
+
import { CdsTable } from '@central-design-system/components';
|
|
142
|
+
import { ref } from 'vue';
|
|
143
|
+
|
|
144
|
+
type TRow = { key: string; name: string; category: string; price: string; stock: string };
|
|
145
|
+
|
|
146
|
+
const columns = ref<NTable.NColumn.TColumns<TRow>>([
|
|
147
|
+
{ title: 'Наименование', dataIndex: 'name', key: 'name' },
|
|
148
|
+
{ title: 'Категория', dataIndex: 'category', key: 'category' },
|
|
149
|
+
{ title: 'Цена', dataIndex: 'price', key: 'price', align: 'right' },
|
|
150
|
+
{ title: 'Наличие', dataIndex: 'stock', key: 'stock' }
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
const data: TRow[] = [
|
|
154
|
+
{ key: '1', name: 'iPhone 17 Pro Max', category: 'Смартфоны', price: '$1500', stock: 'В наличии' },
|
|
155
|
+
{ key: '2', name: 'MacBook Air M5', category: 'Ноутбуки', price: '$2000', stock: 'В наличии' },
|
|
156
|
+
{ key: '3', name: 'Apple Vision Pro', category: 'Гарнитуры', price: '$3500', stock: 'Нет в наличии' }
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const onColumnReorder = (info: NTable.NDraggable.IColumnReorderInfo<TRow>) => {
|
|
160
|
+
const next = [...columns.value];
|
|
161
|
+
const dragIndex = next.indexOf(info.dragColumn);
|
|
162
|
+
if (dragIndex === -1) return;
|
|
163
|
+
const [moved] = next.splice(dragIndex, 1);
|
|
164
|
+
const dropIndex = next.indexOf(info.dropColumn);
|
|
165
|
+
next.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
|
|
166
|
+
columns.value = next;
|
|
167
|
+
};
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<template>
|
|
171
|
+
<CdsTable :columns="columns" :data="data" column-draggable :pagination="false" @column-reorder="onColumnReorder" />
|
|
172
|
+
</template>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Dragging columns with groups
|
|
176
|
+
|
|
177
|
+
A leaf column is reordered inside its own group, while a group header is dragged as a whole among neighboring groups.
|
|
178
|
+
|
|
179
|
+
**Example (SFC):**
|
|
180
|
+
|
|
181
|
+
```vue
|
|
182
|
+
<script setup lang="ts">
|
|
183
|
+
import type { NTable } from '@central-design-system/components';
|
|
184
|
+
import { CdsTable } from '@central-design-system/components';
|
|
185
|
+
import { ref } from 'vue';
|
|
186
|
+
|
|
187
|
+
const columns = ref<NTable.NColumn.TColumns>([
|
|
188
|
+
{ title: 'Имя', dataIndex: 'name', key: 'name', width: 120 },
|
|
189
|
+
{
|
|
190
|
+
title: 'Контакты',
|
|
191
|
+
key: 'contacts',
|
|
192
|
+
children: [
|
|
193
|
+
{ title: 'Телефон', dataIndex: 'phone', key: 'phone', width: 160 },
|
|
194
|
+
{ title: 'Email', dataIndex: 'email', key: 'email', width: 200 }
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
title: 'Компания',
|
|
199
|
+
key: 'company',
|
|
200
|
+
children: [
|
|
201
|
+
{ title: 'Название', dataIndex: 'companyName', key: 'companyName', width: 160 },
|
|
202
|
+
{ title: 'Город', dataIndex: 'city', key: 'city', width: 140 }
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
const data = [...Array(5)].map((_, i) => ({
|
|
208
|
+
key: i,
|
|
209
|
+
name: 'Иван',
|
|
210
|
+
phone: '+7 900 000-00-00',
|
|
211
|
+
email: 'ivan@b2b-center.ru',
|
|
212
|
+
companyName: 'B2B-Center',
|
|
213
|
+
city: 'Москва'
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// Находит массив-родитель (уровень группы), содержащий колонку по ссылке.
|
|
217
|
+
const findParentList = (
|
|
218
|
+
list: NTable.NColumn.TColumns,
|
|
219
|
+
column: NTable.NColumn.IColumn
|
|
220
|
+
): NTable.NColumn.TColumns | null => {
|
|
221
|
+
for (const col of list) {
|
|
222
|
+
if (col === column) return list;
|
|
223
|
+
const children = (col as NTable.NColumn.IColumnGroup).children;
|
|
224
|
+
if (children) {
|
|
225
|
+
const found = findParentList(children, column);
|
|
226
|
+
if (found) return found;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const onColumnReorder = (info: NTable.NDraggable.IColumnReorderInfo) => {
|
|
233
|
+
const next = [...columns.value];
|
|
234
|
+
const parentList = findParentList(next, info.dragColumn);
|
|
235
|
+
if (!parentList) return;
|
|
236
|
+
const dragIndex = parentList.indexOf(info.dragColumn);
|
|
237
|
+
const [moved] = parentList.splice(dragIndex, 1);
|
|
238
|
+
const dropIndex = parentList.indexOf(info.dropColumn);
|
|
239
|
+
parentList.splice(info.dropPosition === -1 ? dropIndex : dropIndex + 1, 0, moved);
|
|
240
|
+
columns.value = next;
|
|
241
|
+
};
|
|
242
|
+
</script>
|
|
243
|
+
|
|
244
|
+
<template>
|
|
245
|
+
<CdsTable
|
|
246
|
+
:columns="columns"
|
|
247
|
+
:data="data"
|
|
248
|
+
:scroll="{ x: 'max-content' }"
|
|
249
|
+
bordered
|
|
250
|
+
column-draggable
|
|
251
|
+
:pagination="false"
|
|
252
|
+
@column-reorder="onColumnReorder"
|
|
253
|
+
/>
|
|
254
|
+
</template>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Редактирование
|
|
258
|
+
|
|
259
|
+
Правка данных прямо в таблице: отдельные ячейки, построчный режим
|
|
260
|
+
с сохранением и отменой или редактирование всей таблицы разом.
|
|
261
|
+
|
|
262
|
+
### Editable cells
|
|
263
|
+
|
|
264
|
+
The `bodyCell` slot replaces the content of a cell: on click the value turns into an input, on confirmation it turns back into text.
|
|
265
|
+
|
|
266
|
+
**Example (SFC):**
|
|
267
|
+
|
|
268
|
+
```vue
|
|
269
|
+
<script setup lang="ts">
|
|
270
|
+
import type { NTable } from '@central-design-system/components';
|
|
271
|
+
import { CdsButton, CdsFunctionalButton, CdsTable, CdsTextInput, CdsTooltip } from '@central-design-system/components';
|
|
272
|
+
import type { Ref, UnwrapRef } from 'vue';
|
|
273
|
+
import { computed, reactive, ref } from 'vue';
|
|
274
|
+
|
|
275
|
+
type TDataItem = { key: string; name: string; age: number; address: string };
|
|
276
|
+
|
|
277
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
278
|
+
{ title: 'Имя', dataIndex: 'name', width: '30%' },
|
|
279
|
+
{ title: 'Возраст', dataIndex: 'age' },
|
|
280
|
+
{ title: 'Адрес', dataIndex: 'address' },
|
|
281
|
+
{ title: 'Действия', dataIndex: 'actions' }
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
const data: Ref<TDataItem[]> = ref([
|
|
285
|
+
{ key: '0', name: 'Иван Иванов 0', age: 28, address: 'г. Москва, ул. Ленина, д. 0' },
|
|
286
|
+
{ key: '1', name: 'Иван Иванов 1', age: 28, address: 'г. Москва, ул. Ленина, д. 1' }
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
const count = computed(() => data.value.length + 1);
|
|
290
|
+
const editableData: UnwrapRef<Record<string, TDataItem>> = reactive({});
|
|
291
|
+
|
|
292
|
+
function edit(key: string) {
|
|
293
|
+
editableData[key] = data.value.filter((item) => key === item.key)[0];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function save(key: string) {
|
|
297
|
+
Object.assign(data.value.filter((item) => key === item.key)[0], editableData[key]);
|
|
298
|
+
delete editableData[key];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function remove(key: string) {
|
|
302
|
+
data.value = data.value.filter((item) => item.key !== key);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const handleAdd = () => {
|
|
306
|
+
data.value.push({
|
|
307
|
+
key: `${count.value}`,
|
|
308
|
+
name: `Иван Иванов ${count.value}`,
|
|
309
|
+
age: 28,
|
|
310
|
+
address: `г. Москва, ул. Ленина, д. ${count.value}`
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
</script>
|
|
314
|
+
|
|
315
|
+
<template>
|
|
316
|
+
<CdsTable :columns="columns" :data="data">
|
|
317
|
+
<template #toolbar>
|
|
318
|
+
<div class="flex justify-end">
|
|
319
|
+
<CdsButton text="Добавить" @click="handleAdd" />
|
|
320
|
+
</div>
|
|
321
|
+
</template>
|
|
322
|
+
|
|
323
|
+
<template #bodyCell="{ column, text, record }">
|
|
324
|
+
<template v-if="column.dataIndex === 'name'">
|
|
325
|
+
<div class="flex">
|
|
326
|
+
<div v-if="editableData[record.key]" class="flex items-center grow-1">
|
|
327
|
+
<CdsTextInput
|
|
328
|
+
class="grow-1"
|
|
329
|
+
v-model="editableData[record.key].name"
|
|
330
|
+
hide-messages
|
|
331
|
+
@keydown.enter="save(record.key)"
|
|
332
|
+
/>
|
|
333
|
+
<CdsTooltip location="right">
|
|
334
|
+
<template #activator="{ props }">
|
|
335
|
+
<CdsFunctionalButton icon="save" class="ml-2" v-bind="props" @click="save(record.key)" />
|
|
336
|
+
</template>
|
|
337
|
+
Сохранить
|
|
338
|
+
</CdsTooltip>
|
|
339
|
+
</div>
|
|
340
|
+
<div v-else class="flex items-center">
|
|
341
|
+
{{ text || ' ' }}
|
|
342
|
+
<CdsTooltip location="right">
|
|
343
|
+
<template #activator="{ props }">
|
|
344
|
+
<CdsFunctionalButton icon="edit" class="ml-2" v-bind="props" @click="edit(record.key)" />
|
|
345
|
+
</template>
|
|
346
|
+
Редактировать
|
|
347
|
+
</CdsTooltip>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</template>
|
|
351
|
+
<template v-else-if="column.dataIndex === 'actions'">
|
|
352
|
+
<CdsTooltip location="right">
|
|
353
|
+
<template #activator="{ props }">
|
|
354
|
+
<CdsFunctionalButton icon="delete" v-bind="props" @click="remove(record.key)" />
|
|
355
|
+
</template>
|
|
356
|
+
Удалить
|
|
357
|
+
</CdsTooltip>
|
|
358
|
+
</template>
|
|
359
|
+
</template>
|
|
360
|
+
</CdsTable>
|
|
361
|
+
</template>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Editable rows
|
|
365
|
+
|
|
366
|
+
The whole row switches into edit mode: inputs in every cell, the "Save" and "Cancel" buttons in the actions column.
|
|
367
|
+
|
|
368
|
+
**Example (SFC):**
|
|
369
|
+
|
|
370
|
+
```vue
|
|
371
|
+
<script setup lang="ts">
|
|
372
|
+
import type { NTable } from '@central-design-system/components';
|
|
373
|
+
import { CdsFunctionalButton, CdsTable, CdsTextInput, CdsTooltip } from '@central-design-system/components';
|
|
374
|
+
import type { UnwrapRef } from 'vue';
|
|
375
|
+
import { reactive, ref } from 'vue';
|
|
376
|
+
|
|
377
|
+
type TDataItem = { key: string; name: string; age: number; address: string };
|
|
378
|
+
|
|
379
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
380
|
+
{ title: 'Имя', dataIndex: 'name', width: '25%' },
|
|
381
|
+
{ title: 'Возраст', dataIndex: 'age', width: '15%' },
|
|
382
|
+
{ title: 'Адрес', dataIndex: 'address', width: '40%' },
|
|
383
|
+
{ title: 'Действия', dataIndex: 'actions' }
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
const data: TDataItem[] = [...Array(20)].map((_, i) => ({
|
|
387
|
+
key: i.toString(),
|
|
388
|
+
name: `Иван Иванов ${i}`,
|
|
389
|
+
age: 28,
|
|
390
|
+
address: `г. Москва, ул. Ленина, д. ${i}`
|
|
391
|
+
}));
|
|
392
|
+
|
|
393
|
+
const dataSource = ref(data);
|
|
394
|
+
const editableData: UnwrapRef<Record<string, TDataItem>> = reactive({});
|
|
395
|
+
|
|
396
|
+
function edit(key: string) {
|
|
397
|
+
editableData[key] = dataSource.value.filter((item) => key === item.key)[0];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function save(key: string) {
|
|
401
|
+
Object.assign(dataSource.value.filter((item) => key === item.key)[0], editableData[key]);
|
|
402
|
+
delete editableData[key];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function cancel(key: string) {
|
|
406
|
+
delete editableData[key];
|
|
407
|
+
}
|
|
408
|
+
</script>
|
|
409
|
+
|
|
410
|
+
<template>
|
|
411
|
+
<CdsTable :columns="columns" :data="dataSource">
|
|
412
|
+
<template #bodyCell="{ column, text, record }">
|
|
413
|
+
<template v-if="['name', 'age', 'address'].includes(String(column.dataIndex))">
|
|
414
|
+
<div>
|
|
415
|
+
<CdsTextInput
|
|
416
|
+
v-if="editableData[record.key]"
|
|
417
|
+
v-model="editableData[record.key][column.dataIndex as keyof TDataItem]"
|
|
418
|
+
hide-messages
|
|
419
|
+
/>
|
|
420
|
+
<template v-else>
|
|
421
|
+
{{ text }}
|
|
422
|
+
</template>
|
|
423
|
+
</div>
|
|
424
|
+
</template>
|
|
425
|
+
<template v-else-if="column.dataIndex === 'actions'">
|
|
426
|
+
<div>
|
|
427
|
+
<span v-if="editableData[record.key]">
|
|
428
|
+
<CdsTooltip>
|
|
429
|
+
<template #activator="{ props }">
|
|
430
|
+
<CdsFunctionalButton icon="save" v-bind="props" @click="save(record.key)" />
|
|
431
|
+
</template>
|
|
432
|
+
Сохранить
|
|
433
|
+
</CdsTooltip>
|
|
434
|
+
<CdsTooltip>
|
|
435
|
+
<template #activator="{ props }">
|
|
436
|
+
<CdsFunctionalButton icon="remove" v-bind="props" @click="cancel(record.key)" />
|
|
437
|
+
</template>
|
|
438
|
+
Отменить
|
|
439
|
+
</CdsTooltip>
|
|
440
|
+
</span>
|
|
441
|
+
<span v-else>
|
|
442
|
+
<CdsTooltip location="left">
|
|
443
|
+
<template #activator="{ props }">
|
|
444
|
+
<CdsFunctionalButton icon="edit" v-bind="props" @click="edit(record.key)" />
|
|
445
|
+
</template>
|
|
446
|
+
Редактировать
|
|
447
|
+
</CdsTooltip>
|
|
448
|
+
</span>
|
|
449
|
+
</div>
|
|
450
|
+
</template>
|
|
451
|
+
</template>
|
|
452
|
+
</CdsTable>
|
|
453
|
+
</template>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Editable table
|
|
457
|
+
|
|
458
|
+
Edit mode is turned on for the entire table at once — convenient for filling in reference data in bulk.
|
|
459
|
+
|
|
460
|
+
**Example (SFC):**
|
|
461
|
+
|
|
462
|
+
```vue
|
|
463
|
+
<script setup lang="ts">
|
|
464
|
+
import type { NTable } from '@central-design-system/components';
|
|
465
|
+
import { CdsButton, CdsTable, CdsTextInput } from '@central-design-system/components';
|
|
466
|
+
import type { UnwrapRef } from 'vue';
|
|
467
|
+
import { reactive, ref } from 'vue';
|
|
468
|
+
|
|
469
|
+
type TDataItem = { key: string; name: string; age: number; address: string; quantity: number };
|
|
470
|
+
|
|
471
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
472
|
+
{ title: 'Имя', dataIndex: 'name', width: '25%' },
|
|
473
|
+
{ title: 'Возраст', dataIndex: 'age', width: '15%' },
|
|
474
|
+
{ title: 'Адрес', dataIndex: 'address', width: '40%' },
|
|
475
|
+
{ title: 'Количество', dataIndex: 'quantity' }
|
|
476
|
+
];
|
|
477
|
+
|
|
478
|
+
const data: TDataItem[] = [...Array(10)].map((_, i) => ({
|
|
479
|
+
key: i.toString(),
|
|
480
|
+
name: `Иван Иванов ${i}`,
|
|
481
|
+
age: 32,
|
|
482
|
+
address: `г. Москва, ул. Ленина, д. ${i}`,
|
|
483
|
+
quantity: Math.round(Math.random() * 100)
|
|
484
|
+
}));
|
|
485
|
+
|
|
486
|
+
const dataSource = ref(data);
|
|
487
|
+
const editing = ref(false);
|
|
488
|
+
const editableData: UnwrapRef<Record<string, TDataItem>> = reactive({});
|
|
489
|
+
|
|
490
|
+
function edit() {
|
|
491
|
+
if (!editing.value) {
|
|
492
|
+
editing.value = true;
|
|
493
|
+
dataSource.value.forEach((item) => {
|
|
494
|
+
editableData[item.key] = { ...item };
|
|
495
|
+
});
|
|
496
|
+
} else {
|
|
497
|
+
editing.value = false;
|
|
498
|
+
Object.assign(dataSource.value, editableData);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function cancel() {
|
|
503
|
+
editing.value = false;
|
|
504
|
+
dataSource.value.forEach((item) => {
|
|
505
|
+
delete editableData[item.key];
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
</script>
|
|
509
|
+
|
|
510
|
+
<template>
|
|
511
|
+
<CdsTable :columns="columns" :data="dataSource">
|
|
512
|
+
<template #toolbar>
|
|
513
|
+
<div class="flex justify-end">
|
|
514
|
+
<div class="flex gap-2">
|
|
515
|
+
<CdsButton appearance="secondary" :disabled="!editing" @click="cancel">Отменить</CdsButton>
|
|
516
|
+
<CdsButton @click="edit">{{ editing ? 'Сохранить' : 'Редактировать' }}</CdsButton>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
</template>
|
|
520
|
+
|
|
521
|
+
<template #bodyCell="{ column, text, record }">
|
|
522
|
+
<div>
|
|
523
|
+
<CdsTextInput
|
|
524
|
+
v-if="editing"
|
|
525
|
+
v-model="editableData[record.key][column.dataIndex as keyof TDataItem]"
|
|
526
|
+
hide-messages
|
|
527
|
+
/>
|
|
528
|
+
<template v-else>
|
|
529
|
+
{{ text }}
|
|
530
|
+
</template>
|
|
531
|
+
</div>
|
|
532
|
+
</template>
|
|
533
|
+
</CdsTable>
|
|
534
|
+
</template>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Раскрытие и деревья
|
|
538
|
+
|
|
539
|
+
Дополнительное содержимое строки: раскрываемые строки со слотом,
|
|
540
|
+
вложенные таблицы и древовидные данные с каскадным выбором.
|
|
541
|
+
|
|
542
|
+
### Expandable rows
|
|
543
|
+
|
|
544
|
+
The `expandedRowRender` slot renders the content of an expanded row — the column with the chevron appears automatically.
|
|
545
|
+
|
|
546
|
+
**Example (SFC):**
|
|
547
|
+
|
|
548
|
+
```vue
|
|
549
|
+
<script setup lang="ts">
|
|
550
|
+
import type { NTable } from '@central-design-system/components';
|
|
551
|
+
import { CdsTable } from '@central-design-system/components';
|
|
552
|
+
|
|
553
|
+
type TDataType = { key: number; name: string; age: number; phone: string; address: string; description: string };
|
|
554
|
+
|
|
555
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
556
|
+
{ title: 'Имя', dataIndex: 'name', key: 'name' },
|
|
557
|
+
{ title: 'Возраст', dataIndex: 'age', key: 'age' },
|
|
558
|
+
{ title: 'Адрес', dataIndex: 'address', key: 'address' },
|
|
559
|
+
{ title: 'Телефон', dataIndex: 'phone', key: 'phone' }
|
|
560
|
+
];
|
|
561
|
+
const data: TDataType[] = [
|
|
562
|
+
{
|
|
563
|
+
key: 1,
|
|
564
|
+
name: 'Иванов Иван',
|
|
565
|
+
age: 20,
|
|
566
|
+
phone: '79999999999',
|
|
567
|
+
address: 'г. Москва, ул. Ленина д. 1',
|
|
568
|
+
description: 'Меня зовут Иванов Иван, мне 20 лет и я живу по адресу г. Москва, ул. Ленина д. 1'
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
key: 2,
|
|
572
|
+
name: 'Иванов Василий',
|
|
573
|
+
age: 32,
|
|
574
|
+
phone: '79999999999',
|
|
575
|
+
address: 'г. Москва, ул. Ленина д. 2',
|
|
576
|
+
description: 'Меня зовут Иванов Василий, мне 32 года и я живу по адресу г. Москва, ул. Ленина д. 2'
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
key: 3,
|
|
580
|
+
name: 'Иванов Кирилл',
|
|
581
|
+
age: 41,
|
|
582
|
+
phone: '79999999999',
|
|
583
|
+
address: 'г. Москва, ул. Ленина д. 3',
|
|
584
|
+
description: 'Меня зовут Иванов Кирилл, мне 41 год и я живу по адресу г. Москва, ул. Ленина д. 3'
|
|
585
|
+
}
|
|
586
|
+
];
|
|
587
|
+
</script>
|
|
588
|
+
|
|
589
|
+
<template>
|
|
590
|
+
<CdsTable :columns="columns" :data="data" :pagination="false">
|
|
591
|
+
<template #expandedRowRender="{ record }">
|
|
592
|
+
<p>{{ record.description }}</p>
|
|
593
|
+
</template>
|
|
594
|
+
</CdsTable>
|
|
595
|
+
</template>
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Nested tables
|
|
599
|
+
|
|
600
|
+
Anything can live inside `expandedRowRender` — including another `CdsTable` with its own columns and data.
|
|
601
|
+
|
|
602
|
+
**Example (SFC):**
|
|
603
|
+
|
|
604
|
+
```vue
|
|
605
|
+
<script setup lang="ts">
|
|
606
|
+
import type { NTable } from '@central-design-system/components';
|
|
607
|
+
import { CdsFunctionalButton, CdsStatus, CdsTable, CdsToggle } from '@central-design-system/components';
|
|
608
|
+
|
|
609
|
+
const columns: NTable.NColumn.TColumns = [
|
|
610
|
+
{ title: 'Название', dataIndex: 'name', key: 'name' },
|
|
611
|
+
{ title: 'Платформа', dataIndex: 'platform', key: 'platform' },
|
|
612
|
+
{ title: 'Версия', dataIndex: 'version', key: 'version' },
|
|
613
|
+
{ title: 'Покрытие', dataIndex: 'coverage', key: 'coverage' },
|
|
614
|
+
{ title: 'Автор', dataIndex: 'creator', key: 'creator' },
|
|
615
|
+
{ title: 'Дата', dataIndex: 'createdAt', key: 'createdAt' },
|
|
616
|
+
{ title: 'Действия', key: 'operation' }
|
|
617
|
+
];
|
|
618
|
+
const data = [...Array(3)].map((_, i) => ({
|
|
619
|
+
key: i,
|
|
620
|
+
name: `Компонент ${i + 1}`,
|
|
621
|
+
platform: 'iOS',
|
|
622
|
+
version: '3.2.1',
|
|
623
|
+
coverage: 100,
|
|
624
|
+
creator: 'i.ivanov',
|
|
625
|
+
createdAt: new Date('2023-02-07 23:12:00').toLocaleDateString()
|
|
626
|
+
}));
|
|
627
|
+
|
|
628
|
+
const innerColumns: NTable.NColumn.TColumns = [
|
|
629
|
+
{ title: 'Дата', dataIndex: 'date', key: 'date' },
|
|
630
|
+
{ title: 'Название', dataIndex: 'name', key: 'name' },
|
|
631
|
+
{ title: 'Статус', key: 'state' },
|
|
632
|
+
{ title: 'Покрытие', dataIndex: 'coverage', key: 'coverage' },
|
|
633
|
+
{ title: 'Действие', dataIndex: 'operation', key: 'operation' }
|
|
634
|
+
];
|
|
635
|
+
const innerData = [...Array(3)].map((_, i) => ({
|
|
636
|
+
key: i,
|
|
637
|
+
date: new Date('2023-02-07 23:12:00').toLocaleDateString(),
|
|
638
|
+
name: `Это компонент ${i + 1}`,
|
|
639
|
+
coverage: 'Покрыто: 56'
|
|
640
|
+
}));
|
|
641
|
+
</script>
|
|
642
|
+
|
|
643
|
+
<template>
|
|
644
|
+
<CdsTable :columns="columns" :data="data" :pagination="false">
|
|
645
|
+
<template #bodyCell="{ column }">
|
|
646
|
+
<template v-if="column.key === 'operation'">
|
|
647
|
+
<CdsToggle hide-messages />
|
|
648
|
+
</template>
|
|
649
|
+
</template>
|
|
650
|
+
<template #expandedRowRender>
|
|
651
|
+
<CdsTable :columns="innerColumns" :data="innerData" :pagination="false">
|
|
652
|
+
<template #bodyCell="{ column }">
|
|
653
|
+
<template v-if="column.key === 'state'">
|
|
654
|
+
<span>
|
|
655
|
+
<CdsStatus status="success" text="Готово" />
|
|
656
|
+
</span>
|
|
657
|
+
</template>
|
|
658
|
+
<template v-else-if="column.key === 'operation'">
|
|
659
|
+
<span class="flex gap-2">
|
|
660
|
+
<CdsFunctionalButton icon="edit" />
|
|
661
|
+
<CdsFunctionalButton icon="delete" />
|
|
662
|
+
</span>
|
|
663
|
+
</template>
|
|
664
|
+
</template>
|
|
665
|
+
</CdsTable>
|
|
666
|
+
</template>
|
|
667
|
+
</CdsTable>
|
|
668
|
+
</template>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### Tree data
|
|
672
|
+
|
|
673
|
+
Records with a `children` field expand as a tree. Together with `row-selection`, the `checkStrictly: false` option turns on cascading selection: checking a parent selects its children.
|
|
674
|
+
|
|
675
|
+
**Example (SFC):**
|
|
676
|
+
|
|
677
|
+
```vue
|
|
678
|
+
<script setup lang="ts">
|
|
679
|
+
import type { NTable } from '@central-design-system/components';
|
|
680
|
+
import { CdsCheckbox, CdsTable } from '@central-design-system/components';
|
|
681
|
+
import { ref } from 'vue';
|
|
682
|
+
|
|
683
|
+
type TDataItem = {
|
|
684
|
+
key: number;
|
|
685
|
+
name: string;
|
|
686
|
+
price: number;
|
|
687
|
+
description: string;
|
|
688
|
+
children?: TDataItem[];
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
692
|
+
{ title: 'Название', dataIndex: 'name', key: 'name' },
|
|
693
|
+
{ title: 'Цена, $', dataIndex: 'price', key: 'price', width: '12%' },
|
|
694
|
+
{ title: 'Описание', dataIndex: 'description', width: '30%', key: 'description' }
|
|
695
|
+
];
|
|
696
|
+
const data: TDataItem[] = [
|
|
697
|
+
{
|
|
698
|
+
key: 1,
|
|
699
|
+
name: 'Apple iPhone',
|
|
700
|
+
price: 3250,
|
|
701
|
+
description: 'Описание товара',
|
|
702
|
+
children: [
|
|
703
|
+
{ key: 11, name: 'iPhone 14 Pro', price: 2000, description: 'Описание товара' },
|
|
704
|
+
{
|
|
705
|
+
key: 12,
|
|
706
|
+
name: 'Power Bank',
|
|
707
|
+
price: 50,
|
|
708
|
+
description: 'Описание товара',
|
|
709
|
+
children: [{ key: 121, name: 'MagSafe Battery Pack', price: 50, description: 'Описание товара' }]
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
key: 13,
|
|
713
|
+
name: 'Headphones',
|
|
714
|
+
price: 1200,
|
|
715
|
+
description: 'Описание товара',
|
|
716
|
+
children: [
|
|
717
|
+
{
|
|
718
|
+
key: 131,
|
|
719
|
+
name: 'AirPods',
|
|
720
|
+
price: 1200,
|
|
721
|
+
description: 'Описание товара',
|
|
722
|
+
children: [
|
|
723
|
+
{ key: 1311, name: 'AirPods Pro', price: 200, description: 'Описание товара' },
|
|
724
|
+
{ key: 1312, name: 'AirPods Max', price: 1000, description: 'Описание товара' }
|
|
725
|
+
]
|
|
726
|
+
}
|
|
727
|
+
]
|
|
728
|
+
}
|
|
729
|
+
]
|
|
730
|
+
},
|
|
731
|
+
{ key: 2, name: 'Xiaomi Mi', price: 200, description: 'Описание товара' }
|
|
732
|
+
];
|
|
733
|
+
|
|
734
|
+
const rowSelection = ref({
|
|
735
|
+
checkStrictly: false,
|
|
736
|
+
onChange: (selectedRowKeys: NTable.TKey[], selectedRows: TDataItem[]) => {
|
|
737
|
+
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
|
738
|
+
},
|
|
739
|
+
onSelect: (record: TDataItem, selected: boolean, selectedRows: TDataItem[]) => {
|
|
740
|
+
console.log('onSelect', record, selected, selectedRows);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
const rowClassName = (record: TDataItem) => (!record.children?.length ? 'cds-table__row--no-children' : '');
|
|
745
|
+
</script>
|
|
746
|
+
|
|
747
|
+
<template>
|
|
748
|
+
<div class="flex flex-col gap-4">
|
|
749
|
+
<CdsCheckbox v-model="rowSelection.checkStrictly" label="Строгий выбор" hide-messages />
|
|
750
|
+
<CdsTable :columns="columns" :data="data" :row-selection="rowSelection" :row-class-name="rowClassName" />
|
|
751
|
+
</div>
|
|
752
|
+
</template>
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Expanding every node
|
|
756
|
+
|
|
757
|
+
The `expanded-row-keys` prop makes expansion controlled: collect the keys of every parent record — and an "Expand all" button unfolds the whole tree.
|
|
758
|
+
|
|
759
|
+
**Example (SFC):**
|
|
760
|
+
|
|
761
|
+
```vue
|
|
762
|
+
<script setup lang="ts">
|
|
763
|
+
import type { NTable } from '@central-design-system/components';
|
|
764
|
+
import { CdsFunctionalButton, CdsTable } from '@central-design-system/components';
|
|
765
|
+
import { computed, ref } from 'vue';
|
|
766
|
+
|
|
767
|
+
type TDataItem = {
|
|
768
|
+
key: number;
|
|
769
|
+
name: string;
|
|
770
|
+
price: number;
|
|
771
|
+
description: string;
|
|
772
|
+
children?: TDataItem[];
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
776
|
+
{ title: 'Название', dataIndex: 'name', key: 'name' },
|
|
777
|
+
{ title: 'Цена, $', dataIndex: 'price', key: 'price', width: '12%' },
|
|
778
|
+
{ title: 'Описание', dataIndex: 'description', width: '30%', key: 'description' }
|
|
779
|
+
];
|
|
780
|
+
const data = ref<TDataItem[]>([
|
|
781
|
+
{
|
|
782
|
+
key: 1,
|
|
783
|
+
name: 'Apple iPhone',
|
|
784
|
+
price: 3250,
|
|
785
|
+
description: 'Описание товара',
|
|
786
|
+
children: [
|
|
787
|
+
{ key: 11, name: 'iPhone 14 Pro', price: 2000, description: 'Описание товара' },
|
|
788
|
+
{
|
|
789
|
+
key: 12,
|
|
790
|
+
name: 'Power Bank',
|
|
791
|
+
price: 50,
|
|
792
|
+
description: 'Описание товара',
|
|
793
|
+
children: [{ key: 121, name: 'MagSafe Battery Pack', price: 50, description: 'Описание товара' }]
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
key: 13,
|
|
797
|
+
name: 'Headphones',
|
|
798
|
+
price: 1200,
|
|
799
|
+
description: 'Описание товара',
|
|
800
|
+
children: [
|
|
801
|
+
{
|
|
802
|
+
key: 131,
|
|
803
|
+
name: 'AirPods',
|
|
804
|
+
price: 1200,
|
|
805
|
+
description: 'Описание товара',
|
|
806
|
+
children: [
|
|
807
|
+
{ key: 1311, name: 'AirPods Pro', price: 200, description: 'Описание товара' },
|
|
808
|
+
{ key: 1312, name: 'AirPods Max', price: 1000, description: 'Описание товара' }
|
|
809
|
+
]
|
|
810
|
+
}
|
|
811
|
+
]
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
},
|
|
815
|
+
{ key: 2, name: 'Xiaomi Mi', price: 200, description: 'Описание товара' }
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
const rowClassName = (record: TDataItem) => (!record.children?.length ? 'cds-table__row--no-children' : '');
|
|
819
|
+
|
|
820
|
+
// Массив для хранения ключей развернутых узлов
|
|
821
|
+
const expandedRowKeys = ref<NTable.TKey[]>([]);
|
|
822
|
+
|
|
823
|
+
// Рекурсивная функция для сбора всех ключей из данных
|
|
824
|
+
const getAllKeys = (items: TDataItem[]): NTable.TKey[] => {
|
|
825
|
+
let keys: NTable.TKey[] = [];
|
|
826
|
+
items.forEach((item) => {
|
|
827
|
+
keys.push(item.key);
|
|
828
|
+
if (item.children && item.children.length > 0) {
|
|
829
|
+
keys = keys.concat(getAllKeys(item.children));
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
return keys;
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
const allRowKeys = computed(() => getAllKeys(data.value));
|
|
836
|
+
|
|
837
|
+
const allExpanded = computed(() => allRowKeys.value.every((key) => expandedRowKeys.value.includes(key)));
|
|
838
|
+
|
|
839
|
+
const toggleAll = () => {
|
|
840
|
+
expandedRowKeys.value = allExpanded.value ? [] : allRowKeys.value;
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
const handleExpand = (expanded: boolean, record: TDataItem) => {
|
|
844
|
+
if (expanded) {
|
|
845
|
+
expandedRowKeys.value.push(record.key);
|
|
846
|
+
} else {
|
|
847
|
+
expandedRowKeys.value = expandedRowKeys.value.filter((key) => key !== record.key);
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
</script>
|
|
851
|
+
|
|
852
|
+
<template>
|
|
853
|
+
<CdsTable
|
|
854
|
+
:columns="columns"
|
|
855
|
+
:data="data"
|
|
856
|
+
:expanded-row-keys="expandedRowKeys"
|
|
857
|
+
:row-class-name="rowClassName"
|
|
858
|
+
@expand="handleExpand"
|
|
859
|
+
>
|
|
860
|
+
<template #headerCell="{ title, column }">
|
|
861
|
+
<template v-if="column.key === 'name'">
|
|
862
|
+
<CdsFunctionalButton :icon="allExpanded ? 'chevron-down' : 'chevron-right'" @click="toggleAll" />
|
|
863
|
+
</template>
|
|
864
|
+
{{ title }}
|
|
865
|
+
</template>
|
|
866
|
+
</CdsTable>
|
|
867
|
+
</template>
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
## Фильтры и сортировка
|
|
871
|
+
|
|
872
|
+
Фильтры и сортировка описываются прямо в колонках: меню фильтра с
|
|
873
|
+
вложенными пунктами, сортировка по одной или нескольким колонкам,
|
|
874
|
+
управляемое состояние со сбросом и собственная панель фильтра.
|
|
875
|
+
|
|
876
|
+
### Filters and sorting
|
|
877
|
+
|
|
878
|
+
The column filter menu is set by `filters` and `onFilter`, the sorting by the `sorter` function. `sortDirections` lists the directions, `defaultSortOrder` sets the initial order, and `filterMultiple: false` switches the filter to single choice. Every change is reported by the `change` event.
|
|
879
|
+
|
|
880
|
+
**Example (SFC):**
|
|
881
|
+
|
|
882
|
+
```vue
|
|
883
|
+
<script setup lang="ts">
|
|
884
|
+
import type { ITableProps, NTable } from '@central-design-system/components';
|
|
885
|
+
import { CdsTable } from '@central-design-system/components';
|
|
886
|
+
|
|
887
|
+
type TDataType = { key: string; name: string; age: number; address: string };
|
|
888
|
+
|
|
889
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
890
|
+
{
|
|
891
|
+
title: 'Имя',
|
|
892
|
+
dataIndex: 'name',
|
|
893
|
+
filters: [
|
|
894
|
+
{ text: 'Иван', value: 'Иван' },
|
|
895
|
+
{ text: 'Петр', value: 'Петр' },
|
|
896
|
+
{
|
|
897
|
+
text: 'Фамилии',
|
|
898
|
+
value: 'Submenu',
|
|
899
|
+
children: [
|
|
900
|
+
{ text: 'Иванов', value: 'Иван Иванов' },
|
|
901
|
+
{ text: 'Петров', value: 'Петр Петров' }
|
|
902
|
+
]
|
|
903
|
+
}
|
|
904
|
+
],
|
|
905
|
+
onFilter: (value, record: TDataType) => record.name.indexOf(value as string) === 0,
|
|
906
|
+
sorter: (a: TDataType, b: TDataType) => a.name.length - b.name.length,
|
|
907
|
+
sortDirections: ['descend']
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
title: 'Возраст',
|
|
911
|
+
dataIndex: 'age',
|
|
912
|
+
defaultSortOrder: 'descend',
|
|
913
|
+
sorter: (a: TDataType, b: TDataType) => a.age - b.age
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
title: 'Адрес',
|
|
917
|
+
dataIndex: 'address',
|
|
918
|
+
filters: [
|
|
919
|
+
{ text: 'Москва', value: 'Москва' },
|
|
920
|
+
{ text: 'Санкт-Петербург', value: 'Санкт-Петербург' }
|
|
921
|
+
],
|
|
922
|
+
filterMultiple: false,
|
|
923
|
+
onFilter: (value, record: TDataType) => record.address.indexOf(value as string) === 0,
|
|
924
|
+
sorter: (a: TDataType, b: TDataType) => a.address.length - b.address.length,
|
|
925
|
+
sortDirections: ['descend', 'ascend']
|
|
926
|
+
}
|
|
927
|
+
];
|
|
928
|
+
|
|
929
|
+
const data: TDataType[] = [
|
|
930
|
+
{ key: '1', name: 'Иван Иванов', age: 40, address: 'Москва, ул. Ленина, д. 1' },
|
|
931
|
+
{ key: '2', name: 'Петр Петров', age: 42, address: 'Санкт-Петербург, ул. Ленина, д. 1' },
|
|
932
|
+
{ key: '3', name: 'Иван Иванов', age: 18, address: 'Санкт-Петербург, ул. Ленина, д. 2' },
|
|
933
|
+
{ key: '4', name: 'Петр Петров', age: 28, address: 'Москва, ул. Ленина, д. 2' }
|
|
934
|
+
];
|
|
935
|
+
|
|
936
|
+
const onChange: ITableProps<TDataType>['onChange'] = (pagination, filters, sorter) => {
|
|
937
|
+
console.log('onChange', pagination, filters, sorter);
|
|
938
|
+
};
|
|
939
|
+
</script>
|
|
940
|
+
|
|
941
|
+
<template>
|
|
942
|
+
<CdsTable :columns="columns" :data="data" @change="onChange" />
|
|
943
|
+
</template>
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Multiple sorting
|
|
947
|
+
|
|
948
|
+
A `sorter` object with a `multiple` field allows sorting by several columns at once — the number sets the column's priority in the chain.
|
|
949
|
+
|
|
950
|
+
**Example (SFC):**
|
|
951
|
+
|
|
952
|
+
```vue
|
|
953
|
+
<script setup lang="ts">
|
|
954
|
+
import type { ITableProps, NTable } from '@central-design-system/components';
|
|
955
|
+
import { CdsTable } from '@central-design-system/components';
|
|
956
|
+
|
|
957
|
+
type TDataType = { key: string; product: string; usd: number; eur: number; rub: number };
|
|
958
|
+
|
|
959
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
960
|
+
{ title: 'Продукт', dataIndex: 'product' },
|
|
961
|
+
{
|
|
962
|
+
title: 'Цена USD',
|
|
963
|
+
dataIndex: 'usd',
|
|
964
|
+
sorter: { compare: (a, b) => a.usd - b.usd, multiple: 3 }
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
title: 'Цена EUR',
|
|
968
|
+
dataIndex: 'eur',
|
|
969
|
+
sorter: { compare: (a, b) => a.eur - b.eur, multiple: 2 }
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
title: 'Цена RUB',
|
|
973
|
+
dataIndex: 'rub',
|
|
974
|
+
sorter: { compare: (a, b) => a.rub - b.rub, multiple: 1 }
|
|
975
|
+
}
|
|
976
|
+
];
|
|
977
|
+
|
|
978
|
+
const data: TDataType[] = [
|
|
979
|
+
{ key: '1', product: 'Позиция 1', usd: 100, eur: 92, rub: 9100 },
|
|
980
|
+
{ key: '2', product: 'Позиция 2', usd: 50, eur: 46, rub: 4530 },
|
|
981
|
+
{ key: '3', product: 'Позиция 3', usd: 150, eur: 138, rub: 13590 },
|
|
982
|
+
{ key: '4', product: 'Позиция 4', usd: 25, eur: 23, rub: 2265 }
|
|
983
|
+
];
|
|
984
|
+
|
|
985
|
+
const onChange: ITableProps['onChange'] = (pagination, filters, sorter, extra) => {
|
|
986
|
+
console.log('params', pagination, filters, sorter, extra);
|
|
987
|
+
};
|
|
988
|
+
</script>
|
|
989
|
+
|
|
990
|
+
<template>
|
|
991
|
+
<CdsTable :columns="columns" :data="data" @change="onChange" />
|
|
992
|
+
</template>
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### Resetting filters and sorting
|
|
996
|
+
|
|
997
|
+
The `filteredValue` and `sortOrder` column props make the state controlled: keep it outside, update it in the `change` handler — and reset it with buttons in a single assignment.
|
|
998
|
+
|
|
999
|
+
**Example (SFC):**
|
|
1000
|
+
|
|
1001
|
+
```vue
|
|
1002
|
+
<script setup lang="ts">
|
|
1003
|
+
import type { ITableProps, NTable } from '@central-design-system/components';
|
|
1004
|
+
import { CdsButton, CdsTable } from '@central-design-system/components';
|
|
1005
|
+
import { computed, ref } from 'vue';
|
|
1006
|
+
|
|
1007
|
+
type TDataItem = { key: string; name: string; age: number; address: string };
|
|
1008
|
+
|
|
1009
|
+
const filteredInfo = ref();
|
|
1010
|
+
const sortedInfo = ref();
|
|
1011
|
+
|
|
1012
|
+
const columns = computed<NTable.NColumn.TColumns<TDataItem>>(() => {
|
|
1013
|
+
const filtered = filteredInfo.value || {};
|
|
1014
|
+
const sorted = sortedInfo.value || {};
|
|
1015
|
+
|
|
1016
|
+
return [
|
|
1017
|
+
{
|
|
1018
|
+
title: 'Имя',
|
|
1019
|
+
dataIndex: 'name',
|
|
1020
|
+
key: 'name',
|
|
1021
|
+
filters: [
|
|
1022
|
+
{ text: 'Иван', value: 'Иван' },
|
|
1023
|
+
{ text: 'Петр', value: 'Петр' }
|
|
1024
|
+
],
|
|
1025
|
+
filteredValue: filtered.name || null,
|
|
1026
|
+
onFilter: (value, record: TDataItem) => record.name.includes(value as string),
|
|
1027
|
+
sorter: (a: TDataItem, b: TDataItem) => a.name.length - b.name.length,
|
|
1028
|
+
sortOrder: sorted.columnKey === 'name' && sorted.order,
|
|
1029
|
+
ellipsis: true
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
title: 'Возраст',
|
|
1033
|
+
dataIndex: 'age',
|
|
1034
|
+
key: 'age',
|
|
1035
|
+
sorter: (a: TDataItem, b: TDataItem) => a.age - b.age,
|
|
1036
|
+
sortOrder: sorted.columnKey === 'age' && sorted.order
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
title: 'Адрес',
|
|
1040
|
+
dataIndex: 'address',
|
|
1041
|
+
key: 'address',
|
|
1042
|
+
filters: [
|
|
1043
|
+
{ text: 'Москва', value: 'Москва' },
|
|
1044
|
+
{ text: 'Санкт-Петербург', value: 'Санкт-Петербург' }
|
|
1045
|
+
],
|
|
1046
|
+
filteredValue: filtered.address || null,
|
|
1047
|
+
onFilter: (value, record: TDataItem) => record.address.includes(value as string),
|
|
1048
|
+
sorter: (a: TDataItem, b: TDataItem) => a.address.length - b.address.length,
|
|
1049
|
+
sortOrder: sorted.columnKey === 'address' && sorted.order,
|
|
1050
|
+
ellipsis: true
|
|
1051
|
+
}
|
|
1052
|
+
] as NTable.NColumn.TColumns<TDataItem>;
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
const data: TDataItem[] = [
|
|
1056
|
+
{ key: '1', name: 'Иван Иванов', age: 40, address: 'Москва, ул. Ленина, д. 1' },
|
|
1057
|
+
{ key: '2', name: 'Петр Петров', age: 42, address: 'Санкт-Петербург, ул. Ленина, д. 1' },
|
|
1058
|
+
{ key: '3', name: 'Иван Иванов', age: 18, address: 'Санкт-Петербург, ул. Ленина, д. 2' },
|
|
1059
|
+
{ key: '4', name: 'Петр Петров', age: 28, address: 'Москва, ул. Ленина, д. 2' }
|
|
1060
|
+
];
|
|
1061
|
+
|
|
1062
|
+
const handleChange: ITableProps['onChange'] = (pagination, filters, sorter) => {
|
|
1063
|
+
filteredInfo.value = filters;
|
|
1064
|
+
sortedInfo.value = sorter;
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
const clearFilters = () => {
|
|
1068
|
+
filteredInfo.value = null;
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
const clearAll = () => {
|
|
1072
|
+
filteredInfo.value = null;
|
|
1073
|
+
sortedInfo.value = null;
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
const setAgeSort = () => {
|
|
1077
|
+
sortedInfo.value = { order: 'descend', columnKey: 'age' };
|
|
1078
|
+
};
|
|
1079
|
+
</script>
|
|
1080
|
+
|
|
1081
|
+
<template>
|
|
1082
|
+
<CdsTable :columns="columns" :data="data" @change="handleChange">
|
|
1083
|
+
<template #toolbar>
|
|
1084
|
+
<div class="flex justify-end gap-2">
|
|
1085
|
+
<CdsButton appearance="secondary" @click="clearFilters">Очистить фильтры</CdsButton>
|
|
1086
|
+
<CdsButton appearance="secondary" @click="clearAll">Очистить фильтры и сортировки</CdsButton>
|
|
1087
|
+
<CdsButton appearance="primary" @click="setAgeSort">Сортировать возраст</CdsButton>
|
|
1088
|
+
</div>
|
|
1089
|
+
</template>
|
|
1090
|
+
</CdsTable>
|
|
1091
|
+
</template>
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
### Custom filter panel
|
|
1095
|
+
|
|
1096
|
+
The column's `customFilterDropdown: true` flag enables the `customFilterDropdown` and `customFilterIcon` slots — the filter panel and its icon are drawn by your markup, a search field for example.
|
|
1097
|
+
|
|
1098
|
+
**Example (SFC):**
|
|
1099
|
+
|
|
1100
|
+
```vue
|
|
1101
|
+
<script setup lang="ts">
|
|
1102
|
+
import type { NTable } from '@central-design-system/components';
|
|
1103
|
+
import { CdsButton, CdsDivider, CdsIcon, CdsTable, CdsTextInput } from '@central-design-system/components';
|
|
1104
|
+
import { reactive, ref, toRefs } from 'vue';
|
|
1105
|
+
|
|
1106
|
+
type TDataType = { key: string; name: string; age: number; address: string };
|
|
1107
|
+
|
|
1108
|
+
const state = reactive({
|
|
1109
|
+
searchText: '',
|
|
1110
|
+
searchedColumn: ''
|
|
1111
|
+
});
|
|
1112
|
+
const searchInput = ref();
|
|
1113
|
+
|
|
1114
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
1115
|
+
{
|
|
1116
|
+
title: 'Имя',
|
|
1117
|
+
dataIndex: 'name',
|
|
1118
|
+
key: 'name',
|
|
1119
|
+
customFilterDropdown: true,
|
|
1120
|
+
onFilter: (value, record) =>
|
|
1121
|
+
record.name
|
|
1122
|
+
.toString()
|
|
1123
|
+
.toLowerCase()
|
|
1124
|
+
.includes((value as string).toLowerCase()),
|
|
1125
|
+
onFilterDropdownVisibleChange: (visible) => {
|
|
1126
|
+
if (visible) {
|
|
1127
|
+
setTimeout(() => {
|
|
1128
|
+
searchInput.value?.focus?.();
|
|
1129
|
+
}, 100);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
{ title: 'Возраст', dataIndex: 'age', key: 'age' },
|
|
1134
|
+
{
|
|
1135
|
+
title: 'Адрес',
|
|
1136
|
+
dataIndex: 'address',
|
|
1137
|
+
key: 'address',
|
|
1138
|
+
customFilterDropdown: true,
|
|
1139
|
+
onFilter: (value, record) =>
|
|
1140
|
+
record.address
|
|
1141
|
+
.toString()
|
|
1142
|
+
.toLowerCase()
|
|
1143
|
+
.includes((value as string).toLowerCase()),
|
|
1144
|
+
onFilterDropdownVisibleChange: (visible) => {
|
|
1145
|
+
if (visible) {
|
|
1146
|
+
setTimeout(() => {
|
|
1147
|
+
searchInput.value?.focus?.();
|
|
1148
|
+
}, 100);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
];
|
|
1153
|
+
const data: TDataType[] = [
|
|
1154
|
+
{ key: '1', name: 'Иван Иванов', age: 18, address: 'г. Москва ул. Ленина д. 1' },
|
|
1155
|
+
{ key: '2', name: 'Петр Петров', age: 25, address: 'г. Санкт-Петербург ул. Ленина д. 1' },
|
|
1156
|
+
{ key: '3', name: 'Александр Александрович', age: 48, address: 'г. Екатеринбург ул. Ленина д. 1' },
|
|
1157
|
+
{ key: '4', name: 'Иван Петрович', age: 42, address: 'г. Новосибирск ул. Ленина д. 1' }
|
|
1158
|
+
];
|
|
1159
|
+
|
|
1160
|
+
const handleSearch = (selectedKeys: NTable.TKey[], confirm: Function, dataIndex?: NTable.TDataIndex) => {
|
|
1161
|
+
confirm();
|
|
1162
|
+
state.searchText = String(selectedKeys[0] ?? '');
|
|
1163
|
+
state.searchedColumn = String(dataIndex ?? '');
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
const handleReset = (clearFilters: ({ confirm }: { confirm: boolean }) => void) => {
|
|
1167
|
+
clearFilters({ confirm: true });
|
|
1168
|
+
state.searchText = '';
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
const searchingRegExp = (searchText: string) => new RegExp(`(?<=${searchText})|(?=${searchText})`, 'i');
|
|
1172
|
+
|
|
1173
|
+
const { searchText, searchedColumn } = toRefs(state);
|
|
1174
|
+
</script>
|
|
1175
|
+
|
|
1176
|
+
<template>
|
|
1177
|
+
<CdsTable :columns="columns" :data="data">
|
|
1178
|
+
<template #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
|
|
1179
|
+
<div class="p-1">
|
|
1180
|
+
<cds-text-input
|
|
1181
|
+
ref="searchInput"
|
|
1182
|
+
:placeholder="'Введите ' + String(column?.title ?? '').toLowerCase()"
|
|
1183
|
+
:model-value="selectedKeys[0]"
|
|
1184
|
+
size="sm"
|
|
1185
|
+
clearable
|
|
1186
|
+
hide-messages
|
|
1187
|
+
class="w-[176px]"
|
|
1188
|
+
@update:model-value="(value: any) => setSelectedKeys(value ? [value] : [])"
|
|
1189
|
+
@keydown.enter="handleSearch(selectedKeys, confirm, column?.dataIndex)"
|
|
1190
|
+
/>
|
|
1191
|
+
<cds-divider class="my-3" />
|
|
1192
|
+
<div class="flex gap-2">
|
|
1193
|
+
<cds-button text="Сбросить" size="sm" appearance="secondary" @click="handleReset(clearFilters)" />
|
|
1194
|
+
<cds-button
|
|
1195
|
+
text="Найти"
|
|
1196
|
+
size="sm"
|
|
1197
|
+
prepend-icon="search"
|
|
1198
|
+
@click="handleSearch(selectedKeys, confirm, column?.dataIndex)"
|
|
1199
|
+
/>
|
|
1200
|
+
</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
</template>
|
|
1203
|
+
<template #customFilterIcon>
|
|
1204
|
+
<cds-icon name="search" />
|
|
1205
|
+
</template>
|
|
1206
|
+
<template #bodyCell="{ text, column }">
|
|
1207
|
+
<span v-if="searchText && searchedColumn === column.dataIndex">
|
|
1208
|
+
<template v-for="(fragment, i) in text.toString().split(searchingRegExp(searchText))">
|
|
1209
|
+
<mark v-if="fragment.toLowerCase() === searchText.toLowerCase()" :key="i">
|
|
1210
|
+
{{ fragment }}
|
|
1211
|
+
</mark>
|
|
1212
|
+
<template v-else>{{ fragment }}</template>
|
|
1213
|
+
</template>
|
|
1214
|
+
</span>
|
|
1215
|
+
</template>
|
|
1216
|
+
</CdsTable>
|
|
1217
|
+
</template>
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
## Фиксация и прокрутка
|
|
1221
|
+
|
|
1222
|
+
Работа с таблицами, которые не помещаются на экран: закрепление шапки
|
|
1223
|
+
и колонок при прокрутке и кнопки горизонтального скролла.
|
|
1224
|
+
|
|
1225
|
+
### Fixed header
|
|
1226
|
+
|
|
1227
|
+
The `scroll.y` property limits the height of the table body — the column header stays in place while you scroll vertically.
|
|
1228
|
+
|
|
1229
|
+
**Example (SFC):**
|
|
1230
|
+
|
|
1231
|
+
```vue
|
|
1232
|
+
<script setup lang="ts">
|
|
1233
|
+
import type { NTable } from '@central-design-system/components';
|
|
1234
|
+
import { CdsTable } from '@central-design-system/components';
|
|
1235
|
+
|
|
1236
|
+
type TDataType = { key: number; name: string; age: number; address: string };
|
|
1237
|
+
|
|
1238
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
1239
|
+
{ title: 'Имя', dataIndex: 'name', width: 150 },
|
|
1240
|
+
{ title: 'Возраст', dataIndex: 'age', width: 150 },
|
|
1241
|
+
{ title: 'Адрес', dataIndex: 'address' }
|
|
1242
|
+
];
|
|
1243
|
+
|
|
1244
|
+
const data: TDataType[] = [...Array(100)].map((_, i) => ({
|
|
1245
|
+
key: i,
|
|
1246
|
+
name: `Иван Иванов ${i}`,
|
|
1247
|
+
age: 27,
|
|
1248
|
+
address: `г. Москва, ул. Ленина, д. ${i}`
|
|
1249
|
+
}));
|
|
1250
|
+
</script>
|
|
1251
|
+
|
|
1252
|
+
<template>
|
|
1253
|
+
<CdsTable :columns="columns" :data="data" :pagination="{ total: 50 }" :scroll="{ y: 245 }" />
|
|
1254
|
+
</template>
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
### Fixed columns
|
|
1258
|
+
|
|
1259
|
+
The column's `fixed: 'left' | 'right'` property pins it during horizontal scrolling. Set `scroll.x` wider than the container so that scrolling appears.
|
|
1260
|
+
|
|
1261
|
+
**Example (SFC):**
|
|
1262
|
+
|
|
1263
|
+
```vue
|
|
1264
|
+
<script setup lang="ts">
|
|
1265
|
+
import type { NTable } from '@central-design-system/components';
|
|
1266
|
+
import { CdsButton, CdsTable } from '@central-design-system/components';
|
|
1267
|
+
|
|
1268
|
+
type TDataType = { key: string; name: string; age: number; address: string };
|
|
1269
|
+
|
|
1270
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
1271
|
+
{ title: 'Имя', dataIndex: 'name', key: 'name', fixed: 'left', width: 100 },
|
|
1272
|
+
{ title: 'Возраст', dataIndex: 'age', key: 'age', fixed: 'left', width: 100 },
|
|
1273
|
+
{ title: 'Заголовок 1', dataIndex: 'address', key: '1' },
|
|
1274
|
+
{ title: 'Заголовок 2', dataIndex: 'address', key: '2' },
|
|
1275
|
+
{ title: 'Заголовок 3', dataIndex: 'address', key: '3' },
|
|
1276
|
+
{ title: 'Заголовок 4', dataIndex: 'address', key: '4' },
|
|
1277
|
+
{ title: 'Заголовок 5', dataIndex: 'address', key: '5' },
|
|
1278
|
+
{ title: 'Заголовок 6', dataIndex: 'address', key: '6' },
|
|
1279
|
+
{ title: 'Заголовок 7', dataIndex: 'address', key: '7' },
|
|
1280
|
+
{ title: 'Заголовок 8', dataIndex: 'address', key: '8' },
|
|
1281
|
+
{ title: 'Действия', key: 'actions', fixed: 'right', width: 100 }
|
|
1282
|
+
];
|
|
1283
|
+
|
|
1284
|
+
const data: TDataType[] = [
|
|
1285
|
+
{ key: '1', name: 'Иван', age: 32, address: 'г. Москва' },
|
|
1286
|
+
{ key: '2', name: 'Петр', age: 40, address: 'г. Пермь' },
|
|
1287
|
+
{ key: '3', name: 'Вадим', age: 32, address: 'г. Москва' },
|
|
1288
|
+
{ key: '4', name: 'Евгений', age: 40, address: 'г. Балашиха' }
|
|
1289
|
+
];
|
|
1290
|
+
</script>
|
|
1291
|
+
|
|
1292
|
+
<template>
|
|
1293
|
+
<CdsTable :columns="columns" :data="data" :scroll="{ x: 1300, y: 1000 }" :pagination="{ total: 2 }">
|
|
1294
|
+
<template #bodyCell="{ column }">
|
|
1295
|
+
<template v-if="column.key === 'actions'">
|
|
1296
|
+
<CdsButton size="sm" appearance="secondary">Удалить</CdsButton>
|
|
1297
|
+
</template>
|
|
1298
|
+
</template>
|
|
1299
|
+
</CdsTable>
|
|
1300
|
+
</template>
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
### Fixed columns and header
|
|
1304
|
+
|
|
1305
|
+
Together, `scroll.x` and `scroll.y` pin both the columns and the header — the center of the table scrolls in both directions.
|
|
1306
|
+
|
|
1307
|
+
**Example (SFC):**
|
|
1308
|
+
|
|
1309
|
+
```vue
|
|
1310
|
+
<script setup lang="ts">
|
|
1311
|
+
import type { NTable } from '@central-design-system/components';
|
|
1312
|
+
import { CdsButton, CdsTable } from '@central-design-system/components';
|
|
1313
|
+
|
|
1314
|
+
type TDataType = { key: number; name: string; age: number; address: string };
|
|
1315
|
+
|
|
1316
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
1317
|
+
{ title: 'Имя', dataIndex: 'name', key: 'name', fixed: 'left', width: 100 },
|
|
1318
|
+
{ title: 'Возраст', dataIndex: 'age', key: 'age', fixed: 'left', width: 100 },
|
|
1319
|
+
{ title: 'Заголовок 1', dataIndex: 'address', key: '1', width: 150 },
|
|
1320
|
+
{ title: 'Заголовок 2', dataIndex: 'address', key: '2', width: 150 },
|
|
1321
|
+
{ title: 'Заголовок 3', dataIndex: 'address', key: '3', width: 150 },
|
|
1322
|
+
{ title: 'Заголовок 4', dataIndex: 'address', key: '4', width: 150 },
|
|
1323
|
+
{ title: 'Заголовок 5', dataIndex: 'address', key: '5', width: 150 },
|
|
1324
|
+
{ title: 'Заголовок 6', dataIndex: 'address', key: '6', width: 150 },
|
|
1325
|
+
{ title: 'Заголовок 7', dataIndex: 'address', key: '7', width: 150 },
|
|
1326
|
+
{ title: 'Заголовок 8', dataIndex: 'address', key: '8', width: 150 },
|
|
1327
|
+
{ title: 'Действия', key: 'actions', fixed: 'right', width: 100 }
|
|
1328
|
+
];
|
|
1329
|
+
|
|
1330
|
+
const data: TDataType[] = [...Array(10)].map((_, i) => ({
|
|
1331
|
+
key: i,
|
|
1332
|
+
name: `Иван ${i}`,
|
|
1333
|
+
age: 24,
|
|
1334
|
+
address: `ул. Главная, д. ${i}`
|
|
1335
|
+
}));
|
|
1336
|
+
</script>
|
|
1337
|
+
|
|
1338
|
+
<template>
|
|
1339
|
+
<CdsTable :columns="columns" :data="data" :scroll="{ x: 1500, y: 300 }" :pagination="false">
|
|
1340
|
+
<template #bodyCell="{ column }">
|
|
1341
|
+
<template v-if="column.key === 'actions'">
|
|
1342
|
+
<CdsButton size="sm" appearance="secondary">Удалить</CdsButton>
|
|
1343
|
+
</template>
|
|
1344
|
+
</template>
|
|
1345
|
+
</CdsTable>
|
|
1346
|
+
</template>
|
|
1347
|
+
```
|
|
1348
|
+
|
|
1349
|
+
### Scroll buttons
|
|
1350
|
+
|
|
1351
|
+
A table with horizontal scrolling can get scroll buttons: take the DOM node of the table body (`.cds-table__body`) through the component `ref` and change its `scrollLeft` on click.
|
|
1352
|
+
|
|
1353
|
+
**Example (SFC):**
|
|
1354
|
+
|
|
1355
|
+
```vue
|
|
1356
|
+
<script setup lang="ts">
|
|
1357
|
+
import type { NTable } from '@central-design-system/components';
|
|
1358
|
+
import { CdsButton, CdsTable } from '@central-design-system/components';
|
|
1359
|
+
import { ref, unref } from 'vue';
|
|
1360
|
+
|
|
1361
|
+
type TDataType = { key: string; name: string; age: number; address: string };
|
|
1362
|
+
|
|
1363
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
1364
|
+
{ title: 'Имя', dataIndex: 'name', key: 'name', fixed: 'left', width: 100 },
|
|
1365
|
+
{ title: 'Возраст', dataIndex: 'age', key: 'age', fixed: 'left', width: 100 },
|
|
1366
|
+
{ title: 'Заголовок 1', dataIndex: 'address', key: '1' },
|
|
1367
|
+
{ title: 'Заголовок 2', dataIndex: 'address', key: '2' },
|
|
1368
|
+
{ title: 'Заголовок 3', dataIndex: 'address', key: '3' },
|
|
1369
|
+
{ title: 'Заголовок 4', dataIndex: 'address', key: '4' },
|
|
1370
|
+
{ title: 'Заголовок 5', dataIndex: 'address', key: '5' },
|
|
1371
|
+
{ title: 'Заголовок 6', dataIndex: 'address', key: '6' },
|
|
1372
|
+
{ title: 'Заголовок 7', dataIndex: 'address', key: '7' },
|
|
1373
|
+
{ title: 'Заголовок 8', dataIndex: 'address', key: '8' },
|
|
1374
|
+
{ title: 'Заголовок 9', dataIndex: 'address', key: '9' },
|
|
1375
|
+
{ title: 'Заголовок 10', dataIndex: 'address', key: '10' },
|
|
1376
|
+
{ title: 'Действия', key: 'actions', fixed: 'right', width: 100 }
|
|
1377
|
+
];
|
|
1378
|
+
|
|
1379
|
+
const data: TDataType[] = [
|
|
1380
|
+
{ key: '1', name: 'Иван', age: 32, address: 'г. Москва' },
|
|
1381
|
+
{ key: '2', name: 'Петр', age: 40, address: 'г. Москва' }
|
|
1382
|
+
];
|
|
1383
|
+
|
|
1384
|
+
// Функционал для реализации кнопок скролла
|
|
1385
|
+
const scrollStep = 50;
|
|
1386
|
+
const tableRef = ref<InstanceType<typeof CdsTable> | null>(null);
|
|
1387
|
+
|
|
1388
|
+
function handleClickScrollButton(_event: MouseEvent, isLeft: boolean) {
|
|
1389
|
+
const tableBody = unref(tableRef)?.$el.querySelector('.cds-table__body');
|
|
1390
|
+
|
|
1391
|
+
if (tableBody) {
|
|
1392
|
+
tableBody.scrollLeft += isLeft ? -scrollStep : scrollStep;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
</script>
|
|
1396
|
+
|
|
1397
|
+
<template>
|
|
1398
|
+
<CdsTable ref="tableRef" :columns="columns" :data="data" :scroll="{ x: 1300, y: 1000 }" :pagination="false">
|
|
1399
|
+
<template #header>
|
|
1400
|
+
<div class="flex justify-between">
|
|
1401
|
+
<div>
|
|
1402
|
+
<h4 class="mb-2">Заголовок таблицы</h4>
|
|
1403
|
+
<p class="text-content-secondary">Описание таблицы в слоте заголовка таблицы</p>
|
|
1404
|
+
</div>
|
|
1405
|
+
<div>
|
|
1406
|
+
<CdsButton text="Действие" />
|
|
1407
|
+
</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
</template>
|
|
1410
|
+
<template #toolbar>
|
|
1411
|
+
<div class="flex justify-end">
|
|
1412
|
+
<div class="flex gap-2">
|
|
1413
|
+
<CdsButton icon="chevron-left" appearance="tertiary" @click="(e) => handleClickScrollButton(e, true)" />
|
|
1414
|
+
<CdsButton icon="chevron-right" appearance="tertiary" @click="(e) => handleClickScrollButton(e, false)" />
|
|
1415
|
+
</div>
|
|
1416
|
+
</div>
|
|
1417
|
+
</template>
|
|
1418
|
+
|
|
1419
|
+
<template #bodyCell="{ column }">
|
|
1420
|
+
<template v-if="column.key === 'actions'">
|
|
1421
|
+
<CdsButton appearance="secondary" size="sm">Удалить</CdsButton>
|
|
1422
|
+
</template>
|
|
1423
|
+
</template>
|
|
1424
|
+
|
|
1425
|
+
<template #footer>
|
|
1426
|
+
<div class="flex justify-end gap-2">
|
|
1427
|
+
<CdsButton icon="chevron-left" appearance="tertiary" @click="(e) => handleClickScrollButton(e, true)" />
|
|
1428
|
+
<CdsButton icon="chevron-right" appearance="tertiary" @click="(e) => handleClickScrollButton(e, false)" />
|
|
1429
|
+
</div>
|
|
1430
|
+
</template>
|
|
1431
|
+
</CdsTable>
|
|
1432
|
+
</template>
|
|
1433
|
+
```
|
|
1434
|
+
|
|
1435
|
+
## Сценарии
|
|
1436
|
+
|
|
1437
|
+
Готовые рецепты продуктовых таблиц: данные с сервера, модерация со
|
|
1438
|
+
сложной шапкой, встроенная пагинация со скелетоном и вложенные списки.
|
|
1439
|
+
|
|
1440
|
+
### Data from the server
|
|
1441
|
+
|
|
1442
|
+
Pagination, sorting and filters are handled on the backend: the `change` event gives you the current state of the table, the handler requests a page of data, and the `loading` prop shows the request in progress.
|
|
1443
|
+
|
|
1444
|
+
**Example (SFC):**
|
|
1445
|
+
|
|
1446
|
+
```vue
|
|
1447
|
+
<script setup lang="ts">
|
|
1448
|
+
import type { ITableProps } from '@central-design-system/components';
|
|
1449
|
+
import { CdsTable } from '@central-design-system/components';
|
|
1450
|
+
import { computed, onMounted, ref } from 'vue';
|
|
1451
|
+
|
|
1452
|
+
type APIResponse = {
|
|
1453
|
+
results: {
|
|
1454
|
+
gender: 'female' | 'male';
|
|
1455
|
+
name: { first: string; last: string };
|
|
1456
|
+
email: string;
|
|
1457
|
+
login: { uuid: string };
|
|
1458
|
+
location: { city: string; country: string };
|
|
1459
|
+
}[];
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
const columns = [
|
|
1463
|
+
{ title: 'Имя', dataIndex: 'name', sorter: true, width: '20%' },
|
|
1464
|
+
{
|
|
1465
|
+
title: 'Пол',
|
|
1466
|
+
dataIndex: 'gender',
|
|
1467
|
+
filters: [
|
|
1468
|
+
{ text: 'Мужчина', value: 'male' },
|
|
1469
|
+
{ text: 'Женщина', value: 'female' }
|
|
1470
|
+
],
|
|
1471
|
+
width: '20%'
|
|
1472
|
+
},
|
|
1473
|
+
{ title: 'Email', dataIndex: 'email' },
|
|
1474
|
+
{ title: 'Адрес', dataIndex: 'location', sorter: true }
|
|
1475
|
+
];
|
|
1476
|
+
const dataSource = ref<APIResponse['results']>([]);
|
|
1477
|
+
|
|
1478
|
+
const FIRST_PAGE = 1;
|
|
1479
|
+
const PER_PAGE = 10;
|
|
1480
|
+
const TOTAL = 100;
|
|
1481
|
+
const pagination = computed(() => ({
|
|
1482
|
+
total: TOTAL,
|
|
1483
|
+
length: PER_PAGE
|
|
1484
|
+
}));
|
|
1485
|
+
|
|
1486
|
+
const isLoading = ref(false);
|
|
1487
|
+
|
|
1488
|
+
async function queryData(params: Record<string, any>) {
|
|
1489
|
+
try {
|
|
1490
|
+
isLoading.value = true;
|
|
1491
|
+
|
|
1492
|
+
const query = new URLSearchParams(
|
|
1493
|
+
Object.entries(params)
|
|
1494
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
1495
|
+
.map(([key, value]) => [key, String(value)])
|
|
1496
|
+
);
|
|
1497
|
+
const response = await fetch(`https://randomuser.me/api?${query}`);
|
|
1498
|
+
const data: APIResponse = await response.json();
|
|
1499
|
+
|
|
1500
|
+
dataSource.value = data.results ?? [];
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
console.error(error);
|
|
1503
|
+
} finally {
|
|
1504
|
+
isLoading.value = false;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
onMounted(() => {
|
|
1509
|
+
queryData({ results: PER_PAGE, page: FIRST_PAGE });
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
const handleTableChange: ITableProps['onChange'] = (paginationInfo, filters: any, sorter: any) => {
|
|
1513
|
+
queryData({
|
|
1514
|
+
results: paginationInfo.length as number,
|
|
1515
|
+
page: paginationInfo.modelValue,
|
|
1516
|
+
sortField: sorter.field,
|
|
1517
|
+
sortOrder: sorter.order,
|
|
1518
|
+
...filters
|
|
1519
|
+
});
|
|
1520
|
+
};
|
|
1521
|
+
</script>
|
|
1522
|
+
|
|
1523
|
+
<template>
|
|
1524
|
+
<CdsTable
|
|
1525
|
+
:data="dataSource"
|
|
1526
|
+
:columns="columns"
|
|
1527
|
+
:row-key="(record) => record.login.uuid"
|
|
1528
|
+
:pagination="pagination"
|
|
1529
|
+
:loading="isLoading"
|
|
1530
|
+
@change="handleTableChange"
|
|
1531
|
+
>
|
|
1532
|
+
<template #bodyCell="{ column, text }">
|
|
1533
|
+
<template v-if="column.dataIndex === 'name'">{{ text.first }} {{ text.last }}</template>
|
|
1534
|
+
<template v-if="column.dataIndex === 'location'">{{ text.country }}, {{ text.city }}</template>
|
|
1535
|
+
</template>
|
|
1536
|
+
</CdsTable>
|
|
1537
|
+
</template>
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1540
|
+
### Product moderation
|
|
1541
|
+
|
|
1542
|
+
A header with tabs, a toolbar with search and filters, an empty state with an action and external pagination in the footer — a typical table for a moderation section.
|
|
1543
|
+
|
|
1544
|
+
**Example (SFC):**
|
|
1545
|
+
|
|
1546
|
+
```vue
|
|
1547
|
+
<script setup lang="ts">
|
|
1548
|
+
import type { NTable } from '@central-design-system/components';
|
|
1549
|
+
import {
|
|
1550
|
+
CdsBadge,
|
|
1551
|
+
CdsButton,
|
|
1552
|
+
CdsEmptyState,
|
|
1553
|
+
CdsFunctionalButton,
|
|
1554
|
+
CdsIcon,
|
|
1555
|
+
CdsLoader,
|
|
1556
|
+
CdsPagination,
|
|
1557
|
+
CdsSelect,
|
|
1558
|
+
CdsSkeletonLoader,
|
|
1559
|
+
CdsTab,
|
|
1560
|
+
CdsTable,
|
|
1561
|
+
CdsTabs,
|
|
1562
|
+
CdsTag,
|
|
1563
|
+
CdsTextInput,
|
|
1564
|
+
CdsTooltip,
|
|
1565
|
+
utils
|
|
1566
|
+
} from '@central-design-system/components';
|
|
1567
|
+
import { computed, onMounted, ref, unref, watch } from 'vue';
|
|
1568
|
+
|
|
1569
|
+
const { debounce, plurals } = utils;
|
|
1570
|
+
|
|
1571
|
+
type TDataItem = {
|
|
1572
|
+
key: number;
|
|
1573
|
+
sku: string;
|
|
1574
|
+
date: string;
|
|
1575
|
+
name: string;
|
|
1576
|
+
category: string;
|
|
1577
|
+
tolerance: number;
|
|
1578
|
+
count: string;
|
|
1579
|
+
base_price: number | string;
|
|
1580
|
+
percent: number;
|
|
1581
|
+
price: number | string;
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
1585
|
+
{ title: 'Артикул', dataIndex: 'sku', width: 140 },
|
|
1586
|
+
{ title: 'Дата', dataIndex: 'date', width: 140 },
|
|
1587
|
+
{ title: 'Наименование', dataIndex: 'name', width: 350 },
|
|
1588
|
+
{ title: 'Толеранс, %', dataIndex: 'tolerance', align: 'right', width: 110 },
|
|
1589
|
+
{ title: 'Единица', dataIndex: 'count', width: 110 },
|
|
1590
|
+
{ title: 'Базовая цена, ₽', dataIndex: 'base_price', align: 'right', width: 140 },
|
|
1591
|
+
{ title: '%', dataIndex: 'percent', align: 'right', width: 70 },
|
|
1592
|
+
{ title: 'Новая цена, ₽', dataIndex: 'price', align: 'right', width: 140 }
|
|
1593
|
+
];
|
|
1594
|
+
const data = ref<TDataItem[]>([]);
|
|
1595
|
+
const filtersData = ['Узлы, устройства и инструменты', 'Изделия производственные', 'Машины установки и оборудование'];
|
|
1596
|
+
const isLoading = ref(false);
|
|
1597
|
+
const tableDataSwitcher = ref(0);
|
|
1598
|
+
const isActiveFilters = ref(false);
|
|
1599
|
+
|
|
1600
|
+
const currentPage = ref(1);
|
|
1601
|
+
const totalData = ref(100);
|
|
1602
|
+
const perPage = ref(10);
|
|
1603
|
+
const pagesLength = computed(() => Math.ceil(unref(totalData) / unref(perPage)));
|
|
1604
|
+
const pageSizeItems = Array.from(new Set([5, 10, 15, 20, 30, 50]), (item) => ({
|
|
1605
|
+
value: item,
|
|
1606
|
+
title: item.toString()
|
|
1607
|
+
}));
|
|
1608
|
+
|
|
1609
|
+
const queryParams = computed(() => ({
|
|
1610
|
+
page: unref(currentPage),
|
|
1611
|
+
perPage: unref(perPage)
|
|
1612
|
+
}));
|
|
1613
|
+
|
|
1614
|
+
const formatPrice = (price: number | string) =>
|
|
1615
|
+
new Intl.NumberFormat('ru-RU', {
|
|
1616
|
+
style: 'decimal',
|
|
1617
|
+
minimumFractionDigits: 2,
|
|
1618
|
+
maximumFractionDigits: 2
|
|
1619
|
+
}).format(+price);
|
|
1620
|
+
|
|
1621
|
+
const queryData = async (params?: Record<string, any>) => {
|
|
1622
|
+
try {
|
|
1623
|
+
isLoading.value = true;
|
|
1624
|
+
|
|
1625
|
+
const query = new URLSearchParams({ example: 'example1', ...params });
|
|
1626
|
+
const response = await fetch(`https://cds-api.b2b-center.ru/api/v1/examples/data-table?${query}`);
|
|
1627
|
+
const { data: examples, meta: metaInfo } = await response.json();
|
|
1628
|
+
// Принудительная задержка для примера загрузки
|
|
1629
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1630
|
+
|
|
1631
|
+
totalData.value = metaInfo.total;
|
|
1632
|
+
perPage.value = metaInfo.perPage;
|
|
1633
|
+
|
|
1634
|
+
data.value =
|
|
1635
|
+
examples.map((item: TDataItem, idx: number) => ({
|
|
1636
|
+
...item,
|
|
1637
|
+
base_price: formatPrice(item.base_price),
|
|
1638
|
+
price: formatPrice(item.price),
|
|
1639
|
+
key: idx
|
|
1640
|
+
})) ?? [];
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
console.error(error);
|
|
1643
|
+
} finally {
|
|
1644
|
+
isLoading.value = false;
|
|
1645
|
+
}
|
|
1646
|
+
};
|
|
1647
|
+
|
|
1648
|
+
const getDataTableExample = () => {
|
|
1649
|
+
queryData(unref(queryParams));
|
|
1650
|
+
};
|
|
1651
|
+
const clearDataTableExample = () => {
|
|
1652
|
+
data.value = [];
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
const isPublishLoading = ref(false);
|
|
1656
|
+
const publishData = async () => {
|
|
1657
|
+
isPublishLoading.value = true;
|
|
1658
|
+
|
|
1659
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1660
|
+
selectedRowKeys.value = [];
|
|
1661
|
+
|
|
1662
|
+
isPublishLoading.value = false;
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
const selectedRowKeys = ref<NTable.TKey[]>([]);
|
|
1666
|
+
const selectedRowText = computed(() => plurals(unref(selectedRowKeys).length, ['строка', 'строки', 'строк']));
|
|
1667
|
+
|
|
1668
|
+
const cancelSelected = () => {
|
|
1669
|
+
selectedRowKeys.value = [];
|
|
1670
|
+
};
|
|
1671
|
+
|
|
1672
|
+
const onSelectChange = (selectedKeys: NTable.TKey[]) => {
|
|
1673
|
+
selectedRowKeys.value = selectedKeys;
|
|
1674
|
+
};
|
|
1675
|
+
|
|
1676
|
+
const handleSearch = debounce(async () => {
|
|
1677
|
+
await queryData(unref(queryParams));
|
|
1678
|
+
}, 500);
|
|
1679
|
+
|
|
1680
|
+
onMounted(() => {
|
|
1681
|
+
queryData(unref(queryParams));
|
|
1682
|
+
});
|
|
1683
|
+
|
|
1684
|
+
watch([currentPage, perPage], () => {
|
|
1685
|
+
queryData(unref(queryParams));
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
watch(tableDataSwitcher, () => {
|
|
1689
|
+
queryData(unref(queryParams));
|
|
1690
|
+
});
|
|
1691
|
+
</script>
|
|
1692
|
+
|
|
1693
|
+
<template>
|
|
1694
|
+
<CdsTable
|
|
1695
|
+
class="min-w-[860px]"
|
|
1696
|
+
:columns="columns"
|
|
1697
|
+
:data="data"
|
|
1698
|
+
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
|
1699
|
+
:pagination="false"
|
|
1700
|
+
>
|
|
1701
|
+
<template #header>
|
|
1702
|
+
<div class="flex justify-between">
|
|
1703
|
+
<div>
|
|
1704
|
+
<h4>Список товаров, поступивших на модерацию</h4>
|
|
1705
|
+
<p class="mb-4 text-content-secondary">
|
|
1706
|
+
Согласуйте или отклоните товары поставщика для их доступности в каталоге
|
|
1707
|
+
</p>
|
|
1708
|
+
<div class="flex">
|
|
1709
|
+
<CdsTabs v-model="tableDataSwitcher">
|
|
1710
|
+
<CdsTab>Все 654</CdsTab>
|
|
1711
|
+
<CdsTab>Новые 340</CdsTab>
|
|
1712
|
+
<CdsTab>Обновленные 314</CdsTab>
|
|
1713
|
+
</CdsTabs>
|
|
1714
|
+
</div>
|
|
1715
|
+
</div>
|
|
1716
|
+
<div>
|
|
1717
|
+
<CdsButton appearance="secondary" text="Удалить данные" @click="clearDataTableExample" />
|
|
1718
|
+
</div>
|
|
1719
|
+
</div>
|
|
1720
|
+
</template>
|
|
1721
|
+
|
|
1722
|
+
<template #toolbar>
|
|
1723
|
+
<div class="flex items-center justify-between">
|
|
1724
|
+
<div class="flex items-center gap-4">
|
|
1725
|
+
<p>
|
|
1726
|
+
Выбрано: <strong>{{ selectedRowKeys.length }}</strong> {{ selectedRowText }}
|
|
1727
|
+
</p>
|
|
1728
|
+
<CdsTextInput
|
|
1729
|
+
prepend-inner-icon="search"
|
|
1730
|
+
placeholder="Артикул, наименование"
|
|
1731
|
+
hide-messages
|
|
1732
|
+
class="w-[250px]"
|
|
1733
|
+
@update:model-value="handleSearch"
|
|
1734
|
+
/>
|
|
1735
|
+
<CdsButton
|
|
1736
|
+
:appearance="isActiveFilters ? 'secondary' : 'tertiary'"
|
|
1737
|
+
prepend-icon="filters"
|
|
1738
|
+
@click="isActiveFilters = !isActiveFilters"
|
|
1739
|
+
>
|
|
1740
|
+
Фильтры
|
|
1741
|
+
<CdsBadge v-if="isActiveFilters" content="10" inline />
|
|
1742
|
+
</CdsButton>
|
|
1743
|
+
</div>
|
|
1744
|
+
<div class="flex gap-2">
|
|
1745
|
+
<CdsButton appearance="secondary" text="Отклонить" @click="cancelSelected" />
|
|
1746
|
+
<CdsButton text="Опубликовать" :loading="isPublishLoading" @click="publishData" />
|
|
1747
|
+
</div>
|
|
1748
|
+
</div>
|
|
1749
|
+
<div v-if="isActiveFilters" class="mt-4">
|
|
1750
|
+
<h6 class="mb-2">Активные фильтры:</h6>
|
|
1751
|
+
<div class="flex flex-col gap-2">
|
|
1752
|
+
<div class="flex">
|
|
1753
|
+
<div class="flex items-start">
|
|
1754
|
+
<CdsFunctionalButton icon="remove" @click="isActiveFilters = false" />
|
|
1755
|
+
<div class="mx-1 flex h-[24px] items-center">Категории:</div>
|
|
1756
|
+
</div>
|
|
1757
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
1758
|
+
<CdsTag v-for="item in filtersData" :key="item">{{ item }}</CdsTag>
|
|
1759
|
+
</div>
|
|
1760
|
+
</div>
|
|
1761
|
+
</div>
|
|
1762
|
+
</div>
|
|
1763
|
+
</template>
|
|
1764
|
+
|
|
1765
|
+
<template #headerCell="{ title, column }">
|
|
1766
|
+
<div class="flex items-center" :class="{ 'justify-end': column.align === 'right' }">
|
|
1767
|
+
<span>{{ title }}</span>
|
|
1768
|
+
<CdsTooltip v-if="['count', 'percent'].includes(String(column.dataIndex))">
|
|
1769
|
+
<template #activator="{ props }">
|
|
1770
|
+
<CdsIcon name="info" class="ml-2" v-bind="props" />
|
|
1771
|
+
</template>
|
|
1772
|
+
Информация о столбце
|
|
1773
|
+
</CdsTooltip>
|
|
1774
|
+
</div>
|
|
1775
|
+
</template>
|
|
1776
|
+
|
|
1777
|
+
<template #bodyCell>
|
|
1778
|
+
<template v-if="isLoading">
|
|
1779
|
+
<CdsSkeletonLoader type="text" />
|
|
1780
|
+
</template>
|
|
1781
|
+
</template>
|
|
1782
|
+
|
|
1783
|
+
<template #footer>
|
|
1784
|
+
<div class="flex items-center justify-between">
|
|
1785
|
+
<CdsPagination v-model="currentPage" :length="pagesLength" class="w-full" />
|
|
1786
|
+
<div class="flex w-full justify-center gap-1">
|
|
1787
|
+
Отображено <strong>{{ perPage }}</strong> из <strong>{{ totalData }}</strong>
|
|
1788
|
+
</div>
|
|
1789
|
+
<div class="flex w-full items-center justify-end gap-2">
|
|
1790
|
+
<span class="text-content-tertiary">Строк на странице:</span>
|
|
1791
|
+
<CdsSelect v-model="perPage" :items="pageSizeItems" hide-messages class="max-w-[70px]" />
|
|
1792
|
+
</div>
|
|
1793
|
+
</div>
|
|
1794
|
+
</template>
|
|
1795
|
+
|
|
1796
|
+
<template #emptyText>
|
|
1797
|
+
<div class="flex h-[500px] items-center justify-center">
|
|
1798
|
+
<CdsLoader v-if="isLoading" fixed overlay />
|
|
1799
|
+
<CdsEmptyState
|
|
1800
|
+
title="Нет данных"
|
|
1801
|
+
description="Нажмите кнопку ниже чтобы добавить данные в таблицу"
|
|
1802
|
+
illustration="documents"
|
|
1803
|
+
>
|
|
1804
|
+
<template #actions>
|
|
1805
|
+
<CdsButton text="Добавить данные" @click="getDataTableExample" />
|
|
1806
|
+
</template>
|
|
1807
|
+
</CdsEmptyState>
|
|
1808
|
+
</div>
|
|
1809
|
+
</template>
|
|
1810
|
+
</CdsTable>
|
|
1811
|
+
</template>
|
|
1812
|
+
```
|
|
1813
|
+
|
|
1814
|
+
### Items and prices
|
|
1815
|
+
|
|
1816
|
+
Built-in pagination with a counter and a page size selector, a skeleton while loading and composite cells with an extra line of text.
|
|
1817
|
+
|
|
1818
|
+
**Example (SFC):**
|
|
1819
|
+
|
|
1820
|
+
```vue
|
|
1821
|
+
<script setup lang="ts">
|
|
1822
|
+
import type { ITableProps, NTable } from '@central-design-system/components';
|
|
1823
|
+
import {
|
|
1824
|
+
CdsButton,
|
|
1825
|
+
CdsEmptyState,
|
|
1826
|
+
CdsLoader,
|
|
1827
|
+
CdsSkeletonLoader,
|
|
1828
|
+
CdsTab,
|
|
1829
|
+
CdsTable,
|
|
1830
|
+
CdsTabs,
|
|
1831
|
+
CdsTextInput,
|
|
1832
|
+
utils
|
|
1833
|
+
} from '@central-design-system/components';
|
|
1834
|
+
import { computed, onMounted, ref, unref, watch } from 'vue';
|
|
1835
|
+
|
|
1836
|
+
const { plurals } = utils;
|
|
1837
|
+
|
|
1838
|
+
type TDataItem = {
|
|
1839
|
+
key: number;
|
|
1840
|
+
id: number;
|
|
1841
|
+
name: string;
|
|
1842
|
+
best_price: number | string;
|
|
1843
|
+
qty: number;
|
|
1844
|
+
price: number | string;
|
|
1845
|
+
tax: number;
|
|
1846
|
+
delay: number;
|
|
1847
|
+
download: boolean;
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1850
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
1851
|
+
{ title: '№', dataIndex: 'id', width: 40 },
|
|
1852
|
+
{ title: 'Позиция', dataIndex: 'name', width: 350 },
|
|
1853
|
+
{ title: 'Лучшая цена с НДС, ₽', dataIndex: 'best_price', width: 200, align: 'right' },
|
|
1854
|
+
{ title: 'Количество', dataIndex: 'qty', align: 'right', width: 80 },
|
|
1855
|
+
{ title: 'Цена за единицу с НДС, ₽', dataIndex: 'price', align: 'right', width: 200 },
|
|
1856
|
+
{ title: 'НДС, %', dataIndex: 'tax', align: 'right', width: 80 },
|
|
1857
|
+
{ title: 'Срок поставки, дней', dataIndex: 'delay', align: 'right', width: 180 },
|
|
1858
|
+
{ title: 'Документация', dataIndex: 'docs', align: 'right', width: 80 }
|
|
1859
|
+
];
|
|
1860
|
+
|
|
1861
|
+
const data = ref<TDataItem[]>([]);
|
|
1862
|
+
const total = ref(10);
|
|
1863
|
+
const isLoading = ref(false);
|
|
1864
|
+
const tableDataSwitcher = ref(0);
|
|
1865
|
+
|
|
1866
|
+
const formatPrice = (price: number | string) =>
|
|
1867
|
+
new Intl.NumberFormat('ru-RU', {
|
|
1868
|
+
style: 'decimal',
|
|
1869
|
+
minimumFractionDigits: 2,
|
|
1870
|
+
maximumFractionDigits: 2
|
|
1871
|
+
}).format(+price);
|
|
1872
|
+
|
|
1873
|
+
const queryDataTableExample = async () => {
|
|
1874
|
+
try {
|
|
1875
|
+
isLoading.value = true;
|
|
1876
|
+
|
|
1877
|
+
const response = await fetch('https://cds-api.b2b-center.ru/api/v1/examples/data-table?example=example2');
|
|
1878
|
+
const { data: examples } = await response.json();
|
|
1879
|
+
// Принудительная задержка для примера загрузки
|
|
1880
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1881
|
+
|
|
1882
|
+
data.value =
|
|
1883
|
+
examples.map((item: TDataItem, idx: number) => ({
|
|
1884
|
+
...item,
|
|
1885
|
+
best_price: formatPrice(item.best_price),
|
|
1886
|
+
price: formatPrice(item.price),
|
|
1887
|
+
key: idx
|
|
1888
|
+
})) ?? [];
|
|
1889
|
+
} catch (error) {
|
|
1890
|
+
console.error(error);
|
|
1891
|
+
} finally {
|
|
1892
|
+
isLoading.value = false;
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1896
|
+
const selectedRowKeys = ref<NTable.TKey[]>([]);
|
|
1897
|
+
const selectedRowText = computed(() => plurals(unref(selectedRowKeys).length, ['строка', 'строки', 'строк']));
|
|
1898
|
+
|
|
1899
|
+
const onSelectChange = (selectedKeys: NTable.TKey[]) => {
|
|
1900
|
+
selectedRowKeys.value = selectedKeys;
|
|
1901
|
+
};
|
|
1902
|
+
|
|
1903
|
+
const onTableChange: ITableProps<TDataItem>['onChange'] = (pagination) => {
|
|
1904
|
+
total.value = pagination.total!;
|
|
1905
|
+
};
|
|
1906
|
+
|
|
1907
|
+
onMounted(() => {
|
|
1908
|
+
queryDataTableExample();
|
|
1909
|
+
});
|
|
1910
|
+
|
|
1911
|
+
watch(tableDataSwitcher, () => {
|
|
1912
|
+
queryDataTableExample();
|
|
1913
|
+
});
|
|
1914
|
+
</script>
|
|
1915
|
+
|
|
1916
|
+
<template>
|
|
1917
|
+
<CdsTable
|
|
1918
|
+
class="min-w-[860px]"
|
|
1919
|
+
:columns="columns"
|
|
1920
|
+
:data="data"
|
|
1921
|
+
:pagination="{ total, position: ['bottomLeft'], counter: true, size: true }"
|
|
1922
|
+
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
|
1923
|
+
@change="onTableChange"
|
|
1924
|
+
>
|
|
1925
|
+
<template #header>
|
|
1926
|
+
<div class="flex justify-between">
|
|
1927
|
+
<div>
|
|
1928
|
+
<h4 class="mb-4">Позиции и цены</h4>
|
|
1929
|
+
<CdsTabs v-model="tableDataSwitcher">
|
|
1930
|
+
<CdsTab>Заявки на позиции</CdsTab>
|
|
1931
|
+
<CdsTab>Позиции без заявки</CdsTab>
|
|
1932
|
+
</CdsTabs>
|
|
1933
|
+
</div>
|
|
1934
|
+
<div>
|
|
1935
|
+
<CdsButton prepend-icon="upload" appearance="secondary" text="Загрузить из файла" />
|
|
1936
|
+
</div>
|
|
1937
|
+
</div>
|
|
1938
|
+
</template>
|
|
1939
|
+
|
|
1940
|
+
<template #toolbar>
|
|
1941
|
+
<div class="flex items-end justify-between">
|
|
1942
|
+
<div>
|
|
1943
|
+
<p>
|
|
1944
|
+
Выбрано: <strong>{{ selectedRowKeys.length }}</strong> {{ selectedRowText }}
|
|
1945
|
+
</p>
|
|
1946
|
+
</div>
|
|
1947
|
+
<div class="flex gap-2">
|
|
1948
|
+
<CdsTextInput prefix="на" suffix="%" model-value="5" hide-messages class="mr-2 w-[127px]" />
|
|
1949
|
+
<CdsButton appearance="secondary" text="Пересчитать" />
|
|
1950
|
+
<CdsButton text="Сохранить цены" />
|
|
1951
|
+
</div>
|
|
1952
|
+
</div>
|
|
1953
|
+
</template>
|
|
1954
|
+
|
|
1955
|
+
<template #bodyCell="{ column, record, text }">
|
|
1956
|
+
<template v-if="isLoading">
|
|
1957
|
+
<CdsSkeletonLoader type="text@4" />
|
|
1958
|
+
</template>
|
|
1959
|
+
<template v-else>
|
|
1960
|
+
<div v-if="column.dataIndex === 'name'" class="flex flex-col">
|
|
1961
|
+
<p>{{ text }}</p>
|
|
1962
|
+
<span class="text-content-secondary">Количество: 100 шт</span>
|
|
1963
|
+
<span class="text-content-secondary">Максимальная цена: 150.00 ₽ с НДС 20%</span>
|
|
1964
|
+
</div>
|
|
1965
|
+
<div v-if="column.dataIndex === 'docs'">
|
|
1966
|
+
<CdsButton
|
|
1967
|
+
size="sm"
|
|
1968
|
+
:prepend-icon="record.download ? 'download' : 'upload'"
|
|
1969
|
+
:appearance="record.download ? 'tertiary' : 'secondary'"
|
|
1970
|
+
:text="record.download ? 'Скачать' : 'Добавить'"
|
|
1971
|
+
/>
|
|
1972
|
+
</div>
|
|
1973
|
+
</template>
|
|
1974
|
+
</template>
|
|
1975
|
+
|
|
1976
|
+
<template #emptyText>
|
|
1977
|
+
<div class="flex h-[500px] items-center justify-center">
|
|
1978
|
+
<CdsLoader v-if="isLoading" fixed overlay />
|
|
1979
|
+
<CdsEmptyState
|
|
1980
|
+
title="Нет данных"
|
|
1981
|
+
description="Нажмите кнопку ниже чтобы добавить данные в таблицу"
|
|
1982
|
+
illustration="documents"
|
|
1983
|
+
>
|
|
1984
|
+
<template #actions>
|
|
1985
|
+
<CdsButton text="Добавить данные" @click="queryDataTableExample" />
|
|
1986
|
+
</template>
|
|
1987
|
+
</CdsEmptyState>
|
|
1988
|
+
</div>
|
|
1989
|
+
</template>
|
|
1990
|
+
</CdsTable>
|
|
1991
|
+
</template>
|
|
1992
|
+
```
|
|
1993
|
+
|
|
1994
|
+
### Item list
|
|
1995
|
+
|
|
1996
|
+
A nested table inside an expanded row and a toolbar with fields and a dropdown calendar — a recipe for documents with line items.
|
|
1997
|
+
|
|
1998
|
+
**Example (SFC):**
|
|
1999
|
+
|
|
2000
|
+
```vue
|
|
2001
|
+
<script setup lang="ts">
|
|
2002
|
+
import type { ITableProps, NTable } from '@central-design-system/components';
|
|
2003
|
+
import {
|
|
2004
|
+
CdsButton,
|
|
2005
|
+
CdsDatePicker,
|
|
2006
|
+
CdsEmptyState,
|
|
2007
|
+
CdsFunctionalButton,
|
|
2008
|
+
CdsLoader,
|
|
2009
|
+
CdsMenu,
|
|
2010
|
+
CdsSkeletonLoader,
|
|
2011
|
+
CdsTab,
|
|
2012
|
+
CdsTable,
|
|
2013
|
+
CdsTabs,
|
|
2014
|
+
CdsTextInput,
|
|
2015
|
+
utils
|
|
2016
|
+
} from '@central-design-system/components';
|
|
2017
|
+
import { computed, onMounted, ref, unref, watch } from 'vue';
|
|
2018
|
+
|
|
2019
|
+
const { plurals } = utils;
|
|
2020
|
+
|
|
2021
|
+
type TInnerDataItem = {
|
|
2022
|
+
key: number | string;
|
|
2023
|
+
factory: string;
|
|
2024
|
+
firm: number;
|
|
2025
|
+
place: string;
|
|
2026
|
+
date: string;
|
|
2027
|
+
price_one: number | string;
|
|
2028
|
+
qty: number;
|
|
2029
|
+
price: number | string;
|
|
2030
|
+
};
|
|
2031
|
+
type TDataItem = {
|
|
2032
|
+
key: number | string;
|
|
2033
|
+
id: number;
|
|
2034
|
+
name: string;
|
|
2035
|
+
group: number | string;
|
|
2036
|
+
size: number;
|
|
2037
|
+
qty: number | string;
|
|
2038
|
+
price: number | string;
|
|
2039
|
+
innerData: TInnerDataItem[];
|
|
2040
|
+
};
|
|
2041
|
+
|
|
2042
|
+
const columns: NTable.NColumn.TColumns<TDataItem> = [
|
|
2043
|
+
{ title: '№', dataIndex: 'id', width: 59 },
|
|
2044
|
+
{ title: 'Код ОЗМ', dataIndex: 'code', width: 91 },
|
|
2045
|
+
{ title: 'Позиция', dataIndex: 'name', width: 545 },
|
|
2046
|
+
{ title: 'Группа закупок', dataIndex: 'group', width: 187 },
|
|
2047
|
+
{ title: 'Единица', dataIndex: 'size', width: 89 },
|
|
2048
|
+
{ title: 'Количество', dataIndex: 'qty', align: 'right', width: 107 },
|
|
2049
|
+
{ title: 'Плановая стоимость, ₽', dataIndex: 'price', align: 'right', width: 187 },
|
|
2050
|
+
{ title: '', dataIndex: 'action', width: 45 }
|
|
2051
|
+
];
|
|
2052
|
+
const innerColumns: NTable.NColumn.TColumns<TInnerDataItem> = [
|
|
2053
|
+
{ title: 'Завод', dataIndex: 'factory', width: 98 },
|
|
2054
|
+
{ title: 'Склад', dataIndex: 'firm', width: 92 },
|
|
2055
|
+
{ title: 'Место поставки', dataIndex: 'place', width: 218 },
|
|
2056
|
+
{ title: 'Плановая дата поставки', dataIndex: 'date', width: 280 },
|
|
2057
|
+
{ title: 'Цена за единицу, ₽', dataIndex: 'price_one', align: 'right', width: 290 },
|
|
2058
|
+
{ title: 'Количество', dataIndex: 'qty', align: 'right', width: 107 },
|
|
2059
|
+
{ title: 'Плановая стоимость, ₽', dataIndex: 'price', align: 'right', width: 185 },
|
|
2060
|
+
{ title: '', dataIndex: 'action', width: 44 }
|
|
2061
|
+
];
|
|
2062
|
+
|
|
2063
|
+
const data = ref<TDataItem[]>([]);
|
|
2064
|
+
const total = ref(10);
|
|
2065
|
+
const isLoading = ref(false);
|
|
2066
|
+
const tableDataSwitcher = ref(0);
|
|
2067
|
+
const date = ref('');
|
|
2068
|
+
|
|
2069
|
+
const formatPrice = (price: number | string) =>
|
|
2070
|
+
new Intl.NumberFormat('ru-RU', {
|
|
2071
|
+
style: 'decimal',
|
|
2072
|
+
minimumFractionDigits: 2,
|
|
2073
|
+
maximumFractionDigits: 2
|
|
2074
|
+
}).format(+price);
|
|
2075
|
+
|
|
2076
|
+
const queryDataTableExample = async () => {
|
|
2077
|
+
try {
|
|
2078
|
+
isLoading.value = true;
|
|
2079
|
+
|
|
2080
|
+
const response = await fetch('https://cds-api.b2b-center.ru/api/v1/examples/data-table?example=example3');
|
|
2081
|
+
const { data: examples } = await response.json();
|
|
2082
|
+
// Принудительная задержка для примера загрузки
|
|
2083
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2084
|
+
|
|
2085
|
+
data.value =
|
|
2086
|
+
examples.map((item: TDataItem, idx: number) => ({
|
|
2087
|
+
...item,
|
|
2088
|
+
price: formatPrice(item.price),
|
|
2089
|
+
innerData: item.innerData.map((innerItem, innerIdx) => ({
|
|
2090
|
+
...innerItem,
|
|
2091
|
+
price_one: formatPrice(innerItem.price_one),
|
|
2092
|
+
key: `${idx}.${innerIdx}`
|
|
2093
|
+
})),
|
|
2094
|
+
key: idx
|
|
2095
|
+
})) ?? [];
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
console.error(error);
|
|
2098
|
+
} finally {
|
|
2099
|
+
isLoading.value = false;
|
|
2100
|
+
}
|
|
2101
|
+
};
|
|
2102
|
+
|
|
2103
|
+
const selectedRowKeys = ref<NTable.TKey[]>([]);
|
|
2104
|
+
const selectedRowText = computed(() => plurals(unref(selectedRowKeys).length, ['строка', 'строки', 'строк']));
|
|
2105
|
+
|
|
2106
|
+
const onSelectChange = (selectedKeys: NTable.TKey[]) => {
|
|
2107
|
+
selectedRowKeys.value = selectedKeys;
|
|
2108
|
+
};
|
|
2109
|
+
|
|
2110
|
+
const onTableChange: ITableProps<TDataItem>['onChange'] = (pagination) => {
|
|
2111
|
+
total.value = pagination.total!;
|
|
2112
|
+
};
|
|
2113
|
+
|
|
2114
|
+
onMounted(() => {
|
|
2115
|
+
queryDataTableExample();
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2118
|
+
watch(tableDataSwitcher, () => {
|
|
2119
|
+
queryDataTableExample();
|
|
2120
|
+
});
|
|
2121
|
+
</script>
|
|
2122
|
+
|
|
2123
|
+
<template>
|
|
2124
|
+
<CdsTable
|
|
2125
|
+
class="min-w-[860px]"
|
|
2126
|
+
:columns="columns"
|
|
2127
|
+
:data="data"
|
|
2128
|
+
:pagination="{ total, position: ['bottomLeft'], counter: true, size: true }"
|
|
2129
|
+
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
|
2130
|
+
@change="onTableChange"
|
|
2131
|
+
>
|
|
2132
|
+
<template #header>
|
|
2133
|
+
<div class="flex justify-between">
|
|
2134
|
+
<div>
|
|
2135
|
+
<h4 class="mb-4">Список позиций</h4>
|
|
2136
|
+
<CdsTabs v-model="tableDataSwitcher">
|
|
2137
|
+
<CdsTab text="Позиции" />
|
|
2138
|
+
<CdsTab text="Адреса" />
|
|
2139
|
+
</CdsTabs>
|
|
2140
|
+
</div>
|
|
2141
|
+
<div class="flex gap-2">
|
|
2142
|
+
<CdsButton prepend-icon="download" appearance="secondary" text="Скачать шаблон" />
|
|
2143
|
+
<CdsButton prepend-icon="upload" appearance="secondary" text="Загрузить из файла" />
|
|
2144
|
+
</div>
|
|
2145
|
+
</div>
|
|
2146
|
+
</template>
|
|
2147
|
+
|
|
2148
|
+
<template #toolbar>
|
|
2149
|
+
<div class="flex flex-wrap items-end justify-between gap-4">
|
|
2150
|
+
<div class="flex items-end gap-4">
|
|
2151
|
+
<p class="min-w-[121px]">
|
|
2152
|
+
Выбрано: <strong>{{ selectedRowKeys.length }}</strong> {{ selectedRowText }}
|
|
2153
|
+
</p>
|
|
2154
|
+
<CdsTextInput label="Все адреса" placeholder="Введите адрес" hide-messages class="w-[250px]" />
|
|
2155
|
+
<CdsTextInput
|
|
2156
|
+
v-model="date"
|
|
2157
|
+
append-inner-icon="calendar"
|
|
2158
|
+
label="Дата"
|
|
2159
|
+
placeholder="Выберите дату"
|
|
2160
|
+
hide-messages
|
|
2161
|
+
class="w-[250px]"
|
|
2162
|
+
>
|
|
2163
|
+
<CdsMenu activator="parent" :close-on-content-click="false">
|
|
2164
|
+
<CdsDatePicker v-model="date" />
|
|
2165
|
+
</CdsMenu>
|
|
2166
|
+
</CdsTextInput>
|
|
2167
|
+
</div>
|
|
2168
|
+
<div class="flex gap-2">
|
|
2169
|
+
<CdsButton appearance="secondary" prepend-icon="add" text="Добавить позицию" />
|
|
2170
|
+
<CdsButton appearance="secondary" prepend-icon="add" text="Добавить адреса и позиции" />
|
|
2171
|
+
</div>
|
|
2172
|
+
</div>
|
|
2173
|
+
</template>
|
|
2174
|
+
|
|
2175
|
+
<template #bodyCell="{ column }">
|
|
2176
|
+
<template v-if="isLoading">
|
|
2177
|
+
<CdsSkeletonLoader type="text" />
|
|
2178
|
+
</template>
|
|
2179
|
+
<template v-else>
|
|
2180
|
+
<CdsFunctionalButton v-if="column.dataIndex === 'action'" icon="delete" />
|
|
2181
|
+
</template>
|
|
2182
|
+
</template>
|
|
2183
|
+
|
|
2184
|
+
<template #expandedRowRender="{ record }">
|
|
2185
|
+
<CdsTable :columns="innerColumns" :data="record.innerData" :pagination="false">
|
|
2186
|
+
<template #bodyCell="{ column }">
|
|
2187
|
+
<template v-if="column.dataIndex === 'action'">
|
|
2188
|
+
<CdsFunctionalButton icon="delete" />
|
|
2189
|
+
</template>
|
|
2190
|
+
</template>
|
|
2191
|
+
</CdsTable>
|
|
2192
|
+
</template>
|
|
2193
|
+
|
|
2194
|
+
<template #emptyText>
|
|
2195
|
+
<div class="flex h-[500px] items-center justify-center">
|
|
2196
|
+
<CdsLoader v-if="isLoading" fixed overlay />
|
|
2197
|
+
<CdsEmptyState
|
|
2198
|
+
title="Нет данных"
|
|
2199
|
+
description="Нажмите кнопку ниже чтобы добавить данные в таблицу"
|
|
2200
|
+
illustration="documents"
|
|
2201
|
+
>
|
|
2202
|
+
<template #actions>
|
|
2203
|
+
<CdsButton text="Добавить данные" @click="queryDataTableExample" />
|
|
2204
|
+
</template>
|
|
2205
|
+
</CdsEmptyState>
|
|
2206
|
+
</div>
|
|
2207
|
+
</template>
|
|
2208
|
+
</CdsTable>
|
|
2209
|
+
</template>
|
|
2210
|
+
```
|
|
2211
|
+
|
|
2212
|
+
## Выбор строк
|
|
2213
|
+
|
|
2214
|
+
Проп `row-selection` добавляет колонку с чекбоксами: выбранные ключи
|
|
2215
|
+
строк управляются снаружи, а меню выбора расширяется своими пунктами.
|
|
2216
|
+
|
|
2217
|
+
### Selection and operations
|
|
2218
|
+
|
|
2219
|
+
The `row-selection` object turns on row selection: `selectedRowKeys` holds the selected keys, `onChange` reports the changes. Actions on the selected set fit nicely in the `toolbar` slot.
|
|
2220
|
+
|
|
2221
|
+
**Example (SFC):**
|
|
2222
|
+
|
|
2223
|
+
```vue
|
|
2224
|
+
<script setup lang="ts">
|
|
2225
|
+
import type { NTable } from '@central-design-system/components';
|
|
2226
|
+
import { CdsButton, CdsTable } from '@central-design-system/components';
|
|
2227
|
+
import { reactive, toRefs } from 'vue';
|
|
2228
|
+
|
|
2229
|
+
const state = reactive<{ selectedRowKeys: NTable.TKey[]; loading: boolean }>({
|
|
2230
|
+
selectedRowKeys: [],
|
|
2231
|
+
loading: false
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
const start = () => {
|
|
2235
|
+
state.loading = true;
|
|
2236
|
+
|
|
2237
|
+
setTimeout(() => {
|
|
2238
|
+
state.loading = false;
|
|
2239
|
+
state.selectedRowKeys = [];
|
|
2240
|
+
}, 1000);
|
|
2241
|
+
};
|
|
2242
|
+
|
|
2243
|
+
const cancel = () => {
|
|
2244
|
+
state.selectedRowKeys = [];
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
const onSelectChange = (selectedRowKeys: NTable.TKey[]) => {
|
|
2248
|
+
state.selectedRowKeys = selectedRowKeys;
|
|
2249
|
+
};
|
|
2250
|
+
|
|
2251
|
+
const columns: NTable.NColumn.TColumns = [
|
|
2252
|
+
{ title: 'Заголовок 1', dataIndex: 'name' },
|
|
2253
|
+
{ title: 'Заголовок 2', dataIndex: 'age' },
|
|
2254
|
+
{ title: 'Заголовок 3', dataIndex: 'address' },
|
|
2255
|
+
{ title: 'Заголовок 4', dataIndex: 'phone' }
|
|
2256
|
+
];
|
|
2257
|
+
const data = [...Array(8)].map((_, i) => ({
|
|
2258
|
+
key: i,
|
|
2259
|
+
name: `Текст ячейки ${i}`,
|
|
2260
|
+
age: `Текст ячейки ${i}`,
|
|
2261
|
+
address: `Текст ячейки ${i}`,
|
|
2262
|
+
phone: `Текст ячейки ${i}`
|
|
2263
|
+
}));
|
|
2264
|
+
|
|
2265
|
+
const { selectedRowKeys, loading } = toRefs(state);
|
|
2266
|
+
</script>
|
|
2267
|
+
|
|
2268
|
+
<template>
|
|
2269
|
+
<CdsTable
|
|
2270
|
+
:columns="columns"
|
|
2271
|
+
:data="data"
|
|
2272
|
+
:row-selection="{ selectedRowKeys, onChange: onSelectChange }"
|
|
2273
|
+
:pagination="false"
|
|
2274
|
+
>
|
|
2275
|
+
<template #toolbar>
|
|
2276
|
+
<div class="flex items-center justify-between">
|
|
2277
|
+
<p>
|
|
2278
|
+
Выбрано: <strong>{{ selectedRowKeys.length }}</strong> строк
|
|
2279
|
+
</p>
|
|
2280
|
+
<div class="flex gap-2">
|
|
2281
|
+
<CdsButton appearance="secondary" @click="cancel">Отменить</CdsButton>
|
|
2282
|
+
<CdsButton :loading="loading" @click="start">Загрузить</CdsButton>
|
|
2283
|
+
</div>
|
|
2284
|
+
</div>
|
|
2285
|
+
</template>
|
|
2286
|
+
</CdsTable>
|
|
2287
|
+
</template>
|
|
2288
|
+
```
|
|
2289
|
+
|
|
2290
|
+
### Custom selection menu items
|
|
2291
|
+
|
|
2292
|
+
The `selections` property fills the menu in the selection column header: the ready-made `SELECTION_ALL`, `SELECTION_INVERT` and `SELECTION_NONE` constants plus your own items with arbitrary logic.
|
|
2293
|
+
|
|
2294
|
+
**Example (SFC):**
|
|
2295
|
+
|
|
2296
|
+
```vue
|
|
2297
|
+
<script setup lang="ts">
|
|
2298
|
+
import type { NTable } from '@central-design-system/components';
|
|
2299
|
+
import { CdsTable, SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE } from '@central-design-system/components';
|
|
2300
|
+
import { computed, ref, unref } from 'vue';
|
|
2301
|
+
|
|
2302
|
+
const selectedRowKeys = ref<NTable.TKey[]>([]);
|
|
2303
|
+
|
|
2304
|
+
const onSelectChange = (selectedRows: NTable.TKey[]) => {
|
|
2305
|
+
selectedRowKeys.value = selectedRows;
|
|
2306
|
+
};
|
|
2307
|
+
|
|
2308
|
+
const rowSelection = computed(() => ({
|
|
2309
|
+
selectedRowKeys: unref(selectedRowKeys),
|
|
2310
|
+
onChange: onSelectChange,
|
|
2311
|
+
hideDefaultSelections: true,
|
|
2312
|
+
selections: [
|
|
2313
|
+
SELECTION_ALL,
|
|
2314
|
+
SELECTION_INVERT,
|
|
2315
|
+
SELECTION_NONE,
|
|
2316
|
+
{
|
|
2317
|
+
key: 'odd',
|
|
2318
|
+
text: 'Выбрать нечетные',
|
|
2319
|
+
onSelect: (selectedRows: NTable.TKey[]) => {
|
|
2320
|
+
selectedRowKeys.value = selectedRows.filter((_key, index) => index % 2 === 0);
|
|
2321
|
+
}
|
|
2322
|
+
},
|
|
2323
|
+
{
|
|
2324
|
+
key: 'even',
|
|
2325
|
+
text: 'Выбрать четные',
|
|
2326
|
+
onSelect: (selectedRows: NTable.TKey[]) => {
|
|
2327
|
+
selectedRowKeys.value = selectedRows.filter((_key, index) => index % 2 !== 0);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
]
|
|
2331
|
+
}));
|
|
2332
|
+
|
|
2333
|
+
const columns: NTable.NColumn.TColumns = [
|
|
2334
|
+
{ title: 'Заголовок 1', dataIndex: 'name' },
|
|
2335
|
+
{ title: 'Заголовок 2', dataIndex: 'age' },
|
|
2336
|
+
{ title: 'Заголовок 3', dataIndex: 'address' },
|
|
2337
|
+
{ title: 'Заголовок 4', dataIndex: 'phone' }
|
|
2338
|
+
];
|
|
2339
|
+
const data = [...Array(6)].map((_, i) => ({
|
|
2340
|
+
key: i,
|
|
2341
|
+
name: `Текст ячейки ${i}`,
|
|
2342
|
+
age: `Текст ячейки ${i}`,
|
|
2343
|
+
address: `Текст ячейки ${i}`,
|
|
2344
|
+
phone: `Текст ячейки ${i}`
|
|
2345
|
+
}));
|
|
2346
|
+
</script>
|
|
2347
|
+
|
|
2348
|
+
<template>
|
|
2349
|
+
<CdsTable :columns="columns" :data="data" :row-selection="rowSelection" :pagination="false" />
|
|
2350
|
+
</template>
|
|
2351
|
+
```
|
|
2352
|
+
|
|
2353
|
+
## Обзор
|
|
2354
|
+
|
|
2355
|
+
Таблица выводит большие наборы записей в колонках и берёт на себя работу
|
|
2356
|
+
с данными: сортировку, фильтры, пагинацию, выбор и раскрытие строк,
|
|
2357
|
+
фиксацию шапки и колонок. Колонки описываются пропом `columns`,
|
|
2358
|
+
данные — пропом `data`.
|
|
2359
|
+
|
|
2360
|
+
### Showing data
|
|
2361
|
+
|
|
2362
|
+
A minimal table: an array of column descriptions in `columns`, the records in `data`. The row key comes from the record's `key` field or from the `row-key` prop.
|
|
2363
|
+
|
|
2364
|
+
**Example (SFC):**
|
|
2365
|
+
|
|
2366
|
+
```vue
|
|
2367
|
+
<script setup lang="ts">
|
|
2368
|
+
import type { NTable } from '@central-design-system/components';
|
|
2369
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2370
|
+
|
|
2371
|
+
type TSimpleRow = {
|
|
2372
|
+
key: string;
|
|
2373
|
+
name: string;
|
|
2374
|
+
age: string;
|
|
2375
|
+
address: string;
|
|
2376
|
+
phone: string;
|
|
2377
|
+
};
|
|
2378
|
+
|
|
2379
|
+
const simpleColumns: NTable.NColumn.TColumns<TSimpleRow> = [
|
|
2380
|
+
{ title: 'Заголовок', dataIndex: 'name' },
|
|
2381
|
+
{ title: 'Заголовок', dataIndex: 'age' },
|
|
2382
|
+
{ title: 'Заголовок', dataIndex: 'address' },
|
|
2383
|
+
{ title: 'Заголовок', dataIndex: 'phone' }
|
|
2384
|
+
];
|
|
2385
|
+
|
|
2386
|
+
const makeSimpleData = (count: number): TSimpleRow[] =>
|
|
2387
|
+
[...Array(count)].map((_, i) => ({
|
|
2388
|
+
key: i.toString(),
|
|
2389
|
+
name: 'Текст ячейки',
|
|
2390
|
+
age: 'Текст ячейки',
|
|
2391
|
+
address: 'Текст ячейки',
|
|
2392
|
+
phone: 'Текст ячейки'
|
|
2393
|
+
}));
|
|
2394
|
+
|
|
2395
|
+
const columns = simpleColumns;
|
|
2396
|
+
const data = makeSimpleData(5);
|
|
2397
|
+
</script>
|
|
2398
|
+
|
|
2399
|
+
<template>
|
|
2400
|
+
<CdsTable :columns="columns" :data="data" />
|
|
2401
|
+
</template>
|
|
2402
|
+
```
|
|
2403
|
+
|
|
2404
|
+
### Showing data
|
|
2405
|
+
|
|
2406
|
+
The column's `align` property aligns the content — numeric values are conventionally aligned to the right.
|
|
2407
|
+
|
|
2408
|
+
**Example (SFC):**
|
|
2409
|
+
|
|
2410
|
+
```vue
|
|
2411
|
+
<script setup lang="ts">
|
|
2412
|
+
import type { NTable } from '@central-design-system/components';
|
|
2413
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2414
|
+
|
|
2415
|
+
type TDataType = { key: string; name: string; price: string; address: string; tags: string };
|
|
2416
|
+
|
|
2417
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
2418
|
+
{ title: 'Наименование', dataIndex: 'name', key: 'name' },
|
|
2419
|
+
{ title: 'Цена', dataIndex: 'price', key: 'price', align: 'right' },
|
|
2420
|
+
{ title: 'Адрес', dataIndex: 'address', key: 'address' },
|
|
2421
|
+
{ title: 'Наличие', key: 'tags', dataIndex: 'tags' }
|
|
2422
|
+
];
|
|
2423
|
+
const data: TDataType[] = [
|
|
2424
|
+
{ key: '1', name: 'iPhone 17 Pro Max', price: '$1500', address: 'г. Москва', tags: 'В наличии' },
|
|
2425
|
+
{ key: '2', name: 'MacBook Air M5', price: '$2000', address: 'г. Москва', tags: 'В наличии' },
|
|
2426
|
+
{ key: '3', name: 'Apple Vision Pro', price: '$3500', address: 'г. Нью-Йорк', tags: 'Нет в наличии' }
|
|
2427
|
+
];
|
|
2428
|
+
</script>
|
|
2429
|
+
|
|
2430
|
+
<template>
|
|
2431
|
+
<CdsTable :columns="columns" :data="data" :pagination="false" />
|
|
2432
|
+
</template>
|
|
2433
|
+
```
|
|
2434
|
+
|
|
2435
|
+
### Sizes
|
|
2436
|
+
|
|
2437
|
+
The `size` prop controls the density: `md` is the default, `sm` and `xs` are for data-dense screens.
|
|
2438
|
+
|
|
2439
|
+
**Example (SFC):**
|
|
2440
|
+
|
|
2441
|
+
```vue
|
|
2442
|
+
<script setup lang="ts">
|
|
2443
|
+
import type { NTable } from '@central-design-system/components';
|
|
2444
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2445
|
+
|
|
2446
|
+
const columns: NTable.NColumn.TColumns = [...Array(4)].map((_, i) => ({
|
|
2447
|
+
title: 'Заголовок',
|
|
2448
|
+
dataIndex: `header${i + 1}`,
|
|
2449
|
+
key: (i + 1).toString()
|
|
2450
|
+
}));
|
|
2451
|
+
const data = [...Array(4)].map((_, i) => ({
|
|
2452
|
+
key: (i + 1).toString(),
|
|
2453
|
+
header1: 'Текст ячейки',
|
|
2454
|
+
header2: 'Текст ячейки',
|
|
2455
|
+
header3: 'Текст ячейки',
|
|
2456
|
+
header4: 'Текст ячейки'
|
|
2457
|
+
}));
|
|
2458
|
+
</script>
|
|
2459
|
+
|
|
2460
|
+
<template>
|
|
2461
|
+
<div class="flex flex-col gap-5">
|
|
2462
|
+
<CdsTable :columns="columns" :data="data" size="md" :pagination="false">
|
|
2463
|
+
<template #header>
|
|
2464
|
+
<h4>Medium size</h4>
|
|
2465
|
+
</template>
|
|
2466
|
+
</CdsTable>
|
|
2467
|
+
|
|
2468
|
+
<CdsTable :columns="columns" :data="data" size="sm" :pagination="false">
|
|
2469
|
+
<template #header>
|
|
2470
|
+
<h4>Small size</h4>
|
|
2471
|
+
</template>
|
|
2472
|
+
</CdsTable>
|
|
2473
|
+
|
|
2474
|
+
<CdsTable :columns="columns" :data="data" size="xs" :pagination="false">
|
|
2475
|
+
<template #header>
|
|
2476
|
+
<h4>Extra small size</h4>
|
|
2477
|
+
</template>
|
|
2478
|
+
</CdsTable>
|
|
2479
|
+
</div>
|
|
2480
|
+
</template>
|
|
2481
|
+
```
|
|
2482
|
+
|
|
2483
|
+
### Rows
|
|
2484
|
+
|
|
2485
|
+
The `row-class-name` property computes a row class from the record: the built-in `cds-table--striped` class alternates the background, `cds-table__row--warning` and `cds-table__row--error` add a status highlight. The `hovered` prop highlights the row under the cursor.
|
|
2486
|
+
|
|
2487
|
+
**Example (SFC):**
|
|
2488
|
+
|
|
2489
|
+
```vue
|
|
2490
|
+
<script setup lang="ts">
|
|
2491
|
+
import type { NTable } from '@central-design-system/components';
|
|
2492
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2493
|
+
|
|
2494
|
+
type TSimpleRow = {
|
|
2495
|
+
key: string;
|
|
2496
|
+
name: string;
|
|
2497
|
+
age: string;
|
|
2498
|
+
address: string;
|
|
2499
|
+
phone: string;
|
|
2500
|
+
};
|
|
2501
|
+
|
|
2502
|
+
const simpleColumns: NTable.NColumn.TColumns<TSimpleRow> = [
|
|
2503
|
+
{ title: 'Заголовок', dataIndex: 'name' },
|
|
2504
|
+
{ title: 'Заголовок', dataIndex: 'age' },
|
|
2505
|
+
{ title: 'Заголовок', dataIndex: 'address' },
|
|
2506
|
+
{ title: 'Заголовок', dataIndex: 'phone' }
|
|
2507
|
+
];
|
|
2508
|
+
|
|
2509
|
+
const makeSimpleData = (count: number): TSimpleRow[] =>
|
|
2510
|
+
[...Array(count)].map((_, i) => ({
|
|
2511
|
+
key: i.toString(),
|
|
2512
|
+
name: 'Текст ячейки',
|
|
2513
|
+
age: 'Текст ячейки',
|
|
2514
|
+
address: 'Текст ячейки',
|
|
2515
|
+
phone: 'Текст ячейки'
|
|
2516
|
+
}));
|
|
2517
|
+
|
|
2518
|
+
const columns = simpleColumns;
|
|
2519
|
+
const data = makeSimpleData(4);
|
|
2520
|
+
</script>
|
|
2521
|
+
|
|
2522
|
+
<template>
|
|
2523
|
+
<CdsTable
|
|
2524
|
+
:columns="columns"
|
|
2525
|
+
:data="data"
|
|
2526
|
+
:row-class-name="(_record: unknown, index: number) => (index % 2 === 1 ? 'cds-table--striped' : '')"
|
|
2527
|
+
:pagination="false"
|
|
2528
|
+
hovered
|
|
2529
|
+
/>
|
|
2530
|
+
</template>
|
|
2531
|
+
```
|
|
2532
|
+
|
|
2533
|
+
### Rows
|
|
2534
|
+
|
|
2535
|
+
Статусная подсветка строк классами `cds-table__row--warning` и
|
|
2536
|
+
`cds-table__row--error` через `row-class-name`.
|
|
2537
|
+
|
|
2538
|
+
**Example (SFC):**
|
|
2539
|
+
|
|
2540
|
+
```vue
|
|
2541
|
+
<script setup lang="ts">
|
|
2542
|
+
import type { NTable } from '@central-design-system/components';
|
|
2543
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2544
|
+
|
|
2545
|
+
type TSimpleRow = {
|
|
2546
|
+
key: string;
|
|
2547
|
+
name: string;
|
|
2548
|
+
age: string;
|
|
2549
|
+
address: string;
|
|
2550
|
+
phone: string;
|
|
2551
|
+
};
|
|
2552
|
+
|
|
2553
|
+
const simpleColumns: NTable.NColumn.TColumns<TSimpleRow> = [
|
|
2554
|
+
{ title: 'Заголовок', dataIndex: 'name' },
|
|
2555
|
+
{ title: 'Заголовок', dataIndex: 'age' },
|
|
2556
|
+
{ title: 'Заголовок', dataIndex: 'address' },
|
|
2557
|
+
{ title: 'Заголовок', dataIndex: 'phone' }
|
|
2558
|
+
];
|
|
2559
|
+
|
|
2560
|
+
const makeSimpleData = (count: number): TSimpleRow[] =>
|
|
2561
|
+
[...Array(count)].map((_, i) => ({
|
|
2562
|
+
key: i.toString(),
|
|
2563
|
+
name: 'Текст ячейки',
|
|
2564
|
+
age: 'Текст ячейки',
|
|
2565
|
+
address: 'Текст ячейки',
|
|
2566
|
+
phone: 'Текст ячейки'
|
|
2567
|
+
}));
|
|
2568
|
+
|
|
2569
|
+
const columns = simpleColumns;
|
|
2570
|
+
const data = makeSimpleData(4);
|
|
2571
|
+
</script>
|
|
2572
|
+
|
|
2573
|
+
<template>
|
|
2574
|
+
<CdsTable
|
|
2575
|
+
:columns="columns"
|
|
2576
|
+
:data="data"
|
|
2577
|
+
:row-class-name="(_record, index) => (index % 2 === 1 ? 'cds-table__row--warning' : 'cds-table__row--error')"
|
|
2578
|
+
:pagination="false"
|
|
2579
|
+
hovered
|
|
2580
|
+
/>
|
|
2581
|
+
</template>
|
|
2582
|
+
```
|
|
2583
|
+
|
|
2584
|
+
### Cell content
|
|
2585
|
+
|
|
2586
|
+
The column's `ellipsis` property truncates long text to a single line with an ellipsis — in cells and in headers alike.
|
|
2587
|
+
|
|
2588
|
+
**Example (SFC):**
|
|
2589
|
+
|
|
2590
|
+
```vue
|
|
2591
|
+
<script setup lang="ts">
|
|
2592
|
+
import type { NTable } from '@central-design-system/components';
|
|
2593
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2594
|
+
|
|
2595
|
+
type TDataType = { key: string; name: string; address: string };
|
|
2596
|
+
|
|
2597
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
2598
|
+
{ title: 'Заголовок таблицы', dataIndex: 'name', key: 'name' },
|
|
2599
|
+
{ title: 'Заголовок таблицы', dataIndex: 'address', key: 'address 1', ellipsis: true },
|
|
2600
|
+
{ title: 'Заголовок таблицы Заголовок таблицы', dataIndex: 'address', key: 'address 2', ellipsis: true },
|
|
2601
|
+
{ title: 'Заголовок таблицы Заголовок таблицы', dataIndex: 'address', key: 'address 3', ellipsis: true },
|
|
2602
|
+
{ title: 'Заголовок таблицы Заголовок таблицы', dataIndex: 'address', key: 'address 4', ellipsis: true }
|
|
2603
|
+
];
|
|
2604
|
+
const data: TDataType[] = [...Array(3)].map((_, i) => ({
|
|
2605
|
+
key: (i + 1).toString(),
|
|
2606
|
+
name: 'Текст ячейки',
|
|
2607
|
+
address: 'Текст ячейки Текст ячейки Текст ячейки Текст ячейки'
|
|
2608
|
+
}));
|
|
2609
|
+
</script>
|
|
2610
|
+
|
|
2611
|
+
<template>
|
|
2612
|
+
<CdsTable :columns="columns" :data="data" :pagination="false" />
|
|
2613
|
+
</template>
|
|
2614
|
+
```
|
|
2615
|
+
|
|
2616
|
+
### Responsiveness
|
|
2617
|
+
|
|
2618
|
+
The column's `responsive` property lists the breakpoints at which the column is visible — secondary columns get hidden on narrow screens.
|
|
2619
|
+
|
|
2620
|
+
**Example (SFC):**
|
|
2621
|
+
|
|
2622
|
+
```vue
|
|
2623
|
+
<script setup lang="ts">
|
|
2624
|
+
import type { NTable } from '@central-design-system/components';
|
|
2625
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2626
|
+
|
|
2627
|
+
const columns: NTable.NColumn.TColumns = [
|
|
2628
|
+
{ title: 'Заголовок (all screens)', dataIndex: 'name', key: 'name' },
|
|
2629
|
+
{ title: 'Заголовок (small screen or bigger)', dataIndex: 'age', key: 'age', responsive: ['smUp'] },
|
|
2630
|
+
{ title: 'Заголовок (medium screen or bigger)', dataIndex: 'address', key: 'address', responsive: ['mdUp'] },
|
|
2631
|
+
{
|
|
2632
|
+
title: 'Заголовок (only md, sm, xs screen)',
|
|
2633
|
+
dataIndex: 'phone',
|
|
2634
|
+
key: 'phone',
|
|
2635
|
+
responsive: ['md', 'sm', 'xs']
|
|
2636
|
+
}
|
|
2637
|
+
];
|
|
2638
|
+
const data = [{ key: '1', name: 'Текст ячейки', age: 'Текст ячейки', address: 'Текст ячейки', phone: 'Текст ячейки' }];
|
|
2639
|
+
</script>
|
|
2640
|
+
|
|
2641
|
+
<template>
|
|
2642
|
+
<CdsTable :columns="columns" :data="data" :pagination="false" />
|
|
2643
|
+
</template>
|
|
2644
|
+
```
|
|
2645
|
+
|
|
2646
|
+
## Структура и заголовки
|
|
2647
|
+
|
|
2648
|
+
Управление структурой таблицы: группировка шапки, объединение ячеек,
|
|
2649
|
+
слоты вокруг таблицы, изменение ширины колонок и декларативное описание
|
|
2650
|
+
колонок компонентами.
|
|
2651
|
+
|
|
2652
|
+
### Grouping headers
|
|
2653
|
+
|
|
2654
|
+
A nested `children` array on a column joins several columns under a shared header — the header becomes multi-level.
|
|
2655
|
+
|
|
2656
|
+
**Example (SFC):**
|
|
2657
|
+
|
|
2658
|
+
```vue
|
|
2659
|
+
<script setup lang="ts">
|
|
2660
|
+
import type { NTable } from '@central-design-system/components';
|
|
2661
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2662
|
+
|
|
2663
|
+
const columns: NTable.NColumn.TColumns = [
|
|
2664
|
+
{ title: 'Имя', dataIndex: 'name', key: 'name', width: 100, fixed: 'left' },
|
|
2665
|
+
{
|
|
2666
|
+
title: 'Информация',
|
|
2667
|
+
children: [
|
|
2668
|
+
{ title: 'Возраст', dataIndex: 'age', key: 'age', width: 200 },
|
|
2669
|
+
{
|
|
2670
|
+
title: 'Адрес',
|
|
2671
|
+
children: [
|
|
2672
|
+
{ title: 'Улица', dataIndex: 'street', key: 'street', width: 200 },
|
|
2673
|
+
{
|
|
2674
|
+
title: 'Дом',
|
|
2675
|
+
children: [
|
|
2676
|
+
{ title: 'Корпус', dataIndex: 'building', key: 'building', width: 100 },
|
|
2677
|
+
{ title: 'Квартира', dataIndex: 'number', key: 'number', width: 100 }
|
|
2678
|
+
]
|
|
2679
|
+
}
|
|
2680
|
+
]
|
|
2681
|
+
}
|
|
2682
|
+
]
|
|
2683
|
+
},
|
|
2684
|
+
{
|
|
2685
|
+
title: 'Компания',
|
|
2686
|
+
children: [
|
|
2687
|
+
{ title: 'Адрес компании', dataIndex: 'companyAddress', key: 'companyAddress', width: 200 },
|
|
2688
|
+
{ title: 'Название компании', dataIndex: 'companyName', key: 'companyName' }
|
|
2689
|
+
]
|
|
2690
|
+
},
|
|
2691
|
+
{ title: 'Пол', dataIndex: 'gender', key: 'gender', width: 80, fixed: 'right' }
|
|
2692
|
+
];
|
|
2693
|
+
|
|
2694
|
+
const data = [...Array(10)].map((_, i) => ({
|
|
2695
|
+
key: i,
|
|
2696
|
+
name: 'Иван',
|
|
2697
|
+
age: i + 1,
|
|
2698
|
+
street: 'Главная',
|
|
2699
|
+
building: 'Б',
|
|
2700
|
+
number: 1004,
|
|
2701
|
+
companyAddress: 'г. Москва, ул. Ленина',
|
|
2702
|
+
companyName: 'B2B-Center',
|
|
2703
|
+
gender: 'Муж'
|
|
2704
|
+
}));
|
|
2705
|
+
</script>
|
|
2706
|
+
|
|
2707
|
+
<template>
|
|
2708
|
+
<CdsTable :columns="columns" :data="data" :pagination="false" :scroll="{ x: 'calc(700px + 50%)', y: 240 }" bordered />
|
|
2709
|
+
</template>
|
|
2710
|
+
```
|
|
2711
|
+
|
|
2712
|
+
### Merging cells
|
|
2713
|
+
|
|
2714
|
+
The column's `customCell` function returns `colSpan`/`rowSpan` for a specific cell, while `colSpan` on the column itself merges header cells. A cell with `colSpan: 0` is not rendered.
|
|
2715
|
+
|
|
2716
|
+
**Example (SFC):**
|
|
2717
|
+
|
|
2718
|
+
```vue
|
|
2719
|
+
<script setup lang="ts">
|
|
2720
|
+
import type { NTable } from '@central-design-system/components';
|
|
2721
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2722
|
+
|
|
2723
|
+
type TDataType = { key: string; name: string; age: number; tel: string; phone: string; address: string };
|
|
2724
|
+
|
|
2725
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
2726
|
+
{
|
|
2727
|
+
title: 'Имя',
|
|
2728
|
+
dataIndex: 'name',
|
|
2729
|
+
customCell: (_, index) => ({ colSpan: index! < 4 ? 1 : 5 })
|
|
2730
|
+
},
|
|
2731
|
+
{
|
|
2732
|
+
title: 'Возраст',
|
|
2733
|
+
dataIndex: 'age',
|
|
2734
|
+
customCell: (_, index) => {
|
|
2735
|
+
if (index === 4) return { colSpan: 0 };
|
|
2736
|
+
}
|
|
2737
|
+
},
|
|
2738
|
+
{
|
|
2739
|
+
title: 'Номер телефона',
|
|
2740
|
+
colSpan: 2,
|
|
2741
|
+
dataIndex: 'tel',
|
|
2742
|
+
customCell: (_, index) => {
|
|
2743
|
+
if (index === 2) return { rowSpan: 2 };
|
|
2744
|
+
if (index === 3) return { rowSpan: 0 };
|
|
2745
|
+
if (index === 4) return { colSpan: 0 };
|
|
2746
|
+
}
|
|
2747
|
+
},
|
|
2748
|
+
{
|
|
2749
|
+
title: 'Номер домашнего телефона',
|
|
2750
|
+
colSpan: 0,
|
|
2751
|
+
dataIndex: 'phone',
|
|
2752
|
+
customCell: (_, index) => {
|
|
2753
|
+
if (index === 4) return { colSpan: 0 };
|
|
2754
|
+
}
|
|
2755
|
+
},
|
|
2756
|
+
{
|
|
2757
|
+
title: 'Адрес',
|
|
2758
|
+
dataIndex: 'address',
|
|
2759
|
+
customCell: (_, index) => {
|
|
2760
|
+
if (index === 4) return { colSpan: 0 };
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
];
|
|
2764
|
+
const data: TDataType[] = [
|
|
2765
|
+
{
|
|
2766
|
+
key: '1',
|
|
2767
|
+
name: 'Иванов Иван',
|
|
2768
|
+
age: 20,
|
|
2769
|
+
tel: '79999999999',
|
|
2770
|
+
phone: '74959999999',
|
|
2771
|
+
address: 'г. Москва, ул. Ленина д. 1'
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
key: '2',
|
|
2775
|
+
name: 'Иванов Василий',
|
|
2776
|
+
tel: '79999999999',
|
|
2777
|
+
phone: '74959999999',
|
|
2778
|
+
age: 32,
|
|
2779
|
+
address: 'г. Москва, ул. Ленина д. 2'
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
key: '3',
|
|
2783
|
+
name: 'Иванов Кирилл',
|
|
2784
|
+
age: 41,
|
|
2785
|
+
tel: '79999999999',
|
|
2786
|
+
phone: '74959999999',
|
|
2787
|
+
address: 'г. Москва, ул. Ленина д. 3'
|
|
2788
|
+
},
|
|
2789
|
+
{
|
|
2790
|
+
key: '4',
|
|
2791
|
+
name: 'Иванов Александр',
|
|
2792
|
+
age: 30,
|
|
2793
|
+
tel: '79999999999',
|
|
2794
|
+
phone: '74959999999',
|
|
2795
|
+
address: 'г. Екатеринбург, ул. Ленина д. 4'
|
|
2796
|
+
},
|
|
2797
|
+
{
|
|
2798
|
+
key: '5',
|
|
2799
|
+
name: 'Иванов Иван',
|
|
2800
|
+
age: 18,
|
|
2801
|
+
tel: '79999999999',
|
|
2802
|
+
phone: '74959999999',
|
|
2803
|
+
address: 'г. Новосибирск, ул. Ленина д. 5'
|
|
2804
|
+
}
|
|
2805
|
+
];
|
|
2806
|
+
</script>
|
|
2807
|
+
|
|
2808
|
+
<template>
|
|
2809
|
+
<CdsTable :columns="columns" :data="data" :pagination="false" />
|
|
2810
|
+
</template>
|
|
2811
|
+
```
|
|
2812
|
+
|
|
2813
|
+
### Header, toolbar and footer
|
|
2814
|
+
|
|
2815
|
+
The `header`, `toolbar` and `footer` slots add your own areas around the table, and the `pagination` prop adds built-in pagination with a record counter and a page size selector.
|
|
2816
|
+
|
|
2817
|
+
**Example (SFC):**
|
|
2818
|
+
|
|
2819
|
+
```vue
|
|
2820
|
+
<script setup lang="ts">
|
|
2821
|
+
import type { ITableProps, NTable } from '@central-design-system/components';
|
|
2822
|
+
import { CdsButton, CdsTab, CdsTable, CdsTabs, CdsTextInput } from '@central-design-system/components';
|
|
2823
|
+
import { ref } from 'vue';
|
|
2824
|
+
|
|
2825
|
+
type TDataType = { key: number; name: string; money: string; address: string };
|
|
2826
|
+
|
|
2827
|
+
const columns: NTable.NColumn.TColumns<TDataType> = [
|
|
2828
|
+
{ title: 'Название', dataIndex: 'name' },
|
|
2829
|
+
{ title: 'Адрес', dataIndex: 'address' },
|
|
2830
|
+
{ title: 'Цена', className: 'column-money', dataIndex: 'money', align: 'right' }
|
|
2831
|
+
];
|
|
2832
|
+
const data: TDataType[] = [...Array(100)].map((_, i) => ({
|
|
2833
|
+
key: i,
|
|
2834
|
+
name: `Позиция ${i + 1}`,
|
|
2835
|
+
money: new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' }).format((i + 1) * 10000),
|
|
2836
|
+
address: `г. Москва, ул. Ленина, д.${i}`
|
|
2837
|
+
}));
|
|
2838
|
+
|
|
2839
|
+
const contentSwitcher = ref(0);
|
|
2840
|
+
const total = ref(10);
|
|
2841
|
+
|
|
2842
|
+
const onChange: ITableProps<TDataType>['onChange'] = (pagination) => {
|
|
2843
|
+
total.value = pagination.total!;
|
|
2844
|
+
};
|
|
2845
|
+
</script>
|
|
2846
|
+
|
|
2847
|
+
<template>
|
|
2848
|
+
<CdsTable
|
|
2849
|
+
:columns="columns"
|
|
2850
|
+
:data="data"
|
|
2851
|
+
:pagination="{ total, counter: true, size: true, position: ['bottomLeft'] }"
|
|
2852
|
+
@change="onChange"
|
|
2853
|
+
>
|
|
2854
|
+
<template #header>
|
|
2855
|
+
<div class="flex justify-between">
|
|
2856
|
+
<div>
|
|
2857
|
+
<h4 class="mb-2">Заголовок таблицы</h4>
|
|
2858
|
+
<p class="mb-4 text-content-secondary">Описание таблицы в слоте заголовка таблицы</p>
|
|
2859
|
+
<div class="flex">
|
|
2860
|
+
<CdsTabs v-model="contentSwitcher">
|
|
2861
|
+
<CdsTab v-for="i in 5" :key="i">Текст</CdsTab>
|
|
2862
|
+
</CdsTabs>
|
|
2863
|
+
</div>
|
|
2864
|
+
</div>
|
|
2865
|
+
<div>
|
|
2866
|
+
<CdsButton appearance="secondary" text="Внешнее действия" />
|
|
2867
|
+
</div>
|
|
2868
|
+
</div>
|
|
2869
|
+
</template>
|
|
2870
|
+
|
|
2871
|
+
<template #toolbar>
|
|
2872
|
+
<div class="flex justify-between">
|
|
2873
|
+
<div class="flex gap-4">
|
|
2874
|
+
<CdsTextInput prepend-inner-icon="search" placeholder="Описание поиска" hide-messages class="w-[250px]" />
|
|
2875
|
+
<CdsButton prepend-icon="filters" text="Фильтры" appearance="tertiary" />
|
|
2876
|
+
</div>
|
|
2877
|
+
<div class="flex gap-2">
|
|
2878
|
+
<CdsButton appearance="secondary" text="Отменить" />
|
|
2879
|
+
<CdsButton text="Действие" />
|
|
2880
|
+
</div>
|
|
2881
|
+
</div>
|
|
2882
|
+
</template>
|
|
2883
|
+
|
|
2884
|
+
<template #footer>
|
|
2885
|
+
<div class="flex items-center justify-between">
|
|
2886
|
+
<h4>Подвал таблицы</h4>
|
|
2887
|
+
<div class="flex gap-2">
|
|
2888
|
+
<CdsButton appearance="secondary" text="Отменить" />
|
|
2889
|
+
<CdsButton text="Действие" />
|
|
2890
|
+
</div>
|
|
2891
|
+
</div>
|
|
2892
|
+
</template>
|
|
2893
|
+
</CdsTable>
|
|
2894
|
+
</template>
|
|
2895
|
+
```
|
|
2896
|
+
|
|
2897
|
+
### Resizing columns
|
|
2898
|
+
|
|
2899
|
+
The column's `resizable` property enables dragging the header border. The new width is reported by the `resize-column` event — store it in the column description.
|
|
2900
|
+
|
|
2901
|
+
**Example (SFC):**
|
|
2902
|
+
|
|
2903
|
+
```vue
|
|
2904
|
+
<script setup lang="ts">
|
|
2905
|
+
import type { NTable } from '@central-design-system/components';
|
|
2906
|
+
import { CdsTable } from '@central-design-system/components';
|
|
2907
|
+
import { ref } from 'vue';
|
|
2908
|
+
|
|
2909
|
+
type TDataType = { key: string; name: string; price: string; address: string; tags: string };
|
|
2910
|
+
|
|
2911
|
+
const columns = ref<NTable.NColumn.TColumns<TDataType>>([
|
|
2912
|
+
{ title: 'Наименование', dataIndex: 'name', key: 'name', resizable: true, width: 150 },
|
|
2913
|
+
{
|
|
2914
|
+
title: 'Цена',
|
|
2915
|
+
dataIndex: 'price',
|
|
2916
|
+
key: 'price',
|
|
2917
|
+
align: 'right',
|
|
2918
|
+
resizable: true,
|
|
2919
|
+
width: 100,
|
|
2920
|
+
minWidth: 100,
|
|
2921
|
+
maxWidth: 200
|
|
2922
|
+
},
|
|
2923
|
+
{ title: 'Адрес', dataIndex: 'address', key: 'address' },
|
|
2924
|
+
{ title: 'Наличие', key: 'tags', dataIndex: 'tags' }
|
|
2925
|
+
]);
|
|
2926
|
+
const data: TDataType[] = [
|
|
2927
|
+
{
|
|
2928
|
+
key: '1',
|
|
2929
|
+
name: 'iPhone 17 Pro Max',
|
|
2930
|
+
price: '$1500',
|
|
2931
|
+
address: 'г. Москва, ул. Ленина, д. 1',
|
|
2932
|
+
tags: 'В наличии'
|
|
2933
|
+
},
|
|
2934
|
+
{
|
|
2935
|
+
key: '2',
|
|
2936
|
+
name: 'MacBook Air M5',
|
|
2937
|
+
price: '$2000',
|
|
2938
|
+
address: 'г. Москва, ул. Ленина, д. 1',
|
|
2939
|
+
tags: 'В наличии'
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
key: '3',
|
|
2943
|
+
name: 'Apple Vision Pro',
|
|
2944
|
+
price: '$3500',
|
|
2945
|
+
address: 'г. Нью-Йорк, ул. Ленина, д. 1',
|
|
2946
|
+
tags: 'Нет в наличии'
|
|
2947
|
+
}
|
|
2948
|
+
];
|
|
2949
|
+
|
|
2950
|
+
const handleResizeColumn = (width: number, column: NTable.NColumn.IColumn) => {
|
|
2951
|
+
column.width = width;
|
|
2952
|
+
};
|
|
2953
|
+
</script>
|
|
2954
|
+
|
|
2955
|
+
<template>
|
|
2956
|
+
<CdsTable :columns="columns" :data="data" :pagination="false" @resize-column="handleResizeColumn" />
|
|
2957
|
+
</template>
|
|
2958
|
+
```
|
|
2959
|
+
|
|
2960
|
+
### Declarative column descriptions
|
|
2961
|
+
|
|
2962
|
+
Instead of the `columns` array, columns can be described in the template with the `CdsTableColumn` and `CdsTableColumnGroup` components — handy when headers and cells need their own markup.
|
|
2963
|
+
|
|
2964
|
+
**Example (SFC):**
|
|
2965
|
+
|
|
2966
|
+
```vue
|
|
2967
|
+
<script setup lang="ts">
|
|
2968
|
+
import { CdsStatus, CdsTable, CdsTableColumn, CdsTableColumnGroup, CdsToggle } from '@central-design-system/components';
|
|
2969
|
+
|
|
2970
|
+
type TDataType = {
|
|
2971
|
+
key: string;
|
|
2972
|
+
firstName: string;
|
|
2973
|
+
lastName: string;
|
|
2974
|
+
age: number;
|
|
2975
|
+
address: string;
|
|
2976
|
+
tags: string[];
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
const data: TDataType[] = [
|
|
2980
|
+
{
|
|
2981
|
+
key: '1',
|
|
2982
|
+
firstName: 'Иван',
|
|
2983
|
+
lastName: 'Иванов',
|
|
2984
|
+
age: 20,
|
|
2985
|
+
address: 'г. Москва, ул. Ленина, д. 1',
|
|
2986
|
+
tags: ['Senior', 'Developer']
|
|
2987
|
+
},
|
|
2988
|
+
{
|
|
2989
|
+
key: '2',
|
|
2990
|
+
firstName: 'Анатолий',
|
|
2991
|
+
lastName: 'Анатольев',
|
|
2992
|
+
age: 42,
|
|
2993
|
+
address: 'г. Москва, ул. Ленина, д. 2',
|
|
2994
|
+
tags: ['Junior', 'Developer']
|
|
2995
|
+
},
|
|
2996
|
+
{
|
|
2997
|
+
key: '3',
|
|
2998
|
+
firstName: 'Игнат',
|
|
2999
|
+
lastName: 'Игнатьев',
|
|
3000
|
+
age: 33,
|
|
3001
|
+
address: 'г. Москва, ул. Ленина, д. 3',
|
|
3002
|
+
tags: ['Junior', 'Developer']
|
|
3003
|
+
},
|
|
3004
|
+
{
|
|
3005
|
+
key: '4',
|
|
3006
|
+
firstName: 'Петр',
|
|
3007
|
+
lastName: 'Петров',
|
|
3008
|
+
age: 28,
|
|
3009
|
+
address: 'г. Москва, ул. Ленина, д. 4',
|
|
3010
|
+
tags: ['Middle', 'Developer']
|
|
3011
|
+
}
|
|
3012
|
+
];
|
|
3013
|
+
</script>
|
|
3014
|
+
|
|
3015
|
+
<template>
|
|
3016
|
+
<CdsTable :data="data" :pagination="false">
|
|
3017
|
+
<CdsTableColumnGroup>
|
|
3018
|
+
<template #title>
|
|
3019
|
+
<span class="text-brand-primary">Сотрудник</span>
|
|
3020
|
+
</template>
|
|
3021
|
+
<CdsTableColumn key="firstName" data-index="firstName">
|
|
3022
|
+
<template #title>
|
|
3023
|
+
<span class="text-brand-primary">Имя</span>
|
|
3024
|
+
</template>
|
|
3025
|
+
</CdsTableColumn>
|
|
3026
|
+
<CdsTableColumn key="lastName" title="Фамилия" data-index="lastName" />
|
|
3027
|
+
</CdsTableColumnGroup>
|
|
3028
|
+
<CdsTableColumn key="age" title="Возраст" data-index="age" />
|
|
3029
|
+
<CdsTableColumn key="address" title="Адрес" data-index="address" />
|
|
3030
|
+
<CdsTableColumn key="tags" title="Особенности" data-index="tags">
|
|
3031
|
+
<template #default="{ text: tags }">
|
|
3032
|
+
<CdsStatus
|
|
3033
|
+
v-for="tag in tags"
|
|
3034
|
+
:key="tag"
|
|
3035
|
+
:status="tag === 'Junior' ? 'danger' : tag === 'developer' ? 'info' : 'success'"
|
|
3036
|
+
class="mr-2"
|
|
3037
|
+
>
|
|
3038
|
+
{{ tag }}
|
|
3039
|
+
</CdsStatus>
|
|
3040
|
+
</template>
|
|
3041
|
+
</CdsTableColumn>
|
|
3042
|
+
<CdsTableColumn key="action" title="Действия" data-index="action">
|
|
3043
|
+
<template #default="{ record }">
|
|
3044
|
+
<CdsToggle :disabled="!record.tags.includes('Junior')" hide-messages />
|
|
3045
|
+
</template>
|
|
3046
|
+
</CdsTableColumn>
|
|
3047
|
+
</CdsTable>
|
|
3048
|
+
</template>
|
|
3049
|
+
```
|