@citizenplane/pimp 18.0.5 → 18.1.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": "18.0.5",
3
+ "version": "18.1.0",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -0,0 +1,137 @@
1
+ <template>
2
+ <button
3
+ :aria-disabled="disabled"
4
+ :aria-pressed="isSelected"
5
+ class="cpToggleButton"
6
+ :class="dynamicClasses"
7
+ :disabled="disabled"
8
+ type="button"
9
+ @click="handleClick"
10
+ >
11
+ <span class="cpToggleButton__leading">
12
+ <slot name="leading">
13
+ <cp-icon size="16" :type="leadingIcon" />
14
+ </slot>
15
+ </span>
16
+ <span v-if="showLabel" class="cpToggleButton__label">
17
+ <slot>
18
+ {{ label }}
19
+ </slot>
20
+ </span>
21
+ </button>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { computed, useSlots } from 'vue'
26
+
27
+ interface Props {
28
+ disabled?: boolean
29
+ isSelected?: boolean
30
+ label?: string
31
+ leadingIcon?: string
32
+ }
33
+
34
+ const props = withDefaults(defineProps<Props>(), {
35
+ isSelected: false,
36
+ disabled: false,
37
+ label: '',
38
+ leadingIcon: 'dashed-circle',
39
+ })
40
+
41
+ const emit = defineEmits<{
42
+ click: [event: MouseEvent]
43
+ }>()
44
+
45
+ const slots = useSlots()
46
+
47
+ const showLabel = computed(() => !!props.label || !!slots.default)
48
+
49
+ const dynamicClasses = computed(() => ({
50
+ 'cpToggleButton--isSelected': props.isSelected,
51
+ }))
52
+
53
+ const handleClick = (event: MouseEvent) => {
54
+ emit('click', event)
55
+ }
56
+ </script>
57
+
58
+ <style lang="scss">
59
+ .cpToggleButton {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ gap: var(--cp-spacing-md);
64
+ padding: var(--cp-spacing-lg);
65
+ border-radius: var(--cp-radius-md);
66
+ box-shadow:
67
+ var(--cp-shadows-3xs-inset),
68
+ 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft);
69
+ background-color: var(--cp-background-primary);
70
+ color: var(--cp-text-secondary);
71
+ transition:
72
+ background-color 100ms ease-out,
73
+ box-shadow 100ms ease-out,
74
+ color 100ms ease-out;
75
+
76
+ &:hover:not(:disabled) {
77
+ background-color: var(--cp-background-primary-hover);
78
+ color: var(--cp-text-secondary-hover);
79
+ box-shadow:
80
+ var(--cp-shadows-3xs-inset),
81
+ 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft-hover);
82
+ }
83
+
84
+ &:focus-visible {
85
+ box-shadow:
86
+ 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft-hover),
87
+ var(--cp-shadow-focus-ring-accent);
88
+ }
89
+
90
+ &__leading {
91
+ display: flex;
92
+ align-items: center;
93
+ }
94
+
95
+ &__label {
96
+ font-size: var(--cp-text-size-sm);
97
+ line-height: var(--cp-line-height-sm);
98
+ font-weight: 500;
99
+ }
100
+
101
+ &__defaultLeading,
102
+ &__defaultLeading * {
103
+ pointer-events: none;
104
+ }
105
+
106
+ &:disabled {
107
+ cursor: not-allowed;
108
+ background-color: var(--cp-background-disabled);
109
+ color: var(--cp-text-disabled);
110
+ box-shadow:
111
+ var(--cp-shadows-3xs-inset),
112
+ 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-disabled);
113
+ }
114
+
115
+ &--isSelected:not(:disabled) {
116
+ background-color: var(--cp-background-accent-secondary);
117
+ color: var(--cp-text-accent-primary);
118
+ box-shadow:
119
+ var(--cp-shadows-3xs-inset),
120
+ 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-accent-solid);
121
+
122
+ &:hover {
123
+ background-color: var(--cp-background-accent-secondary-hover);
124
+ color: var(--cp-text-accent-primary-hover);
125
+ box-shadow:
126
+ var(--cp-shadows-3xs-inset),
127
+ 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-accent-solid);
128
+ }
129
+
130
+ &:focus-visible {
131
+ box-shadow:
132
+ 0 0 0 var(--cp-dimensions-0_25) var(--cp-border-accent-solid),
133
+ var(--cp-shadow-focus-ring-accent);
134
+ }
135
+ }
136
+ }
137
+ </style>
@@ -1,7 +1,12 @@
1
1
  <template>
