Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ef3613e91 | |||
| 940eebe29f | |||
| 8ecaffe165 | |||
| e5cb31ffb1 | |||
| 7c2635fc13 | |||
| eb3d396c68 | |||
| 13ba5670f0 | |||
| 961b811b7a | |||
| cd491e1517 | |||
| b8a03def79 | |||
| 2b6798083d | |||
| 3c7b5dc690 | |||
| 2f4afddf73 | |||
| 212a46894e | |||
| 653ef109be | |||
| a0b17132ad | |||
| 486ec11ce6 | |||
| a24d28d4e0 | |||
| 8cc45a53e9 | |||
| edf7a86f07 | |||
| 8b8a8ff943 | |||
| 59610f463e | |||
| c1672bb8ae | |||
| 3e101840a6 | |||
| e87898ab82 | |||
| 8a1be59a51 | |||
| a3b2ace88d | |||
| c34037265e | |||
| 8c230fe3af | |||
| a695d60770 | |||
| ea30cbd381 | |||
| 39a4bf0dd3 | |||
| d5c9bc69b3 | |||
| 27f8ab752d | |||
| 5948fc83ea | |||
| 72e3d6a09e | |||
| de6f4a3ac5 | |||
| eecdc51557 | |||
| c841c49e1e | |||
| 2595d822d0 | |||
| 3ae0541065 | |||
| 4b735b768a | |||
| 9422edbfa1 | |||
| 37c5e92d6d | |||
| c7503de11e | |||
| 408362f3be | |||
| b3f5ab3d31 | |||
| 8d954b17ad | |||
| ac9cc8cfed | |||
| a1e808345e | |||
| efea2d62d9 | |||
| 2f95979cc6 | |||
| b3f098b41e | |||
| a0d5462ff1 | |||
| f1c204f790 |
170
changelog.md
170
changelog.md
@@ -1,5 +1,175 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-12 - 3.77.0 - feat(dees-table)
|
||||
add configurable cell flash comparison and border highlight mode
|
||||
|
||||
- adds column-level flashCompare support so update highlighting can detect meaningful changes for custom cell values
|
||||
- adds flashBorder styling for cells with badges, icons, or custom rendered content where text-color flashing is not visible
|
||||
- avoids false-positive flash animations for non-primitive cell values unless a custom comparator is provided
|
||||
|
||||
## 2026-04-12 - 3.76.1 - fix(demo-inputs)
|
||||
wrap input component demos in dees-form containers for consistent form integration
|
||||
|
||||
- Adds dees-form wrappers across multiple input demo pages including checkbox, dropdown, file upload, iban, list, multitoggle, phone, quantity selector, radio group, tags, text, toggle, and typelist demos
|
||||
- Keeps horizontal and grid example layouts intact while nesting them inside form containers
|
||||
- Cleans up file upload and text demo markup to better match expected dees-form structure
|
||||
|
||||
## 2026-04-12 - 3.76.0 - feat(input)
|
||||
separate label info tooltips from description text across input components
|
||||
|
||||
- adds a dedicated infoText property for dees-label tooltips while keeping description available for helper text rendered below inputs
|
||||
- introduces a shared renderDescription() helper in the input base component and updates multiple input components to use the unified description styling
|
||||
- updates demos and consuming components to migrate tooltip content from description to infoText where appropriate
|
||||
|
||||
## 2026-04-12 - 3.75.0 - feat(dees-tile)
|
||||
add configurable overscroll handling to tile content and use it in modals
|
||||
|
||||
- introduces a reflected overscroll property on dees-tile with contain, auto, and none options
|
||||
- moves tile content scrolling and scrollbar styling into dees-tile instead of modal-specific styling
|
||||
- updates dees-modal to enable contained overscroll through the new dees-tile API
|
||||
|
||||
## 2026-04-12 - 3.74.2 - fix(modal,tile,input-text)
|
||||
move scroll handling from tile content to modal and update input text demo to use changeSubject subscriptions
|
||||
|
||||
- bump @design.estate/dees-wcctools from ^3.8.2 to ^3.8.4
|
||||
- set dees-tile content overflow to hidden and apply scroll styling through dees-modal part selectors
|
||||
- simplify the interactive dees-input-text demo by subscribing directly to changeSubject for live value updates
|
||||
|
||||
## 2026-04-12 - 3.74.1 - fix(dees-input-text)
|
||||
adjust password toggle and validation icon alignment in text input
|
||||
|
||||
- positions the password toggle and validation icon with fixed top offsets for improved vertical alignment
|
||||
- updates the validation icon styling to use a larger themed icon without the circular background
|
||||
|
||||
## 2026-04-12 - 3.74.0 - feat(input-text)
|
||||
add validated success state and text editing context menu to text inputs
|
||||
|
||||
- show a delayed checkmark confirmation for successful validation and hide inline validation text afterward
|
||||
- move IBAN validation handling into the shared text input validation function
|
||||
- improve the email validation demo to use a stricter regex-based check
|
||||
- add cut, copy, paste, and select-all context menu actions for text inputs
|
||||
|
||||
## 2026-04-12 - 3.73.2 - fix(input,label)
|
||||
correct validation state attribute handling in text inputs and refine label description icon styling
|
||||
|
||||
- Change dees-input-text validationState to reflect as a string attribute and align validation selectors with the emitted host attribute
|
||||
- Wrap the dees-label description icon in a dedicated container to improve sizing, hover feedback, and alignment
|
||||
|
||||
## 2026-04-12 - 3.73.1 - fix(dees-label)
|
||||
align label content and icon consistently using inline flex layout
|
||||
|
||||
- change the label container from inline-block to inline-flex with centered alignment
|
||||
- remove icon-specific vertical transform in favor of layout-based alignment
|
||||
|
||||
## 2026-04-12 - 3.73.0 - feat(dees-label)
|
||||
expand dees-label demo coverage and update supporting dependencies
|
||||
|
||||
- replace the minimal dees-label demo with a structured showcase for basic, required, description, combined, and empty-label states
|
||||
- add themed demo styling and inline annotations to better document component behavior
|
||||
- update @design.estate/dees-wcctools, lucide, and @types/node dependency versions
|
||||
|
||||
## 2026-04-12 - 3.72.1 - fix(dees-stepper)
|
||||
improve stepper exit animation timing for cancel confirmation flow
|
||||
|
||||
- Animate step tiles downward with fade-out during teardown instead of only fading the container
|
||||
- Delay stepper destruction briefly after dismissing the confirmation modal so both exit transitions render smoothly
|
||||
- Increase teardown delay to match the updated exit animation duration
|
||||
|
||||
## 2026-04-11 - 3.72.0 - feat(dees-stepper)
|
||||
add configurable cancellation flow with confirmation modal
|
||||
|
||||
- adds a cancelable option to control whether steppers can be dismissed
|
||||
- shows a confirmation modal when canceling via the new Cancel button or overlay backdrop
|
||||
- updates footer button rendering and separators to support the new cancel action consistently
|
||||
|
||||
## 2026-04-11 - 3.71.1 - fix(dees-modal)
|
||||
move modal content scrolling into dees-tile so long content stays scrollable with pinned header and actions
|
||||
|
||||
- Update dees-tile content area to use vertical scrolling when constrained by a max-height while keeping horizontal overflow clipped.
|
||||
- Remove duplicate scrolling styles from dees-modal and rely on the shared tile container behavior.
|
||||
- Add modal demo cases for long article, list, and form content to verify internal scrolling.
|
||||
|
||||
## 2026-04-11 - 3.71.0 - feat(dees-stepper)
|
||||
add footer menu actions with form-aware step validation
|
||||
|
||||
- replace step footer submit handling with configurable menuOptions actions
|
||||
- disable the primary footer action until required form fields are completed and show a completion hint
|
||||
- dispatch form data before running primary step actions and clean up form subscriptions on destroy
|
||||
- adjust overlay host positioning so the stepper container controls viewport layering correctly
|
||||
|
||||
## 2026-04-11 - 3.70.1 - fix(dees-modal)
|
||||
use icon font sizing for modal header buttons
|
||||
|
||||
- replace fixed width and height on header button icons with font-size to align dees-icon rendering
|
||||
|
||||
## 2026-04-08 - 3.70.0 - feat(dees-table)
|
||||
add opt-in flash highlighting for updated table cells
|
||||
|
||||
- introduces highlight-updates and highlight-duration properties for diff-based cell update highlighting
|
||||
- adds a warning banner when flash highlighting is enabled without rowKey
|
||||
- keeps selection stable across data refreshes and avoids flashing user-edited cells
|
||||
- includes a live demo showcasing flashing updates and reduced-motion support
|
||||
|
||||
## 2026-04-08 - 3.69.1 - fix(ui)
|
||||
refine heading emphasis and animate app dashboard subview expansion
|
||||
|
||||
- Adjust heading color hierarchy so h1-h2 use primary text while h3-h6 use secondary text, and reduce h1 font weight for better visual balance
|
||||
- Replace app dashboard subview conditional rendering with animated expand/collapse behavior using grid transitions and inert state handling
|
||||
|
||||
## 2026-04-08 - 3.69.0 - feat(dees-heading)
|
||||
add numeric aliases for horizontal rule heading levels and refine heading spacing styles
|
||||
|
||||
- Support level="7" as an alias for "hr" and level="8" as an alias for "hr-small".
|
||||
- Update heading and hr variant styles to use design tokens for spacing and colors, with per-level margin tuning.
|
||||
- Extend the demo to show both named and numeric hr heading level variants.
|
||||
|
||||
## 2026-04-08 - 3.68.0 - feat(dees-simple-appdash)
|
||||
add nested sidebar subviews and preserve submit labels from slotted text
|
||||
|
||||
- support grouped navigation items with expandable subviews and parent-to-first-subview fallback in the app dashboard
|
||||
- allow dees-form-submit to derive its button text from light DOM content when no explicit text property is set
|
||||
|
||||
## 2026-04-07 - 3.67.1 - fix(repo)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-04-07 - 3.67.0 - feat(dees-table)
|
||||
improve inline cell editors with integrated input styling and auto-open dropdowns
|
||||
|
||||
- add a visually integrated mode to dees-input-text and dees-input-dropdown for table cell editing
|
||||
- auto-open dropdown editors when a table cell enters edit mode
|
||||
- refine table editing cell outline and dropdown value matching for inline editors
|
||||
|
||||
## 2026-04-07 - 3.66.0 - feat(dees-table)
|
||||
add virtualized row rendering for large tables and optimize table rendering performance
|
||||
|
||||
- add a virtualized mode with configurable overscan to render only visible rows while preserving scroll height
|
||||
- improve table render performance with memoized column and view-data computation plus deferred floating header rendering
|
||||
- update the dees-table demo to showcase virtualized scrolling in the fixed-height example
|
||||
|
||||
## 2026-04-07 - 3.65.0 - feat(dees-table)
|
||||
add schema-based in-cell editing with keyboard navigation and cell edit events
|
||||
|
||||
- replace editableFields with per-column editor configuration for text, number, checkbox, dropdown, date, and tags inputs
|
||||
- add focused/editing cell state with arrow key navigation plus Enter, Tab, Shift+Tab, F2, and Escape editing controls
|
||||
- dispatch cellEdit and cellEditError events with typed payloads and support column-level format, parse, validate, and editorOptions hooks
|
||||
- update table styles and demos to reflect editable cell behavior and rename sticky header usage to fixedHeight
|
||||
|
||||
## 2026-04-07 - 3.64.0 - feat(dees-table)
|
||||
add file-manager style row selection and JSON copy support
|
||||
|
||||
- adds optional selection checkbox rendering via the show-selection-checkbox property
|
||||
- supports plain, ctrl/cmd, and shift-click row selection with range selection behavior
|
||||
- adds Ctrl/Cmd+C and context menu actions to copy selected rows as formatted JSON
|
||||
- updates row selection styling to prevent native text selection during range selection
|
||||
|
||||
## 2026-04-07 - 3.63.0 - feat(dees-table)
|
||||
add floating header support with fixed-height table mode
|
||||
|
||||
- replace the sticky-header option with a fixed-height mode for internal scrolling
|
||||
- add a JS-managed floating header so column headers remain visible when tables scroll inside ancestor containers
|
||||
- sync floating header column widths and filter rows with the rendered table
|
||||
|
||||
## 2026-04-07 - 3.62.0 - feat(dees-table)
|
||||
add multi-column sorting with header menu controls and priority indicators
|
||||
|
||||
|
||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@design.estate/dees-catalog",
|
||||
"version": "3.62.0",
|
||||
"version": "3.77.0",
|
||||
"private": false,
|
||||
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
||||
"main": "dist_ts_web/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"dependencies": {
|
||||
"@design.estate/dees-domtools": "^2.5.4",
|
||||
"@design.estate/dees-element": "^2.2.4",
|
||||
"@design.estate/dees-wcctools": "^3.8.0",
|
||||
"@design.estate/dees-wcctools": "^3.8.4",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.2.0",
|
||||
@@ -35,10 +35,10 @@
|
||||
"@tiptap/starter-kit": "^2.23.0",
|
||||
"@tsclass/tsclass": "^9.5.0",
|
||||
"echarts": "^5.6.0",
|
||||
"lightweight-charts": "^5.1.0",
|
||||
"highlight.js": "11.11.1",
|
||||
"ibantools": "^4.5.1",
|
||||
"lucide": "^0.577.0",
|
||||
"lightweight-charts": "^5.1.0",
|
||||
"lucide": "^1.8.0",
|
||||
"monaco-editor": "0.55.1",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"xterm": "^5.3.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"@git.zone/tstest": "^3.6.3",
|
||||
"@git.zone/tswatch": "^3.3.2",
|
||||
"@push.rocks/projectinfo": "^5.1.0",
|
||||
"@types/node": "^25.5.0"
|
||||
"@types/node": "^25.6.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
|
||||
65
pnpm-lock.yaml
generated
65
pnpm-lock.yaml
generated
@@ -15,8 +15,8 @@ importers:
|
||||
specifier: ^2.2.4
|
||||
version: 2.2.4
|
||||
'@design.estate/dees-wcctools':
|
||||
specifier: ^3.8.0
|
||||
version: 3.8.0
|
||||
specifier: ^3.8.4
|
||||
version: 3.8.4
|
||||
'@fortawesome/fontawesome-svg-core':
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0
|
||||
@@ -75,8 +75,8 @@ importers:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
lucide:
|
||||
specifier: ^0.577.0
|
||||
version: 0.577.0
|
||||
specifier: ^1.8.0
|
||||
version: 1.8.0
|
||||
monaco-editor:
|
||||
specifier: 0.55.1
|
||||
version: 0.55.1
|
||||
@@ -106,8 +106,8 @@ importers:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
'@types/node':
|
||||
specifier: ^25.5.0
|
||||
version: 25.5.0
|
||||
specifier: ^25.6.0
|
||||
version: 25.6.0
|
||||
|
||||
packages:
|
||||
|
||||
@@ -323,8 +323,8 @@ packages:
|
||||
'@design.estate/dees-element@2.2.4':
|
||||
resolution: {integrity: sha512-O9cA6flBMMd+pBwMQrZXwAWel9yVxgokolb+Em6gvkXxPJ0P/B5UDn4Vc2d4ts3ta55PTBm+l2dPeDVGx/bl7Q==}
|
||||
|
||||
'@design.estate/dees-wcctools@3.8.0':
|
||||
resolution: {integrity: sha512-CC14iVKUrguzD9jIrdPBd9fZ4egVJEZMxl5y8iy0l7WLumeoYvGsoXj5INVkRPLRVLqziIdi4Je1hXqHt2NU+g==}
|
||||
'@design.estate/dees-wcctools@3.8.4':
|
||||
resolution: {integrity: sha512-KpFK/azK+a/Xpq33pXKcho+tdFKVHhKZM5ArvHqo9QMwTczgp5DZZgowTDUuqAofjZwnuVfCPHK/Pw9e64N46A==}
|
||||
|
||||
'@emnapi/core@1.8.1':
|
||||
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
|
||||
@@ -2121,11 +2121,11 @@ packages:
|
||||
'@types/node@16.9.1':
|
||||
resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==}
|
||||
|
||||
'@types/node@22.19.15':
|
||||
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
||||
'@types/node@22.19.17':
|
||||
resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
|
||||
|
||||
'@types/node@25.5.0':
|
||||
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
|
||||
'@types/node@25.6.0':
|
||||
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
|
||||
|
||||
'@types/randomatic@3.1.5':
|
||||
resolution: {integrity: sha512-VCwCTw6qh1pRRw+5rNTAwqPmf6A+hdrkdM7dBpZVmhl7g+em3ONXlYK/bWPVKqVGMWgP0d1bog8Vc/X6zRwRRQ==}
|
||||
@@ -3054,6 +3054,9 @@ packages:
|
||||
lucide@0.577.0:
|
||||
resolution: {integrity: sha512-PpC/m5eOItp/WU/GlQPFBXDOhq6HibL73KzYP37OX3LM7VmzWQF8voEj8QRWUFvy9FIKfeDQkWYoyS1D/MdWFA==}
|
||||
|
||||
lucide@1.8.0:
|
||||
resolution: {integrity: sha512-JjV/QnadgFLj1Pyu9IKl0lknrolFEzo04B64QcYLLeRzZl/iEHpdbSrRRKbyXcv45SZNv+WGjIUCT33e7xHO6Q==}
|
||||
|
||||
make-dir@3.1.0:
|
||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4002,8 +4005,8 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici-types@7.18.2:
|
||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||
undici-types@7.19.2:
|
||||
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
|
||||
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
@@ -4708,7 +4711,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@design.estate/dees-domtools': 2.5.4
|
||||
'@design.estate/dees-element': 2.2.4
|
||||
'@design.estate/dees-wcctools': 3.8.0
|
||||
'@design.estate/dees-wcctools': 3.8.4
|
||||
'@fortawesome/fontawesome-svg-core': 7.2.0
|
||||
'@fortawesome/free-brands-svg-icons': 7.2.0
|
||||
'@fortawesome/free-regular-svg-icons': 7.2.0
|
||||
@@ -4784,7 +4787,7 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@design.estate/dees-wcctools@3.8.0':
|
||||
'@design.estate/dees-wcctools@3.8.4':
|
||||
dependencies:
|
||||
'@design.estate/dees-domtools': 2.5.4
|
||||
'@design.estate/dees-element': 2.2.4
|
||||
@@ -5257,7 +5260,7 @@ snapshots:
|
||||
'@inquirer/figures': 1.0.15
|
||||
'@inquirer/type': 2.0.0
|
||||
'@types/mute-stream': 0.0.4
|
||||
'@types/node': 22.19.15
|
||||
'@types/node': 22.19.17
|
||||
'@types/wrap-ansi': 3.0.0
|
||||
ansi-escapes: 4.3.2
|
||||
cli-width: 4.1.0
|
||||
@@ -7235,7 +7238,7 @@ snapshots:
|
||||
|
||||
'@types/clean-css@4.2.11':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
source-map: 0.6.1
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
@@ -7245,7 +7248,7 @@ snapshots:
|
||||
'@types/fs-extra@11.0.4':
|
||||
dependencies:
|
||||
'@types/jsonfile': 6.1.4
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
@@ -7265,7 +7268,7 @@ snapshots:
|
||||
|
||||
'@types/jsonfile@6.1.4':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
|
||||
@@ -7288,21 +7291,21 @@ snapshots:
|
||||
|
||||
'@types/mute-stream@0.0.4':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/node-forge@1.3.14':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/node@16.9.1': {}
|
||||
|
||||
'@types/node@22.19.15':
|
||||
'@types/node@22.19.17':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/node@25.5.0':
|
||||
'@types/node@25.6.0':
|
||||
dependencies:
|
||||
undici-types: 7.18.2
|
||||
undici-types: 7.19.2
|
||||
|
||||
'@types/randomatic@3.1.5': {}
|
||||
|
||||
@@ -7314,11 +7317,11 @@ snapshots:
|
||||
|
||||
'@types/tar-stream@3.1.4':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/through2@2.0.41':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
@@ -7344,11 +7347,11 @@ snapshots:
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
'@types/node': 25.5.0
|
||||
'@types/node': 25.6.0
|
||||
optional: true
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
@@ -8300,6 +8303,8 @@ snapshots:
|
||||
|
||||
lucide@0.577.0: {}
|
||||
|
||||
lucide@1.8.0: {}
|
||||
|
||||
make-dir@3.1.0:
|
||||
dependencies:
|
||||
semver: 6.3.1
|
||||
@@ -9570,7 +9575,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
undici-types@7.19.2: {}
|
||||
|
||||
unified@11.0.5:
|
||||
dependencies:
|
||||
|
||||
31
readme.md
31
readme.md
@@ -59,8 +59,8 @@ For developers working on this library, please refer to the [UI Components Playb
|
||||
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputToggle`](#deesinputtoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputList`](#deesinputlist), [`DeesInputProfilepicture`](#deesinputprofilepicture), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesInputCode`](#deesinputcode), [`DeesFormSubmit`](#deesformsubmit) |
|
||||
| **App Shell (Layout)** | [`DeesAppui`](#deesappui-️), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiBottombar`](#deesappuibottombar), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
||||
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination), [`DeesStorageBrowser`](#deesstorgebrowser) |
|
||||
| **Media & Tiles** | [`DeesTilePdf`](#deestilepdf), [`DeesTileImage`](#deestileimage), [`DeesTileAudio`](#deestileaudio), [`DeesTileVideo`](#deestilevideo), [`DeesTileNote`](#deestilenote), [`DeesTileFolder`](#deestilefolder), [`DeesPreview`](#deespreview), [`DeesPdfViewer`](#deespdfviewer), [`DeesPdfPreview`](#deespdfpreview), [`DeesImageViewer`](#deesimageviewer), [`DeesAudioViewer`](#deesaudioviewer), [`DeesVideoViewer`](#deesvideoviewer) |
|
||||
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
||||
| **Media & Thumbnails** | [`DeesThumbnailPdf`](#deesthumbnailpdf), [`DeesThumbnailImage`](#deesthumbnailimage), [`DeesThumbnailAudio`](#deesthumbnailaudio), [`DeesThumbnailVideo`](#deesthumbnalvideo), [`DeesThumbnailNote`](#deesthumbnailnote), [`DeesThumbnailFolder`](#deesthumbnailfolder), [`DeesPreview`](#deespreview), [`DeesPdfViewer`](#deespdfviewer), [`DeesImageViewer`](#deesimageviewer), [`DeesAudioViewer`](#deesaudioviewer), [`DeesVideoViewer`](#deesvideoviewer) |
|
||||
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartBar`](#deeschartbar), [`DeesChartDonut`](#deeschartdonut), [`DeesChartGauge`](#deeschartgauge), [`DeesChartRadar`](#deeschartradar), [`DeesChartLog`](#deeschartlog) |
|
||||
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
||||
| **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
|
||||
| **Workspace / IDE** | [`DeesWorkspace`](#deesworkspace), [`DeesWorkspaceMonaco`](#deesworkspacemonaco), [`DeesWorkspaceDiffEditor`](#deesworkspacediffeditor), [`DeesWorkspaceFiletree`](#deesworkspacefiletree), [`DeesWorkspaceTerminal`](#deesworkspaceterminal), [`DeesWorkspaceTerminalPreview`](#deesworkspaceterminalpreview), [`DeesWorkspaceMarkdown`](#deesworkspacemarkdown), [`DeesWorkspaceMarkdownoutlet`](#deesworkspacemarkdownoutlet), [`DeesWorkspaceBottombar`](#deesworkspacebottombar) |
|
||||
@@ -143,14 +143,13 @@ Display icons from FontAwesome and Lucide icon libraries with library prefixes.
|
||||
```
|
||||
|
||||
#### `DeesLabel`
|
||||
Text label component with optional icon and status indicators.
|
||||
Text label component with optional required indicator and info tooltip. Used internally by all input components.
|
||||
|
||||
```typescript
|
||||
<dees-label
|
||||
text="Status" // Label text
|
||||
icon="info-circle" // Optional: icon name
|
||||
type="info" // Options: default, info, success, warning, error
|
||||
size="medium" // Options: small, medium, large
|
||||
.label=${'Email Address'} // Label text
|
||||
.required=${true} // Optional: shows red asterisk
|
||||
.infoText=${'We will never share your email'} // Optional: shows hover info icon with tooltip
|
||||
></dees-label>
|
||||
```
|
||||
|
||||
@@ -321,7 +320,7 @@ Container component for form elements with built-in validation and data handling
|
||||
```
|
||||
|
||||
#### `DeesInputText`
|
||||
Text input field with validation and formatting options.
|
||||
Text input field with validation, info tooltips, description text, and context menu (Cut/Copy/Paste/Select All).
|
||||
|
||||
```typescript
|
||||
<dees-input-text
|
||||
@@ -330,10 +329,20 @@ Text input field with validation and formatting options.
|
||||
value="initial@value.com" // Initial value
|
||||
required // Makes the field required
|
||||
disabled // Disables the input
|
||||
placeholder="Enter your email"
|
||||
.infoText=${'Hover icon tooltip text'} // Shows ⓘ icon on label with hover tooltip
|
||||
.description=${'Permanent help text below the input'} // Small text below the input
|
||||
.validationFunction=${(value) => { // Auto-validates on every keystroke
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (emailRegex.test(value)) {
|
||||
return { valid: true, message: 'Email is valid' };
|
||||
}
|
||||
return { valid: false, message: 'Please enter a valid email' };
|
||||
}}
|
||||
></dees-input-text>
|
||||
```
|
||||
|
||||
> 💡 **All input components** share these common properties from `DeesInputBase`: `key`, `label`, `required`, `disabled`, `infoText`, `description`, `layoutMode`, `labelPosition`.
|
||||
|
||||
#### `DeesInputCheckbox`
|
||||
Checkbox input component for boolean values.
|
||||
|
||||
@@ -1780,7 +1789,7 @@ interface ITileFolderItem {
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./license) file.
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
@@ -1798,5 +1807,3 @@ Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
|
||||
352
readme.plan.md
Normal file
352
readme.plan.md
Normal file
@@ -0,0 +1,352 @@
|
||||
# Plan: dees-stepper — adopt dees-tile + optional overlay window layer
|
||||
|
||||
> First line (per CLAUDE.md): Please reread `/home/philkunz/.claude/CLAUDE.md` before continuing.
|
||||
|
||||
## Context
|
||||
|
||||
Today `dees-stepper` is an inline-only layout component: it hard-codes each step as a custom `.step` `<div>` with its own border / background / box-shadow / border-radius, and its `:host` is `position: absolute; width: 100%; height: 100%;` so it can only live inside a bounded parent container.
|
||||
|
||||
The user wants it to behave more like `dees-modal`:
|
||||
|
||||
1. Each step should be wrapped in a `<dees-tile>` — the unified "rounded on rounded" frame used by modals and panels — rather than a bespoke `.step` div.
|
||||
2. A `DeesWindowLayer` should be added behind the stepper, the same way `DeesModal.createAndShow` does, so the stepper can appear as an overlay on top of the page.
|
||||
|
||||
User has confirmed (via AskUserQuestion in this session):
|
||||
- **API**: keep the current inline usage working AND add a static `createAndShow()` like dees-modal.
|
||||
- **Layout**: keep the current vertical stack + SweetScroll behavior inside the overlay (don't switch to single-tile swap).
|
||||
- **Nav placement**: split header/footer — goBack + step counter go into the `dees-tile` header slot; the title stays in the content area; the tile footer is used for optional next/submit buttons supplied per-step.
|
||||
|
||||
No external consumers of `dees-stepper` were found inside this package (`grep dees-stepper|DeesStepper` only matches its own source, demo, index, changelog, readme). External consumers in dependent projects may exist — the refactor is kept backward-compatible for the inline path.
|
||||
|
||||
## Current state (reference)
|
||||
|
||||
**File:** `ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts` (lines 20–299)
|
||||
|
||||
- `IStep` interface — `title`, `content: TemplateResult`, `validationFunc`, `onReturnToStepFunc`, internal flags (lines 20–27).
|
||||
- `:host { position: absolute; width: 100%; height: 100%; }` (lines 59–63).
|
||||
- `.stepperContainer` — absolute, 100% w/h, `overflow: hidden`, holds SweetScroll (lines 64–69).
|
||||
- `.step` — max-width 500, min-height 300, `border-radius: 12px`, theme background, theme border, `box-shadow: 0 8px 32px rgba(0,0,0,0.4)`, `filter: opacity(0.55) saturate(0.85)`, transform transition (lines 71–97). **These frame styles overlap with what `dees-tile` already provides.**
|
||||
- `.step.selected` — `filter: opacity(1) saturate(1)` (lines 89–93). **Scroll-through visual cue, keep.**
|
||||
- `.step.hiddenStep` — `filter: opacity(0)` (line 95). **Keep.**
|
||||
- `.step.entrance` — faster transition variant for first-render (lines 99–105). **Keep.**
|
||||
- `.step .stepCounter` — `position: absolute; top: 12px; right: 12px;` pill (lines 111–121). **Move into header slot as a flex child.**
|
||||
- `.step .goBack` — `position: absolute; top: 12px; left: 12px;` pill + icon + hover (lines 123–161). **Move into header slot as a flex child.**
|
||||
- `.step .title` — centered, 24px, 64px top padding (lines 163–171). **Keep inside the tile's content slot; remove the 64px top padding since goBack/counter no longer overlap it.**
|
||||
- `.step .content` — 32px padding (lines 173–175). **Keep.**
|
||||
- `render()` (lines 179–204) — maps `steps` to `.step` divs.
|
||||
- `setScrollStatus()` (lines 226–263) — SweetScroll container setup + step validation kick-off. **Keep mostly as-is; selectors still target `.step`/`.selected` so rename cautiously.**
|
||||
- `firstUpdated` (lines 210–218), `updated` (lines 220–222), `goBack` (lines 265–282), `goNext` (lines 284–298) — untouched in behavior, only DOM selectors may need adjusting.
|
||||
|
||||
**Reference files (read, do not modify):**
|
||||
- `ts_web/elements/00group-overlay/dees-modal/dees-modal.ts` — canonical `createAndShow` + `destroy` + window-layer coordination + z-index registry usage.
|
||||
- `ts_web/elements/00group-layout/dees-tile/dees-tile.ts` — slot API: `slot="header"`, default slot, `slot="footer"`. Auto-hides footer when slotted nodes are empty. Uses `part="outer"`, `part="header"`, `part="content"`, `part="footer"` for external shadow-part styling.
|
||||
- `ts_web/elements/00group-overlay/dees-windowlayer/dees-windowlayer.ts` — `createAndShow({ blur })`, `destroy()`, dispatches `clicked` event on backdrop click, uses `zIndexRegistry`.
|
||||
- `ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts` — existing inline demo.
|
||||
|
||||
## Target state
|
||||
|
||||
### 1. IStep interface — add one optional field
|
||||
|
||||
```ts
|
||||
export interface IStep {
|
||||
title: string;
|
||||
content: TemplateResult;
|
||||
footerContent?: TemplateResult; // NEW: optional, rendered in dees-tile footer slot
|
||||
validationFunc?: (stepper: DeesStepper, htmlElement: HTMLElement, signal?: AbortSignal) => Promise<any>;
|
||||
onReturnToStepFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise<any>;
|
||||
validationFuncCalled?: boolean;
|
||||
abortController?: AbortController;
|
||||
}
|
||||
```
|
||||
|
||||
Form-based steps don't need `footerContent` — their `dees-form-submit` stays inside the form in the content slot (as today). `footerContent` is for non-form steps that need an explicit primary action, or for any step that wants buttons in the conventional tile footer location.
|
||||
|
||||
### 2. New overlay-mode state + API on DeesStepper
|
||||
|
||||
```ts
|
||||
@state() accessor overlay: boolean = false;
|
||||
@state() accessor stepperZIndex: number = 1000;
|
||||
private windowLayer?: DeesWindowLayer;
|
||||
|
||||
public static async createAndShow(optionsArg: {
|
||||
steps: IStep[];
|
||||
}): Promise<DeesStepper> {
|
||||
const body = document.body;
|
||||
const stepper = new DeesStepper();
|
||||
stepper.steps = optionsArg.steps;
|
||||
stepper.overlay = true;
|
||||
stepper.windowLayer = await DeesWindowLayer.createAndShow({ blur: true });
|
||||
stepper.windowLayer.addEventListener('click', async () => {
|
||||
await stepper.destroy();
|
||||
});
|
||||
body.append(stepper.windowLayer); // (already appended inside createAndShow, but mirror dees-modal's pattern; see note)
|
||||
body.append(stepper);
|
||||
stepper.stepperZIndex = zIndexRegistry.getNextZIndex();
|
||||
zIndexRegistry.register(stepper, stepper.stepperZIndex);
|
||||
return stepper;
|
||||
}
|
||||
|
||||
public async destroy() {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const container = this.shadowRoot!.querySelector('.stepperContainer');
|
||||
container?.classList.add('predestroy');
|
||||
await domtools.convenience.smartdelay.delayFor(200);
|
||||
if (this.parentElement) this.parentElement.removeChild(this);
|
||||
if (this.windowLayer) await this.windowLayer.destroy();
|
||||
zIndexRegistry.unregister(this);
|
||||
}
|
||||
```
|
||||
|
||||
**Note on `body.append(windowLayer)`:** `DeesWindowLayer.createAndShow` already appends the window layer to `document.body` (line 27 of `dees-windowlayer.ts`). `dees-modal.ts:71` still calls `body.append(modal.windowLayer)` — that's either a no-op (already-attached nodes) or a re-parent to keep ordering. I will match dees-modal's exact sequence verbatim to avoid introducing subtle differences; if it's a bug in dees-modal it is out of scope for this task.
|
||||
|
||||
**Minimum new scope for createAndShow:** just `steps` for now. No `onComplete`, no `showCloseButton`, no width options. Future-proofing via additional options is an explicit follow-up — this plan keeps scope razor-sharp (per CLAUDE.md). The caller can already wire completion via the last step's `validationFunc` calling back into their own code.
|
||||
|
||||
### 3. Render template — wrap each step in `<dees-tile>`
|
||||
|
||||
```ts
|
||||
public render() {
|
||||
return html`
|
||||
<div class="stepperContainer ${this.overlay ? 'overlay' : ''}" style="${this.overlay ? `z-index: ${this.stepperZIndex}` : ''}">
|
||||
${this.steps.map((stepArg, i) => {
|
||||
const isSelected = stepArg === this.selectedStep;
|
||||
const isHidden = this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep);
|
||||
const isFirst = i === 0;
|
||||
const stepNumber = i + 1;
|
||||
return html`
|
||||
<dees-tile
|
||||
class="step ${isSelected ? 'selected' : ''} ${isHidden ? 'hiddenStep' : ''} ${isFirst ? 'entrance' : ''}"
|
||||
>
|
||||
<div slot="header" class="step-header">
|
||||
${!isFirst
|
||||
? html`<div class="goBack" @click=${this.goBack}>
|
||||
<span>←</span> go to previous step
|
||||
</div>`
|
||||
: html`<div class="goBack-spacer"></div>`}
|
||||
<div class="stepCounter">Step ${stepNumber} of ${this.steps.length}</div>
|
||||
</div>
|
||||
<div class="step-body">
|
||||
<div class="title">${stepArg.title}</div>
|
||||
<div class="content">${stepArg.content}</div>
|
||||
</div>
|
||||
${stepArg.footerContent
|
||||
? html`<div slot="footer" class="step-footer">${stepArg.footerContent}</div>`
|
||||
: ''}
|
||||
</dees-tile>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
**Key detail:** on the first step, render a `.goBack-spacer` (empty div) in the header instead of nothing — so the `stepCounter` stays right-aligned via `justify-content: space-between`. Without a spacer, flex would left-align the counter on step 1.
|
||||
|
||||
### 4. CSS changes
|
||||
|
||||
**Remove from `.step`:**
|
||||
- `border-radius: 12px;`
|
||||
- `background: ${cssManager.bdTheme(...)};`
|
||||
- `border: 1px solid ${cssManager.bdTheme(...)};`
|
||||
- `color: ${cssManager.bdTheme(...)};`
|
||||
- `box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);`
|
||||
- `overflow: hidden;`
|
||||
|
||||
**Why:** `dees-tile` owns all of these now. The `.step` selector still exists (since `dees-tile` has `class="step ..."` on it), but it only controls the outer animation wrapper: `max-width`, `min-height`, `margin`, `filter`, `transform`, `transition`, `user-select`, `pointer-events`.
|
||||
|
||||
**Keep on `.step`:**
|
||||
- `position: relative;`
|
||||
- `pointer-events: none;` + `.step.selected { pointer-events: all; }`
|
||||
- `max-width: 500px;` / `min-height: 300px;`
|
||||
- `margin: auto; margin-bottom: 20px;`
|
||||
- `filter: opacity(0.55) saturate(0.85);` + `.selected { filter: opacity(1) saturate(1); }`
|
||||
- `.hiddenStep { filter: opacity(0); }`
|
||||
- All the cubic-bezier transitions (transform/filter/box-shadow — but box-shadow is now a no-op since dees-tile provides the shadow; leave the transition spec in so we don't have to re-check browser parsing; or just drop `box-shadow` from the transition list — I'll drop it for cleanliness).
|
||||
- `.step.entrance` + `.step.entrance.hiddenStep { transform: translateY(16px); }`
|
||||
- `.step:last-child { margin-bottom: 100vh; }`
|
||||
|
||||
**Add for dees-tile shadow enhancement:** use `::part(outer)` to apply the modal-style elevated shadow only when in overlay mode (optional polish — inline mode stays flat):
|
||||
```css
|
||||
.stepperContainer.overlay dees-tile.step::part(outer) {
|
||||
box-shadow:
|
||||
0 0 0 1px ${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 100% / 0.03)')},
|
||||
0 8px 40px ${cssManager.bdTheme('hsl(0 0% 0% / 0.12)', 'hsl(0 0% 0% / 0.5)')},
|
||||
0 2px 8px ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 0% / 0.25)')};
|
||||
}
|
||||
```
|
||||
This exactly mirrors the dees-modal::part(outer) shadow stack (dees-modal.ts:157–161) so the overlay stepper reads as "same visual language as modal."
|
||||
|
||||
**Restyle `.step-header` (NEW — the `<div slot="header">`):**
|
||||
```css
|
||||
.step-header {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
```
|
||||
|
||||
**Restyle `.step .stepCounter` → `.step-header .stepCounter` (move from absolute to flex child):**
|
||||
- Drop `position: absolute; top: 12px; right: 12px;`
|
||||
- Keep everything else (padding, font-size, border-radius, background, border).
|
||||
|
||||
**Restyle `.step .goBack` → `.step-header .goBack` (move from absolute to flex child):**
|
||||
- Drop `position: absolute; top: 12px; left: 12px;`
|
||||
- Keep everything else (padding, font-size, border-radius, background, border, hover/active states).
|
||||
|
||||
**Add `.goBack-spacer`:**
|
||||
```css
|
||||
.goBack-spacer { width: 1px; } /* placeholder so flex space-between works on step 1 */
|
||||
```
|
||||
|
||||
**Restyle `.step .title`:**
|
||||
- Drop `padding-top: 64px;` — no longer overlaps anything since header is in its own slot.
|
||||
- Keep `text-align: center; font-family: 'Geist Sans', sans-serif; font-size: 24px; font-weight: 600; letter-spacing: -0.01em; color: inherit;`
|
||||
- Add `padding-top: 32px;` (or similar) so there's consistent breathing room above the title inside the tile content.
|
||||
|
||||
**Add `.step-footer` (new container for `stepArg.footerContent`):**
|
||||
```css
|
||||
.step-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
```
|
||||
|
||||
**Add overlay-mode positioning:**
|
||||
```css
|
||||
.stepperContainer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stepperContainer.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
.stepperContainer.predestroy {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
```
|
||||
|
||||
**Adjust `:host` for dual-mode:**
|
||||
```css
|
||||
:host {
|
||||
position: absolute; /* inline default */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: ${cssGeistFontFamily};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
:host([overlay]) {
|
||||
position: fixed; /* overlay mode */
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
```
|
||||
|
||||
The `overlay` @state needs to reflect to an attribute for the `:host([overlay])` selector to work. Since `@state` doesn't reflect attributes, use `@property({ type: Boolean, reflect: true })` instead — change the decorator accordingly.
|
||||
|
||||
### 5. Imports to add in `dees-stepper.ts`
|
||||
|
||||
```ts
|
||||
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
|
||||
import { zIndexRegistry } from '../../00zindex.js';
|
||||
import { cssGeistFontFamily } from '../../00fonts.js';
|
||||
import '../../00group-layout/dees-tile/dees-tile.js';
|
||||
```
|
||||
|
||||
`dees-tile` side-effect import registers the custom element. `cssGeistFontFamily` is only needed if I add it to `:host` (which I want, to match modal).
|
||||
|
||||
### 6. SweetScroll selector stability
|
||||
|
||||
`setScrollStatus()` selectors target `.step` and `.selected` (lines 228–229). These continue to match since I'm keeping those class names on the `<dees-tile>` elements. **No selector changes needed.**
|
||||
|
||||
One subtlety: `offsetTop` / `offsetHeight` on `<dees-tile>` should still work — the tile's `:host` is `display: flex; flex-direction: column;` which participates in layout. I'll verify visually in the demo.
|
||||
|
||||
### 7. Demo update
|
||||
|
||||
**File:** `ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts`
|
||||
|
||||
Current demo renders one inline stepper directly. I'll keep that and add an **overlay launcher button** above it:
|
||||
|
||||
```ts
|
||||
export const stepperDemo = () => html`
|
||||
<div style="padding: 16px;">
|
||||
<dees-button @click=${async () => {
|
||||
const stepper = await DeesStepper.createAndShow({
|
||||
steps: [/* same steps as inline demo */],
|
||||
});
|
||||
}}>Open stepper as overlay</dees-button>
|
||||
</div>
|
||||
<dees-stepper .steps=${[/* ... existing inline demo steps ... */]}></dees-stepper>
|
||||
`;
|
||||
```
|
||||
|
||||
Extract the step definitions into a `const demoSteps = [...]` above the template so both the inline and overlay paths reuse them (DRY). Import `DeesStepper` at the top of the demo file.
|
||||
|
||||
## Files to modify
|
||||
|
||||
1. **`ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts`** — main refactor (IStep, imports, render, styles, createAndShow, destroy, overlay state).
|
||||
2. **`ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts`** — add overlay launcher button, extract shared `demoSteps` const, import `DeesStepper`.
|
||||
|
||||
**Files explicitly NOT modified:**
|
||||
- `dees-tile.ts` — used as-is via its slot API.
|
||||
- `dees-windowlayer.ts` — used as-is via `createAndShow` / `destroy` / `click` event.
|
||||
- `dees-modal.ts` — reference only.
|
||||
- `00zindex.ts` — reference only.
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Build**: `pnpm run build` — must pass with no TS errors. Pure refactor, no new dependencies, no lib-check regressions expected.
|
||||
|
||||
2. **Inline demo (backward compat)**:
|
||||
- Start the demo server (port 8080 is already running) and navigate to the dees-stepper demo page.
|
||||
- Confirm the stepper renders inline exactly like before: first step centered, subsequent steps dimmed below, scroll-through animation on goNext / goBack.
|
||||
- Fill out the first form, submit → stepper scrolls to step 2. Click goBack → scrolls back.
|
||||
- Confirm the `dees-tile` frame is visible on each step (rounded, bordered, themed) and that the title + form are inside the tile's content area.
|
||||
- Confirm goBack button + step counter sit in the tile's header row, space-between, left/right respectively.
|
||||
|
||||
3. **Overlay demo (new path)**:
|
||||
- Click the "Open stepper as overlay" button.
|
||||
- Confirm a `dees-windowlayer` with blur appears behind the stepper.
|
||||
- Confirm the stepper fills the viewport (fixed, 100vw×100vh).
|
||||
- Confirm z-index stacking: stepper above window layer above page content.
|
||||
- Click the window layer (outside the tile) → stepper animates out, then destroys along with the window layer.
|
||||
- Re-open and step through forward & back — behavior identical to inline mode.
|
||||
|
||||
4. **Playwright visual check** (per CLAUDE.md: screenshots MUST go in `.playwright-mcp/`):
|
||||
- `.playwright-mcp/dees-stepper-inline.png` — inline mode, step 1 with form.
|
||||
- `.playwright-mcp/dees-stepper-overlay.png` — overlay mode, same step.
|
||||
- `.playwright-mcp/dees-stepper-overlay-step3.png` — overlay mode mid-flow, to verify scroll-stack visual.
|
||||
- Both light and dark themes if the demo has a theme toggle.
|
||||
|
||||
5. **Grep sanity**:
|
||||
- Confirm `dees-stepper` has no new unexpected match locations: `grep dees-stepper ts_web/` should still only match stepper's own files.
|
||||
- Confirm no `.step` class collisions elsewhere (unlikely — `.step` is a plain class name; all usages should be shadow-scoped to `dees-stepper`).
|
||||
|
||||
## Open assumptions & deferred scope
|
||||
|
||||
These are explicit defaults in this plan. If the user wants different behavior for any of them, they should flag it on review — each is a simple follow-up but not in scope right now (CLAUDE.md: stay focused, no "while we're at it"):
|
||||
|
||||
- **No close button on overlay stepper.** Clicking the window layer backdrop is the only way to dismiss. Matches how dees-modal with `showCloseButton: false` behaves. Can add a close button in a follow-up.
|
||||
- **No `onComplete` callback in `createAndShow`.** The last step doesn't auto-destroy the overlay — the app controls it via the step's `validationFunc`. Can add a callback option in a follow-up.
|
||||
- **No width/size options in `createAndShow`.** The step tile continues to use the stepper's existing `max-width: 500px`. Can parameterize in a follow-up.
|
||||
- **Box-shadow in the `.step` transition list** is dropped from the transition for cleanliness — the box-shadow is now on `dees-tile::part(outer)` and doesn't change between selected/hiddenStep, so transitioning it was already a no-op.
|
||||
- **`pnpm start` / dev server path**: I'll reuse the existing server on port 8080 that was already listening when this session began; if that server doesn't serve the stepper demo, I'll start wcctools manually.
|
||||
|
||||
## Risk
|
||||
|
||||
- **Low-medium.** The change is localized to one component and its demo. No API removal, only an additive `createAndShow` + an optional `footerContent` field. External consumers of the inline API continue to work if they only set `steps` + `selectedStep`.
|
||||
- **Biggest risk:** SweetScroll's `offsetTop` / `offsetHeight` measurements on `<dees-tile>` may compute differently than on the former `<div class="step">` because `dees-tile` has an internal `display: flex; flex-direction: column;` host and a `.tile-outer { flex: 1; min-height: 0; }` inner frame. If the scroll math drifts, the mitigation is to keep the `.step` wrapper as an outer `<div>` that **contains** a `<dees-tile>`, rather than putting the class directly on `<dees-tile>`. That preserves the exact box model SweetScroll was measuring. I'll try the direct-class approach first (simpler) and fall back to the wrapper approach if the scroll target looks off in the demo.
|
||||
- **Second risk:** The `:host([overlay])` attribute selector requires `overlay` to be a reflected `@property`, not `@state`. I've already accounted for this in the plan (decorator change).
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.62.0',
|
||||
version: '3.77.0',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type ITableAction } from './dees-table.js';
|
||||
import * as plugins from '../../00plugins.js';
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
|
||||
interface ITableDemoData {
|
||||
date: string;
|
||||
@@ -55,36 +56,66 @@ export const demoFunc = () => html`
|
||||
<div class="demo-container">
|
||||
<div class="demo-section">
|
||||
<h2 class="demo-title">Basic Table with Actions</h2>
|
||||
<p class="demo-description">A standard table with row actions, editable fields, and context menu support. Double-click on descriptions to edit. Grid lines are enabled by default.</p>
|
||||
<p class="demo-description">A standard table with row actions, editable cells, and context menu support. Double-click any cell to edit. Tab moves to the next editable cell, Enter to the row below, Esc cancels.</p>
|
||||
<dees-table
|
||||
heading1="Current Account Statement"
|
||||
heading2="Bunq - Payment Account 2 - April 2021"
|
||||
.editableFields="${['description']}"
|
||||
.columns=${[
|
||||
{ key: 'date', header: 'Date', sortable: true, editable: true, editor: 'date' },
|
||||
{ key: 'amount', header: 'Amount', editable: true, editor: 'text' },
|
||||
{
|
||||
key: 'category',
|
||||
header: 'Category',
|
||||
editable: true,
|
||||
editor: 'dropdown',
|
||||
editorOptions: {
|
||||
options: [
|
||||
{ option: 'Office Supplies', key: 'office' },
|
||||
{ option: 'Hardware', key: 'hardware' },
|
||||
{ option: 'Software', key: 'software' },
|
||||
{ option: 'Travel', key: 'travel' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ key: 'description', header: 'Description', editable: true },
|
||||
{ key: 'reconciled', header: 'OK', editable: true, editor: 'checkbox' },
|
||||
]}
|
||||
@cellEdit=${(e: CustomEvent) => console.log('cellEdit', e.detail)}
|
||||
.data=${[
|
||||
{
|
||||
date: '2021-04-01',
|
||||
amount: '2464.65 €',
|
||||
description: 'Printing Paper (Office Supplies) - STAPLES BREMEN',
|
||||
category: 'office',
|
||||
description: 'Printing Paper - STAPLES BREMEN',
|
||||
reconciled: true,
|
||||
},
|
||||
{
|
||||
date: '2021-04-02',
|
||||
amount: '165.65 €',
|
||||
description: 'Logitech Mouse (Hardware) - logi.com OnlineShop',
|
||||
category: 'hardware',
|
||||
description: 'Logitech Mouse - logi.com OnlineShop',
|
||||
reconciled: false,
|
||||
},
|
||||
{
|
||||
date: '2021-04-03',
|
||||
amount: '2999,00 €',
|
||||
description: 'Macbook Pro 16inch (Hardware) - Apple.de OnlineShop',
|
||||
category: 'hardware',
|
||||
description: 'Macbook Pro 16inch - Apple.de OnlineShop',
|
||||
reconciled: false,
|
||||
},
|
||||
{
|
||||
date: '2021-04-01',
|
||||
amount: '2464.65 €',
|
||||
category: 'office',
|
||||
description: 'Office-Supplies - STAPLES BREMEN',
|
||||
reconciled: true,
|
||||
},
|
||||
{
|
||||
date: '2021-04-01',
|
||||
amount: '2464.65 €',
|
||||
category: 'office',
|
||||
description: 'Office-Supplies - STAPLES BREMEN',
|
||||
reconciled: true,
|
||||
},
|
||||
]}
|
||||
dataName="transactions"
|
||||
@@ -510,13 +541,13 @@ export const demoFunc = () => html`
|
||||
<h2 class="demo-title">Column Filters + Sticky Header (New)</h2>
|
||||
<p class="demo-description">Per-column quick filters and sticky header with internal scroll. Try filtering the Name column. Uses --table-max-height var.</p>
|
||||
<style>
|
||||
dees-table[sticky-header] { --table-max-height: 220px; }
|
||||
dees-table[fixed-height] { --table-max-height: 220px; }
|
||||
</style>
|
||||
<dees-table
|
||||
heading1="Employees"
|
||||
heading2="Quick filter per column + sticky header"
|
||||
.showColumnFilters=${true}
|
||||
.stickyHeader=${true}
|
||||
.fixedHeight=${true}
|
||||
.columns=${[
|
||||
{ key: 'name', header: 'Name', sortable: true },
|
||||
{ key: 'email', header: 'Email', sortable: true },
|
||||
@@ -669,8 +700,9 @@ export const demoFunc = () => html`
|
||||
</style>
|
||||
<dees-table
|
||||
id="scrollSmallHeight"
|
||||
.stickyHeader=${true}
|
||||
heading1="People Directory (Scrollable)"
|
||||
.fixedHeight=${true}
|
||||
.virtualized=${true}
|
||||
heading1="People Directory (Scrollable, Virtualized)"
|
||||
heading2="Forced scrolling with many items"
|
||||
.columns=${[
|
||||
{ key: 'id', header: 'ID', sortable: true },
|
||||
@@ -711,6 +743,71 @@ export const demoFunc = () => html`
|
||||
] as ITableAction[]}
|
||||
></dees-table>
|
||||
</div>
|
||||
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
const tableEl = elementArg.querySelector('#demoLiveFlash') as any;
|
||||
if (!tableEl) return;
|
||||
// Guard against double-start if runAfterRender fires more than once
|
||||
// (e.g. across hot-reload cycles).
|
||||
if (tableEl.__liveFlashTimerId) {
|
||||
window.clearInterval(tableEl.__liveFlashTimerId);
|
||||
}
|
||||
const tick = () => {
|
||||
if (!Array.isArray(tableEl.data) || tableEl.data.length === 0) return;
|
||||
const next = tableEl.data.map((r: any) => ({ ...r }));
|
||||
const count = 1 + Math.floor(Math.random() * 3);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const idx = Math.floor(Math.random() * next.length);
|
||||
const delta = +((Math.random() * 2 - 1) * 3).toFixed(2);
|
||||
const newPrice = Math.max(1, +(next[idx].price + delta).toFixed(2));
|
||||
next[idx] = {
|
||||
...next[idx],
|
||||
price: newPrice,
|
||||
change: delta,
|
||||
updatedAt: new Date().toLocaleTimeString(),
|
||||
};
|
||||
}
|
||||
tableEl.data = next;
|
||||
};
|
||||
tableEl.__liveFlashTimerId = window.setInterval(tick, 1500);
|
||||
}}>
|
||||
<div class="demo-section">
|
||||
<h2 class="demo-title">Live Updates with Flash Highlighting</h2>
|
||||
<p class="demo-description">
|
||||
Opt-in cell-flash via <code>highlight-updates="flash"</code>. The ticker below mutates
|
||||
random rows every 1.5s and reassigns <code>.data</code>. Updated cells briefly flash
|
||||
amber and fade out. Requires <code>rowKey</code> (here <code>"symbol"</code>). Honors
|
||||
<code>prefers-reduced-motion</code>. Row selection persists across updates — click a
|
||||
row, then watch it stay selected as the data churns.
|
||||
</p>
|
||||
<dees-table
|
||||
id="demoLiveFlash"
|
||||
.rowKey=${'symbol'}
|
||||
highlight-updates="flash"
|
||||
.selectionMode=${'multi'}
|
||||
heading1="Live Market Feed"
|
||||
heading2="Flashing cells indicate updated values"
|
||||
.columns=${[
|
||||
{ key: 'symbol', header: 'Symbol', sortable: true },
|
||||
{ key: 'price', header: 'Price', sortable: true },
|
||||
{ key: 'change', header: 'Δ', sortable: true },
|
||||
{ key: 'updatedAt', header: 'Updated' },
|
||||
]}
|
||||
.data=${[
|
||||
{ symbol: 'AAPL', price: 182.52, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'MSFT', price: 414.18, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'GOOG', price: 168.74, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'AMZN', price: 186.13, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'TSLA', price: 248.50, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'NVDA', price: 877.35, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'META', price: 492.96, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'NFLX', price: 605.88, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'AMD', price: 165.24, change: 0, updatedAt: '—' },
|
||||
{ symbol: 'INTC', price: 42.15, change: 0, updatedAt: '—' },
|
||||
]}
|
||||
></dees-table>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -114,29 +114,48 @@ export const tableStyles: CSSResult[] = [
|
||||
border-bottom-width: 0px;
|
||||
}
|
||||
|
||||
/* Default mode (Mode B, page sticky): horizontal scroll lives on
|
||||
.tableScroll (so wide tables don't get clipped by an ancestor
|
||||
overflow:hidden such as dees-tile). Vertical sticky is handled by
|
||||
a JS-managed floating header (.floatingHeader, position:fixed),
|
||||
which is unaffected by ancestor overflow. */
|
||||
.tableScroll {
|
||||
/* enable horizontal scroll only when content exceeds width */
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
/* prevent vertical scroll inside the table container */
|
||||
overflow-y: hidden;
|
||||
/* avoid reserving extra space for classic scrollbars where possible */
|
||||
scrollbar-gutter: stable both-edges;
|
||||
overflow-y: visible;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
/* Hide horizontal scrollbar entirely when not using sticky header */
|
||||
:host(:not([sticky-header])) .tableScroll {
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
scrollbar-width: none; /* Firefox (hides both axes) */
|
||||
}
|
||||
:host(:not([sticky-header])) .tableScroll::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari */
|
||||
}
|
||||
/* In sticky-header mode, hide only the horizontal scrollbar in WebKit/Blink */
|
||||
:host([sticky-header]) .tableScroll::-webkit-scrollbar:horizontal {
|
||||
height: 0px;
|
||||
}
|
||||
:host([sticky-header]) .tableScroll {
|
||||
/* Mode A, internal scroll: opt-in via fixed-height attribute.
|
||||
The table scrolls inside its own box and the header sticks via plain CSS sticky. */
|
||||
:host([fixed-height]) .tableScroll {
|
||||
max-height: var(--table-max-height, 360px);
|
||||
overflow: auto;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
:host([fixed-height]) .tableScroll::-webkit-scrollbar:horizontal {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
/* Floating header overlay (Mode B). Position is managed by JS so it
|
||||
escapes any ancestor overflow:hidden (position:fixed is not clipped
|
||||
by overflow ancestors). */
|
||||
.floatingHeader {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.floatingHeader.active {
|
||||
visibility: visible;
|
||||
}
|
||||
.floatingHeader table {
|
||||
margin: 0;
|
||||
}
|
||||
.floatingHeader th {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
@@ -159,15 +178,25 @@ export const tableStyles: CSSResult[] = [
|
||||
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')};
|
||||
border-bottom: 1px solid var(--dees-color-border-strong);
|
||||
}
|
||||
:host([sticky-header]) thead th {
|
||||
/* th needs its own background so sticky cells paint over scrolled rows
|
||||
(browsers don't paint the <thead> box behind a sticky <th>). */
|
||||
th {
|
||||
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')};
|
||||
}
|
||||
/* Mode A — internal scroll sticky */
|
||||
:host([fixed-height]) thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
:host([fixed-height]) thead tr.filtersRow th {
|
||||
top: 36px; /* matches th { height: 36px } below */
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
transition: background-color 0.15s ease;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Default horizontal lines (bottom border only) */
|
||||
@@ -343,32 +372,136 @@ export const tableStyles: CSSResult[] = [
|
||||
min-height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
td input {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
width: calc(100% - 40px);
|
||||
height: calc(100% - 8px);
|
||||
padding: 0 12px;
|
||||
outline: none;
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 6px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
transition: all 0.15s ease;
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
|
||||
/* ---- Cell flash highlighting (opt-in via highlight-updates="flash") ----
|
||||
Bloomberg/TradingView-style: the text itself briefly takes an accent
|
||||
color then fades back to the default. No background tint, no layout
|
||||
shift, no weight change. Readable, modern, subtle.
|
||||
Consumers can override per instance:
|
||||
dees-table#myTable { --dees-table-flash-color: hsl(142 76% 40%); }
|
||||
*/
|
||||
:host {
|
||||
--dees-table-flash-color: ${cssManager.bdTheme(
|
||||
'hsl(32 95% 44%)',
|
||||
'hsl(45 93% 62%)'
|
||||
)};
|
||||
--dees-table-flash-easing: cubic-bezier(0.22, 0.61, 0.36, 1);
|
||||
}
|
||||
|
||||
td input:focus {
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.2)', 'hsl(217.2 91.2% 59.8% / 0.2)')};
|
||||
|
||||
.innerCellContainer.flashing {
|
||||
animation: dees-table-cell-flash
|
||||
var(--dees-table-flash-duration, 900ms)
|
||||
var(--dees-table-flash-easing);
|
||||
}
|
||||
|
||||
/* Hold the accent color briefly, then fade back to the theme's default
|
||||
text color. Inherits to child text and to SVG icons that use
|
||||
currentColor. Cells with explicit color overrides in renderers are
|
||||
intentionally unaffected. */
|
||||
@keyframes dees-table-cell-flash {
|
||||
0%,
|
||||
35% { color: var(--dees-table-flash-color); }
|
||||
100% { color: var(--dees-color-text-primary); }
|
||||
}
|
||||
|
||||
/* Border/background flash variant for cells with styled content
|
||||
(badges, icons, custom components) where a text-color animation
|
||||
would be invisible. Activated via flashBorder on Column. */
|
||||
.innerCellContainer.flashing-border {
|
||||
animation: dees-table-cell-flash-border
|
||||
var(--dees-table-flash-duration, 900ms)
|
||||
var(--dees-table-flash-easing);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@keyframes dees-table-cell-flash-border {
|
||||
0%,
|
||||
35% {
|
||||
box-shadow: inset 0 0 0 1.5px var(--dees-table-flash-color);
|
||||
background: ${cssManager.bdTheme(
|
||||
'hsl(45 93% 62% / 0.10)',
|
||||
'hsl(45 93% 62% / 0.08)'
|
||||
)};
|
||||
}
|
||||
100% {
|
||||
box-shadow: inset 0 0 0 1.5px transparent;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.innerCellContainer.flashing {
|
||||
animation: none;
|
||||
color: var(--dees-table-flash-color);
|
||||
}
|
||||
.innerCellContainer.flashing-border {
|
||||
animation: none;
|
||||
box-shadow: inset 0 0 0 1.5px var(--dees-table-flash-color);
|
||||
background: ${cssManager.bdTheme(
|
||||
'hsl(45 93% 62% / 0.10)',
|
||||
'hsl(45 93% 62% / 0.08)'
|
||||
)};
|
||||
}
|
||||
}
|
||||
|
||||
/* Dev-time warning banner shown when highlight-updates="flash" but
|
||||
rowKey is missing. Consumers should never ship this to production. */
|
||||
.flashConfigWarning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 8px 16px 0;
|
||||
padding: 8px 12px;
|
||||
border-left: 3px solid ${cssManager.bdTheme('hsl(38 92% 50%)', 'hsl(48 96% 63%)')};
|
||||
background: ${cssManager.bdTheme('hsl(48 96% 89% / 0.6)', 'hsl(48 96% 30% / 0.15)')};
|
||||
color: ${cssManager.bdTheme('hsl(32 81% 29%)', 'hsl(48 96% 80%)')};
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.flashConfigWarning dees-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.flashConfigWarning code {
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.6)', 'hsl(0 0% 0% / 0.3)')};
|
||||
font-family: ${cssGeistFontFamily};
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Editable cell affordances */
|
||||
td.editable {
|
||||
cursor: text;
|
||||
}
|
||||
td.focused {
|
||||
outline: 2px solid ${cssManager.bdTheme(
|
||||
'hsl(222.2 47.4% 51.2% / 0.6)',
|
||||
'hsl(217.2 91.2% 59.8% / 0.6)'
|
||||
)};
|
||||
outline-offset: -2px;
|
||||
}
|
||||
td.editingCell {
|
||||
padding: 0;
|
||||
outline: 2px solid ${cssManager.bdTheme(
|
||||
'hsl(222.2 47.4% 51.2% / 0.6)',
|
||||
'hsl(217.2 91.2% 59.8% / 0.6)'
|
||||
)};
|
||||
outline-offset: -2px;
|
||||
}
|
||||
td.editingCell .innerCellContainer {
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
td.editingCell dees-input-text,
|
||||
td.editingCell dees-input-checkbox,
|
||||
td.editingCell dees-input-dropdown,
|
||||
td.editingCell dees-input-datepicker,
|
||||
td.editingCell dees-input-tags {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* filter row */
|
||||
|
||||
@@ -15,15 +15,75 @@ export interface ITableAction<T = any> {
|
||||
actionFunc: (actionDataArg: ITableActionDataArg<T>) => Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Available cell editor types. Each maps to a dees-input-* component.
|
||||
* Use `editor` on `Column<T>` to opt a column into in-cell editing.
|
||||
*/
|
||||
export type TCellEditorType =
|
||||
| 'text'
|
||||
| 'number'
|
||||
| 'checkbox'
|
||||
| 'dropdown'
|
||||
| 'date'
|
||||
| 'tags';
|
||||
|
||||
/** Detail payload for the `cellEdit` CustomEvent dispatched on commit. */
|
||||
export interface ICellEditDetail<T = any> {
|
||||
row: T;
|
||||
key: string;
|
||||
oldValue: any;
|
||||
newValue: any;
|
||||
}
|
||||
|
||||
/** Detail payload for the `cellEditError` CustomEvent dispatched on validation failure. */
|
||||
export interface ICellEditErrorDetail<T = any> {
|
||||
row: T;
|
||||
key: string;
|
||||
value: any;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Column<T = any> {
|
||||
key: keyof T | string;
|
||||
header?: string | TemplateResult;
|
||||
value?: (row: T) => any;
|
||||
renderer?: (value: any, row: T, ctx: { rowIndex: number; colIndex: number; column: Column<T> }) => TemplateResult | string;
|
||||
/** Whether this column can be sorted by clicking its header. Defaults to `true`; set to `false` to disable. */
|
||||
sortable?: boolean;
|
||||
/** whether this column participates in per-column quick filtering (default: true) */
|
||||
filterable?: boolean;
|
||||
hidden?: boolean;
|
||||
/** Marks the column as editable. Shorthand for `editor: 'text'` if no editor is specified. */
|
||||
editable?: boolean;
|
||||
/** Editor type — picks the dees-input-* component used for in-cell editing. */
|
||||
editor?: TCellEditorType;
|
||||
/** Editor-specific options forwarded to the editor (e.g. `{ options: [...] }` for dropdowns). */
|
||||
editorOptions?: Record<string, any>;
|
||||
/** Convert raw row value -> editor value. Defaults to identity. */
|
||||
format?: (raw: any, row: T) => any;
|
||||
/** Convert editor value -> raw row value. Defaults to identity. */
|
||||
parse?: (editorValue: any, row: T) => any;
|
||||
/** Validate the parsed value before commit. Return string for error, true/void for ok. */
|
||||
validate?: (value: any, row: T) => true | string | void;
|
||||
|
||||
// ─── Flash highlight options ───
|
||||
|
||||
/**
|
||||
* Custom comparison for flash-on-update diffing.
|
||||
* Return `true` if the cell should flash (i.e. the values differ).
|
||||
* When absent, non-primitive cell values are skipped entirely
|
||||
* (only strings, numbers, booleans, null, and undefined are diffed).
|
||||
*/
|
||||
flashCompare?: (prevVal: any, nextVal: any) => boolean;
|
||||
|
||||
/**
|
||||
* When `true`, flash this cell with a border/background pulse instead of
|
||||
* the default text-color animation. Useful for cells containing styled
|
||||
* badges, icons, or custom web-component renderings where a text-color
|
||||
* change would be invisible.
|
||||
* @default false
|
||||
*/
|
||||
flashBorder?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,12 +75,23 @@ export class DeesFormSubmit extends DeesElement {
|
||||
.text=${this.text}
|
||||
?disabled=${this.disabled}
|
||||
@clicked=${this.submit}
|
||||
>
|
||||
<slot></slot>
|
||||
</dees-button>
|
||||
></dees-button>
|
||||
`;
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
// Capture light DOM text content as the button label. dees-button wipes
|
||||
// its own light DOM during extractLightDom(), so we cannot simply forward
|
||||
// a <slot> into it — we have to hoist the text onto the .text property
|
||||
// ourselves before handing it to dees-button.
|
||||
if (!this.text) {
|
||||
const slotText = this.textContent?.trim();
|
||||
if (slotText) {
|
||||
this.text = slotText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async submit() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
|
||||
@@ -92,7 +92,7 @@ export const demoFunc = () => html`
|
||||
.required=${true}
|
||||
key="firstName"
|
||||
label="First Name"
|
||||
.description=${'Your given name'}
|
||||
.infoText=${'Your given name'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
@@ -105,7 +105,7 @@ export const demoFunc = () => html`
|
||||
.required=${true}
|
||||
key="email"
|
||||
label="Email Address"
|
||||
.description=${'We will use this to contact you'}
|
||||
.infoText=${'We will use this to contact you'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-dropdown
|
||||
@@ -126,7 +126,7 @@ export const demoFunc = () => html`
|
||||
key="password"
|
||||
label="Password"
|
||||
isPasswordBool
|
||||
.description=${'Minimum 8 characters'}
|
||||
.infoText=${'Minimum 8 characters'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-checkbox
|
||||
@@ -300,7 +300,7 @@ export const demoFunc = () => html`
|
||||
<dees-input-fileupload
|
||||
key="documents"
|
||||
.label=${'Upload Documents'}
|
||||
.description=${'PDF, DOC, or DOCX files up to 10MB'}
|
||||
.infoText=${'PDF, DOC, or DOCX files up to 10MB'}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-form-submit>Submit Application</dees-form-submit>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
DeesElement,
|
||||
property,
|
||||
html,
|
||||
css,
|
||||
type CSSResult,
|
||||
cssManager,
|
||||
@@ -42,6 +43,9 @@ export abstract class DeesInputBase<T = any> extends DeesElement {
|
||||
@property({ type: Boolean })
|
||||
accessor disabled: boolean = false;
|
||||
|
||||
@property({ type: String })
|
||||
accessor infoText!: string;
|
||||
|
||||
@property({ type: String })
|
||||
accessor description!: string;
|
||||
|
||||
@@ -90,6 +94,14 @@ export abstract class DeesInputBase<T = any> extends DeesElement {
|
||||
:host([label-position="none"]) dees-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Description text below input */
|
||||
.descriptionText {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
@@ -155,6 +167,14 @@ export abstract class DeesInputBase<T = any> extends DeesElement {
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the description text below the input.
|
||||
* Call ${this.renderDescription()} at the end of your render template.
|
||||
*/
|
||||
public renderDescription() {
|
||||
return this.description ? html`<div class="descriptionText">${this.description}</div>` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method that child classes must implement to get their value
|
||||
*/
|
||||
|
||||
@@ -111,90 +111,92 @@ export const demoFunc = () => html`
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'Basic Checkboxes'} .subtitle=${'Simple checkbox examples with various labels'}>
|
||||
<div class="checkbox-group">
|
||||
<dees-input-checkbox
|
||||
.label=${'I agree to the Terms and Conditions'}
|
||||
<dees-form>
|
||||
<dees-input-checkbox
|
||||
.label=${'I agree to the Terms and Conditions'}
|
||||
.value=${true}
|
||||
.key=${'terms'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Subscribe to newsletter'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Subscribe to newsletter'}
|
||||
.value=${false}
|
||||
.key=${'newsletter'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Enable notifications'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Enable notifications'}
|
||||
.value=${false}
|
||||
.description=${'Receive email updates about your account'}
|
||||
.key=${'notifications'}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Checkbox States'} .subtitle=${'Different checkbox states and configurations'}>
|
||||
<div class="checkbox-group">
|
||||
<dees-input-checkbox
|
||||
.label=${'Default state'}
|
||||
<dees-form>
|
||||
<dees-input-checkbox
|
||||
.label=${'Default state'}
|
||||
.value=${false}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Checked state'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Checked state'}
|
||||
.value=${true}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Disabled unchecked'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Disabled unchecked'}
|
||||
.value=${false}
|
||||
.disabled=${true}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Disabled checked'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Disabled checked'}
|
||||
.value=${true}
|
||||
.disabled=${true}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Required checkbox'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Required checkbox'}
|
||||
.required=${true}
|
||||
.key=${'required'}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Checkboxes arranged horizontally for compact forms'}>
|
||||
<div class="horizontal-checkboxes">
|
||||
<dees-input-checkbox
|
||||
.label=${'Option A'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionA'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Option B'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionB'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Option C'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionC'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Option D'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionD'}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="horizontal-checkboxes">
|
||||
<dees-input-checkbox
|
||||
.label=${'Option A'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionA'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Option B'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionB'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Option C'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionC'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Option D'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'optionD'}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Feature Selection Example'} .subtitle=${'Common use case for feature toggles with batch operations'}>
|
||||
@@ -204,76 +206,76 @@ export const demoFunc = () => html`
|
||||
</div>
|
||||
|
||||
<div class="feature-list">
|
||||
<div class="checkbox-group">
|
||||
<dees-input-checkbox
|
||||
.label=${'Dark Mode Support'}
|
||||
<dees-form>
|
||||
<dees-input-checkbox
|
||||
.label=${'Dark Mode Support'}
|
||||
.value=${true}
|
||||
.key=${'feature1'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Email Notifications'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Email Notifications'}
|
||||
.value=${true}
|
||||
.key=${'feature2'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Two-Factor Authentication'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Two-Factor Authentication'}
|
||||
.value=${false}
|
||||
.key=${'feature3'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'API Access'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'API Access'}
|
||||
.value=${true}
|
||||
.key=${'feature4'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Advanced Analytics'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Advanced Analytics'}
|
||||
.value=${false}
|
||||
.key=${'feature5'}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
</dees-form>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Privacy Settings Example'} .subtitle=${'Checkboxes in a typical form context'}>
|
||||
<div class="form-section">
|
||||
<h4 class="section-title">Privacy Preferences</h4>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<dees-input-checkbox
|
||||
.label=${'Share analytics data'}
|
||||
|
||||
<dees-form>
|
||||
<dees-input-checkbox
|
||||
.label=${'Share analytics data'}
|
||||
.value=${true}
|
||||
.description=${'Help us improve by sharing anonymous usage data'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Personalized recommendations'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Personalized recommendations'}
|
||||
.value=${true}
|
||||
.description=${'Get suggestions based on your activity'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Marketing communications'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Marketing communications'}
|
||||
.value=${false}
|
||||
.description=${'Receive promotional emails and special offers'}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Third-party integrations'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Third-party integrations'}
|
||||
.value=${false}
|
||||
.description=${'Allow approved partners to access your data'}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
</dees-form>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Interactive Example'} .subtitle=${'Click checkboxes to see value changes'}>
|
||||
<div class="checkbox-group">
|
||||
<dees-input-checkbox
|
||||
.label=${'Feature toggle'}
|
||||
<dees-form>
|
||||
<dees-input-checkbox
|
||||
.label=${'Feature toggle'}
|
||||
.value=${false}
|
||||
@changeSubject=${(event: CustomEvent) => {
|
||||
const output = document.querySelector('#checkbox-output');
|
||||
@@ -283,9 +285,9 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Debug mode'}
|
||||
|
||||
<dees-input-checkbox
|
||||
.label=${'Debug mode'}
|
||||
.value=${false}
|
||||
@changeSubject=${(event: CustomEvent) => {
|
||||
const output = document.querySelector('#debug-output');
|
||||
@@ -295,8 +297,8 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}
|
||||
></dees-input-checkbox>
|
||||
</div>
|
||||
|
||||
</dees-form>
|
||||
|
||||
<div class="interactive-section">
|
||||
<div id="checkbox-output" class="output-text">Feature is disabled</div>
|
||||
<div id="debug-output" class="output-text" style="margin-top: 8px;">Debug mode: OFF</div>
|
||||
|
||||
@@ -147,12 +147,6 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
}
|
||||
|
||||
/* Description */
|
||||
.description-text {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
line-height: 1.5;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -185,7 +179,7 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
||||
</div>
|
||||
<div class="label-container">
|
||||
${this.label ? html`<div class="checkbox-label">${this.label}</div>` : ''}
|
||||
${this.description ? html`<div class="description-text">${this.description}</div>` : ''}
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -284,7 +284,7 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
}
|
||||
</style>
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>
|
||||
<dees-label .label=${this.label} .infoText=${this.infoText} .required=${this.required}></dees-label>
|
||||
<dees-tile>
|
||||
<div slot="header" class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
@@ -362,6 +362,7 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
</div>
|
||||
</div>
|
||||
</dees-tile>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { DeesInputDatepicker } from './component.js';
|
||||
export const renderDatepicker = (component: DeesInputDatepicker): TemplateResult => {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${component.label} .description=${component.description} .required=${component.required}></dees-label>
|
||||
<dees-label .label=${component.label} .infoText=${component.infoText} .required=${component.required}></dees-label>
|
||||
<div class="input-container">
|
||||
<input
|
||||
type="text"
|
||||
@@ -27,6 +27,7 @@ export const renderDatepicker = (component: DeesInputDatepicker): TemplateResult
|
||||
<dees-icon class="calendar-icon" icon="lucide:calendar" iconSize="16"></dees-icon>
|
||||
</div>
|
||||
</div>
|
||||
${component.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
@@ -31,12 +31,6 @@ export const demoFunc = () => html`
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
height: 200px;
|
||||
display: flex;
|
||||
@@ -69,9 +63,10 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'1. Basic Dropdowns'} .subtitle=${'Standard dropdown with search functionality and various options'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-dropdown
|
||||
.label=${'Select Country'}
|
||||
.description=${'Choose the country where your business is registered'}
|
||||
.options=${[
|
||||
{ option: 'United States', key: 'us' },
|
||||
{ option: 'Canada', key: 'ca' },
|
||||
@@ -94,7 +89,7 @@ export const demoFunc = () => html`
|
||||
{ option: 'Guest', key: 'guest' }
|
||||
]}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
@@ -135,40 +130,42 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}}>
|
||||
<dees-panel .title=${'3. Horizontal Layout'} .subtitle=${'Multiple dropdowns in a horizontal layout for compact forms'}>
|
||||
<div class="horizontal-group">
|
||||
<dees-input-dropdown
|
||||
.label=${'Department'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.options=${[
|
||||
{ option: 'Engineering', key: 'eng' },
|
||||
{ option: 'Design', key: 'design' },
|
||||
{ option: 'Marketing', key: 'marketing' },
|
||||
{ option: 'Sales', key: 'sales' }
|
||||
]}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-dropdown
|
||||
.label=${'Team Size'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.enableSearch=${false}
|
||||
.options=${[
|
||||
{ option: '1-5', key: 'small' },
|
||||
{ option: '6-20', key: 'medium' },
|
||||
{ option: '21-50', key: 'large' },
|
||||
{ option: '50+', key: 'xlarge' }
|
||||
]}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-dropdown
|
||||
.label=${'Location'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.options=${[
|
||||
{ option: 'Remote', key: 'remote' },
|
||||
{ option: 'On-site', key: 'onsite' },
|
||||
{ option: 'Hybrid', key: 'hybrid' }
|
||||
]}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="horizontal-group">
|
||||
<dees-input-dropdown
|
||||
.label=${'Department'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.options=${[
|
||||
{ option: 'Engineering', key: 'eng' },
|
||||
{ option: 'Design', key: 'design' },
|
||||
{ option: 'Marketing', key: 'marketing' },
|
||||
{ option: 'Sales', key: 'sales' }
|
||||
]}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-dropdown
|
||||
.label=${'Team Size'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.enableSearch=${false}
|
||||
.options=${[
|
||||
{ option: '1-5', key: 'small' },
|
||||
{ option: '6-20', key: 'medium' },
|
||||
{ option: '21-50', key: 'large' },
|
||||
{ option: '50+', key: 'xlarge' }
|
||||
]}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-dropdown
|
||||
.label=${'Location'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.options=${[
|
||||
{ option: 'Remote', key: 'remote' },
|
||||
{ option: 'On-site', key: 'onsite' },
|
||||
{ option: 'Hybrid', key: 'hybrid' }
|
||||
]}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
@@ -184,7 +181,7 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'4. States'} .subtitle=${'Different states and configurations'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-dropdown
|
||||
.label=${'Required Field'}
|
||||
.required=${true}
|
||||
@@ -203,7 +200,7 @@ export const demoFunc = () => html`
|
||||
]}
|
||||
.selectedOption=${{ option: 'Cannot Select', key: 'disabled' }}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
})
|
||||
accessor enableSearch: boolean = true;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
})
|
||||
accessor vintegrated: boolean = false;
|
||||
|
||||
@state()
|
||||
accessor isOpened = false;
|
||||
|
||||
@@ -126,13 +132,43 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
.selectedBox.open::after {
|
||||
transform: translateY(-50%) rotate(180deg);
|
||||
}
|
||||
|
||||
/* Visually integrated mode: shed chrome to blend into a host component
|
||||
(e.g. a dees-table cell in edit mode). */
|
||||
:host([vintegrated]) dees-label {
|
||||
display: none;
|
||||
}
|
||||
:host([vintegrated]) .maincontainer {
|
||||
height: 40px;
|
||||
}
|
||||
:host([vintegrated]) .selectedBox {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 32px 0 16px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
}
|
||||
:host([vintegrated]) .selectedBox:hover:not(.disabled),
|
||||
:host([vintegrated]) .selectedBox:focus-visible {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
:host([vintegrated]) .selectedBox::after {
|
||||
right: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>
|
||||
<dees-label .label=${this.label} .infoText=${this.infoText} .required=${this.required}></dees-label>
|
||||
<div class="maincontainer">
|
||||
<div
|
||||
class="selectedBox ${this.isOpened ? 'open' : ''} ${this.disabled ? 'disabled' : ''}"
|
||||
@@ -143,6 +179,7 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
${this.selectedOption?.option || 'Select an option'}
|
||||
</div>
|
||||
</div>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
<div class="input-wrapper">
|
||||
<dees-label
|
||||
.label=${this.label}
|
||||
.description=${this.description}
|
||||
.infoText=${this.infoText}
|
||||
.required=${this.required}
|
||||
></dees-label>
|
||||
<dees-tile
|
||||
@@ -114,6 +114,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
${this.validationMessage
|
||||
? html`<div class="validation-message" aria-live="polite">${this.validationMessage}</div>`
|
||||
: html``}
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -55,40 +55,43 @@ export const demoFunc = () => html`
|
||||
.title=${'Modern file uploader'}
|
||||
.subtitle=${'Shadcn-inspired layout with drag & drop, previews and validation'}
|
||||
>
|
||||
<div class="demo-grid demo-grid--two">
|
||||
<div class="demo-stack">
|
||||
<dees-input-fileupload
|
||||
.label=${'Attachments'}
|
||||
.description=${'Upload supporting documents for your request'}
|
||||
.accept=${'image/*,.pdf,.zip'}
|
||||
.maxSize=${10 * 1024 * 1024}
|
||||
></dees-input-fileupload>
|
||||
<dees-form>
|
||||
<div class="demo-grid demo-grid--two">
|
||||
<div class="demo-stack">
|
||||
<dees-input-fileupload
|
||||
.label=${'Attachments'}
|
||||
.infoText=${'Upload supporting documents for your request'}
|
||||
.description=${'Accepted formats: images, PDF, and ZIP archives up to 10MB'}
|
||||
.accept=${'image/*,.pdf,.zip'}
|
||||
.maxSize=${10 * 1024 * 1024}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Brand assets'}
|
||||
.description=${'Upload high-resolution imagery (JPG/PNG)'}
|
||||
.accept=${'image/jpeg,image/png'}
|
||||
.multiple=${false}
|
||||
.maxSize=${5 * 1024 * 1024}
|
||||
.buttonText=${'Select cover image'}
|
||||
></dees-input-fileupload>
|
||||
<dees-input-fileupload
|
||||
.label=${'Brand assets'}
|
||||
.infoText=${'Upload high-resolution imagery (JPG/PNG)'}
|
||||
.accept=${'image/jpeg,image/png'}
|
||||
.multiple=${false}
|
||||
.maxSize=${5 * 1024 * 1024}
|
||||
.buttonText=${'Select cover image'}
|
||||
></dees-input-fileupload>
|
||||
</div>
|
||||
|
||||
<div class="demo-stack">
|
||||
<dees-input-fileupload
|
||||
.label=${'Audio uploads'}
|
||||
.infoText=${'Share podcast drafts (MP3/WAV, max 25MB each)'}
|
||||
.accept=${'audio/*'}
|
||||
.maxSize=${25 * 1024 * 1024}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Disabled example'}
|
||||
.infoText=${'Uploader is disabled while moderation is pending'}
|
||||
.disabled=${true}
|
||||
></dees-input-fileupload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-stack">
|
||||
<dees-input-fileupload
|
||||
.label=${'Audio uploads'}
|
||||
.description=${'Share podcast drafts (MP3/WAV, max 25MB each)'}
|
||||
.accept=${'audio/*'}
|
||||
.maxSize=${25 * 1024 * 1024}
|
||||
></dees-input-fileupload>
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Disabled example'}
|
||||
.description=${'Uploader is disabled while moderation is pending'}
|
||||
.disabled=${true}
|
||||
></dees-input-fileupload>
|
||||
</div>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel
|
||||
@@ -97,10 +100,9 @@ export const demoFunc = () => html`
|
||||
>
|
||||
<div class="demo-grid">
|
||||
<dees-form>
|
||||
<div class="demo-stack">
|
||||
<dees-input-text
|
||||
.label=${'Project name'}
|
||||
.description=${'How should we refer to this project internally?'}
|
||||
.infoText=${'How should we refer to this project internally?'}
|
||||
.required=${true}
|
||||
.key=${'projectName'}
|
||||
></dees-input-text>
|
||||
@@ -114,7 +116,7 @@ export const demoFunc = () => html`
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Statement of work'}
|
||||
.description=${'Upload a signed statement of work (PDF, max 15MB)'}
|
||||
.infoText=${'Upload a signed statement of work (PDF, max 15MB)'}
|
||||
.required=${true}
|
||||
.accept=${'application/pdf'}
|
||||
.maxSize=${15 * 1024 * 1024}
|
||||
@@ -124,7 +126,7 @@ export const demoFunc = () => html`
|
||||
|
||||
<dees-input-fileupload
|
||||
.label=${'Creative references'}
|
||||
.description=${'Optional. Upload up to five visual references'}
|
||||
.infoText=${'Optional. Upload up to five visual references'}
|
||||
.accept=${'image/*'}
|
||||
.maxFiles=${5}
|
||||
.maxSize=${8 * 1024 * 1024}
|
||||
@@ -133,13 +135,12 @@ export const demoFunc = () => html`
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Notes'}
|
||||
.description=${'Add optional context for reviewers'}
|
||||
.infoText=${'Add optional context for reviewers'}
|
||||
.inputType=${'textarea'}
|
||||
.key=${'notes'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-form-submit .text=${'Submit briefing'}></dees-form-submit>
|
||||
</div>
|
||||
</dees-form>
|
||||
|
||||
<div class="demo-note">
|
||||
|
||||
@@ -13,12 +13,6 @@ export const demoFunc = () => html`
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.payment-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -30,58 +24,61 @@ export const demoFunc = () => html`
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'Basic IBAN Input'} .subtitle=${'International Bank Account Number with automatic formatting'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-iban
|
||||
.label=${'Bank Account IBAN'}
|
||||
.description=${'Enter your International Bank Account Number'}
|
||||
.infoText=${'Enter your International Bank Account Number'}
|
||||
.description=${'Your IBAN can be found on your bank statement'}
|
||||
></dees-input-iban>
|
||||
|
||||
<dees-input-iban
|
||||
.label=${'Verified IBAN'}
|
||||
.description=${'This IBAN has been verified'}
|
||||
.infoText=${'This IBAN has been verified'}
|
||||
.value=${'DE89370400440532013000'}
|
||||
></dees-input-iban>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Payment Information'} .subtitle=${'IBAN input with horizontal layout for payment forms'}>
|
||||
<div class="payment-group">
|
||||
<dees-input-text
|
||||
.label=${'Account Holder'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${'John Doe'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-iban
|
||||
.label=${'IBAN'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${'GB82WEST12345698765432'}
|
||||
></dees-input-iban>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="payment-group">
|
||||
<dees-input-text
|
||||
.label=${'Account Holder'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${'John Doe'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-iban
|
||||
.label=${'IBAN'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${'GB82WEST12345698765432'}
|
||||
></dees-input-iban>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Validation & States'} .subtitle=${'Required fields and disabled states'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-iban
|
||||
.label=${'Payment Account'}
|
||||
.description=${'Required for processing payments'}
|
||||
.infoText=${'Required for processing payments'}
|
||||
.required=${true}
|
||||
></dees-input-iban>
|
||||
|
||||
<dees-input-iban
|
||||
.label=${'Locked IBAN'}
|
||||
.description=${'This IBAN cannot be changed'}
|
||||
.infoText=${'This IBAN cannot be changed'}
|
||||
.value=${'FR1420041010050500013M02606'}
|
||||
.disabled=${true}
|
||||
></dees-input-iban>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Bank Transfer Form'} .subtitle=${'Complete form example with IBAN validation'}>
|
||||
<dees-form>
|
||||
<dees-input-text .label=${'Recipient Name'} .required=${true}></dees-input-text>
|
||||
<dees-input-iban .label=${'Recipient IBAN'} .required=${true}></dees-input-iban>
|
||||
<dees-input-text .label=${'Transfer Reference'} .description=${'Optional reference for the transfer'}></dees-input-text>
|
||||
<dees-input-text .label=${'Transfer Reference'} .infoText=${'Optional reference for the transfer'}></dees-input-text>
|
||||
<dees-input-text .label=${'Amount'} .inputType=${'number'} .required=${true}></dees-input-text>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
@@ -44,16 +44,27 @@ export class DeesInputIban extends DeesInputBase<DeesInputIban> {
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label || 'IBAN'} .description=${this.description}></dees-label>
|
||||
<dees-label .label=${this.label || 'IBAN'} .infoText=${this.infoText}></dees-label>
|
||||
<dees-input-text
|
||||
.value=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.placeholder=${'DE89 3704 0044 0532 0130 00'}
|
||||
.validationFunction=${(value: string) => {
|
||||
const normalized = value.replace(/ /g, '');
|
||||
if (normalized.length === 0) {
|
||||
return { valid: true, message: '' };
|
||||
}
|
||||
const isValid = ibantools.isValidIBAN(normalized);
|
||||
return isValid
|
||||
? { valid: true, message: 'IBAN is valid' }
|
||||
: { valid: false, message: 'Please enter a valid IBAN' };
|
||||
}}
|
||||
@input=${(eventArg: InputEvent) => {
|
||||
this.validateIban(eventArg);
|
||||
}}
|
||||
></dees-input-text>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -81,10 +92,6 @@ export class DeesInputIban extends DeesInputBase<DeesInputIban> {
|
||||
}
|
||||
}
|
||||
this.enteredIbanIsValid = ibantools.isValidIBAN(this.enteredString.replace(/ /g, ''));
|
||||
const deesInputText = this.shadowRoot!.querySelector('dees-input-text') as any;
|
||||
if (deesInputText) {
|
||||
deesInputText.validationText = `IBAN is valid: ${this.enteredIbanIsValid}`;
|
||||
}
|
||||
}
|
||||
|
||||
public getValue(): string {
|
||||
|
||||
@@ -109,25 +109,27 @@ export const demoFunc = () => html`
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'3. Validation & Constraints'} .subtitle=${'Lists with minimum/maximum items and duplicate prevention'}>
|
||||
<div class="grid-layout">
|
||||
<dees-input-list
|
||||
.label=${'Team Members (Min 2, Max 5)'}
|
||||
.placeholder=${'Add team member...'}
|
||||
.minItems=${2}
|
||||
.maxItems=${5}
|
||||
.value=${['Alice', 'Bob']}
|
||||
.required=${true}
|
||||
.description=${'Add 2-5 team members'}
|
||||
></dees-input-list>
|
||||
|
||||
<dees-input-list
|
||||
.label=${'Unique Tags (No Duplicates)'}
|
||||
.placeholder=${'Add unique tag...'}
|
||||
.allowDuplicates=${false}
|
||||
.value=${['frontend', 'backend', 'database']}
|
||||
.description=${'Duplicate items are not allowed'}
|
||||
></dees-input-list>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="grid-layout">
|
||||
<dees-input-list
|
||||
.label=${'Team Members (Min 2, Max 5)'}
|
||||
.placeholder=${'Add team member...'}
|
||||
.minItems=${2}
|
||||
.maxItems=${5}
|
||||
.value=${['Alice', 'Bob']}
|
||||
.required=${true}
|
||||
.description=${'Add 2-5 team members'}
|
||||
></dees-input-list>
|
||||
|
||||
<dees-input-list
|
||||
.label=${'Unique Tags (No Duplicates)'}
|
||||
.placeholder=${'Add unique tag...'}
|
||||
.allowDuplicates=${false}
|
||||
.value=${['frontend', 'backend', 'database']}
|
||||
.description=${'Duplicate items are not allowed'}
|
||||
></dees-input-list>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'4. Delete Confirmation'} .subtitle=${'Require confirmation before deleting items'}>
|
||||
|
||||
@@ -373,13 +373,6 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.list-items::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
@@ -546,9 +539,7 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
|
||||
<div class="validation-message">${this.validationText}</div>
|
||||
` : ''}
|
||||
|
||||
${this.description ? html`
|
||||
<div class="description">${this.description}</div>
|
||||
` : ''}
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -52,80 +52,83 @@ export const demoFunc = () => html`
|
||||
<div class="section">
|
||||
<div class="section-title">Multi-Option Toggle</div>
|
||||
<div class="section-description">Select from multiple options with a smooth sliding indicator animation.</div>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Display Mode'}
|
||||
.description=${'Choose how content is displayed'}
|
||||
.options=${['List View', 'Grid View', 'Compact']}
|
||||
.selectedOption=${'Grid View'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<br><br>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'T-Shirt Size'}
|
||||
.description=${'Select your preferred size'}
|
||||
.options=${['XS', 'S', 'M', 'L', 'XL', 'XXL']}
|
||||
.selectedOption=${'M'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-form>
|
||||
<dees-input-multitoggle
|
||||
.label=${'Display Mode'}
|
||||
.infoText=${'Choose how content is displayed'}
|
||||
.description=${'This setting affects how items appear on your dashboard'}
|
||||
.options=${['List View', 'Grid View', 'Compact']}
|
||||
.selectedOption=${'Grid View'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'T-Shirt Size'}
|
||||
.infoText=${'Select your preferred size'}
|
||||
.options=${['XS', 'S', 'M', 'L', 'XL', 'XXL']}
|
||||
.selectedOption=${'M'}
|
||||
></dees-input-multitoggle>
|
||||
</dees-form>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Boolean Toggle</div>
|
||||
<div class="section-description">Simple on/off switches with customizable labels for clearer context.</div>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Notifications'}
|
||||
.description=${'Enable or disable push notifications'}
|
||||
.type=${'boolean'}
|
||||
.selectedOption=${'true'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<br><br>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Theme Mode'}
|
||||
.description=${'Switch between light and dark theme'}
|
||||
.type=${'boolean'}
|
||||
.booleanTrueName=${'Dark'}
|
||||
.booleanFalseName=${'Light'}
|
||||
.selectedOption=${'Dark'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-form>
|
||||
<dees-input-multitoggle
|
||||
.label=${'Notifications'}
|
||||
.infoText=${'Enable or disable push notifications'}
|
||||
.type=${'boolean'}
|
||||
.selectedOption=${'true'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Theme Mode'}
|
||||
.infoText=${'Switch between light and dark theme'}
|
||||
.type=${'boolean'}
|
||||
.booleanTrueName=${'Dark'}
|
||||
.booleanFalseName=${'Light'}
|
||||
.selectedOption=${'Dark'}
|
||||
></dees-input-multitoggle>
|
||||
</dees-form>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Settings Grid</div>
|
||||
<div class="section-description">Configuration options arranged in a responsive grid layout.</div>
|
||||
|
||||
<div class="settings-grid">
|
||||
<dees-input-multitoggle
|
||||
.label=${'Auto-Save'}
|
||||
.type=${'boolean'}
|
||||
.booleanTrueName=${'Enabled'}
|
||||
.booleanFalseName=${'Disabled'}
|
||||
.selectedOption=${'Enabled'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Language'}
|
||||
.options=${['English', 'German', 'French', 'Spanish']}
|
||||
.selectedOption=${'English'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Quality'}
|
||||
.options=${['Low', 'Medium', 'High', 'Ultra']}
|
||||
.selectedOption=${'High'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Privacy'}
|
||||
.type=${'boolean'}
|
||||
.booleanTrueName=${'Private'}
|
||||
.booleanFalseName=${'Public'}
|
||||
.selectedOption=${'Private'}
|
||||
></dees-input-multitoggle>
|
||||
</div>
|
||||
|
||||
<dees-form>
|
||||
<div class="settings-grid">
|
||||
<dees-input-multitoggle
|
||||
.label=${'Auto-Save'}
|
||||
.type=${'boolean'}
|
||||
.booleanTrueName=${'Enabled'}
|
||||
.booleanFalseName=${'Disabled'}
|
||||
.selectedOption=${'Enabled'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Language'}
|
||||
.options=${['English', 'German', 'French', 'Spanish']}
|
||||
.selectedOption=${'English'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Quality'}
|
||||
.options=${['Low', 'Medium', 'High', 'Ultra']}
|
||||
.selectedOption=${'High'}
|
||||
></dees-input-multitoggle>
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Privacy'}
|
||||
.type=${'boolean'}
|
||||
.booleanTrueName=${'Private'}
|
||||
.booleanFalseName=${'Public'}
|
||||
.selectedOption=${'Private'}
|
||||
></dees-input-multitoggle>
|
||||
</div>
|
||||
</dees-form>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -134,7 +137,7 @@ export const demoFunc = () => html`
|
||||
|
||||
<dees-input-multitoggle
|
||||
.label=${'Account Type'}
|
||||
.description=${'This setting is locked'}
|
||||
.infoText=${'This setting is locked'}
|
||||
.options=${['Free', 'Pro', 'Enterprise']}
|
||||
.selectedOption=${'Enterprise'}
|
||||
.disabled=${true}
|
||||
|
||||
@@ -146,7 +146,7 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description}></dees-label>
|
||||
<dees-label .label=${this.label} .infoText=${this.infoText}></dees-label>
|
||||
<div class="mainbox">
|
||||
<div class="selections">
|
||||
<div class="indicator"></div>
|
||||
@@ -158,6 +158,7 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,6 @@ export const demoFunc = () => html`
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.horizontal-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -30,43 +24,46 @@ export const demoFunc = () => html`
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'Basic Phone Input'} .subtitle=${'Automatic formatting for phone numbers'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-phone
|
||||
.label=${'Phone Number'}
|
||||
.description=${'Enter your phone number with country code'}
|
||||
.infoText=${'Enter your phone number with country code'}
|
||||
.description=${'Include country code for international numbers'}
|
||||
.value=${'5551234567'}
|
||||
></dees-input-phone>
|
||||
|
||||
<dees-input-phone
|
||||
.label=${'Contact Phone'}
|
||||
.description=${'Required for account verification'}
|
||||
.infoText=${'Required for account verification'}
|
||||
.required=${true}
|
||||
.placeholder=${'+1 (555) 000-0000'}
|
||||
></dees-input-phone>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Phone inputs arranged horizontally'}>
|
||||
<div class="horizontal-group">
|
||||
<dees-input-phone
|
||||
.label=${'Mobile'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${'4155551234'}
|
||||
></dees-input-phone>
|
||||
|
||||
<dees-input-phone
|
||||
.label=${'Office'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.placeholder=${'+1 (800) 555-0000'}
|
||||
></dees-input-phone>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="horizontal-group">
|
||||
<dees-input-phone
|
||||
.label=${'Mobile'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${'4155551234'}
|
||||
></dees-input-phone>
|
||||
|
||||
<dees-input-phone
|
||||
.label=${'Office'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.placeholder=${'+1 (800) 555-0000'}
|
||||
></dees-input-phone>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'International Numbers'} .subtitle=${'Supports formatting for numbers with country codes'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-phone
|
||||
.label=${'International Contact'}
|
||||
.description=${'Automatically formats international numbers'}
|
||||
.infoText=${'Automatically formats international numbers'}
|
||||
.value=${'441234567890'}
|
||||
></dees-input-phone>
|
||||
|
||||
@@ -75,7 +72,7 @@ export const demoFunc = () => html`
|
||||
.value=${'911'}
|
||||
.disabled=${true}
|
||||
></dees-input-phone>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Form Integration'} .subtitle=${'Phone input as part of a contact form'}>
|
||||
|
||||
@@ -47,7 +47,7 @@ export class DeesInputPhone extends DeesInputBase<DeesInputPhone> {
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description}></dees-label>
|
||||
<dees-label .label=${this.label} .infoText=${this.infoText}></dees-label>
|
||||
<dees-input-text
|
||||
.value=${this.formattedPhone}
|
||||
.disabled=${this.disabled}
|
||||
@@ -55,6 +55,7 @@ export class DeesInputPhone extends DeesInputBase<DeesInputPhone> {
|
||||
.placeholder=${this.placeholder}
|
||||
@input=${(event: InputEvent) => this.handlePhoneInput(event)}
|
||||
></dees-input-text>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,6 @@ export const demoFunc = () => html`
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.shopping-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
@@ -66,19 +60,20 @@ export const demoFunc = () => html`
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'Basic Quantity Selector'} .subtitle=${'Simple quantity input with increment/decrement buttons'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-quantityselector
|
||||
.label=${'Quantity'}
|
||||
.description=${'Select the desired quantity'}
|
||||
.infoText=${'Select the desired quantity'}
|
||||
.description=${'Minimum order quantity is 1 item'}
|
||||
.value=${1}
|
||||
></dees-input-quantityselector>
|
||||
|
||||
<dees-input-quantityselector
|
||||
.label=${'Items in Cart'}
|
||||
.description=${'Adjust the quantity of items'}
|
||||
.infoText=${'Adjust the quantity of items'}
|
||||
.value=${3}
|
||||
></dees-input-quantityselector>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Shopping Cart'} .subtitle=${'Modern e-commerce product cards with interactive quantity selectors'} .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
@@ -177,21 +172,21 @@ export const demoFunc = () => html`
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Required & Disabled States'} .subtitle=${'Different states for validation and restrictions'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-quantityselector
|
||||
.label=${'Number of Licenses'}
|
||||
.description=${'Select how many licenses you need'}
|
||||
.infoText=${'Select how many licenses you need'}
|
||||
.required=${true}
|
||||
.value=${1}
|
||||
></dees-input-quantityselector>
|
||||
|
||||
<dees-input-quantityselector
|
||||
.label=${'Fixed Quantity'}
|
||||
.description=${'This quantity cannot be changed'}
|
||||
.infoText=${'This quantity cannot be changed'}
|
||||
.disabled=${true}
|
||||
.value=${5}
|
||||
></dees-input-quantityselector>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Order Form'} .subtitle=${'Complete order form with quantity selection'}>
|
||||
@@ -204,7 +199,7 @@ export const demoFunc = () => html`
|
||||
></dees-input-dropdown>
|
||||
<dees-input-quantityselector
|
||||
.label=${'Quantity'}
|
||||
.description=${'Number of licenses'}
|
||||
.infoText=${'Number of licenses'}
|
||||
.value=${1}
|
||||
></dees-input-quantityselector>
|
||||
<dees-input-text
|
||||
|
||||
@@ -129,7 +129,7 @@ export class DeesInputQuantitySelector extends DeesInputBase<DeesInputQuantitySe
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
${this.label ? html`<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>` : ''}
|
||||
${this.label ? html`<dees-label .label=${this.label} .infoText=${this.infoText} .required=${this.required}></dees-label>` : ''}
|
||||
<div
|
||||
class="quantity-container ${this.disabled ? 'disabled' : ''}"
|
||||
data-min="${this.value <= 0}"
|
||||
@@ -162,6 +162,7 @@ export class DeesInputQuantitySelector extends DeesInputBase<DeesInputQuantitySe
|
||||
aria-label="Increase quantity"
|
||||
>+</div>
|
||||
</div>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,6 @@ export const demoFunc = () => html`
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
@@ -48,25 +42,27 @@ export const demoFunc = () => html`
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'1. Basic Radio Groups'} .subtitle=${'Simple string options for common use cases'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'Subscription Plan'}
|
||||
.options=${['Basic - $9/month', 'Pro - $29/month', 'Enterprise - $99/month']}
|
||||
.selectedOption=${'Pro - $29/month'}
|
||||
.description=${'Choose your subscription tier'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Priority Level'}
|
||||
.options=${['High', 'Medium', 'Low']}
|
||||
.selectedOption=${'Medium'}
|
||||
.required=${true}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'Subscription Plan'}
|
||||
.options=${['Basic - $9/month', 'Pro - $29/month', 'Enterprise - $99/month']}
|
||||
.selectedOption=${'Pro - $29/month'}
|
||||
.description=${'Choose your subscription tier'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Priority Level'}
|
||||
.options=${['High', 'Medium', 'Low']}
|
||||
.selectedOption=${'Medium'}
|
||||
.required=${true}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'2. Horizontal Layout'} .subtitle=${'Radio groups with horizontal arrangement'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-radiogroup
|
||||
.label=${'Do you agree with the terms?'}
|
||||
.options=${['Yes', 'No', 'Maybe']}
|
||||
@@ -81,7 +77,7 @@ export const demoFunc = () => html`
|
||||
.selectedOption=${'Intermediate'}
|
||||
.description=${'Select your experience level with web development'}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'3. Advanced Options'} .subtitle=${'Using object format with keys and payloads'}>
|
||||
@@ -106,41 +102,45 @@ export const demoFunc = () => html`
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'4. Survey Example'} .subtitle=${'Multiple radio groups for surveys and forms'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'How satisfied are you?'}
|
||||
.options=${['Very Satisfied', 'Satisfied', 'Neutral', 'Dissatisfied', 'Very Dissatisfied']}
|
||||
.selectedOption=${'Satisfied'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Would you recommend us?'}
|
||||
.options=${['Definitely', 'Probably', 'Not Sure', 'Probably Not', 'Definitely Not']}
|
||||
.selectedOption=${'Probably'}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'How satisfied are you?'}
|
||||
.options=${['Very Satisfied', 'Satisfied', 'Neutral', 'Dissatisfied', 'Very Dissatisfied']}
|
||||
.selectedOption=${'Satisfied'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Would you recommend us?'}
|
||||
.options=${['Definitely', 'Probably', 'Not Sure', 'Probably Not', 'Definitely Not']}
|
||||
.selectedOption=${'Probably'}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'5. States & Validation'} .subtitle=${'Different states and validation examples'}>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'Required Selection'}
|
||||
.options=${['Option A', 'Option B', 'Option C']}
|
||||
.required=${true}
|
||||
.description=${'This field is required'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Disabled State'}
|
||||
.options=${['Disabled Option 1', 'Disabled Option 2', 'Disabled Option 3']}
|
||||
.selectedOption=${'Disabled Option 2'}
|
||||
.disabled=${true}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="demo-grid">
|
||||
<dees-input-radiogroup
|
||||
.label=${'Required Selection'}
|
||||
.options=${['Option A', 'Option B', 'Option C']}
|
||||
.required=${true}
|
||||
.description=${'This field is required'}
|
||||
></dees-input-radiogroup>
|
||||
|
||||
<dees-input-radiogroup
|
||||
.label=${'Disabled State'}
|
||||
.options=${['Disabled Option 1', 'Disabled Option 2', 'Disabled Option 3']}
|
||||
.selectedOption=${'Disabled Option 2'}
|
||||
.disabled=${true}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'6. Settings Example'} .subtitle=${'Common patterns in application settings'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-radiogroup
|
||||
.label=${'Theme Preference'}
|
||||
.options=${[
|
||||
@@ -165,7 +165,7 @@ export const demoFunc = () => html`
|
||||
.selectedOption=${'English'}
|
||||
.direction=${'horizontal'}
|
||||
></dees-input-radiogroup>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'7. Form Integration'} .subtitle=${'Works seamlessly with dees-form'}>
|
||||
|
||||
@@ -189,14 +189,6 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.description-text {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
margin-top: 10px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.003em;
|
||||
}
|
||||
|
||||
/* Validation styles */
|
||||
:host([validationState="invalid"]) .radio-circle {
|
||||
border-color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||
@@ -256,7 +248,7 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
${this.description ? html`<div class="description-text">${this.description}</div>` : ''}
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -278,13 +278,6 @@ export const richtextStyles = [
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:host([disabled]) dees-tile {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
|
||||
@@ -26,7 +26,7 @@ export const renderRichtext = (component: DeesInputRichtext): TemplateResult =>
|
||||
`
|
||||
: ''}
|
||||
</dees-tile>
|
||||
${component.description ? html`<div class="description">${component.description}</div>` : ''}
|
||||
${component.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
@@ -115,24 +115,26 @@ export const demoFunc = () => html`
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'3. Limited Tags'} .subtitle=${'Restrict the number of tags users can add'}>
|
||||
<div class="grid-layout">
|
||||
<dees-input-tags
|
||||
.label=${'Top 3 Skills'}
|
||||
.placeholder=${'Add up to 3 skills...'}
|
||||
.maxTags=${3}
|
||||
.value=${['Design', 'Development']}
|
||||
.description=${'Maximum 3 tags allowed'}
|
||||
></dees-input-tags>
|
||||
|
||||
<dees-input-tags
|
||||
.label=${'Categories (Max 5)'}
|
||||
.placeholder=${'Select categories...'}
|
||||
.maxTags=${5}
|
||||
.suggestions=${['Blog', 'Tutorial', 'News', 'Review', 'Guide', 'Case Study', 'Interview']}
|
||||
.value=${['Tutorial', 'Guide']}
|
||||
.description=${'Choose up to 5 categories'}
|
||||
></dees-input-tags>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="grid-layout">
|
||||
<dees-input-tags
|
||||
.label=${'Top 3 Skills'}
|
||||
.placeholder=${'Add up to 3 skills...'}
|
||||
.maxTags=${3}
|
||||
.value=${['Design', 'Development']}
|
||||
.description=${'Maximum 3 tags allowed'}
|
||||
></dees-input-tags>
|
||||
|
||||
<dees-input-tags
|
||||
.label=${'Categories (Max 5)'}
|
||||
.placeholder=${'Select categories...'}
|
||||
.maxTags=${5}
|
||||
.suggestions=${['Blog', 'Tutorial', 'News', 'Review', 'Guide', 'Case Study', 'Interview']}
|
||||
.value=${['Tutorial', 'Guide']}
|
||||
.description=${'Choose up to 5 categories'}
|
||||
></dees-input-tags>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'4. Required & Validation'} .subtitle=${'Tags input with validation requirements'}>
|
||||
|
||||
@@ -210,14 +210,6 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Description styles */
|
||||
.description {
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
font-size: 13px;
|
||||
margin-top: 6px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.suggestions-dropdown::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
@@ -302,9 +294,7 @@ export class DeesInputTags extends DeesInputBase<DeesInputTags> {
|
||||
<div class="validation-message">${this.validationText}</div>
|
||||
` : ''}
|
||||
|
||||
${this.description ? html`
|
||||
<div class="description">${this.description}</div>
|
||||
` : ''}
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -30,12 +30,6 @@ export const demoFunc = () => html`
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.grid-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@@ -89,17 +83,18 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'Basic Text Inputs'} .subtitle=${'Standard text inputs with labels and descriptions'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.label=${'Username'}
|
||||
.value=${'johndoe'}
|
||||
.key=${'username'}
|
||||
.description=${'Your username will be visible to other users'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Email Address'}
|
||||
.value=${'john@example.com'}
|
||||
.description=${'We will never share your email with anyone'}
|
||||
.infoText=${'We will never share your email with anyone'}
|
||||
.key=${'email'}
|
||||
></dees-input-text>
|
||||
|
||||
@@ -109,7 +104,7 @@ export const demoFunc = () => html`
|
||||
.value=${'secret123'}
|
||||
.key=${'password'}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
@@ -139,28 +134,30 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Multiple inputs arranged horizontally for compact forms'}>
|
||||
<div class="horizontal-group">
|
||||
<dees-input-text
|
||||
.label=${'First Name'}
|
||||
.value=${'John'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'firstName'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Last Name'}
|
||||
.value=${'Doe'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'lastName'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Age'}
|
||||
.value=${'28'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'age'}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
<dees-form>
|
||||
<div class="horizontal-group">
|
||||
<dees-input-text
|
||||
.label=${'First Name'}
|
||||
.value=${'John'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'firstName'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Last Name'}
|
||||
.value=${'Doe'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'lastName'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Age'}
|
||||
.value=${'28'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.key=${'age'}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
@@ -180,7 +177,7 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'Label Positions'} .subtitle=${'Different label positioning options for various layouts'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.label=${'Label on Top (Default)'}
|
||||
.value=${'Standard layout'}
|
||||
@@ -194,57 +191,25 @@ export const demoFunc = () => html`
|
||||
></dees-input-text>
|
||||
|
||||
<div class="grid-layout">
|
||||
<dees-input-text
|
||||
.label=${'City'}
|
||||
.value=${'New York'}
|
||||
<dees-input-text
|
||||
.label=${'City'}
|
||||
.value=${'New York'}
|
||||
.labelPosition=${'left'}
|
||||
></dees-input-text>
|
||||
|
||||
|
||||
<dees-input-text
|
||||
.label=${'ZIP Code'}
|
||||
.value=${'10001'}
|
||||
.labelPosition=${'left'}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
// Demonstrate validation states
|
||||
const requiredInput = elementArg.querySelector('dees-input-text[required]') as DeesInputText;
|
||||
const disabledInput = elementArg.querySelector('dees-input-text[disabled]') as DeesInputText;
|
||||
const errorInput = elementArg.querySelector('dees-input-text[validationState="invalid"]') as DeesInputText;
|
||||
|
||||
if (requiredInput) {
|
||||
// Show validation on blur for empty required field
|
||||
requiredInput.addEventListener('blur', () => {
|
||||
if (!requiredInput.getValue()) {
|
||||
console.log('Required field is empty!');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (disabledInput) {
|
||||
console.log('Disabled input cannot be edited');
|
||||
}
|
||||
|
||||
if (errorInput) {
|
||||
console.log('Error input shows validation message:', errorInput.validationText);
|
||||
|
||||
// Simulate fixing the error
|
||||
errorInput.addEventListener('changeSubject', () => {
|
||||
const value = errorInput.getValue();
|
||||
if (value.includes('@') && value.includes('.')) {
|
||||
errorInput.validationState = 'valid';
|
||||
errorInput.validationText = '';
|
||||
console.log('Email validation passed!');
|
||||
}
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-demowrapper>
|
||||
<dees-panel .title=${'Validation & States'} .subtitle=${'Different validation states and input configurations'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.label=${'Required Field'}
|
||||
.required=${true}
|
||||
@@ -258,12 +223,17 @@ export const demoFunc = () => html`
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'Field with Error'}
|
||||
.label=${'Email with Validation'}
|
||||
.value=${'invalid@'}
|
||||
.validationText=${'Please enter a valid email address'}
|
||||
.validationState=${'invalid'}
|
||||
.validationFunction=${(value: string) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (emailRegex.test(value)) {
|
||||
return { valid: true, message: 'Email address is valid' };
|
||||
}
|
||||
return { valid: false, message: 'Please enter a valid email address' };
|
||||
}}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
@@ -292,59 +262,40 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}}>
|
||||
<dees-panel .title=${'Advanced Features'} .subtitle=${'Password visibility toggle and other advanced features'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.label=${'Password with Toggle'}
|
||||
.isPasswordBool=${true}
|
||||
.value=${'mySecurePassword123'}
|
||||
.description=${'Click the eye icon to show/hide password'}
|
||||
.infoText=${'Click the eye icon to show/hide password'}
|
||||
></dees-input-text>
|
||||
|
||||
<dees-input-text
|
||||
.label=${'API Key'}
|
||||
.isPasswordBool=${true}
|
||||
.value=${'sk-1234567890abcdef'}
|
||||
.description=${'Keep this key secure and never share it'}
|
||||
.infoText=${'Keep this key secure and never share it'}
|
||||
></dees-input-text>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
// Set up interactive example
|
||||
const dynamicInput = elementArg.querySelector('dees-input-text');
|
||||
const dynamicInput = elementArg.querySelector('dees-input-text') as DeesInputText;
|
||||
const output = elementArg.querySelector('#text-input-output');
|
||||
|
||||
|
||||
if (dynamicInput && output) {
|
||||
// Update output on every change
|
||||
dynamicInput.addEventListener('changeSubject', ((event: CustomEvent) => {
|
||||
const value = (event.detail as DeesInputText).getValue();
|
||||
output.textContent = `Current value: "${value}"`;
|
||||
}) as EventListener);
|
||||
|
||||
// Also track focus/blur events
|
||||
dynamicInput.addEventListener('focus', () => {
|
||||
console.log('Input focused');
|
||||
});
|
||||
|
||||
dynamicInput.addEventListener('blur', () => {
|
||||
console.log('Input blurred');
|
||||
});
|
||||
|
||||
// Track keypress events
|
||||
let keypressCount = 0;
|
||||
dynamicInput.addEventListener('keydown', () => {
|
||||
keypressCount++;
|
||||
console.log(`Keypress count: ${keypressCount}`);
|
||||
dynamicInput.changeSubject.subscribe(() => {
|
||||
output.textContent = `Current value: "${dynamicInput.getValue()}"`;
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'Interactive Example'} .subtitle=${'Try typing in the inputs to see real-time value changes'}>
|
||||
<dees-input-text
|
||||
.label=${'Dynamic Input'}
|
||||
<dees-panel .title=${'Interactive Example'} .subtitle=${'Try typing in the input to see real-time value changes'}>
|
||||
<dees-input-text
|
||||
.label=${'Dynamic Input'}
|
||||
.placeholder=${'Type something here...'}
|
||||
></dees-input-text>
|
||||
|
||||
|
||||
<div class="interactive-section">
|
||||
<div id="text-input-output" class="output-text">Current value: ""</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
property,
|
||||
state,
|
||||
html,
|
||||
cssManager,
|
||||
css,
|
||||
} from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -44,7 +46,7 @@ export class DeesInputText extends DeesInputBase {
|
||||
accessor showPasswordBool = false;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
accessor validationState!: 'valid' | 'warn' | 'invalid';
|
||||
@@ -54,8 +56,19 @@ export class DeesInputText extends DeesInputBase {
|
||||
})
|
||||
accessor validationText: string = '';
|
||||
|
||||
@property({})
|
||||
accessor validationFunction!: (value: string) => boolean;
|
||||
@property({ attribute: false })
|
||||
accessor validationFunction!: (value: string) => { valid: boolean; message?: string };
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
})
|
||||
accessor vintegrated: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor validConfirmed: boolean = false;
|
||||
|
||||
private validTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
@@ -121,7 +134,7 @@ export class DeesInputText extends DeesInputBase {
|
||||
.showPassword {
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
top: 50%;
|
||||
top: 20px;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -139,6 +152,29 @@ export class DeesInputText extends DeesInputBase {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
}
|
||||
|
||||
/* Valid checkmark icon */
|
||||
.validIcon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 20px;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||
}
|
||||
|
||||
.validIcon.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.validIcon dees-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Validation styles */
|
||||
.validationContainer {
|
||||
margin-top: 4px;
|
||||
@@ -150,7 +186,7 @@ export class DeesInputText extends DeesInputBase {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.validationContainer.error {
|
||||
.validationContainer.invalid {
|
||||
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.1)', 'hsl(0 72.2% 50.6% / 0.1)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||
}
|
||||
@@ -166,34 +202,64 @@ export class DeesInputText extends DeesInputBase {
|
||||
}
|
||||
|
||||
/* Error state for input */
|
||||
:host([validation-state="invalid"]) input {
|
||||
:host([validationstate="invalid"]) input {
|
||||
border-color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||
}
|
||||
|
||||
:host([validation-state="invalid"]) input:focus {
|
||||
:host([validationstate="invalid"]) input:focus {
|
||||
border-color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.05)', 'hsl(0 72.2% 50.6% / 0.05)')};
|
||||
}
|
||||
|
||||
/* Warning state for input */
|
||||
:host([validation-state="warn"]) input {
|
||||
:host([validationstate="warn"]) input {
|
||||
border-color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
|
||||
}
|
||||
|
||||
:host([validation-state="warn"]) input:focus {
|
||||
:host([validationstate="warn"]) input:focus {
|
||||
border-color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(25 95% 53% / 0.05)', 'hsl(25 95% 63% / 0.05)')};
|
||||
}
|
||||
|
||||
/* Valid state for input */
|
||||
:host([validation-state="valid"]) input {
|
||||
:host([validationstate="valid"]) input {
|
||||
border-color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||
}
|
||||
|
||||
:host([validation-state="valid"]) input:focus {
|
||||
:host([validationstate="valid"]) input:focus {
|
||||
border-color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.05)', 'hsl(142.1 70.6% 45.3% / 0.05)')};
|
||||
}
|
||||
|
||||
/* Visually integrated mode: shed chrome to blend into a host component
|
||||
(e.g. a dees-table cell in edit mode). */
|
||||
:host([vintegrated]) dees-label,
|
||||
:host([vintegrated]) .validationContainer {
|
||||
display: none;
|
||||
}
|
||||
:host([vintegrated]) .maincontainer {
|
||||
height: 40px;
|
||||
}
|
||||
:host([vintegrated]) input {
|
||||
height: 40px;
|
||||
line-height: 24px;
|
||||
padding: 0 16px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
transition: none;
|
||||
}
|
||||
:host([vintegrated]) input:hover:not(:disabled):not(:focus),
|
||||
:host([vintegrated]) input:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
:host([vintegrated]) .showPassword {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -203,9 +269,9 @@ export class DeesInputText extends DeesInputBase {
|
||||
input {
|
||||
font-family: ${this.isPasswordBool ? cssMonoFontFamily : 'inherit'};
|
||||
letter-spacing: ${this.isPasswordBool ? '0.5px' : 'normal'};
|
||||
padding-right: ${this.isPasswordBool ? '48px' : '12px'};
|
||||
padding-right: ${this.isPasswordBool ? '48px' : this.validConfirmed ? '40px' : '12px'};
|
||||
}
|
||||
${this.validationText
|
||||
${this.validationText && !this.validConfirmed
|
||||
? css`
|
||||
.validationContainer {
|
||||
height: auto;
|
||||
@@ -223,7 +289,7 @@ export class DeesInputText extends DeesInputBase {
|
||||
`}
|
||||
</style>
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>
|
||||
<dees-label .label=${this.label} .infoText=${this.infoText} .required=${this.required}></dees-label>
|
||||
<div class="maincontainer">
|
||||
<input
|
||||
type="${this.isPasswordBool && !this.showPasswordBool ? 'password' : 'text'}"
|
||||
@@ -239,28 +305,114 @@ export class DeesInputText extends DeesInputBase {
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
<div class="validIcon ${this.validConfirmed ? 'show' : ''}">
|
||||
<dees-icon .icon=${'lucide:Check'}></dees-icon>
|
||||
</div>
|
||||
${this.validationText
|
||||
? html`
|
||||
<div class="validationContainer ${this.validationState || 'error'}">
|
||||
<div class="validationContainer ${this.validationState || 'invalid'}">
|
||||
${this.validationText}
|
||||
</div>
|
||||
`
|
||||
: html`<div class="validationContainer"></div>`}
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
// Input event handling is already done in updateValue method
|
||||
if (this.validationFunction && this.value) {
|
||||
const result = this.validationFunction(this.value);
|
||||
this.validationState = result.valid ? 'valid' : 'invalid';
|
||||
this.validationText = result.message || '';
|
||||
if (result.valid) {
|
||||
this.validConfirmed = false;
|
||||
this.validTimeout = setTimeout(() => {
|
||||
this.validConfirmed = true;
|
||||
this.validationState = undefined as any;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async updateValue(eventArg: Event) {
|
||||
const target: any = eventArg.target;
|
||||
this.value = target.value;
|
||||
if (this.validationFunction) {
|
||||
const result = this.validationFunction(this.value);
|
||||
this.validationState = result.valid ? 'valid' : 'invalid';
|
||||
this.validationText = result.message || '';
|
||||
if (result.valid) {
|
||||
this.validConfirmed = false;
|
||||
clearTimeout(this.validTimeout);
|
||||
this.validTimeout = setTimeout(() => {
|
||||
this.validConfirmed = true;
|
||||
this.validationState = undefined as any;
|
||||
}, 500);
|
||||
} else {
|
||||
clearTimeout(this.validTimeout);
|
||||
this.validConfirmed = false;
|
||||
}
|
||||
}
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
|
||||
public getContextMenuItems() {
|
||||
const input = this.shadowRoot!.querySelector('input')!;
|
||||
const hasSelection = input.selectionStart !== input.selectionEnd;
|
||||
return [
|
||||
{
|
||||
name: 'Cut',
|
||||
iconName: 'lucide:Scissors',
|
||||
shortcut: 'Cmd+X',
|
||||
disabled: !hasSelection,
|
||||
action: async () => {
|
||||
const selected = this.value.substring(input.selectionStart!, input.selectionEnd!);
|
||||
await navigator.clipboard.writeText(selected);
|
||||
const start = input.selectionStart!;
|
||||
this.value = this.value.substring(0, start) + this.value.substring(input.selectionEnd!);
|
||||
input.value = this.value;
|
||||
input.setSelectionRange(start, start);
|
||||
this.changeSubject.next(this);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Copy',
|
||||
iconName: 'lucide:Copy',
|
||||
shortcut: 'Cmd+C',
|
||||
disabled: !hasSelection,
|
||||
action: async () => {
|
||||
const selected = this.value.substring(input.selectionStart!, input.selectionEnd!);
|
||||
await navigator.clipboard.writeText(selected);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Paste',
|
||||
iconName: 'lucide:ClipboardPaste',
|
||||
shortcut: 'Cmd+V',
|
||||
action: async () => {
|
||||
const text = await navigator.clipboard.readText();
|
||||
const start = input.selectionStart!;
|
||||
const end = input.selectionEnd!;
|
||||
this.value = this.value.substring(0, start) + text + this.value.substring(end);
|
||||
input.value = this.value;
|
||||
input.setSelectionRange(start + text.length, start + text.length);
|
||||
this.changeSubject.next(this);
|
||||
},
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
name: 'Select All',
|
||||
iconName: 'lucide:TextCursorInput',
|
||||
shortcut: 'Cmd+A',
|
||||
action: async () => {
|
||||
input.select();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public getValue(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export const demoFunc = () => html`
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'Basic Toggle'} .subtitle=${'Simple on/off toggle switch with drag support'}>
|
||||
<div class="toggle-group">
|
||||
<dees-form>
|
||||
<dees-input-toggle
|
||||
.label=${'Enable feature'}
|
||||
.value=${false}
|
||||
@@ -135,12 +135,12 @@ export const demoFunc = () => html`
|
||||
.description=${'This toggle has additional helper text explaining its purpose'}
|
||||
.key=${'withDesc'}
|
||||
></dees-input-toggle>
|
||||
</div>
|
||||
</dees-form>
|
||||
<p class="drag-hint">Tip: You can drag the toggle knob to switch states</p>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Toggle States'} .subtitle=${'Different toggle states and configurations'}>
|
||||
<div class="toggle-group">
|
||||
<dees-form>
|
||||
<dees-input-toggle
|
||||
.label=${'Default (off)'}
|
||||
.value=${false}
|
||||
@@ -169,42 +169,44 @@ export const demoFunc = () => html`
|
||||
.required=${true}
|
||||
.description=${'This toggle cannot be turned off'}
|
||||
></dees-input-toggle>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Horizontal Layout'} .subtitle=${'Toggles arranged horizontally for compact interfaces'}>
|
||||
<div class="horizontal-toggles">
|
||||
<dees-input-toggle
|
||||
.label=${'WiFi'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
<dees-form>
|
||||
<div class="horizontal-toggles">
|
||||
<dees-input-toggle
|
||||
.label=${'WiFi'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
|
||||
<dees-input-toggle
|
||||
.label=${'Bluetooth'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
<dees-input-toggle
|
||||
.label=${'Bluetooth'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
|
||||
<dees-input-toggle
|
||||
.label=${'GPS'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
<dees-input-toggle
|
||||
.label=${'GPS'}
|
||||
.value=${true}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
|
||||
<dees-input-toggle
|
||||
.label=${'NFC'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
</div>
|
||||
<dees-input-toggle
|
||||
.label=${'NFC'}
|
||||
.value=${false}
|
||||
.layoutMode=${'horizontal'}
|
||||
></dees-input-toggle>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Settings Example'} .subtitle=${'Toggles in a typical settings context'}>
|
||||
<div class="settings-section">
|
||||
<h4 class="section-title">Notification Settings</h4>
|
||||
|
||||
<div class="toggle-group">
|
||||
<dees-form>
|
||||
<dees-input-toggle
|
||||
.label=${'Push notifications'}
|
||||
.value=${true}
|
||||
@@ -232,7 +234,7 @@ export const demoFunc = () => html`
|
||||
.description=${'Vibrate for notifications'}
|
||||
.key=${'vibration'}
|
||||
></dees-input-toggle>
|
||||
</div>
|
||||
</dees-form>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
@@ -243,7 +245,7 @@ export const demoFunc = () => html`
|
||||
</div>
|
||||
|
||||
<div class="feature-toggles">
|
||||
<div class="toggle-group">
|
||||
<dees-form>
|
||||
<dees-input-toggle
|
||||
.label=${'Dark Mode'}
|
||||
.value=${true}
|
||||
@@ -273,12 +275,12 @@ export const demoFunc = () => html`
|
||||
.value=${false}
|
||||
.key=${'beta'}
|
||||
></dees-input-toggle>
|
||||
</div>
|
||||
</dees-form>
|
||||
</div>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Interactive Example'} .subtitle=${'Toggle to see value changes in real-time'}>
|
||||
<div class="toggle-group">
|
||||
<dees-form>
|
||||
<dees-input-toggle
|
||||
.label=${'Airplane mode'}
|
||||
.value=${false}
|
||||
@@ -300,7 +302,7 @@ export const demoFunc = () => html`
|
||||
}
|
||||
}}
|
||||
></dees-input-toggle>
|
||||
</div>
|
||||
</dees-form>
|
||||
|
||||
<div class="interactive-section">
|
||||
<div id="airplane-output" class="output-text">Airplane mode: OFF</div>
|
||||
|
||||
@@ -170,12 +170,6 @@ export class DeesInputToggle extends DeesInputBase<DeesInputToggle> {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
}
|
||||
|
||||
/* Description */
|
||||
.description-text {
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
line-height: 1.5;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -199,7 +193,7 @@ export class DeesInputToggle extends DeesInputBase<DeesInputToggle> {
|
||||
</div>
|
||||
<div class="label-container">
|
||||
${this.label ? html`<div class="toggle-label">${this.label}</div>` : ''}
|
||||
${this.description ? html`<div class="description-text">${this.description}</div>` : ''}
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,12 +13,6 @@ export const demoFunc = () => html`
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.horizontal-group {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
@@ -45,61 +39,62 @@ export const demoFunc = () => html`
|
||||
|
||||
<div class="demo-container">
|
||||
<dees-panel .title=${'Basic Type List'} .subtitle=${'Add and remove items from a list'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-typelist
|
||||
.label=${'Tags'}
|
||||
.description=${'Add tags by typing and pressing Enter'}
|
||||
.infoText=${'Add tags by typing and pressing Enter'}
|
||||
.description=${'Tags help categorize and filter your content'}
|
||||
.value=${['javascript', 'typescript', 'web-components']}
|
||||
></dees-input-typelist>
|
||||
|
||||
<dees-input-typelist
|
||||
.label=${'Team Members'}
|
||||
.description=${'Add email addresses of team members'}
|
||||
.infoText=${'Add email addresses of team members'}
|
||||
.value=${['alice@example.com', 'bob@example.com']}
|
||||
></dees-input-typelist>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Skills & Keywords'} .subtitle=${'Manage lists of skills and keywords'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-typelist
|
||||
.label=${'Your Skills'}
|
||||
.description=${'List your professional skills'}
|
||||
.infoText=${'List your professional skills'}
|
||||
.value=${['HTML', 'CSS', 'JavaScript', 'Node.js', 'React']}
|
||||
></dees-input-typelist>
|
||||
|
||||
<div class="horizontal-group">
|
||||
<dees-input-typelist
|
||||
.label=${'Categories'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${['Technology', 'Design', 'Business']}
|
||||
></dees-input-typelist>
|
||||
|
||||
<dees-input-typelist
|
||||
.label=${'Keywords'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${['innovation', 'startup', 'growth']}
|
||||
></dees-input-typelist>
|
||||
</div>
|
||||
</div>
|
||||
<dees-input-typelist
|
||||
.label=${'Categories'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${['Technology', 'Design', 'Business']}
|
||||
></dees-input-typelist>
|
||||
|
||||
<dees-input-typelist
|
||||
.label=${'Keywords'}
|
||||
.layoutMode=${'horizontal'}
|
||||
.value=${['innovation', 'startup', 'growth']}
|
||||
></dees-input-typelist>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Required & Disabled States'} .subtitle=${'Different input states for validation'}>
|
||||
<div class="input-group">
|
||||
<dees-form>
|
||||
<dees-input-typelist
|
||||
.label=${'Project Dependencies'}
|
||||
.description=${'List all required npm packages'}
|
||||
.infoText=${'List all required npm packages'}
|
||||
.required=${true}
|
||||
.value=${['@design.estate/dees-element', '@design.estate/dees-domtools']}
|
||||
></dees-input-typelist>
|
||||
|
||||
<dees-input-typelist
|
||||
.label=${'System Tags'}
|
||||
.description=${'These tags are managed by the system'}
|
||||
.infoText=${'These tags are managed by the system'}
|
||||
.disabled=${true}
|
||||
.value=${['system', 'protected', 'readonly']}
|
||||
></dees-input-typelist>
|
||||
</div>
|
||||
</dees-form>
|
||||
</dees-panel>
|
||||
|
||||
<dees-panel .title=${'Article Publishing Form'} .subtitle=${'Complete form with tag management'}>
|
||||
@@ -108,16 +103,16 @@ export const demoFunc = () => html`
|
||||
<dees-input-text
|
||||
.label=${'Summary'}
|
||||
.inputType=${'textarea'}
|
||||
.description=${'Brief description of the article'}
|
||||
.infoText=${'Brief description of the article'}
|
||||
></dees-input-text>
|
||||
<dees-input-typelist
|
||||
.label=${'Tags'}
|
||||
.description=${'Add relevant tags for better discoverability'}
|
||||
.infoText=${'Add relevant tags for better discoverability'}
|
||||
.value=${['tutorial', 'web-development']}
|
||||
></dees-input-typelist>
|
||||
<dees-input-typelist
|
||||
.label=${'Co-Authors'}
|
||||
.description=${'Add email addresses of co-authors'}
|
||||
.infoText=${'Add email addresses of co-authors'}
|
||||
></dees-input-typelist>
|
||||
</dees-form>
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description}></dees-label>
|
||||
<dees-label .label=${this.label} .infoText=${this.infoText}></dees-label>
|
||||
<div class="mainbox">
|
||||
<div class="tags" @click=${() => {
|
||||
this.shadowRoot!.querySelector('input')!.focus();
|
||||
@@ -188,6 +188,7 @@ export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
|
||||
.disabled=${this.disabled}
|
||||
/>
|
||||
</div>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -292,17 +292,18 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
|
||||
return html`
|
||||
<dees-label
|
||||
.label="${this.label}"
|
||||
.description="${this.description}"
|
||||
.infoText="${this.infoText}"
|
||||
.required="${this.required}"
|
||||
></dees-label>
|
||||
<div class="wysiwyg-container">
|
||||
<div
|
||||
<div
|
||||
class="editor-content ${this.draggedBlockId ? 'dragging' : ''}"
|
||||
id="editor-content"
|
||||
>
|
||||
<!-- Blocks will be rendered programmatically -->
|
||||
</div>
|
||||
</div>
|
||||
${this.renderDescription()}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ export class DeesInputProfilePicture extends DeesInputBase<DeesInputProfilePictu
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description} .required=${this.required}></dees-label>
|
||||
<dees-label .label=${this.label} .infoText=${this.infoText} .required=${this.required}></dees-label>
|
||||
|
||||
<div
|
||||
class="profile-container"
|
||||
@@ -329,6 +329,7 @@ export class DeesInputProfilePicture extends DeesInputBase<DeesInputProfilePictu
|
||||
accept="${this.acceptedFormats.join(',')}"
|
||||
@change=${this.handleFileSelect}
|
||||
/>
|
||||
${this.renderDescription()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ export function demoFunc() {
|
||||
<dees-heading level="4">This is a H4 heading</dees-heading>
|
||||
<dees-heading level="5">This is a H5 heading</dees-heading>
|
||||
<dees-heading level="6">This is a H6 heading</dees-heading>
|
||||
<dees-heading level="hr">This is an hr heading</dees-heading>
|
||||
<dees-heading level="hr-small">This is an hr small heading</dees-heading>
|
||||
<dees-heading level="hr">This is an hr heading (level="hr")</dees-heading>
|
||||
<dees-heading level="7">This is an hr heading (level="7")</dees-heading>
|
||||
<dees-heading level="hr-small">This is an hr-small heading (level="hr-small")</dees-heading>
|
||||
<dees-heading level="8">This is an hr-small heading (level="8")</dees-heading>
|
||||
`;
|
||||
}
|
||||
@@ -27,68 +27,104 @@ export class DeesHeading extends DeesElement {
|
||||
|
||||
// properties
|
||||
/**
|
||||
* Heading level: 1-6 for h1-h6, or 'hr' for horizontal rule style
|
||||
* Heading level:
|
||||
* '1'-'6' → <h1>..<h6>
|
||||
* '7'|'hr' → horizontal-rule style heading
|
||||
* '8'|'hr-small' → small horizontal-rule style heading
|
||||
*/
|
||||
@property({ type: String, reflect: true })
|
||||
accessor level: '1' | '2' | '3' | '4' | '5' | '6' | 'hr' | 'hr-small' = '1';
|
||||
accessor level: '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | 'hr' | 'hr-small' = '1';
|
||||
|
||||
// STATIC STYLES
|
||||
public static styles: CSSResult[] = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
/* Heading styles */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 16px 0 8px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
h1 { font-size: 32px; font-family: ${cssCalSansFontFamily}; letter-spacing: 0.025em;}
|
||||
h2 { font-size: 28px; }
|
||||
h3 { font-size: 24px; }
|
||||
h4 { font-size: 20px; }
|
||||
h5 { font-size: 16px; }
|
||||
h6 { font-size: 14px; }
|
||||
|
||||
/* Heading styles.
|
||||
* Color hierarchy: h1-h2 stay prominent with text-primary; h3-h6 step
|
||||
* down to text-secondary so they read as subheadings instead of
|
||||
* mini-h1s. Keeps the visual loudness out of smaller headings. */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 600;
|
||||
}
|
||||
h1, h2 {
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
h3, h4, h5, h6 {
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
/* Per-level typography + spacing.
|
||||
* Margin scales with importance: h1 gets the most breathing room,
|
||||
* h6 the least. Top margin > bottom margin so headings group with
|
||||
* the content that follows them. */
|
||||
h1 {
|
||||
/* h1 uses weight 500, not 600: the Cal Sans display font is
|
||||
* already stylized enough that bold + max contrast + 32px stacks
|
||||
* too much emphasis. 500 keeps the typographic impact without
|
||||
* shouting. */
|
||||
font-weight: 500;
|
||||
font-size: 32px;
|
||||
font-family: ${cssCalSansFontFamily};
|
||||
letter-spacing: 0.025em;
|
||||
margin: var(--dees-spacing-2xl) 0 var(--dees-spacing-lg);
|
||||
}
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
margin: var(--dees-spacing-xl) 0 var(--dees-spacing-md);
|
||||
}
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
margin: var(--dees-spacing-xl) 0 var(--dees-spacing-md);
|
||||
}
|
||||
h4 {
|
||||
font-size: 20px;
|
||||
margin: var(--dees-spacing-lg) 0 var(--dees-spacing-sm);
|
||||
}
|
||||
h5 {
|
||||
font-size: 16px;
|
||||
margin: var(--dees-spacing-md) 0 var(--dees-spacing-sm);
|
||||
}
|
||||
h6 {
|
||||
font-size: 14px;
|
||||
margin: var(--dees-spacing-md) 0 var(--dees-spacing-xs);
|
||||
}
|
||||
|
||||
/* Horizontal rule style heading */
|
||||
.heading-hr {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 16px 0;
|
||||
color: ${cssManager.bdTheme('#999', '#555')};
|
||||
margin: var(--dees-spacing-lg) 0;
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
/* Fade lines toward and away from text for hr style */
|
||||
.heading-hr::before {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
/* fade in toward center */
|
||||
background: ${cssManager.bdTheme(
|
||||
'linear-gradient(to right, transparent, #ccc)',
|
||||
'linear-gradient(to right, transparent, #333)'
|
||||
)};
|
||||
margin: 0 8px;
|
||||
background: linear-gradient(to right, transparent, var(--dees-color-border-strong));
|
||||
margin: 0 var(--dees-spacing-sm);
|
||||
}
|
||||
.heading-hr::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
/* fade out away from center */
|
||||
background: ${cssManager.bdTheme(
|
||||
'linear-gradient(to right, #ccc, transparent)',
|
||||
'linear-gradient(to right, #333, transparent)'
|
||||
)};
|
||||
margin: 0 8px;
|
||||
background: linear-gradient(to right, var(--dees-color-border-strong), transparent);
|
||||
margin: 0 var(--dees-spacing-sm);
|
||||
}
|
||||
/* Small hr variant with reduced margins */
|
||||
.heading-hr.heading-hr-small {
|
||||
margin: 8px 0;
|
||||
margin: var(--dees-spacing-sm) 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.heading-hr.heading-hr-small::before,
|
||||
.heading-hr.heading-hr-small::after {
|
||||
margin: 0 8px;
|
||||
margin: 0 var(--dees-spacing-sm);
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -109,8 +145,10 @@ export class DeesHeading extends DeesElement {
|
||||
return html`<h5><slot></slot></h5>`;
|
||||
case '6':
|
||||
return html`<h6><slot></slot></h6>`;
|
||||
case '7':
|
||||
case 'hr':
|
||||
return html`<div class="heading-hr"><slot></slot></div>`;
|
||||
case '8':
|
||||
case 'hr-small':
|
||||
return html`<div class="heading-hr heading-hr-small"><slot></slot></div>`;
|
||||
default:
|
||||
|
||||
@@ -1,7 +1,128 @@
|
||||
import { html, cssManager } from '@design.estate/dees-element';
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
|
||||
export const demoFunc = () => {
|
||||
return html`
|
||||
<dees-label .label=${'a label'}></dees-label>
|
||||
`;
|
||||
}
|
||||
export const demoFunc = () => html`
|
||||
<style>
|
||||
${css`
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')};
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||
}
|
||||
|
||||
.demo-section h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
||||
}
|
||||
|
||||
.demo-section p {
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.label-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.label-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.label-row .annotation {
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
color: ${cssManager.bdTheme('#888', '#666')};
|
||||
flex-shrink: 0;
|
||||
min-width: 200px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div class="demo-container">
|
||||
<div class="demo-section">
|
||||
<h3>Basic Label</h3>
|
||||
<p>A simple text label with no additional indicators.</p>
|
||||
<div class="label-grid">
|
||||
<div class="label-row">
|
||||
<span class="annotation">label="Username"</span>
|
||||
<dees-label .label=${'Username'}></dees-label>
|
||||
</div>
|
||||
<div class="label-row">
|
||||
<span class="annotation">label="Email Address"</span>
|
||||
<dees-label .label=${'Email Address'}></dees-label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Required Indicator</h3>
|
||||
<p>When <code>required</code> is set, a red asterisk appears after the label text.</p>
|
||||
<div class="label-grid">
|
||||
<div class="label-row">
|
||||
<span class="annotation">required=${'{true}'}</span>
|
||||
<dees-label .label=${'Full Name'} .required=${true}></dees-label>
|
||||
</div>
|
||||
<div class="label-row">
|
||||
<span class="annotation">required=${'{false}'} (default)</span>
|
||||
<dees-label .label=${'Nickname'} .required=${false}></dees-label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Info Text (Info Icon)</h3>
|
||||
<p>When <code>infoText</code> is set, an info icon appears next to the label. Hover over it to see the tooltip.</p>
|
||||
<div class="label-grid">
|
||||
<div class="label-row">
|
||||
<span class="annotation">infoText="..."</span>
|
||||
<dees-label .label=${'API Key'} .infoText=${'Your API key can be found in the developer settings dashboard.'}></dees-label>
|
||||
</div>
|
||||
<div class="label-row">
|
||||
<span class="annotation">short infoText</span>
|
||||
<dees-label .label=${'Region'} .infoText=${'Select your nearest datacenter.'}></dees-label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Required + Info Text</h3>
|
||||
<p>Both indicators can be combined. The asterisk appears first, then the info icon.</p>
|
||||
<div class="label-grid">
|
||||
<div class="label-row">
|
||||
<span class="annotation">required + infoText</span>
|
||||
<dees-label .label=${'Password'} .required=${true} .infoText=${'Must be at least 8 characters with one uppercase letter and one number.'}></dees-label>
|
||||
</div>
|
||||
<div class="label-row">
|
||||
<span class="annotation">required + infoText</span>
|
||||
<dees-label .label=${'Email Address'} .required=${true} .infoText=${'We will send a verification link to this address.'}></dees-label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Empty Label</h3>
|
||||
<p>When <code>label</code> is empty or not set, nothing is rendered. The element below has no label text:</p>
|
||||
<div class="label-grid">
|
||||
<div class="label-row">
|
||||
<span class="annotation">label="" (empty)</span>
|
||||
<dees-label .label=${''}></dees-label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -32,7 +32,7 @@ export class DeesLabel extends DeesElement {
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
accessor description!: string;
|
||||
accessor infoText!: string;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
@@ -50,7 +50,8 @@ export class DeesLabel extends DeesElement {
|
||||
}
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
@@ -66,13 +67,26 @@ export class DeesLabel extends DeesElement {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
dees-icon {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
transform: translateY(1px);
|
||||
margin-left: 4px;
|
||||
.description-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: -6px 0 -6px 2px;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.description-icon:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 100% / 0.08)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 25%)', 'hsl(0 0% 80%)')};
|
||||
}
|
||||
|
||||
.description-icon dees-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -84,10 +98,12 @@ export class DeesLabel extends DeesElement {
|
||||
<div class="label">
|
||||
${this.label}
|
||||
${this.required ? html`<span class="required">*</span>` : ''}
|
||||
${this.description
|
||||
${this.infoText
|
||||
? html`
|
||||
<dees-icon .icon=${'lucide:info'}></dees-icon>
|
||||
<dees-speechbubble .text=${this.description}></dees-speechbubble>
|
||||
<div class="description-icon">
|
||||
<dees-icon .icon=${'lucide:info'}></dees-icon>
|
||||
</div>
|
||||
<dees-speechbubble .text=${this.infoText}></dees-speechbubble>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
|
||||
@@ -1,134 +1,137 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import { DeesStepper, type IStep } from './dees-stepper.js';
|
||||
|
||||
const demoSteps: IStep[] = [
|
||||
{
|
||||
title: 'Account Setup',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text key="email" label="Work Email" required></dees-input-text>
|
||||
<dees-input-text key="password" label="Create Password" type="password" required></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Profile Details',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text key="firstName" label="First Name" required></dees-input-text>
|
||||
<dees-input-text key="lastName" label="Last Name" required></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Contact Information',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-phone key="phone" label="Mobile Number" required></dees-input-phone>
|
||||
<dees-input-text key="company" label="Company"></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Team Size',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-dropdown
|
||||
key="teamSize"
|
||||
label="How big is your team?"
|
||||
.options=${[
|
||||
{ label: '1-5', value: '1-5' },
|
||||
{ label: '6-20', value: '6-20' },
|
||||
{ label: '21-50', value: '21-50' },
|
||||
{ label: '51+', value: '51+' },
|
||||
]}
|
||||
required
|
||||
></dees-input-dropdown>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Goals',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-multitoggle
|
||||
key="goal"
|
||||
label="Main objective"
|
||||
.options=${[
|
||||
{ label: 'Onboarding', value: 'onboarding' },
|
||||
{ label: 'Analytics', value: 'analytics' },
|
||||
{ label: 'Automation', value: 'automation' },
|
||||
]}
|
||||
required
|
||||
></dees-input-multitoggle>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Brand Preferences',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text key="brandColor" label="Primary brand color"></dees-input-text>
|
||||
<dees-input-text key="tone" label="Preferred tone (e.g. friendly, formal)"></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Integrations',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-list
|
||||
key="integrations"
|
||||
label="Integrations in use"
|
||||
placeholder="Add integration"
|
||||
></dees-input-list>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Continue', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Review & Launch',
|
||||
content: html`
|
||||
<dees-panel>
|
||||
<p>Almost there! Review your selections and launch whenever you're ready.</p>
|
||||
</dees-panel>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'Launch', action: async (stepper) => stepper!.goNext() },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const cloneSteps = (): IStep[] => demoSteps.map((step) => ({ ...step }));
|
||||
|
||||
export const stepperDemo = () => html`
|
||||
<dees-stepper
|
||||
.steps=${[
|
||||
{
|
||||
title: 'Account Setup',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text key="email" label="Work Email" required></dees-input-text>
|
||||
<dees-input-text key="password" label="Create Password" type="password" required></dees-input-text>
|
||||
<dees-form-submit>Continue</dees-form-submit>
|
||||
</dees-form>
|
||||
`,
|
||||
validationFunc: async (stepperArg, elementArg) => {
|
||||
const deesForm = elementArg.querySelector('dees-form');
|
||||
deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Profile Details',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text key="firstName" label="First Name" required></dees-input-text>
|
||||
<dees-input-text key="lastName" label="Last Name" required></dees-input-text>
|
||||
<dees-form-submit>Continue</dees-form-submit>
|
||||
</dees-form>
|
||||
`,
|
||||
validationFunc: async (stepperArg, elementArg) => {
|
||||
const deesForm = elementArg.querySelector('dees-form');
|
||||
deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Contact Information',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-phone key="phone" label="Mobile Number" required></dees-input-phone>
|
||||
<dees-input-text key="company" label="Company"></dees-input-text>
|
||||
<dees-form-submit>Continue</dees-form-submit>
|
||||
</dees-form>
|
||||
`,
|
||||
validationFunc: async (stepperArg, elementArg) => {
|
||||
const deesForm = elementArg.querySelector('dees-form');
|
||||
deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Team Size',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-dropdown
|
||||
key="teamSize"
|
||||
label="How big is your team?"
|
||||
.options=${[
|
||||
{ label: '1-5', value: '1-5' },
|
||||
{ label: '6-20', value: '6-20' },
|
||||
{ label: '21-50', value: '21-50' },
|
||||
{ label: '51+', value: '51+' },
|
||||
]}
|
||||
required
|
||||
></dees-input-dropdown>
|
||||
<dees-form-submit>Continue</dees-form-submit>
|
||||
</dees-form>
|
||||
`,
|
||||
validationFunc: async (stepperArg, elementArg) => {
|
||||
const deesForm = elementArg.querySelector('dees-form');
|
||||
deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Goals',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-multitoggle
|
||||
key="goal"
|
||||
label="Main objective"
|
||||
.options=${[
|
||||
{ label: 'Onboarding', value: 'onboarding' },
|
||||
{ label: 'Analytics', value: 'analytics' },
|
||||
{ label: 'Automation', value: 'automation' },
|
||||
]}
|
||||
required
|
||||
></dees-input-multitoggle>
|
||||
<dees-form-submit>Continue</dees-form-submit>
|
||||
</dees-form>
|
||||
`,
|
||||
validationFunc: async (stepperArg, elementArg) => {
|
||||
const deesForm = elementArg.querySelector('dees-form');
|
||||
deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Brand Preferences',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text key="brandColor" label="Primary brand color"></dees-input-text>
|
||||
<dees-input-text key="tone" label="Preferred tone (e.g. friendly, formal)"></dees-input-text>
|
||||
<dees-form-submit>Continue</dees-form-submit>
|
||||
</dees-form>
|
||||
`,
|
||||
validationFunc: async (stepperArg, elementArg) => {
|
||||
const deesForm = elementArg.querySelector('dees-form');
|
||||
deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Integrations',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-list
|
||||
key="integrations"
|
||||
label="Integrations in use"
|
||||
placeholder="Add integration"
|
||||
></dees-input-list>
|
||||
<dees-form-submit>Continue</dees-form-submit>
|
||||
</dees-form>
|
||||
`,
|
||||
validationFunc: async (stepperArg, elementArg) => {
|
||||
const deesForm = elementArg.querySelector('dees-form');
|
||||
deesForm.addEventListener('formData', () => stepperArg.goNext(), { once: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Review & Launch',
|
||||
content: html`
|
||||
<dees-panel>
|
||||
<p>Almost there! Review your selections and launch whenever you're ready.</p>
|
||||
</dees-panel>
|
||||
`,
|
||||
},
|
||||
] as const}
|
||||
></dees-stepper>
|
||||
<div style="position: absolute; inset: 0;">
|
||||
<div
|
||||
style="position: absolute; top: 16px; left: 50%; transform: translateX(-50%); z-index: 10;"
|
||||
>
|
||||
<dees-button
|
||||
@click=${async () => {
|
||||
await DeesStepper.createAndShow({ steps: cloneSteps() });
|
||||
}}
|
||||
>Open stepper as overlay</dees-button>
|
||||
</div>
|
||||
<dees-stepper .steps=${cloneSteps()}></dees-stepper>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as plugins from '../../00plugins.js';
|
||||
import * as colors from '../../00colors.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@@ -16,10 +15,17 @@ import {
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { stepperDemo } from './dees-stepper.demo.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import { cssGeistFontFamily } from '../../00fonts.js';
|
||||
import { zIndexRegistry } from '../../00zindex.js';
|
||||
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
|
||||
import { DeesModal } from '../../00group-overlay/dees-modal/dees-modal.js';
|
||||
import type { DeesForm } from '../../00group-form/dees-form/dees-form.js';
|
||||
import '../dees-tile/dees-tile.js';
|
||||
|
||||
export interface IStep {
|
||||
title: string;
|
||||
content: TemplateResult;
|
||||
menuOptions?: plugins.tsclass.website.IMenuItem<DeesStepper>[];
|
||||
validationFunc?: (stepper: DeesStepper, htmlElement: HTMLElement, signal?: AbortSignal) => Promise<any>;
|
||||
onReturnToStepFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise<any>;
|
||||
validationFuncCalled?: boolean;
|
||||
@@ -34,9 +40,33 @@ declare global {
|
||||
|
||||
@customElement('dees-stepper')
|
||||
export class DeesStepper extends DeesElement {
|
||||
// STATIC
|
||||
public static demo = stepperDemo;
|
||||
public static demoGroups = ['Layout', 'Form'];
|
||||
|
||||
public static async createAndShow(optionsArg: {
|
||||
steps: IStep[];
|
||||
cancelable?: boolean;
|
||||
}): Promise<DeesStepper> {
|
||||
const body = document.body;
|
||||
const stepper = new DeesStepper();
|
||||
stepper.steps = optionsArg.steps;
|
||||
stepper.overlay = true;
|
||||
if (optionsArg.cancelable !== undefined) {
|
||||
stepper.cancelable = optionsArg.cancelable;
|
||||
}
|
||||
stepper.windowLayer = await DeesWindowLayer.createAndShow({ blur: true });
|
||||
body.append(stepper.windowLayer);
|
||||
body.append(stepper);
|
||||
|
||||
// Get z-index for stepper (should be above window layer)
|
||||
stepper.stepperZIndex = zIndexRegistry.getNextZIndex();
|
||||
zIndexRegistry.register(stepper, stepper.stepperZIndex);
|
||||
|
||||
return stepper;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
type: Array,
|
||||
})
|
||||
@@ -47,6 +77,37 @@ export class DeesStepper extends DeesElement {
|
||||
})
|
||||
accessor selectedStep!: IStep;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
})
|
||||
accessor overlay: boolean = false;
|
||||
|
||||
/**
|
||||
* When true (default), the stepper renders a Cancel button in every step's
|
||||
* footer, and clicking the backdrop (overlay mode) triggers the same cancel
|
||||
* confirmation flow. Set to false for forced flows where the user must
|
||||
* complete the stepper — no Cancel button, no backdrop dismissal.
|
||||
*/
|
||||
@property({
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
})
|
||||
accessor cancelable: boolean = true;
|
||||
|
||||
@property({ type: Number, attribute: false })
|
||||
accessor stepperZIndex: number = 1000;
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
accessor activeForm: DeesForm | null = null;
|
||||
|
||||
@property({ type: Boolean, attribute: false })
|
||||
accessor activeFormValid: boolean = true;
|
||||
|
||||
private activeFormSubscription?: { unsubscribe: () => void };
|
||||
|
||||
private windowLayer?: DeesWindowLayer;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -60,7 +121,24 @@ export class DeesStepper extends DeesElement {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: ${cssGeistFontFamily};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
/*
|
||||
* In overlay mode the host is "transparent" to layout — the inner
|
||||
* .stepperContainer.overlay is what pins to the viewport and carries the
|
||||
* z-index. Keeping :host unpositioned avoids nesting the stacking context
|
||||
* under an auto-z-index parent (which was trapping .stepperContainer
|
||||
* below DeesWindowLayer's sibling layers). This mirrors how dees-modal
|
||||
* keeps its own :host unpositioned and lets .modalContainer drive layout.
|
||||
*/
|
||||
:host([overlay]) {
|
||||
position: static;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.stepperContainer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
@@ -68,101 +146,125 @@ export class DeesStepper extends DeesElement {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.step {
|
||||
.stepperContainer.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* Exit animation — reverse of the entry. Tiles slide DOWN + fade out,
|
||||
mirroring the .entrance slide-up. The transition override is needed
|
||||
because dees-tile.step has its own 0.7s transition for step selection
|
||||
which would otherwise make the exit sluggish. */
|
||||
.stepperContainer.predestroy dees-tile.step {
|
||||
transform: translateY(16px);
|
||||
filter: opacity(0);
|
||||
transition: transform 0.25s, filter 0.2s;
|
||||
}
|
||||
|
||||
dees-tile.step {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
transition: transform 0.7s cubic-bezier(0.87, 0, 0.13, 1), box-shadow 0.7s cubic-bezier(0.87, 0, 0.13, 1), filter 0.7s cubic-bezier(0.87, 0, 0.13, 1), border 0.7s cubic-bezier(0.87, 0, 0.13, 1);
|
||||
max-width: 500px;
|
||||
min-height: 300px;
|
||||
border-radius: 12px;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#0f0f11')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e2e8f0', '#272729')};
|
||||
color: ${cssManager.bdTheme('#0f172a', '#f5f5f5')};
|
||||
margin: auto;
|
||||
margin-bottom: 20px;
|
||||
filter: opacity(0.55) saturate(0.85);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
transition: transform 0.7s cubic-bezier(0.87, 0, 0.13, 1), filter 0.7s cubic-bezier(0.87, 0, 0.13, 1);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.step.selected {
|
||||
dees-tile.step.selected {
|
||||
pointer-events: all;
|
||||
filter: opacity(1) saturate(1);
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
.step.hiddenStep {
|
||||
dees-tile.step.hiddenStep {
|
||||
filter: opacity(0);
|
||||
}
|
||||
|
||||
.step.entrance {
|
||||
transition: transform 0.35s ease, box-shadow 0.35s ease, filter 0.35s ease, border 0.35s ease;
|
||||
dees-tile.step.entrance {
|
||||
transition: transform 0.35s ease, filter 0.35s ease;
|
||||
}
|
||||
|
||||
.step.entrance.hiddenStep {
|
||||
dees-tile.step.entrance.hiddenStep {
|
||||
transform: translateY(16px);
|
||||
}
|
||||
|
||||
.step:last-child {
|
||||
dees-tile.step:last-child {
|
||||
margin-bottom: 100vh;
|
||||
}
|
||||
|
||||
.step .stepCounter {
|
||||
color: ${cssManager.bdTheme('#64748b', '#a1a1aa')};
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
border-radius: 999px;
|
||||
background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.5)', 'rgba(63, 63, 70, 0.45)')};
|
||||
border: 1px solid ${cssManager.bdTheme('rgba(226, 232, 240, 0.7)', 'rgba(63, 63, 70, 0.6)')};
|
||||
.stepperContainer.overlay dees-tile.step::part(outer) {
|
||||
box-shadow:
|
||||
0 0 0 1px ${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 100% / 0.03)')},
|
||||
0 8px 40px ${cssManager.bdTheme('hsl(0 0% 0% / 0.12)', 'hsl(0 0% 0% / 0.5)')},
|
||||
0 2px 8px ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 0% / 0.25)')};
|
||||
}
|
||||
|
||||
.step .goBack {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
.step-header {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8px 0 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.goBack-spacer {
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.step-header .goBack {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 999px;
|
||||
border: 1px solid ${cssManager.bdTheme('rgba(226, 232, 240, 0.9)', 'rgba(63, 63, 70, 0.85)')};
|
||||
background: ${cssManager.bdTheme('rgba(255, 255, 255, 0.9)', 'rgba(39, 39, 42, 0.85)')};
|
||||
color: ${cssManager.bdTheme('#475569', '#d4d4d8')};
|
||||
line-height: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--dees-color-text-muted);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: border 0.2s ease, color 0.2s ease, background 0.2s ease, transform 0.2s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.step .goBack:hover {
|
||||
color: ${cssManager.bdTheme('#0f172a', '#fafafa')};
|
||||
border-color: ${cssManager.bdTheme(colors.dark.blue, colors.dark.blue)};
|
||||
background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.95)', 'rgba(63, 63, 70, 0.7)')};
|
||||
.step-header .goBack:hover {
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-secondary);
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
.step .goBack:active {
|
||||
color: ${cssManager.bdTheme('#0f172a', '#fafafa')};
|
||||
border-color: ${cssManager.bdTheme(colors.dark.blueActive, colors.dark.blueActive)};
|
||||
background: ${cssManager.bdTheme('rgba(226, 232, 240, 0.85)', 'rgba(63, 63, 70, 0.6)')};
|
||||
.step-header .goBack:active {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 15%)')};
|
||||
}
|
||||
|
||||
.step .goBack span {
|
||||
.step-header .goBack span {
|
||||
transition: transform 0.2s ease;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.step .goBack:hover span {
|
||||
.step-header .goBack:hover span {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
.step .title {
|
||||
.step-header .stepCounter {
|
||||
color: var(--dees-color-text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.step-body .title {
|
||||
text-align: center;
|
||||
padding-top: 64px;
|
||||
padding-top: 32px;
|
||||
font-family: 'Geist Sans', sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
@@ -170,35 +272,149 @@ export class DeesStepper extends DeesElement {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.step .content {
|
||||
.step-body .content {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
/* --- Footer: modal-style bottom buttons --- */
|
||||
.bottomButtons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton {
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.15s ease;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--dees-color-text-muted);
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Border-left separator on every button EXCEPT the first one.
|
||||
Uses general sibling so the stepHint (if rendered on the left) does
|
||||
not shift which button counts as "first" and create a phantom border. */
|
||||
.bottomButtons .bottomButton ~ .bottomButton {
|
||||
border-left: 1px solid var(--dees-color-border-subtle);
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton:hover {
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton:active {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(0 0% 13%)')};
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton.primary {
|
||||
color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton.primary:hover {
|
||||
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.08)', 'hsl(213.1 93.9% 67.8% / 0.08)')};
|
||||
color: ${cssManager.bdTheme('hsl(217.2 91.2% 50%)', 'hsl(213.1 93.9% 75%)')};
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton.primary:active {
|
||||
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.12)', 'hsl(213.1 93.9% 67.8% / 0.12)')};
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton.disabled:hover {
|
||||
background: transparent;
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
/* Hint shown on the left of the footer when the active step's form has
|
||||
unfilled required fields. Uses margin-right: auto to push right-aligned
|
||||
buttons to the right while keeping the hint flush-left. */
|
||||
.bottomButtons .stepHint {
|
||||
margin-right: auto;
|
||||
padding: 0 16px;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--dees-color-text-muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<div class="stepperContainer">
|
||||
${this.steps.map(
|
||||
(stepArg) =>
|
||||
html`<div
|
||||
class="step ${stepArg === this.selectedStep
|
||||
? 'selected'
|
||||
: null} ${this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep)
|
||||
? 'hiddenStep'
|
||||
: ''} ${this.getIndexOfStep(stepArg) === 0 ? 'entrance' : ''}"
|
||||
>
|
||||
${this.getIndexOfStep(stepArg) > 0
|
||||
? html`<div class="goBack" @click=${this.goBack}><span style="font-family: Inter"><-</span> go to previous step</div>`
|
||||
: ``}
|
||||
<div
|
||||
class="stepperContainer ${this.overlay ? 'overlay' : ''}"
|
||||
style=${this.overlay ? `z-index: ${this.stepperZIndex};` : ''}
|
||||
@click=${this.handleOutsideClick}
|
||||
>
|
||||
${this.steps.map((stepArg, stepIndex) => {
|
||||
const isSelected = stepArg === this.selectedStep;
|
||||
const isHidden =
|
||||
this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep);
|
||||
const isFirst = stepIndex === 0;
|
||||
return html`<dees-tile
|
||||
class="step ${isSelected ? 'selected' : ''} ${isHidden ? 'hiddenStep' : ''} ${isFirst ? 'entrance' : ''}"
|
||||
>
|
||||
<div slot="header" class="step-header">
|
||||
${!isFirst
|
||||
? html`<div class="goBack" @click=${this.goBack}>
|
||||
<span style="font-family: Inter"><-</span> go to previous step
|
||||
</div>`
|
||||
: html`<div class="goBack-spacer"></div>`}
|
||||
<div class="stepCounter">
|
||||
Step ${this.steps.findIndex((elementArg) => elementArg === stepArg) + 1} of
|
||||
${this.steps.length}
|
||||
Step ${stepIndex + 1} of ${this.steps.length}
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-body">
|
||||
<div class="title">${stepArg.title}</div>
|
||||
<div class="content">${stepArg.content}</div>
|
||||
</div> `
|
||||
)}
|
||||
</div>
|
||||
<div slot="footer" class="bottomButtons">
|
||||
${isSelected && this.activeForm !== null && !this.activeFormValid
|
||||
? html`<div class="stepHint">Complete form to continue</div>`
|
||||
: ''}
|
||||
${this.cancelable
|
||||
? html`<div
|
||||
class="bottomButton"
|
||||
@click=${() => this.handleCancelRequest()}
|
||||
>Cancel</div>`
|
||||
: ''}
|
||||
${stepArg.menuOptions?.map((actionArg, actionIndex) => {
|
||||
const isPrimary = actionIndex === stepArg.menuOptions!.length - 1;
|
||||
const isDisabled = isPrimary && this.activeForm !== null && !this.activeFormValid;
|
||||
return html`
|
||||
<div
|
||||
class="bottomButton ${isPrimary ? 'primary' : ''} ${isDisabled ? 'disabled' : ''}"
|
||||
@click=${() => this.handleMenuOptionClick(actionArg, isPrimary)}
|
||||
>${actionArg.name}</div>
|
||||
`;
|
||||
}) ?? ''}
|
||||
</div>
|
||||
</dees-tile>`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -230,6 +446,7 @@ export class DeesStepper extends DeesElement {
|
||||
if (!selectedStepElement) {
|
||||
return;
|
||||
}
|
||||
this.scanActiveForm(selectedStepElement);
|
||||
if (!stepperContainer.style.paddingTop) {
|
||||
stepperContainer.style.paddingTop = `${
|
||||
stepperContainer.offsetHeight / 2 - selectedStepElement.offsetHeight / 2
|
||||
@@ -296,4 +513,165 @@ export class DeesStepper extends DeesElement {
|
||||
nextStep.validationFuncCalled = false;
|
||||
this.selectedStep = nextStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the currently selected step for a <dees-form> in its content. When
|
||||
* found, subscribes to the form's RxJS changeSubject so the primary
|
||||
* menuOption button can auto-enable/disable as required fields are filled.
|
||||
*
|
||||
* If the form reference is the same as the previous activation (e.g. on a
|
||||
* same-step re-render), we just recompute validity without re-subscribing.
|
||||
*/
|
||||
private scanActiveForm(selectedStepElement: HTMLElement) {
|
||||
const form = selectedStepElement.querySelector('dees-form') as DeesForm | null;
|
||||
if (form === this.activeForm) {
|
||||
this.recomputeFormValid();
|
||||
return;
|
||||
}
|
||||
this.activeFormSubscription?.unsubscribe();
|
||||
this.activeFormSubscription = undefined;
|
||||
this.activeForm = form;
|
||||
if (!form) {
|
||||
this.activeFormValid = true;
|
||||
return;
|
||||
}
|
||||
// Initial check before subscribing, in case the form's firstUpdated fires
|
||||
// synchronously between scan and subscribe.
|
||||
this.recomputeFormValid();
|
||||
this.activeFormSubscription = form.changeSubject.subscribe(() => {
|
||||
this.recomputeFormValid();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recomputes activeFormValid by checking every required field in the active
|
||||
* form for a non-empty value. Mirrors dees-form.updateRequiredStatus's logic
|
||||
* but stores the result on the stepper instead of mutating a submit button.
|
||||
*/
|
||||
private recomputeFormValid() {
|
||||
const form = this.activeForm;
|
||||
if (!form) {
|
||||
this.activeFormValid = true;
|
||||
return;
|
||||
}
|
||||
const fields = form.getFormElements();
|
||||
this.activeFormValid = fields.every(
|
||||
(field) => !field.required || (field.value !== null && field.value !== undefined && field.value !== ''),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for menuOption buttons in the footer. For the primary (last)
|
||||
* button, if an active form is present, gates on required-field validity and
|
||||
* triggers the form's gatherAndDispatch() before running the action. The
|
||||
* action is awaited so any async work (e.g. goNext → scroll animation)
|
||||
* completes before the click handler returns.
|
||||
*/
|
||||
private async handleMenuOptionClick(
|
||||
optionArg: plugins.tsclass.website.IMenuItem<DeesStepper>,
|
||||
isPrimary: boolean,
|
||||
) {
|
||||
const form = this.activeForm;
|
||||
if (isPrimary && form) {
|
||||
if (!this.activeFormValid) return;
|
||||
await new Promise<void>((resolve) => {
|
||||
form.addEventListener('formData', () => resolve(), { once: true });
|
||||
form.gatherAndDispatch();
|
||||
});
|
||||
}
|
||||
await optionArg.action(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently-open confirmation modal (if any). Prevents double-stacking when
|
||||
* the user clicks the backdrop or the Cancel button while a confirm modal
|
||||
* is already visible. The reference may become stale (point to a destroyed
|
||||
* modal) if the user dismisses the confirm modal by clicking its own
|
||||
* backdrop — so handleCancelRequest() uses isConnected to detect that.
|
||||
*/
|
||||
private cancelConfirmModal?: DeesModal;
|
||||
|
||||
/**
|
||||
* Click handler on .stepperContainer. Mirrors dees-modal.handleOutsideClick:
|
||||
* when the user clicks the empty backdrop area (target === stepperContainer,
|
||||
* not any descendant tile), trigger the cancel confirmation flow. Clicks
|
||||
* that originate inside a step tile have a different event.target and are
|
||||
* ignored here.
|
||||
*/
|
||||
private handleOutsideClick(eventArg: MouseEvent) {
|
||||
if (!this.overlay) return;
|
||||
if (!this.cancelable) return;
|
||||
eventArg.stopPropagation();
|
||||
const stepperContainer = this.shadowRoot!.querySelector('.stepperContainer');
|
||||
if (eventArg.target === stepperContainer) {
|
||||
this.handleCancelRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shown by both the backdrop click and the Cancel button in the footer.
|
||||
* Presents a dees-modal asking the user to confirm cancellation. If they
|
||||
* confirm, the stepper and window layer are destroyed; otherwise the
|
||||
* confirm modal is dismissed and the stepper stays open.
|
||||
*
|
||||
* The isConnected check on the cached reference handles the case where the
|
||||
* user dismissed the previous confirm modal by clicking ITS OWN backdrop —
|
||||
* dees-modal.handleOutsideClick calls destroy() directly, bypassing our
|
||||
* action callbacks, so our cached reference would be stale without this
|
||||
* fallback check.
|
||||
*/
|
||||
public async handleCancelRequest() {
|
||||
if (!this.cancelable) return;
|
||||
if (this.cancelConfirmModal && this.cancelConfirmModal.isConnected) return;
|
||||
this.cancelConfirmModal = undefined;
|
||||
this.cancelConfirmModal = await DeesModal.createAndShow({
|
||||
heading: 'Cancel setup?',
|
||||
width: 'small',
|
||||
content: html`
|
||||
<p style="margin: 0;">
|
||||
Are you sure you want to cancel? Any progress on the current step will be lost.
|
||||
</p>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Continue setup',
|
||||
action: async (modal) => {
|
||||
this.cancelConfirmModal = undefined;
|
||||
await modal!.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Yes, cancel',
|
||||
action: async (modal) => {
|
||||
this.cancelConfirmModal = undefined;
|
||||
modal!.destroy(); // fire-and-forget — starts the confirm modal fade
|
||||
const domtools = await this.domtoolsPromise;
|
||||
await domtools.convenience.smartdelay.delayFor(100);
|
||||
await this.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public async destroy() {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const container = this.shadowRoot!.querySelector('.stepperContainer');
|
||||
container?.classList.add('predestroy');
|
||||
await domtools.convenience.smartdelay.delayFor(250);
|
||||
if (this.parentElement) {
|
||||
this.parentElement.removeChild(this);
|
||||
}
|
||||
if (this.windowLayer) {
|
||||
await this.windowLayer.destroy();
|
||||
}
|
||||
|
||||
// Tear down form subscription to avoid leaks when the overlay closes.
|
||||
this.activeFormSubscription?.unsubscribe();
|
||||
this.activeFormSubscription = undefined;
|
||||
this.activeForm = null;
|
||||
|
||||
// Unregister from z-index registry
|
||||
zIndexRegistry.unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ export class DeesTile extends DeesElement {
|
||||
@property({ type: String })
|
||||
accessor heading: string = '';
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
accessor overscroll: 'contain' | 'auto' | 'none' = 'auto';
|
||||
|
||||
@state()
|
||||
accessor hasFooter: boolean = false;
|
||||
|
||||
@@ -87,14 +90,27 @@ export class DeesTile extends DeesElement {
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
/* --- Content: the rounded inset --- */
|
||||
/* --- Content: the rounded inset ---
|
||||
Uses overflow-y: auto so that when a consumer (e.g. dees-modal) caps
|
||||
the tile with max-height, long content scrolls inside the tile
|
||||
instead of being clipped. For consumers without max-height
|
||||
(e.g. dees-stepper), the tile grows with content and the scroll
|
||||
never activates. Horizontal overflow stays clipped to preserve the
|
||||
rounded corners. */
|
||||
.tile-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border-top: 1px solid var(--dees-color-border-subtle);
|
||||
border-bottom: 1px solid var(--dees-color-border-subtle);
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
||||
}
|
||||
|
||||
:host([overscroll="contain"]) .tile-content {
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.tile-content.no-footer {
|
||||
|
||||
@@ -352,5 +352,80 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}}>Test Responsive</dees-button>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Scrollable Content</h3>
|
||||
<p>When content exceeds the modal's max-height (<code>calc(100vh - 80px)</code>), the tile caps at that height and the content area scrolls inside. The heading and bottom buttons stay pinned.</p>
|
||||
<div class="button-grid">
|
||||
<dees-button @click=${() => {
|
||||
DeesModal.createAndShow({
|
||||
heading: 'Long Article',
|
||||
width: 'medium',
|
||||
content: html`
|
||||
<h4 style="margin-top: 0;">Lorem ipsum dolor sit amet</h4>
|
||||
${Array.from({ length: 40 }, (_, i) => html`
|
||||
<p>
|
||||
<strong>§ ${i + 1}.</strong>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
|
||||
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
||||
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
||||
sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
`)}
|
||||
`,
|
||||
menuOptions: [{
|
||||
name: 'Cancel',
|
||||
action: async (modal) => modal!.destroy()
|
||||
}, {
|
||||
name: 'Accept',
|
||||
action: async (modal) => modal!.destroy()
|
||||
}],
|
||||
});
|
||||
}}>Long Article</dees-button>
|
||||
|
||||
<dees-button @click=${() => {
|
||||
DeesModal.createAndShow({
|
||||
heading: 'Long List',
|
||||
width: 'small',
|
||||
content: html`
|
||||
<p>Selected items:</p>
|
||||
<ul style="padding-left: 20px; margin: 0;">
|
||||
${Array.from({ length: 80 }, (_, i) => html`
|
||||
<li style="padding: 4px 0;">Item ${i + 1} — option label</li>
|
||||
`)}
|
||||
</ul>
|
||||
`,
|
||||
menuOptions: [{
|
||||
name: 'Done',
|
||||
action: async (modal) => modal!.destroy()
|
||||
}],
|
||||
});
|
||||
}}>Long List</dees-button>
|
||||
|
||||
<dees-button @click=${() => {
|
||||
DeesModal.createAndShow({
|
||||
heading: 'Tall Form',
|
||||
width: 'medium',
|
||||
content: html`
|
||||
<dees-form>
|
||||
${Array.from({ length: 25 }, (_, i) => html`
|
||||
<dees-input-text .label=${`Field ${i + 1}`}></dees-input-text>
|
||||
`)}
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [{
|
||||
name: 'Cancel',
|
||||
action: async (modal) => modal!.destroy()
|
||||
}, {
|
||||
name: 'Submit',
|
||||
action: async (modal) => modal!.destroy()
|
||||
}],
|
||||
});
|
||||
}}>Tall Form</dees-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -268,18 +268,9 @@ export class DeesModal extends DeesElement {
|
||||
}
|
||||
|
||||
.heading .header-button dees-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
||||
}
|
||||
.bottomButtons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -352,7 +343,7 @@ export class DeesModal extends DeesElement {
|
||||
${minWidthStyle ? `dees-tile { min-width: ${minWidthStyle}; }` : ''}
|
||||
</style>
|
||||
<div class="modalContainer" @click=${this.handleOutsideClick} style="z-index: ${this.modalZIndex}">
|
||||
<dees-tile class="${widthClass} ${mobileFullscreenClass}">
|
||||
<dees-tile class="${widthClass} ${mobileFullscreenClass}" .overscroll=${'contain'}>
|
||||
<div slot="header" class="heading">
|
||||
<div class="heading-text">${this.heading}</div>
|
||||
<div class="header-buttons">
|
||||
|
||||
@@ -353,12 +353,35 @@ export const demoFunc = () => html`
|
||||
name: 'Analytics',
|
||||
iconName: 'lucide:lineChart',
|
||||
element: DemoViewAnalytics,
|
||||
subViews: [
|
||||
{
|
||||
name: 'Overview',
|
||||
iconName: 'lucide:activity',
|
||||
element: DemoViewAnalytics,
|
||||
},
|
||||
{
|
||||
name: 'Reports',
|
||||
iconName: 'lucide:fileText',
|
||||
element: DemoViewDashboard,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Settings',
|
||||
iconName: 'lucide:settings',
|
||||
element: DemoViewSettings,
|
||||
}
|
||||
subViews: [
|
||||
{
|
||||
name: 'Profile',
|
||||
iconName: 'lucide:user',
|
||||
element: DemoViewSettings,
|
||||
},
|
||||
{
|
||||
name: 'Billing',
|
||||
iconName: 'lucide:creditCard',
|
||||
element: DemoViewSettings,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as IView[]}
|
||||
@logout=${() => {
|
||||
console.log('Logout event triggered');
|
||||
|
||||
@@ -26,7 +26,8 @@ declare global {
|
||||
export interface IView {
|
||||
name: string;
|
||||
iconName?: string;
|
||||
element: DeesElement['constructor']['prototype'];
|
||||
element?: DeesElement['constructor']['prototype'];
|
||||
subViews?: IView[];
|
||||
}
|
||||
|
||||
export type TGlobalMessageType = 'info' | 'success' | 'warning' | 'error';
|
||||
@@ -250,6 +251,78 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.viewTab .chevron {
|
||||
flex: 0 0 auto;
|
||||
font-size: 14px;
|
||||
opacity: 0.5;
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 0.2s ease, opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.viewTab.hasSubs:hover .chevron {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.viewTab.hasSubs.groupActive .chevron {
|
||||
transform: rotate(0deg);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.subViews {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
margin-left: 12px;
|
||||
position: relative;
|
||||
transition:
|
||||
grid-template-rows 0.25s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
margin-top 0.25s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
margin-bottom 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.subViews.expanded {
|
||||
grid-template-rows: 1fr;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.subViews::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 1px;
|
||||
background: var(--dees-color-border-default);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.subViews.expanded::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.subViews-inner {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.viewTab.sub {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.viewTab.sub dees-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.viewTab.sub.selected::before {
|
||||
left: -12px;
|
||||
}
|
||||
|
||||
.appActions {
|
||||
padding: 12px 8px;
|
||||
border-top: 1px solid var(--dees-color-border-default);
|
||||
@@ -563,10 +636,12 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
<div class="viewTabs-container">
|
||||
<div class="section-label">Navigation</div>
|
||||
<div class="viewTabs">
|
||||
${this.viewTabs.map(
|
||||
(view) => html`
|
||||
${this.viewTabs.map((view) => {
|
||||
const hasSubs = !!view.subViews?.length;
|
||||
const groupActive = hasSubs && this.isGroupActive(view);
|
||||
return html`
|
||||
<div
|
||||
class="viewTab ${this.selectedView === view ? 'selected' : ''}"
|
||||
class="viewTab ${this.selectedView === view ? 'selected' : ''} ${hasSubs ? 'hasSubs' : ''} ${groupActive ? 'groupActive' : ''}"
|
||||
@click=${() => this.loadView(view)}
|
||||
>
|
||||
${view.iconName ? html`
|
||||
@@ -575,9 +650,39 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
<dees-icon .icon="${'lucide:file'}"></dees-icon>
|
||||
`}
|
||||
<span>${view.name}</span>
|
||||
${hasSubs ? html`
|
||||
<dees-icon class="chevron" .icon="${'lucide:chevronDown'}"></dees-icon>
|
||||
` : ''}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
${hasSubs ? html`
|
||||
<div
|
||||
class="subViews ${groupActive ? 'expanded' : ''}"
|
||||
?inert=${!groupActive}
|
||||
>
|
||||
<div class="subViews-inner">
|
||||
${view.subViews!.map(
|
||||
(sub) => html`
|
||||
<div
|
||||
class="viewTab sub ${this.selectedView === sub ? 'selected' : ''}"
|
||||
@click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.loadView(sub);
|
||||
}}
|
||||
>
|
||||
${sub.iconName ? html`
|
||||
<dees-icon .icon="${sub.iconName.includes(':') ? sub.iconName : `lucide:${sub.iconName}`}"></dees-icon>
|
||||
` : html`
|
||||
<dees-icon .icon="${'lucide:dot'}"></dees-icon>
|
||||
`}
|
||||
<span>${sub.name}</span>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div class="appActions">
|
||||
@@ -771,8 +876,23 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
}
|
||||
|
||||
|
||||
private isGroupActive(view: IView): boolean {
|
||||
if (this.selectedView === view) return true;
|
||||
return view.subViews?.some((sv) => sv === this.selectedView) ?? false;
|
||||
}
|
||||
|
||||
private currentView!: DeesElement;
|
||||
public async loadView(viewArg: IView) {
|
||||
// Group-only parent: resolve to first sub view with an element
|
||||
if (!viewArg.element && viewArg.subViews?.length) {
|
||||
const firstNavigable = viewArg.subViews.find((sv) => sv.element);
|
||||
if (firstNavigable) {
|
||||
return this.loadView(firstNavigable);
|
||||
}
|
||||
return; // nothing navigable — ignore click
|
||||
}
|
||||
if (!viewArg.element) return; // safety: no element and no subs → no-op
|
||||
|
||||
const appcontent = this.shadowRoot!.querySelector('.appcontent')!;
|
||||
const view = new viewArg.element();
|
||||
if (this.currentView) {
|
||||
@@ -781,7 +901,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
appcontent.appendChild(view);
|
||||
this.currentView = view;
|
||||
this.selectedView = viewArg;
|
||||
|
||||
|
||||
// Emit view-select event
|
||||
this.dispatchEvent(new CustomEvent('view-select', {
|
||||
detail: { view: viewArg },
|
||||
|
||||
Reference in New Issue
Block a user