@citizenplane/pimp 10.6.1 → 10.7.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": "10.6.1",
3
+ "version": "10.7.0",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8080",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -41,7 +41,8 @@
41
41
  "primevue": "^4.5.4",
42
42
  "vue": "^3.5.27",
43
43
  "vue-bind-once": "^0.2.1",
44
- "vue-tel-input": "^9.5.0"
44
+ "vue-tel-input": "^9.5.0",
45
+ "web-haptics": "^0.0.6"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@babel/core": "^7.28.6",
@@ -8,6 +8,7 @@
8
8
  role="button"
9
9
  tabindex="0"
10
10
  :type="type"
11
+ @click="handleClick"
11
12
  >
12
13
  <span class="cpButton__body">
13
14
  <span v-if="isLoading" class="cpButton__loader"><cp-loader :color="Colors.NEUTRAL" /></span>
@@ -24,8 +25,10 @@
24
25
 
25
26
  <script setup lang="ts">
26
27
  import { computed, useSlots } from 'vue'
28
+ import { useWebHaptics } from 'web-haptics/vue'
27
29
 
28
30
  import { ButtonAppearances, ButtonTags, ButtonTypes } from '@/constants/Button'
31
+ import { Haptics } from '@/constants/Hapitcs'
29
32
 
30
33
  import CpLoader from '@/components/CpLoader.vue'
31
34
 
@@ -36,6 +39,7 @@ interface Props {
36
39
  appearance?: ButtonAppearances
37
40
  color?: Colors
38
41
  disabled?: boolean
42
+ enableHaptics?: boolean
39
43
  isLoading?: boolean
40
44
  isSquare?: boolean
41
45
  size?: Sizes
@@ -46,15 +50,13 @@ interface Props {
46
50
  const props = withDefaults(defineProps<Props>(), {
47
51
  appearance: ButtonAppearances.DEFAULT,
48
52
  color: undefined,
49
- disabled: false,
50
53
  tag: ButtonTags.BUTTON,
51
54
  type: ButtonTypes.BUTTON,
52
- isLoading: false,
53
- isSquare: false,
54
55
  size: Sizes.MD,
55
56
  })
56
57
 
57
58
  const slots = useSlots()
59
+ const { trigger } = useWebHaptics()
58
60
 
59
61
  const capitalizedAppearance = computed(() => capitalizeFirstLetter(props.appearance))
60
62
 
@@ -81,6 +83,12 @@ const dynamicClasses = computed(() => {
81
83
  },
82
84
  ]
83
85
  })
86
+
87
+ const handleClick = () => {
88
+ if (props.enableHaptics) {
89
+ trigger(Haptics.RIGID)
90
+ }
91
+ }
84
92
  </script>
85
93
 
86
94
  <style lang="scss">
@@ -92,6 +92,7 @@ const handleItemClick = async (event: Event) => {
92
92
  }
93
93
 
94
94
  position: relative;
95
+ color: var(--cp-text-primary);
95
96
  display: flex;
96
97
  width: 100%;
97
98
  align-items: center;
@@ -30,6 +30,9 @@
30
30
 
31
31
  <script setup lang="ts">
32
32
  import { computed, useId } from 'vue'
33
+ import { useWebHaptics } from 'web-haptics/vue'
34
+
35
+ import { Haptics } from '@/constants/Hapitcs'
33
36
 
34
37
  import CpTooltip from '@/components/CpTooltip.vue'
35
38
 
