@carbon/vue 3.0.4-alpha.0 → 3.0.5-alpha.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.
@@ -0,0 +1,217 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ 'cv-text-input',
5
+ `${carbonPrefix}--form-item`,
6
+ `${carbonPrefix}--text-input-wrapper`,
7
+ { [`${carbonPrefix}--password-input-wrapper`]: isPassword },
8
+ ]"
9
+ >
10
+ <label
11
+ :for="cvId"
12
+ :class="[
13
+ `${carbonPrefix}--label`,
14
+ {
15
+ [`${carbonPrefix}--label--disabled`]: $attrs.disabled,
16
+ [`${carbonPrefix}--visually-hidden`]: hideLabel,
17
+ },
18
+ ]"
19
+ >
20
+ {{ label }}
21
+ </label>
22
+ <div
23
+ :class="[
24
+ `${carbonPrefix}--text-input__field-wrapper`,
25
+ { [`${carbonPrefix}--text-input__field-wrapper--warning`]: isWarn },
26
+ ]"
27
+ :data-invalid="isInvalid"
28
+ >
29
+ <WarningFilled16
30
+ v-if="isInvalid"
31
+ :class="`${carbonPrefix}--text-input__invalid-icon`"
32
+ />
33
+ <WarningAltFilled16
34
+ v-if="isWarn"
35
+ :class="`${carbonPrefix}--text-input__invalid-icon ${carbonPrefix}--text-input__invalid-icon--warning`"
36
+ />
37
+ <input
38
+ ref="input"
39
+ :id="cvId"
40
+ :class="[
41
+ `${carbonPrefix}--text-input`,
42
+ {
43
+ [`${carbonPrefix}--text-input--invalid`]: isInvalid,
44
+ [`${carbonPrefix}--text-input--light`]: isLight,
45
+ [`${carbonPrefix}--text-input--warning`]: isWarn,
46
+ [`${carbonPrefix}--password-input`]: isPassword,
47
+ },
48
+ ]"
49
+ v-bind="$attrs"
50
+ :type="dataType"
51
+ :value="modelValue"
52
+ :data-toggle-password-visibility="isPassword"
53
+ @input="$event => $emit('update:modelValue', $event.target.value)"
54
+ />
55
+ <button
56
+ v-if="isPassword"
57
+ :class="[
58
+ `${carbonPrefix}--btn`,
59
+ `${carbonPrefix}--btn--icon-only`,
60
+ `${carbonPrefix}--text-input--password__visibility__toggle`,
61
+ `${carbonPrefix}--tooltip__trigger`,
62
+ `${carbonPrefix}--tooltip--a11y`,
63
+ `${carbonPrefix}--tooltip--bottom`,
64
+ `${carbonPrefix}--tooltip--align-center`,
65
+ { [`${carbonPrefix}--btn--disabled`]: $attrs.disabled },
66
+ ]"
67
+ @click="togglePasswordVisibility"
68
+ type="button"
69
+ :disabled="$attrs.disabled"
70
+ >
71
+ <span :class="`${carbonPrefix}--assistive-text`">
72
+ {{ passwordHideShowLabel }}
73
+ </span>
74
+ <ViewOff16
75
+ v-if="isPasswordVisible"
76
+ :class="`${carbonPrefix}--icon-visibility-off`"
77
+ />
78
+ <View16 v-else :class="`${carbonPrefix}--icon-visibility-on`" />
79
+ </button>
80
+ </div>
81
+ <div v-if="isInvalid" :class="`${carbonPrefix}--form-requirement`">
82
+ <slot name="invalid-message">{{ invalidMessage }}</slot>
83
+ </div>
84
+ <div v-if="isWarn" :class="`${carbonPrefix}--form__requirement`">
85
+ <slot name="warn-text">{{ warnText }}</slot>
86
+ </div>
87
+ <div
88
+ v-if="isHelper"
89
+ :class="[
90
+ `${carbonPrefix}--form__helper-text`,
91
+ { [`${carbonPrefix}--form__helper-text--disabled`]: $attrs.disabled },
92
+ ]"
93
+ >
94
+ <slot name="helper-text">{{ helperText }}</slot>
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script setup>
100
+ import {
101
+ onBeforeMount,
102
+ onBeforeUpdate,
103
+ ref,
104
+ useSlots,
105
+ computed,
106
+ watch,
107
+ nextTick,
108
+ } from 'vue';
109
+ import {
110
+ WarningFilled16,
111
+ WarningAltFilled16,
112
+ ViewOff16,
113
+ View16,
114
+ } from '@carbon/icons-vue';
115
+ import { carbonPrefix } from '../../global/settings';
116
+ import { useCvId, props as propsCvId } from '../../use/cvId';
117
+ import { useIsLight, props as propsTheme } from '../../use/cvTheme';
118
+ import { inputTypes } from './const';
119
+
120
+ const props = defineProps({
121
+ helperText: { type: String, default: undefined },
122
+ hideLabel: { type: Boolean, default: false },
123
+ invalidMessage: { type: String, default: undefined },
124
+ label: String,
125
+ modelValue: String,
126
+ passwordHideLabel: { type: String, default: 'Hide password' },
127
+ passwordShowLabel: { type: String, default: 'Show password' },
128
+ passwordVisible: { type: Boolean, default: undefined },
129
+ type: {
130
+ type: String,
131
+ default: 'text',
132
+ validator: value => inputTypes.has(value),
133
+ },
134
+ warnText: { type: String, default: undefined },
135
+ ...propsCvId,
136
+ ...propsTheme,
137
+ });
138
+
139
+ const cvId = useCvId(props);
140
+
141
+ // DOM Elements
142
+ const input = ref();
143
+ const slots = useSlots();
144
+
145
+ // Data
146
+ const isInvalid = ref(false);
147
+ const isWarn = ref(false);
148
+ const isHelper = ref(false);
149
+ const isLight = useIsLight(props);
150
+ const dataType = ref(props.type);
151
+ const dataPasswordVisible = ref(false);
152
+
153
+ // Computed Values
154
+ const isPassword = computed(() => props.type === 'password');
155
+ const isPasswordVisible = computed(
156
+ () => isPassword.value && dataPasswordVisible.value
157
+ );
158
+ const passwordHideShowLabel = computed(() =>
159
+ isPasswordVisible.value ? props.passwordHideLabel : props.passwordShowLabel
160
+ );
161
+
162
+ // Watchers
163
+ watch(
164
+ () => props.passwordVisible,
165
+ newValue => {
166
+ if (newValue !== dataPasswordVisible.value) {
167
+ togglePasswordVisibility();
168
+ }
169
+ }
170
+ );
171
+ watch(
172
+ () => props.type,
173
+ newValue => {
174
+ dataType.value = newValue;
175
+ }
176
+ );
177
+
178
+ // Methods
179
+ function togglePasswordVisibility() {
180
+ const currentValue = input.value.value;
181
+ dataPasswordVisible.value = !dataPasswordVisible.value;
182
+ dataType.value = dataPasswordVisible.value ? 'text' : 'password';
183
+ nextTick(() => {
184
+ input.value.value = currentValue;
185
+ });
186
+ }
187
+
188
+ function checkSlots() {
189
+ // NOTE: slots is not reactive so needs to be managed on updated
190
+ isInvalid.value = !!(
191
+ props.invalidMessage?.length || slots['invalid-message']
192
+ );
193
+ isWarn.value =
194
+ !isInvalid.value && !!(props.warnText?.length || slots['warn-text']);
195
+ isHelper.value =
196
+ !isInvalid.value &&
197
+ !isWarn.value &&
198
+ !!(props.helperText?.length || slots['helper-text']);
199
+ }
200
+
201
+ // Lifecycle Hooks
202
+ onBeforeMount(() => {
203
+ checkSlots();
204
+ // update initial state for dataPasswordVisible & dataType
205
+ if (isPassword.value && props.passwordVisible) {
206
+ dataPasswordVisible.value = true;
207
+ dataType.value = 'text';
208
+ }
209
+ });
210
+ onBeforeUpdate(checkSlots);
211
+ </script>
212
+
213
+ <script>
214
+ export default {
215
+ inheritAttrs: false,
216
+ };
217
+ </script>
@@ -0,0 +1,2 @@
1
+ // Currently available types for CvTextInput
2
+ export const inputTypes = new Set(['password', 'text']);
@@ -0,0 +1,4 @@
1
+ import CvTextInput from './CvTextInput.vue';
2
+
3
+ export { CvTextInput };
4
+ export default CvTextInput;
@@ -0,0 +1,156 @@
1
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
2
+ import CvTile from './CvTile.vue';
3
+ import CvTileStandard from './CvTileStandard.vue';
4
+ import CvTileClickable from './CvTileClickable.vue';
5
+ import { sbCompPrefix } from '../../global/storybook-utils';
6
+ import { action } from '@storybook/addon-actions';
7
+
8
+ <Meta title={`${sbCompPrefix}/CvTile`} component={CvTile} />
9
+
10
+ export const Template = args => ({
11
+ // Components used in your story `template` are defined in the `components` object
12
+ components: {
13
+ CvTile,
14
+ CvTileStandard,
15
+ CvTileClickable,
16
+ },
17
+ // The story's `args` need to be mapped into the template through the `setup()` method
18
+ setup() {
19
+ return {
20
+ args: {
21
+ light: args.light,
22
+ kind: args.kind,
23
+ disabled: args.disabled,
24
+ expanded: args.expanded,
25
+ tileCollapsedLabel: args.tileCollapsedLabel,
26
+ tileExpandedLabel: args.tileExpandedLabel,
27
+ },
28
+ onChange: action('change'),
29
+ onExpanded: action('expanded'),
30
+ onClick: action('click'),
31
+ };
32
+ },
33
+ // And then the `args` are bound to your component with `v-bind="args"`
34
+ template: args.template,
35
+ });
36
+ const defaultTemplate = `<cv-tile v-bind='args'>Hello!</cv-tile>`;
37
+ const defaultCode = defaultTemplate.replace("v-bind='args'", '');
38
+ const expandableTemplate = `<cv-tile v-bind='args' kind="expandable" @expanded="onExpanded">Hello expandable!<template #below><h1>More Content</h1><p>Expanded</p></template></cv-tile>`;
39
+ const expandableCode = expandableTemplate.replace("v-bind='args'", '');
40
+ const clickableTemplate = `<cv-tile v-bind='args' kind="clickable" @click="onClick" to="{name: 'something'}">Hello clickable!</cv-tile>`;
41
+ const clickableCode = clickableTemplate.replace("v-bind='args'", '');
42
+ const selectableTemplate = `<cv-tile v-bind='args' kind="selectable" value="my-selection" @change="onChange">Hello selectable! </cv-tile>`;
43
+ const selectableCode = selectableTemplate.replace("v-bind='args'", '');
44
+
45
+ # CvTile
46
+
47
+ **Migration notes:**
48
+
49
+ - The `light` and '`theme` options continue to be supported but the boolean `light` is preferred.
50
+ - The expandable tile now emits a `expanded` event with a Boolean value.
51
+
52
+ <Canvas>
53
+ <Story
54
+ name="Default"
55
+ parameters={{
56
+ controls: {
57
+ exclude: [
58
+ 'default',
59
+ 'selected',
60
+ 'template',
61
+ 'data',
62
+ 'kind',
63
+ 'expanded',
64
+ 'tileExpandedLabel',
65
+ 'tileCollapsedLabel',
66
+ ],
67
+ },
68
+ docs: { source: { code: defaultCode } },
69
+ }}
70
+ args={{
71
+ kind: '',
72
+ light: false,
73
+ disabled: false,
74
+ template: defaultTemplate,
75
+ }}
76
+ >
77
+ {Template.bind({})}
78
+ </Story>
79
+ </Canvas>
80
+
81
+ <Canvas>
82
+ <Story
83
+ name="Expandable"
84
+ parameters={{
85
+ controls: {
86
+ exclude: ['default', 'selected', 'template', 'data', 'kind', 'slots'],
87
+ },
88
+ docs: { source: { code: expandableCode } },
89
+ }}
90
+ args={{
91
+ light: false,
92
+ disabled: false,
93
+ template: expandableTemplate,
94
+ }}
95
+ >
96
+ {Template.bind({})}
97
+ </Story>
98
+ </Canvas>
99
+
100
+ <Canvas>
101
+ <Story
102
+ name="Clickable"
103
+ parameters={{
104
+ controls: {
105
+ exclude: [
106
+ 'default',
107
+ 'selected',
108
+ 'template',
109
+ 'data',
110
+ 'kind',
111
+ 'slots',
112
+ 'expanded',
113
+ 'tileExpandedLabel',
114
+ 'tileCollapsedLabel',
115
+ ],
116
+ },
117
+ docs: { source: { code: clickableCode } },
118
+ }}
119
+ args={{
120
+ light: false,
121
+ disabled: false,
122
+ template: clickableTemplate,
123
+ }}
124
+ >
125
+ {Template.bind({})}
126
+ </Story>
127
+ </Canvas>
128
+
129
+ <Canvas>
130
+ <Story
131
+ name="Selectable"
132
+ parameters={{
133
+ controls: {
134
+ exclude: [
135
+ 'default',
136
+ 'selected',
137
+ 'template',
138
+ 'data',
139
+ 'kind',
140
+ 'slots',
141
+ 'expanded',
142
+ 'tileExpandedLabel',
143
+ 'tileCollapsedLabel',
144
+ ],
145
+ },
146
+ docs: { source: { code: selectableCode } },
147
+ }}
148
+ args={{
149
+ light: false,
150
+ disabled: false,
151
+ template: selectableTemplate,
152
+ }}
153
+ >
154
+ {Template.bind({})}
155
+ </Story>
156
+ </Canvas>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <component
3
+ :is="tagType"
4
+ :checked="checkProp('checked', selected)"
5
+ :expanded="checkProp('expanded', expanded)"
6
+ :tileCollapsedLabel="checkProp('tileCollapsedLabel', tileCollapsedLabel)"
7
+ :tileExpandedLabel="checkProp('tileCollapsedLabel', tileExpandedLabel)"
8
+ :class="[
9
+ `cv-tile ${carbonPrefix}--tile`,
10
+ { [`${carbonPrefix}--tile--light`]: isLight },
11
+ ]"
12
+ >
13
+ <template v-for="(_, name) in $slots" v-slot:[name]="slotData"
14
+ ><slot :name="name" v-bind="slotData"
15
+ /></template>
16
+ </component>
17
+ </template>
18
+
19
+ <script setup>
20
+ import { computed } from 'vue';
21
+ import { carbonPrefix } from '../../global/settings';
22
+ import { props as propsTheme, useIsLight } from '../../use/cvTheme';
23
+ import CvTileStandard from './CvTileStandard.vue';
24
+ import CvTileClickable from './CvTileClickable.vue';
25
+ import CvTileSelectable from './CvTileSelectable.vue';
26
+ import CvTileExpandable from './CvTileExpandable.vue';
27
+
28
+ const props = defineProps({
29
+ expanded: Boolean,
30
+ selected: Boolean,
31
+ tileCollapsedLabel: { type: String, default: 'Tile collapsed' },
32
+ tileExpandedLabel: { type: String, default: 'Tile expanded' },
33
+ kind: {
34
+ type: String,
35
+ default: '',
36
+ validator: value =>
37
+ ['clickable', 'expandable', 'selectable', 'standard', ''].includes(value),
38
+ },
39
+ ...propsTheme,
40
+ });
41
+
42
+ const tagType = computed(() => {
43
+ switch (props.kind) {
44
+ case 'clickable':
45
+ return CvTileClickable;
46
+ case 'selectable':
47
+ return CvTileSelectable;
48
+ case 'expandable':
49
+ return CvTileExpandable;
50
+ default:
51
+ return CvTileStandard;
52
+ }
53
+ });
54
+ const isLight = useIsLight(props);
55
+
56
+ /**
57
+ * If the prop is defined on the tagType pass it along otherwise, discard it.
58
+ * @param {string} prop
59
+ * @param {any} value
60
+ * @returns {*|undefined}
61
+ */
62
+ function checkProp(prop, value) {
63
+ return prop in (tagType.value.props || {}) ? value : undefined;
64
+ }
65
+ </script>
66
+
67
+ <style scoped></style>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <component
3
+ :is="tagType"
4
+ ref="target"
5
+ data-tile="clickable"
6
+ :class="`cv-tile-clickable ${carbonPrefix}--tile--clickable`"
7
+ v-bind="{ ...$attrs, ...linkProps }"
8
+ tabindex="0"
9
+ >
10
+ <slot></slot>
11
+ </component>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { carbonPrefix } from '../../global/settings';
16
+ import { props as propsLink, useLinkProps, useTagType } from '../../use/cvLink';
17
+
18
+ const props = defineProps({
19
+ ...propsLink,
20
+ });
21
+ const tagType = useTagType(props);
22
+ const linkProps = useLinkProps(props);
23
+ </script>
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <button
3
+ type="button"
4
+ :style="data.styleObject"
5
+ @click="toggle"
6
+ :class="[
7
+ `cv-tile-expandable ${carbonPrefix}--tile--expandable`,
8
+ { [`${carbonPrefix}--tile--is-expanded`]: data.internalExpanded },
9
+ ]"
10
+ ref="el"
11
+ >
12
+ <div :class="`${carbonPrefix}--tile-content`">
13
+ <span
14
+ data-tile-atf
15
+ :class="`${carbonPrefix}--tile-content__above-the-fold`"
16
+ ref="aboveFold"
17
+ >
18
+ <slot>
19
+ <!-- Above the fold content here -->
20
+ </slot>
21
+ </span>
22
+ <div :class="`${carbonPrefix}--tile__chevron`">
23
+ <span>{{ chevronLabel }}</span>
24
+ <ChevronDown16 />
25
+ </div>
26
+ <span
27
+ :class="`${carbonPrefix}--tile-content__below-the-fold`"
28
+ ref="belowFold"
29
+ v-show="data.internalExpanded || data.initialized"
30
+ >
31
+ <slot name="below">
32
+ <!-- Rest of the content here -->
33
+ </slot>
34
+ </span>
35
+ </div>
36
+ </button>
37
+ </template>
38
+
39
+ <script setup>
40
+ import { carbonPrefix } from '../../global/settings';
41
+ import ChevronDown16 from '@carbon/icons-vue/es/chevron--down/16';
42
+ import { computed, nextTick, reactive, ref, watch } from 'vue';
43
+
44
+ const props = defineProps({
45
+ expanded: Boolean,
46
+ tileCollapsedLabel: String,
47
+ tileExpandedLabel: String,
48
+ });
49
+ const data = reactive({
50
+ styleObject: {
51
+ maxHeight: 'initial',
52
+ },
53
+ initialized: false,
54
+ internalExpanded: props.expanded,
55
+ });
56
+
57
+ watch(
58
+ () => props.expanded,
59
+ val => {
60
+ if (val !== data.internalExpanded) {
61
+ toggle(val);
62
+ }
63
+ }
64
+ );
65
+
66
+ const chevronLabel = computed(() => {
67
+ return data.internalExpanded
68
+ ? props.tileExpandedLabel
69
+ : props.tileCollapsedLabel;
70
+ });
71
+
72
+ const el = ref(null);
73
+ const belowFold = ref(null);
74
+ function toggle(force) {
75
+ let currentHeight = el?.value.getBoundingClientRect().height;
76
+ if (!data.initialized) {
77
+ data.styleObject.maxHeight = `${currentHeight}px`;
78
+ data.initialized = true;
79
+ }
80
+
81
+ nextTick(() => {
82
+ const forceType = typeof force;
83
+ data.internalExpanded =
84
+ forceType === 'boolean' ? force : !data.internalExpanded;
85
+
86
+ const belowFoldHeight = belowFold?.value.getBoundingClientRect().height;
87
+
88
+ if (data.internalExpanded) {
89
+ currentHeight += belowFoldHeight;
90
+ } else {
91
+ currentHeight -= belowFoldHeight;
92
+ }
93
+ data.styleObject.maxHeight = `${currentHeight}px`;
94
+ });
95
+ }
96
+ const emit = defineEmits(['expanded']);
97
+ watch(
98
+ () => data.internalExpanded,
99
+ val => {
100
+ emit('expanded', val);
101
+ }
102
+ );
103
+ </script>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <label
3
+ :for="uid"
4
+ :aria-label="ariaLabel"
5
+ :class="[
6
+ `cv-tile-selectable ${carbonPrefix}--tile--selectable`,
7
+ { [`${carbonPrefix}--tile--is-selected`]: isChecked },
8
+ ]"
9
+ data-tile="selectable"
10
+ tabindex="0"
11
+ :data-contained-checkbox-state="isChecked"
12
+ >
13
+ <input
14
+ tabindex="-1"
15
+ data-tile-input
16
+ :id="uid"
17
+ type="checkbox"
18
+ :checked="isChecked === true"
19
+ :aria-checked="`${isChecked}`"
20
+ :class="`${carbonPrefix}--tile-input`"
21
+ v-bind="$attrs"
22
+ @change="onChange"
23
+ />
24
+ <div :class="`${carbonPrefix}--tile__checkmark`">
25
+ <CheckmarkFilled16 />
26
+ </div>
27
+ <div :class="`${carbonPrefix}--tile-content`">
28
+ <slot>
29
+ <!-- Tile content here -->
30
+ </slot>
31
+ </div>
32
+ </label>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { carbonPrefix } from '../../global/settings';
37
+ import { props as propsCvCheck, useCheck } from '../../use/cvCheck';
38
+ import { props as propsCvId, useCvId } from '../../use/cvId';
39
+ import CheckmarkFilled16 from '@carbon/icons-vue/es/checkmark--filled/16';
40
+ import { toRefs } from 'vue';
41
+
42
+ const props = defineProps({
43
+ ariaLabel: { type: String, default: 'tile' },
44
+ ...propsCvCheck,
45
+ ...propsCvId,
46
+ });
47
+
48
+ const uid = useCvId(props, true);
49
+
50
+ const emit = defineEmits(['update:modelValue', 'change']);
51
+ const { onChange, isChecked } = useCheck(toRefs(props), emit);
52
+ </script>
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <div class="cv-tile-standard">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup></script>
@@ -0,0 +1,4 @@
1
+ import CvTile from './CvTile.vue';
2
+
3
+ export { CvTile };
4
+ export default CvTile;