@propelinc/citrus-ui 0.1.1 → 0.2.1
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/dist/citrus-ui.common.js +1149 -187
- package/dist/citrus-ui.common.js.map +1 -1
- package/dist/citrus-ui.css +1 -1
- package/dist/citrus-ui.umd.js +1149 -187
- package/dist/citrus-ui.umd.js.map +1 -1
- package/dist/citrus-ui.umd.min.js +4 -4
- package/dist/citrus-ui.umd.min.js.map +1 -1
- package/dist/fonts/Blitz-Script.85ed9abe.woff2 +0 -0
- package/jest.config.js +5 -0
- package/package.json +4 -3
- package/src/assets/fonts/Blitz-Script.woff2 +0 -0
- package/src/components/CButton.vue +68 -22
- package/src/components/CSegmentedButton.vue +46 -0
- package/src/components/CSegmentedButtonOption.vue +45 -0
- package/src/components/CSelect.vue +67 -0
- package/src/components/CTextField.vue +15 -62
- package/src/components/helpers/FormField.vue +40 -0
- package/src/components/helpers/SelectInput.vue +112 -0
- package/src/index.d.ts +3 -0
- package/src/index.ts +6 -0
- package/src/styles/blitz.less +8 -0
- package/src/styles/core.less +1 -0
- package/src/styles/form-fields.less +46 -0
- package/src/styles/typography.less +8 -0
- package/src/styles/variables.less +8 -0
- package/storybook-static/0.0ebc4b6e.iframe.bundle.js +1 -0
- package/storybook-static/0.1db6407b6f251cb99757.manager.bundle.js +1 -0
- package/storybook-static/1.08e48cd2b295b33abac5.manager.bundle.js +1 -0
- package/storybook-static/10.58b60afe35d013b05f26.manager.bundle.js +1 -0
- package/storybook-static/11.7f35c06cd77f40a88403.manager.bundle.js +2 -0
- package/storybook-static/11.7f35c06cd77f40a88403.manager.bundle.js.LICENSE.txt +12 -0
- package/storybook-static/12.2f8d9cc3bdcfbb59dabf.manager.bundle.js +1 -0
- package/storybook-static/4.99a2e548.iframe.bundle.js +3 -0
- package/storybook-static/4.99a2e548.iframe.bundle.js.LICENSE.txt +8 -0
- package/storybook-static/4.99a2e548.iframe.bundle.js.map +1 -0
- package/storybook-static/5.146a98dc.iframe.bundle.js +1 -0
- package/storybook-static/5.4d5248d1e985d19512e5.manager.bundle.js +2 -0
- package/storybook-static/5.4d5248d1e985d19512e5.manager.bundle.js.LICENSE.txt +8 -0
- package/storybook-static/6.297d139ca80380169c9a.manager.bundle.js +1 -0
- package/storybook-static/6.4c77ef89.iframe.bundle.js +3 -0
- package/storybook-static/6.4c77ef89.iframe.bundle.js.LICENSE.txt +12 -0
- package/storybook-static/6.4c77ef89.iframe.bundle.js.map +1 -0
- package/storybook-static/7.1dd6103d8c4adf231b89.manager.bundle.js +1 -0
- package/storybook-static/7.414cf1e4.iframe.bundle.js +1 -0
- package/storybook-static/8.0c32e96f86b87a58d415.manager.bundle.js +1 -0
- package/storybook-static/9.da52dd791e7234e0495a.manager.bundle.js +1 -0
- package/storybook-static/css/main.918ac2bc.css +1 -0
- package/storybook-static/css/vendors~main.ec1b1c6e.css +1 -0
- package/storybook-static/favicon.ico +0 -0
- package/storybook-static/fonts/ObjectSans-Black.314082dd.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-BlackSlanted.d3163c50.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-Bold.5492f3d5.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-BoldSlanted.29e2a87e.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-Heavy.d0b2f035.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-HeavySlanted.45e9c063.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-Regular.e4ea0b90.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-Slanted.57a90be9.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-Thin.86d44227.woff2 +0 -0
- package/storybook-static/fonts/ObjectSans-ThinSlanted.20342160.woff2 +0 -0
- package/storybook-static/iframe.html +133 -0
- package/storybook-static/index.html +47 -0
- package/storybook-static/main.4c454d37.iframe.bundle.js +1 -0
- package/storybook-static/main.57da3f9fe16e02557812.manager.bundle.js +1 -0
- package/storybook-static/runtime~main.335986b66fb5bcf65138.manager.bundle.js +1 -0
- package/storybook-static/runtime~main.b29ddaa7.iframe.bundle.js +1 -0
- package/storybook-static/vendors~main.0b7555aaeb5d052c07aa.manager.bundle.js +2 -0
- package/storybook-static/vendors~main.0b7555aaeb5d052c07aa.manager.bundle.js.LICENSE.txt +88 -0
- package/storybook-static/vendors~main.4d3a0ad4.iframe.bundle.js +3 -0
- package/storybook-static/vendors~main.4d3a0ad4.iframe.bundle.js.LICENSE.txt +128 -0
- package/storybook-static/vendors~main.4d3a0ad4.iframe.bundle.js.map +1 -0
- package/tsconfig.json +2 -1
|
Binary file
|
package/jest.config.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
|
|
3
3
|
setupFilesAfterEnv: ['<rootDir>/tests/unit/helpers/custom-matchers.ts'],
|
|
4
|
+
|
|
5
|
+
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vuetify/lib)'],
|
|
6
|
+
moduleNameMapper: {
|
|
7
|
+
'^.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
|
|
8
|
+
},
|
|
4
9
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@propelinc/citrus-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/propelinc/citrus-ui"
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
"lint": "npm run lint:js && npm run lint:css",
|
|
13
13
|
"lint:css": "stylelint \"src/**/*.(vue|less)\"",
|
|
14
14
|
"lint:js": "vue-cli-service lint",
|
|
15
|
-
"
|
|
16
|
-
"storybook:
|
|
15
|
+
"publish": "npm run build:dist && npm publish",
|
|
16
|
+
"storybook:build": "vue-cli-service storybook:build -c .storybook",
|
|
17
|
+
"storybook:serve": "vue-cli-service storybook:serve -p 6006 -c .storybook",
|
|
17
18
|
"storybook:publish": "storybook-to-aws-s3 --script=\"storybook:build\" --bucket-path=citrus-ui-storybook",
|
|
18
19
|
"test:unit": "vue-cli-service test:unit"
|
|
19
20
|
},
|
|
Binary file
|
|
@@ -2,18 +2,23 @@
|
|
|
2
2
|
<v-btn
|
|
3
3
|
class="button"
|
|
4
4
|
:class="{
|
|
5
|
+
'button--primary': !secondary && !tertiary,
|
|
5
6
|
'button--secondary': secondary,
|
|
7
|
+
'button--tertiary': tertiary,
|
|
8
|
+
'button--dark': dark,
|
|
9
|
+
'button--light': light,
|
|
6
10
|
'button--large': large,
|
|
7
|
-
|
|
11
|
+
'button--disabled': disabled,
|
|
8
12
|
}"
|
|
9
13
|
data-test="button"
|
|
10
14
|
rounded
|
|
11
15
|
:block="block"
|
|
12
16
|
:elevation="0"
|
|
13
|
-
:color="colorScheme.container"
|
|
14
17
|
:large="large"
|
|
15
18
|
:ripple="false"
|
|
16
19
|
:to="to"
|
|
20
|
+
:text="tertiary"
|
|
21
|
+
:disabled="disabled"
|
|
17
22
|
v-on="$listeners"
|
|
18
23
|
>
|
|
19
24
|
<div v-if="hasIcon" class="button__icon">
|
|
@@ -32,30 +37,18 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
|
|
32
37
|
import { Component, Vue, Prop } from 'vue-property-decorator';
|
|
33
38
|
import { RawLocation } from 'vue-router';
|
|
34
39
|
|
|
35
|
-
interface ColorScheme {
|
|
36
|
-
text: string;
|
|
37
|
-
container: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
40
|
@Component({ name: 'CButton', components: { FontAwesomeIcon } })
|
|
41
|
-
export default class
|
|
42
|
-
@Prop({ type: Boolean, default: false }) dark!: boolean;
|
|
41
|
+
export default class CButton extends Vue {
|
|
43
42
|
@Prop({ type: Boolean, default: false }) block!: boolean;
|
|
43
|
+
@Prop({ type: Boolean, default: false }) dark!: boolean;
|
|
44
|
+
@Prop({ type: Boolean, default: false }) disabled!: boolean;
|
|
45
|
+
@Prop([String, Array, Object]) icon?: IconDefinition;
|
|
44
46
|
@Prop({ type: Boolean, default: false }) large!: boolean;
|
|
47
|
+
@Prop({ type: Boolean, default: false }) light!: boolean;
|
|
45
48
|
@Prop({ type: Boolean, default: false }) secondary!: boolean;
|
|
46
|
-
@Prop(
|
|
49
|
+
@Prop({ type: Boolean, default: false }) tertiary!: boolean;
|
|
47
50
|
@Prop([Object, String]) to?: RawLocation;
|
|
48
51
|
|
|
49
|
-
get colorScheme(): ColorScheme {
|
|
50
|
-
if (this.dark) {
|
|
51
|
-
return { text: 'white', container: 'navy' };
|
|
52
|
-
} else if (this.secondary) {
|
|
53
|
-
return { text: 'primary', container: 'white' };
|
|
54
|
-
} else {
|
|
55
|
-
return { text: 'white', container: 'primary' };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
52
|
get hasIcon(): boolean {
|
|
60
53
|
return !!(this.icon || this.$slots.icon || this.$scopedSlots.icon);
|
|
61
54
|
}
|
|
@@ -75,12 +68,65 @@ export default class Button extends Vue {
|
|
|
75
68
|
}
|
|
76
69
|
}
|
|
77
70
|
|
|
78
|
-
.button--
|
|
79
|
-
|
|
71
|
+
.button--primary {
|
|
72
|
+
background-color: @color-blue-accent !important;
|
|
73
|
+
color: @color-white;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.button--primary.button--dark {
|
|
77
|
+
background-color: @color-navy !important;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.button--primary.button--light {
|
|
81
|
+
background-color: @color-white !important;
|
|
82
|
+
color: @color-navy;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
.button--secondary {
|
|
83
86
|
border: @border !important;
|
|
87
|
+
color: @color-blue-accent;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.button--secondary.button--dark {
|
|
91
|
+
color: @color-navy;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.button--secondary.button--light {
|
|
95
|
+
background-color: @color-navy !important;
|
|
96
|
+
color: @color-white;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.button--tertiary {
|
|
100
|
+
color: @color-blue-accent;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.button--tertiary.button--dark {
|
|
104
|
+
color: @color-navy;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.button--tertiary.button--light {
|
|
108
|
+
color: @color-white;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Add some insane specificity to override Vuetify.
|
|
112
|
+
.theme--light.v-btn.v-btn--disabled:not(.v-btn--outlined) {
|
|
113
|
+
&.button--disabled.button--primary {
|
|
114
|
+
background-color: @color-navy-tint-4 !important;
|
|
115
|
+
color: @color-navy-tint-1 !important;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&.button--disabled.button--secondary {
|
|
119
|
+
background-color: @color-white !important;
|
|
120
|
+
color: @color-navy-tint-2 !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&.button--disabled.button--tertiary {
|
|
124
|
+
color: @color-navy-tint-2 !important;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.button--large {
|
|
129
|
+
font-size: 16px;
|
|
84
130
|
}
|
|
85
131
|
|
|
86
132
|
.button__icon {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-btn-toggle
|
|
3
|
+
data-test="segmented-button"
|
|
4
|
+
:value="value"
|
|
5
|
+
rounded
|
|
6
|
+
class="segmented-button"
|
|
7
|
+
active-class="segmented-button__option--active"
|
|
8
|
+
@change="(value) => $emit('change', value)"
|
|
9
|
+
>
|
|
10
|
+
<slot>
|
|
11
|
+
<c-segmented-button-option
|
|
12
|
+
v-for="option in options"
|
|
13
|
+
:key="option.label"
|
|
14
|
+
:value="option.value"
|
|
15
|
+
:label="option.label"
|
|
16
|
+
/>
|
|
17
|
+
</slot>
|
|
18
|
+
</v-btn-toggle>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
import { Component, Vue, Prop } from 'vue-property-decorator';
|
|
23
|
+
|
|
24
|
+
import CSegmentedButtonOption from '@/components/CSegmentedButtonOption.vue';
|
|
25
|
+
|
|
26
|
+
interface SegmentedButtonOption {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
value: any;
|
|
29
|
+
label: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Component({ name: 'CSegmentedButton', components: { CSegmentedButtonOption } })
|
|
33
|
+
export default class CSegmentedButton extends Vue {
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
@Prop() value?: any;
|
|
36
|
+
@Prop({ type: Array, required: true }) options!: SegmentedButtonOption[];
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<style lang="less" scoped>
|
|
41
|
+
@import '~@/styles/variables.less';
|
|
42
|
+
|
|
43
|
+
.segmented-button {
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-btn
|
|
3
|
+
class="segmented-button__option"
|
|
4
|
+
data-test="segmented-button-option"
|
|
5
|
+
:ripple="false"
|
|
6
|
+
:value="value"
|
|
7
|
+
>
|
|
8
|
+
<slot>
|
|
9
|
+
{{ label }}
|
|
10
|
+
</slot>
|
|
11
|
+
</v-btn>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
import { Component, Vue, Prop } from 'vue-property-decorator';
|
|
16
|
+
|
|
17
|
+
@Component({ name: 'CSegmentedButtonOption' })
|
|
18
|
+
export default class CSegmentedButtonOption extends Vue {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
@Prop({ required: true }) value?: any;
|
|
21
|
+
@Prop(String) label?: string;
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style lang="less" scoped>
|
|
26
|
+
@import '~@/styles/variables.less';
|
|
27
|
+
|
|
28
|
+
.segmented-button__option {
|
|
29
|
+
border: @border !important;
|
|
30
|
+
color: @color-navy;
|
|
31
|
+
flex: 1 1 0;
|
|
32
|
+
font-weight: @font-weight-heavy;
|
|
33
|
+
letter-spacing: 0;
|
|
34
|
+
text-transform: none;
|
|
35
|
+
|
|
36
|
+
&::before {
|
|
37
|
+
color: transparent;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// This class is applied by the CSegmentedButton component.
|
|
42
|
+
.segmented-button__option--active {
|
|
43
|
+
background-color: @color-blue !important;
|
|
44
|
+
}
|
|
45
|
+
</style>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<form-field :field-id="id" :disabled="disabled" :label="label">
|
|
3
|
+
<select-input
|
|
4
|
+
:id="id"
|
|
5
|
+
v-bind="$attrs"
|
|
6
|
+
data-test="select-field"
|
|
7
|
+
class="select__field"
|
|
8
|
+
:class="{ 'select__field--disabled': disabled }"
|
|
9
|
+
outlined
|
|
10
|
+
single-line
|
|
11
|
+
:placeholder="placeholder"
|
|
12
|
+
:disabled="disabled"
|
|
13
|
+
:value="value"
|
|
14
|
+
:rules="rules"
|
|
15
|
+
:items="items"
|
|
16
|
+
validate-on-blur
|
|
17
|
+
hide-details="auto"
|
|
18
|
+
@input="(value) => $emit('input', value)"
|
|
19
|
+
>
|
|
20
|
+
<template #append>
|
|
21
|
+
<font-awesome-icon
|
|
22
|
+
v-if="loading"
|
|
23
|
+
data-test="select-field-icon-loading"
|
|
24
|
+
:icon="faSpinner"
|
|
25
|
+
class="fa-spin"
|
|
26
|
+
/>
|
|
27
|
+
<font-awesome-icon v-else data-test="select-field-icon" :icon="faChevronDown" />
|
|
28
|
+
</template>
|
|
29
|
+
</select-input>
|
|
30
|
+
</form-field>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script lang="ts">
|
|
34
|
+
import { faChevronDown, faSpinner } from '@fortawesome/pro-light-svg-icons';
|
|
35
|
+
import { Component, Vue, Prop } from 'vue-property-decorator';
|
|
36
|
+
|
|
37
|
+
import FormField from '@/components/helpers/FormField.vue';
|
|
38
|
+
import SelectInput from '@/components/helpers/SelectInput.vue';
|
|
39
|
+
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
@Component({ name: 'CSelect', components: { FormField, SelectInput } })
|
|
42
|
+
export default class CSelect extends Vue {
|
|
43
|
+
faChevronDown = faChevronDown;
|
|
44
|
+
faSpinner = faSpinner;
|
|
45
|
+
|
|
46
|
+
@Prop(String) label?: string;
|
|
47
|
+
@Prop(String) placeholder?: string;
|
|
48
|
+
@Prop({ type: String, required: true }) id!: string;
|
|
49
|
+
@Prop({ type: Array, default: () => [] }) items!: { label: string; value: string }[];
|
|
50
|
+
@Prop({ type: Boolean, default: false }) disabled!: boolean;
|
|
51
|
+
@Prop({ type: String, default: '' }) value!: string;
|
|
52
|
+
@Prop({ type: Boolean, default: false }) loading!: boolean;
|
|
53
|
+
@Prop({ type: Array, default: () => [] }) rules!: ((value: string) => string | boolean)[];
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style lang="less" scoped>
|
|
58
|
+
@import '~@/styles/form-fields.less';
|
|
59
|
+
|
|
60
|
+
.select__field {
|
|
61
|
+
.form-field();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.select__field--disabled {
|
|
65
|
+
.form-field-disabled();
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<label
|
|
4
|
-
v-if="label"
|
|
5
|
-
:for="id"
|
|
6
|
-
data-test="text-field-label"
|
|
7
|
-
class="text-field__label"
|
|
8
|
-
:class="{ 'text-field__label--disabled': disabled }"
|
|
9
|
-
>
|
|
10
|
-
{{ label }}
|
|
11
|
-
</label>
|
|
12
|
-
|
|
2
|
+
<form-field :field-id="id" :disabled="disabled" :label="label">
|
|
13
3
|
<v-text-field
|
|
14
4
|
:id="id"
|
|
15
5
|
v-bind="$attrs"
|
|
@@ -26,76 +16,39 @@
|
|
|
26
16
|
validate-on-blur
|
|
27
17
|
hide-details="auto"
|
|
28
18
|
@input="(value) => $emit('input', value)"
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
>
|
|
20
|
+
<template #append>
|
|
21
|
+
<slot name="append" />
|
|
22
|
+
</template>
|
|
23
|
+
</v-text-field>
|
|
24
|
+
</form-field>
|
|
31
25
|
</template>
|
|
32
26
|
|
|
33
27
|
<script lang="ts">
|
|
34
28
|
import { Component, Vue, Prop } from 'vue-property-decorator';
|
|
35
29
|
|
|
36
|
-
|
|
30
|
+
import FormField from '@/components/helpers/FormField.vue';
|
|
31
|
+
|
|
32
|
+
@Component({ name: 'CTextField', components: { FormField } })
|
|
37
33
|
export default class CTextField extends Vue {
|
|
38
34
|
@Prop(String) label?: string;
|
|
39
35
|
@Prop(String) placeholder?: string;
|
|
40
36
|
@Prop({ type: String, required: true }) id!: string;
|
|
41
37
|
@Prop({ type: String, default: 'text' }) type!: string;
|
|
42
38
|
@Prop({ type: Boolean, default: false }) disabled!: boolean;
|
|
43
|
-
@Prop({ type: String, default: '' }) value!:
|
|
44
|
-
@Prop(Array) rules
|
|
39
|
+
@Prop({ type: String, default: '' }) value!: string;
|
|
40
|
+
@Prop({ type: Array, default: () => [] }) rules!: ((value: string) => string | boolean)[];
|
|
45
41
|
}
|
|
46
42
|
</script>
|
|
47
43
|
|
|
48
44
|
<style lang="less" scoped>
|
|
49
|
-
@import '~@/styles/
|
|
50
|
-
|
|
51
|
-
@color-disabled-text: @color-navy-tint-2;
|
|
52
|
-
|
|
53
|
-
.text-field__label {
|
|
54
|
-
font-weight: @font-weight-bold;
|
|
55
|
-
margin: 0 0 4px 2px;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.text-field__label--disabled {
|
|
59
|
-
color: @color-disabled-text;
|
|
60
|
-
}
|
|
45
|
+
@import '~@/styles/form-fields.less';
|
|
61
46
|
|
|
62
47
|
.text-field__field {
|
|
63
|
-
|
|
64
|
-
display: none;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
&.v-input--is-focused /deep/ fieldset,
|
|
68
|
-
&.v-input--has-state /deep/ fieldset {
|
|
69
|
-
border-color: currentColor;
|
|
70
|
-
border-width: 1px;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
& /deep/ .v-input__slot {
|
|
74
|
-
min-height: 48px;
|
|
75
|
-
padding: 0 16px !important; // Override Vuetify.
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
& /deep/ input {
|
|
79
|
-
.body();
|
|
80
|
-
|
|
81
|
-
line-height: 20px;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/deep/ fieldset {
|
|
86
|
-
background: @color-white;
|
|
87
|
-
border-color: @color-neutral-shade;
|
|
88
|
-
border-radius: @border-radius;
|
|
89
|
-
top: 0;
|
|
48
|
+
.form-field();
|
|
90
49
|
}
|
|
91
50
|
|
|
92
51
|
.text-field__field--disabled {
|
|
93
|
-
|
|
94
|
-
background: @color-neutral-shade;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
& /deep/ input {
|
|
98
|
-
color: @color-disabled-text;
|
|
99
|
-
}
|
|
52
|
+
.form-field-disabled();
|
|
100
53
|
}
|
|
101
54
|
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<fieldset :disabled="disabled">
|
|
3
|
+
<label
|
|
4
|
+
v-if="label"
|
|
5
|
+
:for="fieldId"
|
|
6
|
+
data-test="form-field-label"
|
|
7
|
+
class="form-field__label"
|
|
8
|
+
:class="{ 'form-field__label--disabled': disabled }"
|
|
9
|
+
>
|
|
10
|
+
{{ label }}
|
|
11
|
+
</label>
|
|
12
|
+
|
|
13
|
+
<slot />
|
|
14
|
+
</fieldset>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import { Component, Vue, Prop } from 'vue-property-decorator';
|
|
19
|
+
|
|
20
|
+
@Component({ name: 'FormField' })
|
|
21
|
+
export default class FormField extends Vue {
|
|
22
|
+
@Prop(String) label?: string;
|
|
23
|
+
@Prop({ type: String, required: true }) fieldId!: string;
|
|
24
|
+
@Prop({ type: Boolean, default: false }) disabled!: boolean;
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<style lang="less" scoped>
|
|
29
|
+
@import '~@/styles/variables.less';
|
|
30
|
+
@import '~@/styles/form-fields.less';
|
|
31
|
+
|
|
32
|
+
.form-field__label {
|
|
33
|
+
font-weight: @font-weight-bold;
|
|
34
|
+
margin: 0 0 4px 2px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.form-field__label--disabled {
|
|
38
|
+
color: @color-disabled-text;
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* NOTE(mohan): This component is meant to be an alternative to VSelect that uses native
|
|
6
|
+
* <select/> tags.
|
|
7
|
+
*
|
|
8
|
+
* Because Vuetify uses its implementation of a dropdown that imitates a desktop menu,
|
|
9
|
+
* it's hard to use on mobile. This component replaces the inner <input> of a VTextField
|
|
10
|
+
* with a <select/> so that we can use continue to use all of the nice Vuetify features while also
|
|
11
|
+
* having a native <select/>.
|
|
12
|
+
*
|
|
13
|
+
* See: https://github.com/vuetifyjs/vuetify/issues/4059. */
|
|
14
|
+
import { VTextField } from 'vuetify/lib';
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
name: 'SelectInput',
|
|
18
|
+
extends: VTextField,
|
|
19
|
+
|
|
20
|
+
props: {
|
|
21
|
+
items: {
|
|
22
|
+
type: Array,
|
|
23
|
+
default: () => [],
|
|
24
|
+
},
|
|
25
|
+
placeholder: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: null,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
methods: {
|
|
32
|
+
genInput() {
|
|
33
|
+
const listeners = { ...this.listeners$ };
|
|
34
|
+
delete listeners.change; // Change should not be bound externally
|
|
35
|
+
|
|
36
|
+
return this.$createElement(
|
|
37
|
+
'select',
|
|
38
|
+
{
|
|
39
|
+
style: {},
|
|
40
|
+
domProps: {
|
|
41
|
+
value: this.lazyValue,
|
|
42
|
+
},
|
|
43
|
+
attrs: {
|
|
44
|
+
...this.attrs$,
|
|
45
|
+
autofocus: this.autofocus,
|
|
46
|
+
disabled: this.disabled,
|
|
47
|
+
id: this.computedId,
|
|
48
|
+
readonly: this.readonly,
|
|
49
|
+
},
|
|
50
|
+
class: !this.isDirty ? 'select-input--placeholder' : undefined,
|
|
51
|
+
on: {
|
|
52
|
+
...listeners,
|
|
53
|
+
blur: this.onBlur,
|
|
54
|
+
input: this.onInput,
|
|
55
|
+
focus: this.onFocus,
|
|
56
|
+
keydown: this.onKeyDown,
|
|
57
|
+
},
|
|
58
|
+
ref: 'input',
|
|
59
|
+
},
|
|
60
|
+
this.genOptions()
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
genOptions() {
|
|
65
|
+
let options = this.items.map((item) => {
|
|
66
|
+
return this.$createElement('option', {
|
|
67
|
+
domProps: {
|
|
68
|
+
innerHTML: item.label,
|
|
69
|
+
value: item.value,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
if (this.placeholder) {
|
|
74
|
+
options = [
|
|
75
|
+
this.$createElement('option', {
|
|
76
|
+
domProps: {
|
|
77
|
+
innerHTML: this.placeholder,
|
|
78
|
+
value: '',
|
|
79
|
+
hidden: true,
|
|
80
|
+
disabled: true,
|
|
81
|
+
selected: true,
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
...options,
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
return options;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<style lang="less" scoped>
|
|
94
|
+
@import '~@/styles/variables.less';
|
|
95
|
+
|
|
96
|
+
select {
|
|
97
|
+
color: @color-navy;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
height: 100%;
|
|
100
|
+
position: absolute;
|
|
101
|
+
width: 100%;
|
|
102
|
+
z-index: 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
select:focus {
|
|
106
|
+
outline: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.select-input--placeholder {
|
|
110
|
+
color: @color-navy-tint-3;
|
|
111
|
+
}
|
|
112
|
+
</style>
|
package/src/index.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export const CCard: Component;
|
|
|
10
10
|
export const CIconButton: Component;
|
|
11
11
|
export const CListItem: Component;
|
|
12
12
|
export const CModalLoading: Component;
|
|
13
|
+
export const CSegmentedButton: Component;
|
|
14
|
+
export const CSegmentedButtonOption: Component;
|
|
15
|
+
export const CSelect: Component;
|
|
13
16
|
export const CSkeletonLoaderCircle: Component;
|
|
14
17
|
export const CSkeletonLoaderText: Component;
|
|
15
18
|
export const CTextField: Component;
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,9 @@ import _CCard from '@/components/CCard.vue';
|
|
|
7
7
|
import _CIconButton from '@/components/CIconButton.vue';
|
|
8
8
|
import _CListItem from '@/components/CListItem.vue';
|
|
9
9
|
import _CModalLoading from '@/components/CModalLoading.vue';
|
|
10
|
+
import _CSegmentedButton from '@/components/CSegmentedButton.vue';
|
|
11
|
+
import _CSegmentedButtonOption from '@/components/CSegmentedButtonOption.vue';
|
|
12
|
+
import _CSelect from '@/components/CSelect.vue';
|
|
10
13
|
import _CSkeletonLoaderCircle from '@/components/CSkeletonLoaderCircle.vue';
|
|
11
14
|
import _CSkeletonLoaderText from '@/components/CSkeletonLoaderText.vue';
|
|
12
15
|
import _CTextField from '@/components/CTextField.vue';
|
|
@@ -23,6 +26,9 @@ export const CCard = _CCard;
|
|
|
23
26
|
export const CIconButton = _CIconButton;
|
|
24
27
|
export const CListItem = _CListItem;
|
|
25
28
|
export const CModalLoading = _CModalLoading;
|
|
29
|
+
export const CSegmentedButton = _CSegmentedButton;
|
|
30
|
+
export const CSegmentedButtonOption = _CSegmentedButtonOption;
|
|
31
|
+
export const CSelect = _CSelect;
|
|
26
32
|
export const CSkeletonLoaderCircle = _CSkeletonLoaderCircle;
|
|
27
33
|
export const CSkeletonLoaderText = _CSkeletonLoaderText;
|
|
28
34
|
export const CTextField = _CTextField;
|
package/src/styles/core.less
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
@import '~@/styles/variables.less';
|
|
2
|
+
|
|
3
|
+
@color-disabled-text: @color-navy-tint-2;
|
|
4
|
+
|
|
5
|
+
.form-field() {
|
|
6
|
+
& /deep/ legend {
|
|
7
|
+
display: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
&.v-input--is-focused /deep/ fieldset,
|
|
11
|
+
&.v-input--has-state /deep/ fieldset {
|
|
12
|
+
border-color: currentColor;
|
|
13
|
+
border-width: 1px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
& /deep/ .v-input__slot {
|
|
17
|
+
min-height: 48px;
|
|
18
|
+
padding: 0 16px !important; // Override Vuetify.
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
& /deep/ input,
|
|
22
|
+
& /deep/ select {
|
|
23
|
+
.body();
|
|
24
|
+
|
|
25
|
+
line-height: 20px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/deep/ fieldset {
|
|
29
|
+
background: @color-white;
|
|
30
|
+
border-color: @color-neutral-shade;
|
|
31
|
+
border-radius: @border-radius;
|
|
32
|
+
top: 0;
|
|
33
|
+
z-index: -1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.form-field-disabled() {
|
|
38
|
+
& /deep/ fieldset {
|
|
39
|
+
background: @color-neutral-shade;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
& /deep/ input,
|
|
43
|
+
& /deep/ select {
|
|
44
|
+
color: @color-disabled-text;
|
|
45
|
+
}
|
|
46
|
+
}
|