@@ -40,6 +43,7 @@ interface Props {
40
43
  autofocus?: boolean
41
44
  color?: string
42
45
  disabled?: boolean
46
+ enableHaptics?: boolean
43
47
  groupName?: string
44
48
  helper?: string
45
49
  isRequired?: boolean
@@ -68,6 +72,8 @@ const props = withDefaults(defineProps<Props>(), {
68
72
 
69
73
  const emit = defineEmits<Emits>()
70
74
 
75
+ const { trigger } = useWebHaptics()
76
+
71
77
  const switchUniqueId = useId()
72
78
 
73
79
  const capitalizedColor = computed(() => {
@@ -87,6 +93,10 @@ const computedClasses = computed(() => {
87
93
  })
88
94
 
89
95
  const handleClick = (value: boolean): void => {
96
+ if (props.enableHaptics) {
97
+ trigger(Haptics.SOFT)
98
+ }
99
+
90
100
  emit('update:modelValue', !value)
91
101
  }
92
102
  </script>
@@ -695,75 +695,15 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
695
695
  }
696
696
 
697
697
  &__row {
698
- &--body:not(:last-of-type):not(#{&}--isFullWidth) {
699
- box-shadow: inset 0 calc(var(--cp-dimensions-0_25) * -1) 0 var(--cp-border-soft);
700
- }
701
-
702
- &--body:has(+ #{&}--isFullWidth),
703
- &--isFullWidth:has(+ .cpTable__row:hover) {
704
- box-shadow: none !important;
705
- }
706
-
707
- &--isFullWidth:has(+ .cpTable__row:is(:hover, :focus, :focus-within), + .cpTable__row--isSelected)
708
- + .cpTable__row::after {
709
- box-shadow: none !important;
710
- }
711
-
712
- &--isFullWidth {
713
- border-top: none;
714
- }
715
-
716
- &--body:not(#{&}--isFullWidth):is(:hover, :focus, :focus-within) {
717
- position: relative;
718
- transition: background-color 100ms ease-in-out;
698
+ &--body:not(:last-of-type) {
699
+ border-bottom: var(--cp-dimensions-0_25) solid var(--cp-border-soft);
719
700
  }
720
701
 
721
- &--body:not(#{&}--isFullWidth):is(:hover, :focus, :focus-within) td {
702
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):hover,
703
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
704
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus-within {
722
705
  background-color: var(--cp-background-primary-hover);
723
- }
724
-
725
- &--body:not(#{&}--isFullWidth):not(
726
- :has(+ #{&}--isSelected),
727
- :has(+ .cpTable__row:is(:hover, :focus, :focus-within))
728
- ):is(:hover, :focus, :focus-within)::after {
729
- content: '';
730
- position: absolute;
731
- inset: 0;
732
- border-radius: var(--cp-radius-md);
733
- box-shadow: inset 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft);
734
- pointer-events: none;
735
- }
736
-
737
- &--body:not(#{&}--isFullWidth):hover td:first-child,
738
- &--body:not(#{&}--isFullWidth):focus td:first-child,
739
- &--body:not(#{&}--isFullWidth):focus-within td:first-child {
740
- border-top-left-radius: var(--cp-radius-md);
741
- border-bottom-left-radius: var(--cp-radius-md);
742
- }
743
-
744
- &--body:not(#{&}--isFullWidth):hover td:last-child,
745
- &--body:not(#{&}--isFullWidth):focus td:last-child,
746
- &--body:not(#{&}--isFullWidth):focus-within td:last-child {
747
- border-top-right-radius: var(--cp-radius-md);
748
- border-bottom-right-radius: var(--cp-radius-md);
749
- }
750
-
751
- &--body:not(#{&}--isFullWidth):has(.cpTable__cell--isOptions:not([style*='display: none'])):hover
752
- td:nth-last-child(2),
753
- &--body:not(#{&}--isFullWidth):has(.cpTable__cell--isOptions:not([style*='display: none'])):focus
754
- td:nth-last-child(2),
755
- &--body:not(#{&}--isFullWidth):has(.cpTable__cell--isOptions:not([style*='display: none'])):focus-within
756
- td:nth-last-child(2) {
757
- border-top-right-radius: 0;
758
- border-bottom-right-radius: 0;
759
- }
760
-
761
- &--body:not(#{&}--isFullWidth):has(.cpTable__cell--isOptions[style*='display: none']):hover td:nth-last-child(2),
762
- &--body:not(#{&}--isFullWidth):has(.cpTable__cell--isOptions[style*='display: none']):focus td:nth-last-child(2),
763
- &--body:not(#{&}--isFullWidth):has(.cpTable__cell--isOptions[style*='display: none']):focus-within
764
- td:nth-last-child(2) {
765
- border-top-right-radius: var(--cp-radius-md);
766
- border-bottom-right-radius: var(--cp-radius-md);
706
+ transition: background-color 100ms ease-in-out;
767
707
  }
768
708
 
769
709
  &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
@@ -774,8 +714,6 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
774
714
  &--isFullWidth td {
775
715
  padding: var(--cp-spacing-md);
776
716
  background-color: var(--cp-background-secondary);
777
- border-radius: var(--cp-radius-md);
778
- box-shadow: inset 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft);
779
717
  }
780
718
 
781
719
  &--isClickable {
@@ -787,49 +725,6 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
787
725
  color: var(--cp-text-accent-primary);
788
726
  }
789
727
 
790
- &--isSelected {
791
- position: relative;
792
- }
793
-
794
- &--isSelected:not(:has(+ #{&}--isFullWidth))::after {
795
- content: '';
796
- position: absolute;
797
- inset: 0;
798
- border-radius: var(--cp-radius-md);
799
- box-shadow: inset 0 0 0 var(--cp-dimensions-0_25) var(--cp-utility-accent-100);
800
- pointer-events: none;
801
- }
802
-
803
- &--isSelected td:first-child {
804
- border-top-left-radius: var(--cp-radius-md);
805
- border-bottom-left-radius: var(--cp-radius-md);
806
- }
807
-
808
- &--isSelected td:last-child {
809
- border-top-right-radius: var(--cp-radius-md);
810
- border-bottom-right-radius: var(--cp-radius-md);
811
- }
812
-
813
- &--isSelected:has(.cpTable__cell--isOptions:not([style*='display: none'])) td:nth-last-child(2) {
814
- border-top-right-radius: 0;
815
- border-bottom-right-radius: 0;
816
- }
817
-
818
- &--isSelected:has(.cpTable__cell--isOptions[style*='display: none']) td:nth-last-child(2) {
819
- border-top-right-radius: var(--cp-radius-md);
820
- border-bottom-right-radius: var(--cp-radius-md);
821
- }
822
-
823
- &--body:not(#{&}--isFullWidth):is(:hover, :focus, :focus-within),
824
- &--body:not(#{&}--isFullWidth).cpTable__row--isSelected {
825
- box-shadow: none !important;
826
- }
827
-
828
- &--body:not(#{&}--isFullWidth):has(+ #{&}--isSelected),
829
- &--body:not(#{&}--isFullWidth):has(+ #{&}:is(:hover, :focus, :focus-within)) {
830
- box-shadow: none !important;
831
- }
832
-
833
728
  &--body td {
834
729
  font-size: var(--cp-text-size-sm);
835
730
  }
@@ -873,28 +768,6 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
873
768
  }
874
769
  }
875
770
 
876
- &:has(
877
- .cpTable__row--body:first-of-type:is(:hover, :focus, :focus-within),
878
- .cpTable__row--body:first-of-type.cpTable__row--isSelected
879
- ) {
880
- .cpTable__column:after {
881
- background-color: transparent;
882
- }
883
- }
884
-
885
- &:has(
886
- .cpTable__row--body:last-of-type:is(:hover, :focus, :focus-within),
887
- .cpTable__row--body:last-of-type.cpTable__row--isSelected
888
- ) {
889
- .cpTable__container--hasPagination {
890
- border-bottom-color: transparent;
891
- }
892
- }
893
-
894
- &__table:has(.cpTable__row--body:first-of-type.cpTable__row--isFullWidth) &__column:after {
895
- background-color: transparent;
896
- }
897
-
898
771
  &__columnEditor {
899
772
  padding-right: calc(var(--cp-spacing-lg) + var(--cp-dimensions-0_5));
900
773
 
@@ -62,13 +62,18 @@
62
62
 
63
63
  <script setup lang="ts">
64
64
  import Toast from 'primevue/toast'
65
+ import ToastEventBus from 'primevue/toasteventbus'
66
+ import { onMounted } from 'vue'
67
+ import { useWebHaptics } from 'web-haptics/vue'
65
68
 
66
69
  import { CpToastTypes } from '@/constants/CpToastTypes'
70
+ import { Haptics } from '@/constants/Hapitcs'
67
71
 
68
72
  import { capitalizeFirstLetter } from '@/helpers'
69
73
 
70
74
  interface Message {
71
75
  detail?: string
76
+ enableHaptics?: boolean
72
77
  life?: number
73
78
  primaryAction?: {
74
79
  label: string
@@ -87,6 +92,8 @@ interface Message {
87
92
  summary: string
88
93
  }
89
94
 
95
+ onMounted(() => ToastEventBus.on('add', handleToastAdded))
96
+
90
97
  const displayTimer = (message: Message) => message.showTimer && message.life !== undefined
91
98
 
92
99
  const getDynamicClass = (severity: string) => `cpToast--is${capitalizeFirstLetter(severity)}`
@@ -129,6 +136,28 @@ const handleActionClick = (onClick: VoidFunction, closeCallback: VoidFunction) =
129
136
  }
130
137
 
131
138
  const handleMouse = () => true
139
+
140
+ const { trigger } = useWebHaptics()
141
+
142
+ const resolveHapticLevel = (severity: CpToastTypes) => {
143
+ switch (severity) {
144
+ case CpToastTypes.SUCCESS:
145
+ return Haptics.SUCCESS
146
+ case CpToastTypes.WARNING:
147
+ return Haptics.WARNING
148
+ case CpToastTypes.ERROR:
149
+ return Haptics.ERROR
150
+ case CpToastTypes.INFO:
151
+ default:
152
+ return Haptics.SOFT
153
+ }
154
+ }
155
+
156
+ const handleToastAdded = (message: Message) => {
157
+ if (message.enableHaptics) {
158
+ trigger(resolveHapticLevel(message.severity))
159
+ }
160
+ }
132
161
  </script>
133
162
 
134
163
  <style lang="scss">
@@ -0,0 +1,13 @@
1
+ export const Haptics = {
2
+ SUCCESS: 'success',
3
+ WARNING: 'warning',
4
+ ERROR: 'error',
5
+ LIGHT: 'light',
6
+ MEDIUM: 'medium',
7
+ HEAVY: 'heavy',
8
+ SOFT: 'soft',
9
+ RIGID: 'rigid',
10
+ SELECTION: 'selection',
11
+ NUDGE: 'nudge',
12
+ BUZZ: 'buzz',
13
+ }
@@ -112,6 +112,24 @@ export const WithIcons: Story = {
112
112
  }),
113
113
  }
114
114
 
115
+ export const WithHaptics: Story = {
116
+ args: { ...Default.args, enableHaptics: true },
117
+ render: (args) => ({
118
+ components: { CpButton },
119
+ setup() {
120
+ const onClick = () => {
121
+ console.log('Button clicked')
122
+ }
123
+ return { args, onClick }
124
+ },
125
+ template: `
126
+ <CpButton v-bind="args" @click="onClick">
127
+ Button with haptics
128
+ </CpButton>
129
+ `,
130
+ }),
131
+ }
132
+
115
133
  export const Loading: Story = {
116
134
  args: { ...Default.args, isLoading: true },
117
135
  render: defaultRender,
@@ -83,6 +83,28 @@ export const Default: Story = {
83
83
  }),
84
84
  }
85
85
 
86
+ export const WithHaptics: Story = {
87
+ args: {
88
+ ...Default.args,
89
+ enableHaptics: true,
90
+ },
91
+ render: (args) => ({
92
+ components: { CpSwitch },
93
+ setup() {
94
+ const value = ref(false)
95
+ return { args, value }
96
+ },
97
+ template: `
98
+ <div style="padding: 20px;">
99
+ <CpSwitch
100
+ v-model="value"
101
+ v-bind="args"
102
+ />
103
+ </div>
104
+ `,
105
+ }),
106
+ }
107
+
86
108
  export const Disabled: Story = {
87
109
  args: {
88
110
  ...Default.args,
@@ -19,6 +19,7 @@ const meta: Meta<typeof CpToast> = {
19
19
  args: {
20
20
  position: 'top-right',
21
21
  group: undefined,
22
+ enableHaptics: false,
22
23
  },
23
24
  }
24
25
 
@@ -36,6 +37,7 @@ export const Default: Story = {
36
37
  severity: CpToastTypes.SECONDARY,
37
38
  summary: `Hello i'm a cpToast !`,
38
39
  detail: 'This is a cpToast description.',
40
+ enableHaptics: true,
39
41
  }
40
42
 
41
43
  const notifySecondary = () => toast.add({ ...baseOptions })