@veritree/ui 0.27.0-2 → 0.27.0-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veritree/ui",
3
- "version": "0.27.0-2",
3
+ "version": "0.27.0-3",
4
4
  "description": "veritree ui library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,11 +11,8 @@ export default {
11
11
  provide() {
12
12
  return {
13
13
  apiDisclosure: () => {
14
- const details = this.details;
15
- const contents = this.contents;
16
14
  const accordion = this.accordion;
17
- const headers = this.headers;
18
- const icons = this.icons;
15
+ const details = this.details;
19
16
 
20
17
  const register = (objKey, item) => {
21
18
  if (!item) return;
@@ -28,11 +25,8 @@ export default {
28
25
  };
29
26
 
30
27
  return {
31
- details,
32
- contents,
33
- headers,
34
- icons,
35
28
  accordion,
29
+ details,
36
30
  register,
37
31
  unregister,
38
32
  };
@@ -50,9 +44,6 @@ export default {
50
44
  data() {
51
45
  return {
52
46
  details: [],
53
- contents: [],
54
- headers: [],
55
- icons: [],
56
47
  };
57
48
  },
58
49
  };
@@ -4,12 +4,19 @@
4
4
  :class="[
5
5
  headless
6
6
  ? 'details-content'
7
- : 'overflow-hidden transition-[height] duration-300 ease-linear',
8
- invisible ? 'absolute opacity-0' : 'h-[var(--height)]',
7
+ : 'grid transition-[grid-template-rows] duration-300 ease-linear',
8
+ headless
9
+ ? isVisible
10
+ ? 'details-content--expanded'
11
+ : null
12
+ : isVisible
13
+ ? 'grid-rows-[1fr]'
14
+ : 'grid-rows-[0fr]',
9
15
  ]"
10
- :style="`--height: ${height};`"
11
16
  >
12
- <slot></slot>
17
+ <div :class="[headless ? 'detail-content__overflow' : 'overflow-hidden']">
18
+ <slot></slot>
19
+ </div>
13
20
  </div>
14
21
  </template>
15
22
 
@@ -24,14 +31,15 @@ export default {
24
31
  type: Boolean,
25
32
  default: false,
26
33
  },
34
+ open: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
27
38
  },
28
39
 
29
40
  data() {
30
41
  return {
31
42
  idGroup: this.apiDetails().idGroup,
32
- expanded: false,
33
- expandedHeight: null,
34
- invisible: false,
35
43
  };
36
44
  },
37
45
 
@@ -40,56 +48,22 @@ export default {
40
48
  return `disclosure-content-${this.idGroup}`;
41
49
  },
42
50
 
43
- height() {
44
- return this.expanded ? this.expandedHeight : 0;
51
+ isVisible() {
52
+ return this.apiDetails().isVisible();
45
53
  },
46
54
  },
47
55
 
48
- mounted() {
49
- this.handleVisibleHeight();
50
-
51
- const content = {
52
- id: this.id,
53
- idGroup: this.idGroup,
54
- collapse: this.collapse,
55
- expand: this.expand,
56
- isVisible: this.isVisible,
57
- };
58
-
59
- this.apiDisclosure().register('contents', content);
60
- },
61
-
62
- beforeUnmount() {
63
- this.apiDisclosure().unregister('contents', this.id);
64
- },
65
-
66
- methods: {
67
- handleVisibleHeight() {
68
- // make panel position absolute and opacity 0,
69
- // to have it fully opened but not visible
70
- this.invisible = true;
71
-
72
- setTimeout(() => {
73
- // get computed height now since its fully opened
74
- // this height will be used by a css custom property to enable the transition
75
- this.expandedHeight = window.getComputedStyle(this.$el).height;
76
-
77
- // make invisible false once the full height is set
78
- this.invisible = false;
79
- }, 500);
80
- },
81
-
82
- expand() {
83
- this.expanded = true;
84
- },
85
-
86
- collapse() {
87
- this.expanded = false;
56
+ watch: {
57
+ open(value) {
58
+ this.apiDetails().setVisible(value);
59
+ this.expand = value;
88
60
  },
61
+ },
89
62
 
90
- isVisible() {
91
- return this.expanded;
92
- },
63
+ mounted() {
64
+ if (this.open) {
65
+ this.apiDetails().setVisible(this.open);
66
+ }
93
67
  },
94
68
  };
95
69
  </script>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  :class="[
4
- headless ? 'disclosure-details' : 'group relative',
4
+ headless ? 'disclosure-details' : 'relative',
5
5
  headless ? (visible ? 'is-open' : null) : null,
6
6
  ]"
7
7
  >
@@ -26,6 +26,7 @@ export default {
26
26
  const idContent = `details-content-${idGroup}`;
27
27
 
28
28
  const isVisible = () => this.isVisible();
29
+ const setVisible = (visible) => this.setVisible(visible);
29
30
 
30
31
  return {
31
32
  idGroup,
@@ -33,6 +34,8 @@ export default {
33
34
  idIcon,
34
35
  idContent,
35
36
  isVisible,
37
+ setVisible,
38
+ visible: this.visible,
36
39
  };
37
40
  },
38
41
  };
@@ -52,6 +55,22 @@ export default {
52
55
  };
53
56
  },
54
57
 
58
+ watch: {
59
+ visible(value) {
60
+ if (value && this.apiDisclosure().accordion) {
61
+ this.apiDisclosure().details.forEach((detail) => {
62
+ if (detail.idGroup !== this.idGroup) {
63
+ detail.hide();
64
+ }
65
+
66
+ if (detail.idGroup === this.idGroup) {
67
+ detail.show();
68
+ }
69
+ });
70
+ }
71
+ },
72
+ },
73
+
55
74
  mounted() {
56
75
  const detail = {
57
76
  idGroup: this.idGroup,
@@ -63,22 +82,28 @@ export default {
63
82
  this.apiDisclosure().register('details', detail);
64
83
  },
65
84
 
66
- beforeUnmount() {
85
+ beforeDestroy() {
67
86
  this.apiDisclosure().unregister('details', this.id);
68
87
  },
69
88
 
70
89
  methods: {
71
90
  show() {
91
+ this.$emit('toggle', true);
72
92
  this.visible = true;
73
93
  },
74
94
 
75
95
  hide() {
96
+ this.$emit('toggle', false);
76
97
  this.visible = false;
77
98
  },
78
99
 
79
100
  isVisible() {
80
101
  return this.visible;
81
102
  },
103
+
104
+ setVisible(visible) {
105
+ this.visible = visible;
106
+ },
82
107
  },
83
108
  };
84
- </script>
109
+ </script>
@@ -1,17 +1,11 @@
1
1
  <template>
2
2
  <header
3
3
  :id="id"
4
- :class="[
5
- headless
6
- ? 'details-header'
7
- : 'text-body flex cursor-pointer justify-between gap-3 font-semibold',
8
- ]"
9
- :aria-controls="ariaControls"
10
- :aria-expanded="String(ariaExpanded)"
11
- role="button"
12
- tabindex="0"
13
- @click.prevent="toggle"
14
- @keydown.enter="toggle"
4
+ :class="headerClasses"
5
+ v-bind="ariaAttributes"
6
+ @click.prevent="handleClick"
7
+ @keydown.enter.prevent="handleKeydown"
8
+ @keydown.space.prevent="handleKeydown"
15
9
  >
16
10
  <slot></slot>
17
11
  </header>
@@ -24,112 +18,85 @@ export default {
24
18
  inject: ['apiDisclosure', 'apiDetails'],
25
19
 
26
20
  props: {
21
+ /**
22
+ * If true, the header will not toggle visibility of the content.
23
+ * Useful for headers that are purely informational.
24
+ */
25
+ actionless: {
26
+ type: Boolean,
27
+ default: false,
28
+ },
29
+ /**
30
+ * If true, the header will not have any visual styles applied.
31
+ * Useful for headless implementations where you want to style it yourself.
32
+ */
27
33
  headless: {
28
34
  type: Boolean,
29
35
  default: false,
30
36
  },
31
37
  },
32
38
 
33
- data() {
34
- return {
35
- ariaExpanded: false,
36
- };
37
- },
38
-
39
39
  computed: {
40
- id() {
41
- return this.apiDetails().idSummary;
42
- },
40
+ headerClasses() {
41
+ if (this.headless) {
42
+ return 'details-header';
43
+ }
43
44
 
44
- idGroup() {
45
- return this.apiDetails().idGroup;
46
- },
45
+ const classes = ['flex justify-between gap-3 text-body font-semibold'];
46
+ classes.push(this.actionless ? 'cursor-default' : 'cursor-pointer');
47
47
 
48
- ariaControls() {
49
- return this.apiDetails().idContent;
48
+ return classes;
50
49
  },
51
50
 
52
- details() {
53
- return this.apiDisclosure().details;
51
+ ariaAttributes() {
52
+ if (this.actionless) {
53
+ return {};
54
+ }
55
+
56
+ return {
57
+ 'aria-controls': this.ariaControls,
58
+ 'aria-expanded': this.ariaExpanded,
59
+ 'role': 'button',
60
+ 'tabindex': '0',
61
+ };
54
62
  },
55
63
 
56
- headers() {
57
- return this.apiDisclosure().headers;
64
+ id() {
65
+ return this.apiDetails().idSummary;
58
66
  },
59
67
 
60
- icons() {
61
- return this.apiDisclosure().icons;
68
+ ariaControls() {
69
+ return this.apiDetails().idContent;
62
70
  },
63
71
 
64
- contents() {
65
- return this.apiDisclosure().contents;
72
+ isVisible() {
73
+ return this.apiDetails().visible;
66
74
  },
67
- },
68
-
69
- mounted() {
70
- this.isExpanded();
71
-
72
- const header = {
73
- id: this.id,
74
- idGroup: this.idGroup,
75
- isExpanded: this.isExpanded,
76
- };
77
-
78
- this.apiDisclosure().register('headers', header);
79
- },
80
75
 
81
- beforeUnmount() {
82
- this.apiDisclosure().unregister('headers', this.id);
76
+ ariaExpanded() {
77
+ return String(this.isVisible);
78
+ },
83
79
  },
84
80
 
85
81
  methods: {
86
82
  toggle() {
87
- this.toggleDetails();
88
- this.toggleHeaders();
89
- this.toggleContents();
90
- this.toggleIcons();
91
- },
92
-
93
- isExpanded() {
94
- this.ariaExpanded = this.apiDetails().isVisible();
95
- },
96
-
97
- toggleDetails() {
98
- this.details.forEach((detail) => {
99
- const isSameGroup = detail.idGroup === this.idGroup;
100
- const isAccordion = this.apiDisclosure().accordion;
101
-
102
- if (isSameGroup) {
103
- detail.isVisible() ? detail.hide() : detail.show();
104
- }
105
-
106
- if (isAccordion && !isSameGroup) {
107
- detail.hide();
108
- }
109
- });
110
- },
111
-
112
- toggleContents() {
113
- this.contents.forEach((content) => {
114
- const isSameGroup = content.idGroup === this.idGroup;
115
- const isAccordion = this.apiDisclosure().accordion;
116
-
117
- if (isSameGroup) {
118
- content.isVisible() ? content.collapse() : content.expand();
119
- }
83
+ if (this.actionless) {
84
+ return;
85
+ }
120
86
 
121
- if (isAccordion && !isSameGroup) {
122
- content.collapse();
123
- }
124
- });
87
+ this.apiDetails().setVisible(!this.isVisible);
125
88
  },
126
89
 
127
- toggleHeaders() {
128
- this.headers.forEach((summary) => summary.isExpanded());
90
+ handleClick() {
91
+ if (!this.actionless) {
92
+ this.toggle();
93
+ }
129
94
  },
130
95
 
131
- toggleIcons() {
132
- this.icons.forEach((icon) => icon.isExpanded());
96
+ handleKeydown() {
97
+ if (!this.actionless) {
98
+ this.toggle();
99
+ }
133
100
  },
134
101
  },
135
102
  };
@@ -1,12 +1,14 @@
1
1
  <template>
2
- <span
3
- :class="[
4
- headless ? 'disclosure-icon' : 'shrink-0 transition-all',
5
- headless ? null : expanded ? 'rotate-180' : 'rotate-0',
6
- ]"
2
+ <component
3
+ :is="as"
4
+ :class="iconClasses"
5
+ v-bind="ariaAttributes"
6
+ @click.stop="onClick"
7
+ @keydown.enter="onClick"
8
+ @keydown.space.prevent="onClick"
7
9
  >
8
10
  <slot><IconChevronDown /></slot>
9
- </span>
11
+ </component>
10
12
  </template>
11
13
 
12
14
  <script>
@@ -20,43 +22,52 @@ export default {
20
22
  type: Boolean,
21
23
  default: false,
22
24
  },
23
- },
24
-
25
- data() {
26
- return {
27
- expanded: false,
28
- };
25
+ as: {
26
+ type: String,
27
+ default: 'span',
28
+ validator(value) {
29
+ return ['span', 'button', 'div', 'a'].includes(value);
30
+ },
31
+ },
29
32
  },
30
33
 
31
34
  computed: {
32
- id() {
33
- return this.apiDetails().idIcon;
34
- },
35
+ ariaAttributes() {
36
+ if (this.as !== 'button') {
37
+ return {};
38
+ }
35
39
 
36
- idGroup() {
37
- return this.apiDetails().idGroup;
40
+ return {
41
+ 'aria-expanded': String(this.expanded),
42
+ 'aria-controls': this.apiDetails().idContent,
43
+ };
38
44
  },
39
- },
40
45
 
41
- mounted() {
42
- this.isExpanded();
46
+ iconClasses() {
47
+ const classes = [];
43
48
 
44
- const icon = {
45
- id: this.id,
46
- idGroup: this.idGroup,
47
- isExpanded: this.isExpanded,
48
- };
49
+ if (this.headless) {
50
+ classes.push('disclosure-icon');
51
+ } else {
52
+ classes.push('shrink-0 transition-all');
53
+ classes.push(this.expanded ? 'rotate-180' : 'rotate-0');
54
+ classes.push(this.as === 'button' ? null : 'pointer-events-none');
55
+ }
49
56
 
50
- this.apiDisclosure().register('icons', icon);
51
- },
57
+ return classes.filter(Boolean);
58
+ },
52
59
 
53
- beforeUnmount() {
54
- this.apiDisclosure().unregister('icons', this.id);
60
+ expanded() {
61
+ return this.apiDetails().visible;
62
+ },
55
63
  },
56
64
 
57
65
  methods: {
58
- isExpanded() {
59
- this.expanded = this.apiDetails().isVisible();
66
+ onClick() {
67
+ // Only handle click if rendered as a button
68
+ if (this.as === 'button') {
69
+ this.apiDetails().setVisible(!this.expanded);
70
+ }
60
71
  },
61
72
  },
62
73
  };