@citizenplane/pimp 17.0.9 → 17.0.10

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": "@citizenplane/pimp",
3
- "version": "17.0.9",
3
+ "version": "17.0.10",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -1,42 +1,37 @@
1
1
  <template>
2
2
  <div class="cpAccordion">
3
- <button
4
- :id="accordionId"
5
- :aria-controls="accordionContentId"
6
- :aria-expanded="isToggled"
7
- class="cpAccordion__header"
8
- :class="dynamicClasses"
9
- cp-item-actions-trigger
10
- type="button"
11
- @click="handleClick"
12
- >
13
- <div class="cpAccordion__headerContent">
14
- <cp-item-actions
15
- v-if="hasActions"
16
- :actions="resolvedActions"
17
- class="cpAccordion__actions"
18
- :class="actionsDynamicClasses"
19
- :quick-options-limit="resolvedQuickOptionsLimit"
20
- />
21
- <div class="cpAccordion__title">
22
- <cp-icon v-if="displayLeadingIcon" class="cpAccordion__icon" size="16" :type="dynamicIcon" />
23
- <div class="cpAccordion__leading">
24
- <span class="cpAccordion__titleText">{{ title }}</span>
25
- <slot name="leading-slot" />
26
- </div>
27
- </div>
28
- <div v-if="displayTrailingSection" class="cpAccordion__trailing">
29
- <span v-if="hasTrailingSlot" class="cpAccordion__desktopTrailingSlot">
30
- <slot name="trailing-slot" />
31
- </span>
32
- <cp-icon v-if="displayActionTrigger" class="cpAccordion__more" size="16" type="more-horizontal" />
33
- <cp-icon v-if="displayTrailingIcon" class="cpAccordion__icon" size="16" :type="dynamicIcon" />
3
+ <div class="cpAccordion__header" :class="dynamicClasses" cp-item-actions-trigger>
4
+ <button
5
+ :id="accordionId"
6
+ :aria-controls="accordionContentId"
7
+ :aria-expanded="isToggled"
8
+ :aria-labelledby="accordionTitleId"
9
+ class="cpAccordion__trigger"
10
+ type="button"
11
+ @click="handleClick"
12
+ />
13
+
14
+ <div v-if="hasActions" class="cpAccordion__actions" :class="actionsDynamicClasses">
15
+ <cp-item-actions :actions="resolvedActions" :quick-options-limit="resolvedQuickOptionsLimit" />
16
+ </div>
17
+
18
+ <div class="cpAccordion__title">
19
+ <cp-icon v-if="displayLeadingIcon" class="cpAccordion__icon" size="16" :type="dynamicIcon" />
20
+ <div class="cpAccordion__leading">
21
+ <span :id="accordionTitleId" class="cpAccordion__titleText">{{ title }}</span>
22
+ <slot name="leading-slot" />
34
23
  </div>
35
24
  </div>
36
- <span v-if="hasTrailingSlot" class="cpAccordion__mobileTrailingSlot">
25
+
26
+ <span v-if="hasTrailingSlot" class="cpAccordion__trailingSlot">
37
27
  <slot name="trailing-slot" />
38
28
  </span>
39
- </button>
29
+
30
+ <div v-if="displayTrailingIcons" class="cpAccordion__trailingIcons">
31
+ <cp-icon v-if="displayActionTrigger" class="cpAccordion__more" size="16" type="more-horizontal" />
32
+ <cp-icon v-if="displayTrailingIcon" class="cpAccordion__icon" size="16" :type="dynamicIcon" />
33
+ </div>
34
+ </div>
40
35
  <cp-transition-expand>
41
36
  <div v-if="isToggled" :id="accordionContentId" :aria-labelledby="accordionId" class="cpAccordion__content">
42
37
  <slot />
@@ -80,6 +75,8 @@ const accordionId = useId()
80
75
 
81
76
  const accordionContentId = computed(() => `${accordionId}-content`)
82
77
 
78
+ const accordionTitleId = computed(() => `${accordionId}-title`)
79
+
83
80
  const resolvedIconPosition = computed(() => props.iconPosition || 'leading')
84
81
 
