@citizenplane/pimp 12.0.2 → 13.0.0

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": "12.0.2",
3
+ "version": "13.0.0",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8080",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -8,7 +8,7 @@
8
8
  class="cpDialog__dialog"
9
9
  @keydown.esc.stop.prevent="handleClose"
10
10
  >
11
- <div aria-hidden="true" class="cpDialog__overlay" />
11
+ <div aria-hidden="true" class="cpDialog__overlay" :class="overlayDynamicClass" @click="handleOverlayClick" />
12
12
  <main ref="dialogContainer" class="cpDialog__container" :style="dynamicStyle" @keydown.tab="trapFocus">
13
13
  <header class="cpDialog__header">
14
14
  <div v-if="hasTitleOrSubtitle" class="cpDialog__headerTexts">
@@ -44,6 +44,7 @@ import { computed, ref, useSlots, onMounted, nextTick, onBeforeUnmount, useId }
44
44
  import { getKeyboardFocusableElements, handleTrapFocus } from '@/helpers/dom'
45
45
 
46
46
  interface Props {
47
+ isClosableOnClickOutside?: boolean
47
48
  maxWidth?: number
48
49
  subtitle?: string
49
50
  title?: string
@@ -57,6 +58,7 @@ const props = withDefaults(defineProps<Props>(), {
57
58
  maxWidth: 600,
58
59
  title: '',
59
60
  subtitle: '',
61
+ isClosableOnClickOutside: false,
60
62
  })
61
63
 
62
64
  const emit = defineEmits<Emits>()
@@ -74,6 +76,10 @@ const dialogContainer = ref<HTMLElement | null>(null)
74
76
 
75
77
  const dynamicStyle = computed(() => ({ maxWidth: `${props.maxWidth}px` }))
76
78
 
79
+ const overlayDynamicClass = computed(() => ({
80
+ 'cpDialog__overlay--isClosableOnClickOutside': props.isClosableOnClickOutside,
81
+ }))
82
+
77
83
  const hasTitleSlot = computed(() => !!slots.title)
78
84
  const hasTitle = computed(() => !!props.title || hasTitleSlot.value)
79
85
  const hasSubtitleSlot = computed(() => !!slots.subtitle)
@@ -96,6 +102,11 @@ const focusOnFirstFocusableElement = () => {
96
102
  focusableElements[0].focus()
97
103
  }
98
104
 
105
+ const handleOverlayClick = () => {
106
+ if (!props.isClosableOnClickOutside) return
107
+ handleClose()
108
+ }
109
+
99
110
  onMounted(() => {
100
111
  openDialog()
101
112
  nextTick(() => focusOnFirstFocusableElement())
@@ -146,11 +157,18 @@ $dialog-breakpoint: 550px;
146
157
  inset: 0;
147
158
  pointer-events: none;
148
159
  transition: opacity 250ms ease;
160
+
161
+ &--isClosableOnClickOutside {
162
+ pointer-events: auto;
163
+ z-index: 0;
164
+ cursor: pointer;
165
+ }
149
166
  }
150
167
 
151
168
  &__container {
152
169
  position: relative;
153
170
  display: flex;
171
+ z-index: 2;
154
172
  overflow: hidden;
155
173
  width: 100%;
156
174
  max-height: 100%;
@@ -4,10 +4,10 @@
4
4
  class="cpLoader"
5
5
  :class="dynamicClasses"
6
6
  enable-background="new 0 0 40 40"
7
- :height="sizeAttrs.height"
7
+ :height="40"
8
8
  version="1.1"
9
9
  viewBox="0 0 40 40"
10
- :width="sizeAttrs.width"
10
+ :width="40"
11
11
  x="0px"
12
12
  xml:space="preserve"
13
13
  xmlns="http://www.w3.org/2000/svg"
@@ -25,7 +25,7 @@
25
25
  <animateTransform
26
26
  attributeName="transform"
27
27
  attributeType="xml"
28
- dur="0.5s"
28
+ :dur="formatedDuration"
29
29
  from="0 20 20"
30
30
  repeatCount="indefinite"
31
31
  to="360 20 20"
@@ -41,23 +41,22 @@ import { computed } from 'vue'
41
41
  import { Colors } from '@/constants'
42
42
  import { capitalizeFirstLetter } from '@/helpers'
43
43
 
44
+ type LoaderColor = Extract<Colors, 'neutral' | 'accent' | 'error' | 'warning' | 'success'>
45
+ type LoaderSize = '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
46
+
44
47
  interface Props {
45
- color?: Colors
46
- size?: number | string
48
+ color?: LoaderColor
49
+ duration?: number
50
+ size?: LoaderSize
47
51
  }
48
52
 
49
- const props = withDefaults(defineProps<Props>(), { color: 'accent', size: 24 })
53
+ const props = withDefaults(defineProps<Props>(), { color: 'accent', duration: 700, size: 'sm' })
50
54
 
51
- const capitalizedColor = computed(() => capitalizeFirstLetter(props.color))
55
+ const formatedDuration = computed(() => `${props.duration}ms`)
52
56
 
53
57
  const dynamicClasses = computed(() => {
54
- return [`cpLoader--is${capitalizedColor.value}`, `cpLoader--${props.size}`]
58
+ return [`cpLoader--is${capitalizeFirstLetter(props.color)}`, `cpLoader--${props.size}`]
55
59
  })
56
-
57
- const sizeAttrs = computed(() => ({
58
- height: props.size,
59
- width: props.size,
60
- }))
61
60
  </script>
62
61
 
63
62
  <style lang="scss">
@@ -74,12 +73,36 @@ const sizeAttrs = computed(() => ({
74
73
  @include cp-loader-themed('Warning', var(--cp-foreground-warning-secondary));
75
74
  @include cp-loader-themed('Success', var(--cp-foreground-success-secondary));
76
75
 
77
- @include cp-loader-themed('Blue', var(--cp-foreground-blue-secondary));
78
- @include cp-loader-themed('Yellow', var(--cp-utility-yellow-300));
76
+ &--2xs {
77
+ @include mx.square-sizing(16);
78
+ }
79
+
80
+ &--xs {
81
+ @include mx.square-sizing(20);
82
+ }
83
+
84
+ &--sm {
85
+ @include mx.square-sizing(24);
86
+ }
87
+
88
+ &--md {
89
+ @include mx.square-sizing(32);
90
+ }
91
+
92
+ &--lg {
93
+ @include mx.square-sizing(40);
94
+ }
95
+
96
+ &--xl {
97
+ @include mx.square-sizing(48);
98
+ }
99
+
100
+ &--2xl {
101
+ @include mx.square-sizing(64);
102
+ }
79
103
 
80
- @include cp-loader-themed('Purple', var(--cp-text-accent-primary)); // TODO: Should be replace by ACCENT
81
- @include cp-loader-themed('Green', var(--cp-text-success-primary)); // TODO: Should be replace by SUCCESS
82
- @include cp-loader-themed('Orange', var(--cp-text-warning-primary)); // TODO: Should be replace by WARNING
83
- @include cp-loader-themed('Red', var(--cp-text-error-primary)); // TODO: Should be replace by ERROR
104
+ &--3xl {
105
+ @include mx.square-sizing(96);
106
+ }
84
107
  }
85
108
  </style>
@@ -131,3 +131,30 @@ export const TitleSubtitleWithSlots: Story = {
131
131
  `,
132
132
  }),
133
133
  }
134
+
135
+ export const ClosableOnClickOutside: Story = {
136
+ args: {
137
+ maxWidth: 600,
138
+ isClosableOnClickOutside: true,
139
+ },
140
+ render: (args) => ({
141
+ setup() {
142
+ const isOpen = ref(false)
143
+ return { args, isOpen }
144
+ },
145
+ template: `
146
+ <CpButton @click="isOpen = true">Open Dialog</CpButton>
147
+ <CpTransitionDialog>
148
+ <CpDialog v-bind="args" v-if="isOpen" @close="isOpen = false">
149
+ <template #title>Header slot</template>
150
+ <template #subtitle>Subtitle</template>
151
+ <p>This is the default slot content. You can put any content here.</p>
152
+ <template #footer>
153
+ <CpButton @click="isOpen = false">Cancel</CpButton>
154
+ This is the footer slot
155
+ </template>
156
+ </CpDialog>
157
+ </CpTransitionDialog>
158
+ `,
159
+ }),
160
+ }
@@ -7,14 +7,19 @@ const meta = {
7
7
  component: CpLoader,
8
8
  argTypes: {
9
9
  size: {
10
- control: 'number',
10
+ control: 'select',
11
+ options: ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'],
11
12
  description: 'Size of the loader in pixels',
12
13
  },
13
14
  color: {
14
15
  control: 'select',
15
- options: ['neutral', 'accent', 'error', 'warning', 'success', 'blue', 'yellow'],
16
+ options: ['neutral', 'accent', 'error', 'warning', 'success'],
16
17
  description: 'The color variant of the loader',
17
18
  },
19
+ duration: {
20
+ control: 'number',
21
+ description: 'The duration of the loader in ms (700ms by default)',
22
+ },
18
23
  },
19
24
  } satisfies Meta<typeof CpLoader>
20
25