2
2
  <context-menu ref="menu" :model="items" :pt="passThroughConfig">
3
3
  <template #item="{ item, props }">
4
- <cp-menu-item v-bind="{ ...item, ...props.action }" :leading-icon="item.icon" @async-complete="hide" />
4
+ <cp-menu-item
5
+ v-bind="{ ...item, ...props.action }"
6
+ :leading-icon="item.icon"
7
+ @async-complete="hide"
8
+ @click="hide"
9
+ />
5
10
  </template>
6
11
  </context-menu>
7
12
  </template>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <label class="cpSwitch" :class="computedClasses" :for="switchUniqueId">
2
+ <label class="cpSwitch" :class="computedClasses" :for="switchUniqueId" v-bind="inertProps">
3
3
  <span class="cpSwitch__switch">
4
4
  <input
5
5
  :id="switchUniqueId"
@@ -46,6 +46,7 @@ interface Props {
46
46
  enableHaptics?: boolean
47
47
  groupName?: string
48
48
  helper?: string
49
+ inert?: boolean
49
50
  isRequired?: boolean
50
51
  label?: string
51
52
  modelValue?: boolean
@@ -68,6 +69,7 @@ const props = withDefaults(defineProps<Props>(), {
68
69
  autofocus: false,
69
70
  isRequired: false,
70
71
  tooltip: '',
72
+ inert: false,
71
73
  })
72
74
 
73
75
  const emit = defineEmits<Emits>()
@@ -80,6 +82,16 @@ const capitalizedColor = computed(() => {
80
82
  return capitalizeFirstLetter(props.color)
81
83
  })
82
84
 
85
+ const inertProps = computed(() => {
86
+ if (!props.inert) return
87
+
88
+ return {
89
+ 'aria-hidden': true,
90
+ tabindex: -1,
91
+ inert: true,
92
+ }
93
+ })
94
+
83
95
  const computedClasses = computed(() => {
84
96
  return [
85
97
  {
@@ -22,6 +22,7 @@ import CpAlert from './CpAlert.vue'
22
22
  import CpBadge from './CpBadge.vue'
23
23
  import CpButton from './CpButton.vue'
24
24
  import CpButtonGroup from './CpButtonGroup.vue'
25
+ import CpButtonToggle from './CpButtonToggle.vue'
25
26
  import CpCalendar from './CpCalendar.vue'
26
27
  import CpCheckbox from './CpCheckbox.vue'
27
28
  import CpContextualMenu from './CpContextualMenu.vue'
@@ -73,6 +74,7 @@ const Components = {
73
74
  CpAccordion,
74
75
  CpAccordionGroup,
75
76
  CpToast,
77
+ CpButtonToggle,
76
78
  CpBadge,
77
79
  CpTabs,
78
80
  CpHeading,
@@ -148,6 +150,7 @@ export {
148
150
  CpAccordion,
149
151
  CpAccordionGroup,
150
152
  CpToast,
153
+ CpButtonToggle,
151
154
  CpBadge,
152
155
  CpTabs,
153
156
  CpHeading,
@@ -0,0 +1,137 @@
1
+ import { ref } from 'vue'
2
+
3
+ import type { Args, Meta, StoryObj } from '@storybook/vue3-vite'
4
+
5
+ import CpButtonToggle from '@/components/CpButtonToggle.vue'
6
+ import CpSwitch from '@/components/CpSwitch.vue'
7
+
8
+ import { docCellStyle, docLabelStyle, docRowWrapStyle } from '@/stories/documentationStyles'
9
+
10
+ const meta = {
11
+ title: 'Atoms/CpButtonToggle',
12
+ component: CpButtonToggle,
13
+ argTypes: {
14
+ isSelected: {
15
+ control: 'boolean',
16
+ description: 'Whether the toggle button is selected.',
17
+ },
18
+ disabled: {
19
+ control: 'boolean',
20
+ description: 'Whether interactions are disabled.',
21
+ },
22
+ label: {
23
+ control: 'text',
24
+ description: 'Text displayed in the button.',
25
+ },
26
+ leadingIcon: {
27
+ control: 'text',
28
+ description: 'Icon displayed in the button.',
29
+ },
30
+ },
31
+ } satisfies Meta<typeof CpButtonToggle>
32
+
33
+ export default meta
34
+ type Story = StoryObj<typeof meta>
35
+
36
+ const defaultRender = (args: Args) => ({
37
+ components: { CpButtonToggle },
38
+ setup() {
39
+ return { args }
40
+ },
41
+ template: `
42
+ <CpButtonToggle v-bind="args" />
43
+ `,
44
+ })
45
+
46
+ export const Default: Story = {
47
+ args: {
48
+ label: 'Title',
49
+ isSelected: false,
50
+ disabled: false,
51
+ leadingIcon: 'dashed-circle',
52
+ },
53
+ render: defaultRender,
54
+ }
55
+
56
+ export const States: Story = {
57
+ parameters: { controls: { disable: true } },
58
+ render: () => ({
59
+ components: { CpButtonToggle },
60
+ setup() {
61
+ return { docCellStyle, docLabelStyle, docRowWrapStyle }
62
+ },
63
+ template: `
64
+ <div :style="docRowWrapStyle">
65
+ <div :style="docCellStyle">
66
+ <span :style="docLabelStyle">Default</span>
67
+ <CpButtonToggle label="Title" />
68
+ </div>
69
+ <div :style="docCellStyle">
70
+ <span :style="docLabelStyle">Selected</span>
71
+ <CpButtonToggle label="Title" :is-selected="true" />
72
+ </div>
73
+ <div :style="docCellStyle">
74
+ <span :style="docLabelStyle">Disabled</span>
75
+ <CpButtonToggle label="Title" :disabled="true" />
76
+ </div>
77
+ <div :style="docCellStyle">
78
+ <span :style="docLabelStyle">Disabled selected</span>
79
+ <CpButtonToggle label="Title" :is-selected="true" :disabled="true" />
80
+ </div>
81
+ </div>
82
+ `,
83
+ }),
84
+ }
85
+
86
+ export const withInertSwitch: Story = {
87
+ args: {
88
+ ...Default.args,
89
+ isSelected: undefined,
90
+ },
91
+ render: (args: Args) => ({
92
+ components: { CpButtonToggle, CpSwitch },
93
+ setup() {
94
+ const isSelected = ref(false)
95
+ return { docCellStyle, docLabelStyle, docRowWrapStyle, isSelected, args }
96
+ },
97
+ template: `
98
+ <div :style="docRowWrapStyle">
99
+ <div :style="docCellStyle">
100
+ <span :style="docLabelStyle">Selected</span>
101
+ <CpButtonToggle v-bind="args" :is-selected="isSelected" @click="isSelected = !isSelected">
102
+ <template #leading>
103
+ <CpSwitch v-model="isSelected" inert :disabled="args.disabled" />
104
+ </template>
105
+ </CpButtonToggle>
106
+ </div>
107
+ </div>
108
+ `,
109
+ }),
110
+ }
111
+
112
+ export const CustomLeadingSlot: Story = {
113
+ parameters: { controls: { disable: true } },
114
+ render: () => ({
115
+ components: { CpButtonToggle },
116
+ template: `
117
+ <div>
118
+ <CpButtonToggle label="Title" :is-selected="true">
119
+ <template #leading>
120
+ <span style="
121
+ width: 16px;
122
+ height: 16px;
123
+ display: inline-flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ border-radius: 4px;
127
+ background: var(--cp-background-accent-solid);
128
+ color: var(--cp-foreground-white);
129
+ font-size: 11px;
130
+ line-height: 1;
131
+ ">✓</span>
132
+ </template>
133
+ </CpButtonToggle>
134
+ </div>
135
+ `,
136
+ }),
137
+ }