85
82
  const resolvedQuickOptionsLimit = computed(() => props.quickOptionsLimit || 0)
@@ -103,9 +100,7 @@ const displayActionTrigger = computed(() => !resolvedHideActionTrigger.value &&
103
100
  const displayLeadingIcon = computed(() => hasLeadingIcon.value || hasActions.value)
104
101
  const displayTrailingIcon = computed(() => hasTrailingIcon.value && !hasActions.value)
105
102
 
106
- const displayTrailingSection = computed(() => {
107
- return hasTrailingSlot.value || displayActionTrigger.value || displayTrailingIcon.value
108
- })
103
+ const displayTrailingIcons = computed(() => displayActionTrigger.value || displayTrailingIcon.value)
109
104
 
110
105
  const dynamicIcon = computed(() => (isToggled.value ? 'chevron-up' : 'chevron-down'))
111
106
 
@@ -123,36 +118,43 @@ const handleClick = () => (isToggled.value = !isToggled.value)
123
118
  background-color: var(--cp-background-primary);
124
119
  width: 100%;
125
120
 
126
- &__headerContent {
127
- display: flex;
128
- flex-shrink: 0;
129
- width: 100%;
130
- justify-content: space-between;
131
- gap: var(--cp-spacing-lg);
132
- }
133
-
134
121
  &__header {
135
- @extend %u-focus-outline;
136
-
122
+ position: relative;
137
123
  border-radius: var(--cp-radius-none);
138
124
  display: flex;
125
+ flex-wrap: wrap;
139
126
  align-items: center;
140
- justify-content: space-between;
141
127
  padding: var(--cp-spacing-lg);
142
128
  gap: var(--cp-spacing-lg);
143
- cursor: pointer;
144
129
 
145
- &:hover {
130
+ &:hover,
131
+ &:has(.cpAccordion__trigger:focus-visible) {
146
132
  background-color: var(--cp-background-primary-hover);
147
133
  }
148
134
 
149
- &:focus-visible {
150
- outline-offset: calc(var(--cp-spacing-sm) * -1);
135
+ &:has(.cpAccordion__trigger:focus-visible) {
136
+ box-shadow: var(--cp-shadow-focus-ring-accent);
151
137
  border-radius: var(--cp-radius-md);
138
+ z-index: 1;
152
139
  }
153
140
  }
154
141
 
142
+ &__trigger {
143
+ position: absolute;
144
+ inset: 0;
145
+ width: 100%;
146
+ height: 100%;
147
+ background: transparent;
148
+ border: 0;
149
+ padding: 0;
150
+ margin: 0;
151
+ cursor: pointer;
152
+ outline: none;
153
+ }
154
+
155
155
  &__title {
156
+ flex: 1 1 auto;
157
+ min-width: 0;
156
158
  display: flex;
157
159
  align-items: center;
158
160
  gap: var(--cp-spacing-lg);
@@ -160,12 +162,14 @@ const handleClick = () => (isToggled.value = !isToggled.value)
160
162
  font-size: var(--cp-text-size-sm);
161
163
  line-height: var(--cp-line-height-sm);
162
164
  overflow: hidden;
165
+ order: 1;
163
166
  }
164
167
 
165
168
  &__icon {
166
169
  color: var(--cp-foreground-secondary);
167
170
  flex-shrink: 0;
168
171
  padding: var(--cp-spacing-xs);
172
+ pointer-events: none;
169
173
  }
170
174
 
171
175
  &__leading {
@@ -179,60 +183,67 @@ const handleClick = () => (isToggled.value = !isToggled.value)
179
183
  @extend %u-text-ellipsis;
180
184
  }
181
185
 
182
- &__trailing {
186
+ &__trailingSlot {
183
187
  display: flex;
184
188
  align-items: center;
185
- gap: var(--cp-spacing-lg);
186
- position: relative;
189
+ order: 2;
190
+ pointer-events: auto;
187
191
  }
188
192
 
189
- &__desktopTrailingSlot {
193
+ &__trailingIcons {
190
194
  display: flex;
191
- }
192
-
193
- &__mobileTrailingSlot {
194
- display: none;
195
- text-align: left;
196
- padding-left: var(--cp-spacing-3xl);
195
+ align-items: center;
196
+ gap: var(--cp-spacing-lg);
197
+ order: 3;
198
+ pointer-events: none;
197
199
  }
198
200
 
199
201
  &__actions {
202
+ position: absolute;
200
203
  right: 0;
201
204
  top: 0;
202
205
  height: 100%;
203
- background: linear-gradient(270deg, var(--cp-utility-neutral-100) 0%, rgba(242, 246, 250, 0) 100%);
204
206
  padding: 0 var(--cp-spacing-md) 0 15%;
205
- transform: translate3d(calc(var(--cp-dimensions-1) * 1.25), 0%, 0);
206
207
  display: flex;
207
208
  align-items: center;
208
209
  z-index: 1;
210
+ pointer-events: none;
211
+
212
+ & > .cpItemActions {
213
+ transform: translate3d(calc(var(--cp-dimensions-1) * 1.25 + #{fn.px-to-rem(-8)}), -50%, 0);
214
+ }
209
215
  }
210
216
 
211
217
  &__more {
212
218
  color: var(--cp-foreground-primary);
219
+ pointer-events: none;
213
220
  }
214
221
  }
215
222
 
216
223
  @media (hover: hover) and (pointer: fine) {
217
- [cp-item-actions-trigger]:has(.cpItemActions--isDropdownOpen) .cpItemActions,
218
- [cp-item-actions-trigger]:is(:focus-within, :focus-visible, :hover) .cpItemActions {
219
- transform: translate3d(0, 0, 0);
224
+ [cp-item-actions-trigger]:has(.cpItemActions--isDropdownOpen),
225
+ [cp-item-actions-trigger]:is(:focus-within, :focus-visible, :hover) {
226
+ .cpItemActions {
227
+ transform: translate3d(#{fn.px-to-rem(-8)}, -50%, 0);
228
+ }
229
+
230
+ .cpAccordion__actions {
231
+ background: linear-gradient(270deg, var(--cp-utility-neutral-100) 0%, rgba(242, 246, 250, 0) 100%);
232
+ }
220
233
  }
221
234
  }
222
235
 
223
236
  // var(--cp-breakpoint-sm) = 40rem = 400px
224
237
  @include mx.media-query-max(40rem) {
225
238
  .cpAccordion {
226
- &__header {
227
- flex-wrap: wrap;
228
- }
229
-
230
- &__desktopTrailingSlot {
231
- display: none;
239
+ &__trailingIcons {
240
+ order: 2;
232
241
  }
233
242
 
234
- &__mobileTrailingSlot {
235
- display: flex;
243
+ &__trailingSlot {
244
+ order: 3;
245
+ flex-basis: 100%;
246
+ padding-left: var(--cp-spacing-3xl);
236
247
  }
237
248
 
238
249
  &__actions {
@@ -5,6 +5,7 @@ import type { MenuItem } from 'primevue/menuitem'
5
5
 
6
6
  import type { CpAccordionBaseProps } from '@/components/CpAccordion.vue'
7
7
  import CpAccordion from '@/components/CpAccordion.vue'
8
+ import CpButton from '@/components/CpButton.vue'
8
9
 
9
10
  /** Storybook controls: component props remain a discriminated union in `CpAccordion.vue`. */
10
11
  type CpAccordionStoryArgs = CpAccordionBaseProps & {
@@ -279,13 +280,13 @@ export const WithTrailingSlot: Story = {
279
280
  render: (args: Args) => ({
280
281
  components: { CpAccordion },
281
282
  setup() {
282
- return { args, sampleContentStyle, wrapperStyle }
283
+ return { args, sampleContentStyle, wrapperStyle, CpButton }
283
284
  },
284
285
  template: `
285
286
  <div :style="wrapperStyle">
286
287
  <CpAccordion v-bind="args">
287
288
  <template #trailing-slot>
288
- <span style="font-size: 12px; color: #6b7280;">Last edit · 2 min ago</span>
289
+ <CpButton size="xs">Custom action</CpButton>
289
290
  </template>
290
291
  <div :style="sampleContentStyle" />
291
292
  </CpAccordion>