@citizenplane/pimp 18.9.20 → 18.9.22

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": "18.9.20",
3
+ "version": "18.9.22",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -4,9 +4,10 @@
4
4
  :can-backdrop-close="canBackdropClose"
5
5
  :can-swipe-close="canSwipeClose"
6
6
  :duration="animationDuration"
7
- expand-on-content-drag
7
+ :expand-on-content-drag="expandOnContentDrag"
8
8
  :swipe-close-threshold="swipeCloseThreshold"
9
9
  teleport-defer
10
+ :snap-points="resolvedSnapPoints"
10
11
  @closed="onBottomSheetClosed"
11
12
  @opened="onBottomSheetOpened"
12
13
  >
@@ -24,7 +25,7 @@
24
25
 
25
26
  <script setup lang="ts">
26
27
  import VueBottomSheet from '@douxcode/vue-spring-bottom-sheet'
27
- import { useTemplateRef, watch } from 'vue'
28
+ import { computed, useTemplateRef, watch } from 'vue'
28
29
 
29
30
  interface Emits {
30
31
  (e: 'close' | 'open'): void
@@ -34,13 +35,19 @@ interface Props {
34
35
  animationDuration?: number
35
36
  canBackdropClose?: boolean
36
37
  canSwipeClose?: boolean
38
+ expandOnContentDrag?: boolean
39
+ halfAndExpand?: boolean
37
40
  preventClose?: boolean
41
+ snapPoints?: Array<number | `${number}%`>
38
42
  swipeCloseThreshold?: string
39
43
  }
40
44
 
41
45
  const props = withDefaults(defineProps<Props>(), {
42
46
  animationDuration: 300,
43
- swipeCloseThreshold: '20%',
47
+ expandOnContentDrag: true,
48
+ halfAndExpand: false,
49
+ snapPoints: () => [],
50
+ swipeCloseThreshold: '40%',
44
51
  })
45
52
 
46
53
  const emit = defineEmits<Emits>()
@@ -48,7 +55,21 @@ const emit = defineEmits<Emits>()
48
55
  const bottomSheetRef = useTemplateRef('bottomSheetRef')
49
56
  const isVisibleModel = defineModel<boolean>()
50
57
 
58
+ const resolvedSnapPoints = computed((): Array<number | `${number}%`> | undefined => {
59
+ if (props.snapPoints?.length) return props.snapPoints
60
+ return props.halfAndExpand ? ([`${50}%`, `${100}%`] as Array<number | `${number}%`>) : undefined
61
+ })
62
+
63
+ // Set right before syncing a self-close down to the model, so the resulting
64
+ // watch trigger doesn't re-issue a close() the underlying sheet already ran.
65
+ let skipNextToggle = false
66
+
51
67
  const toggleBottomSheet = () => {
68
+ if (skipNextToggle) {
69
+ skipNextToggle = false
70
+ return
71
+ }
72
+
52
73
  if (isVisibleModel.value) {
53
74
  bottomSheetRef.value?.open()
54
75
  return
@@ -62,7 +83,18 @@ watch(isVisibleModel, () => toggleBottomSheet())
62
83
  const onBottomSheetOpened = () => emit('open')
63
84
 
64
85
  const onBottomSheetClosed = () => {
65
- if (props.preventClose) return
86
+ if (props.preventClose) {
87
+ // The underlying sheet already closed itself (e.g. Escape key, which it
88
+ // never gates behind canBackdropClose/canSwipeClose) despite preventClose:
89
+ // reopen it so the visual state matches the model we never touched.
90
+ if (isVisibleModel.value) bottomSheetRef.value?.open()
91
+ return
92
+ }
93
+
94
+ if (isVisibleModel.value) {
95
+ skipNextToggle = true
96
+ isVisibleModel.value = false
97
+ }
66
98
 
67
99
  emit('close')
68
100
  }
@@ -5,7 +5,7 @@
5
5
  v-if="dynamicRenderingCondition"
6
6
  v-bind="dynamicComponentProps"
7
7
  ref="dynamicComponentRef"
8
- @close="emit('close')"
8
+ @close="onDynamicComponentClosed"
9
9
  >
10
10
  <!-- Header slot is only for bottom sheet -->
11
11
  <template v-if="title" #header>
@@ -38,9 +38,14 @@ import CpBottomSheet from '@/components/CpBottomSheet.vue'
38
38
  import CpDialog from '@/components/CpDialog.vue'
39
39
  import CpTransitionDialog from '@/components/CpTransitionDialog.vue'
40
40
 
41
+ import { Breakpoints } from '@/constants/layout'
42
+
41
43
 
42
44
  interface Props {
45
+ breakpoint?: number
43
46
  class?: string
47
+ expandOnContentDrag?: boolean
48
+ halfAndExpand?: boolean
44
49
  isClosableOnClickOutside?: boolean
45
50
  maxWidth?: number | undefined
46
51
  preventClose?: boolean
@@ -52,7 +57,7 @@ const props = defineProps<Props>()
52
57
 
53
58
  const emit = defineEmits<{ (e: 'close'): void }>()
54
59
 
55
- const isMobile = useIsBreakpoint()
60
+ const isMobile = useIsBreakpoint(props.breakpoint ?? Breakpoints.MOBILE)
56
61
 
57
62
  const dynamicComponentRef = useTemplateRef('dynamicComponentRef')
58
63
 
@@ -80,7 +85,9 @@ const dynamicComponentProps = computed(() => {
80
85
  contentClass: props.class,
81
86
  canBackdropClose: !props.preventClose,
82
87
  canSwipeClose: !props.preventClose,
88
+ expandOnContentDrag: props.expandOnContentDrag,
83
89
  preventClose: props.preventClose,
90
+ halfAndExpand: props.halfAndExpand,
84
91
  }
85
92
  }
86
93
 
@@ -94,6 +101,11 @@ const dynamicComponentProps = computed(() => {
94
101
  }
95
102
  })
96
103
 
104
+ const onDynamicComponentClosed = () => {
105
+ isVisibleModel.value = false
106
+ emit('close')
107
+ }
108
+
97
109
  const openBottomSheet = async () => {
98
110
  await nextTick()
99
111
 
@@ -66,6 +66,30 @@ export const Default: Story = {
66
66
  >
67
67
  <template #header><strong>Header slot</strong></template>
68
68
  <p>This is the default slot content. You can put any content here.</p>
69
+ <p>This is the default slot content. You can put any content here.</p>
70
+ <p>This is the default slot content. You can put any content here.</p>
71
+ <p>This is the default slot content. You can put any content here.</p>
72
+ <p>This is the default slot content. You can put any content here.</p>
73
+ <p>This is the default slot content. You can put any content here.</p>
74
+ <p>This is the default slot content. You can put any content here.</p>
75
+ <p>This is the default slot content. You can put any content here.</p>
76
+ <p>This is the default slot content. You can put any content here.</p>
77
+ <p>This is the default slot content. You can put any content here.</p>
78
+ <p>This is the default slot content. You can put any content here.</p>
79
+ <p>This is the default slot content. You can put any content here.</p>
80
+ <p>This is the default slot content. You can put any content here.</p>
81
+ <p>This is the default slot content. You can put any content here.</p>
82
+ <p>This is the default slot content. You can put any content here.</p>
83
+ <p>This is the default slot content. You can put any content here.</p>
84
+ <p>This is the default slot content. You can put any content here.</p>
85
+ <p>This is the default slot content. You can put any content here.</p>
86
+ <p>This is the default slot content. You can put any content here.</p>
87
+ <p>This is the default slot content. You can put any content here.</p>
88
+ <p>This is the default slot content. You can put any content here.</p>
89
+ <p>This is the default slot content. You can put any content here.</p>
90
+ <p>This is the default slot content. You can put any content here.</p>
91
+ <p>This is the default slot content. You can put any content here.</p>
92
+ <p>This is the default slot content. You can put any content here.</p>
69
93
  <template #footer>
70
94
  <CpButton @click="isVisible = false">Close</CpButton>
71
95
  </template>
@@ -43,6 +43,11 @@ const meta = {
43
43
  },
44
44
  },
45
45
  argTypes: {
46
+ breakpoint: {
47
+ control: 'number',
48
+ description:
49
+ 'Viewport width (px) below which the dialog switches to a bottom sheet. Defaults to Breakpoints.MOBILE (552)',
50
+ },
46
51
  maxWidth: {
47
52
  control: 'number',
48
53
  description: 'Maximum width of the desktop dialog (px)',