cja-phoenix 0.3.7 → 0.4.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.
Files changed (31) hide show
  1. package/dist/cja-phoenix.es.js +2112 -2050
  2. package/dist/style.css +1 -1
  3. package/dist/types/components/composite/CjaMenuBar.vue.d.ts +10 -5
  4. package/dist/types/components/forms/NumberInput.vue.d.ts +1 -1
  5. package/dist/types/components/forms/TextInput.vue.d.ts +4 -0
  6. package/dist/types/components/structural/CjaButton.vue.d.ts +2 -0
  7. package/dist/types/components/structural/Modal.vue.d.ts +40 -5
  8. package/dist/types/stories/CjaButton.story.vue.d.ts +2 -0
  9. package/dist/types/stories/ContentTabs.story.vue.d.ts +2 -0
  10. package/dist/types/stories/Modal.story.vue.d.ts +2 -0
  11. package/package.json +1 -1
  12. package/src/assets/breakpoints.scss +12 -0
  13. package/src/assets/forms.scss +3 -3
  14. package/src/assets/main.scss +1 -0
  15. package/src/components/composite/CheckoutCrossSell.vue +2 -0
  16. package/src/components/composite/CheckoutMilestones.vue +4 -2
  17. package/src/components/composite/CjaMenuBar.vue +130 -64
  18. package/src/components/composite/FunnelLayout.vue +7 -5
  19. package/src/components/composite/FunnelTitle.vue +3 -1
  20. package/src/components/forms/TextInput.vue +16 -0
  21. package/src/components/forms/structure/InputError.vue +3 -1
  22. package/src/components/forms/structure/InputTitle.vue +2 -1
  23. package/src/components/structural/CjaButton.vue +37 -15
  24. package/src/components/structural/GridContainer.vue +2 -1
  25. package/src/components/structural/GridItem.vue +3 -1
  26. package/src/components/structural/InfoMessage.vue +5 -3
  27. package/src/components/structural/Modal.vue +76 -63
  28. package/src/stories/CjaButton.story.vue +192 -0
  29. package/src/stories/ContentTabs.story.vue +59 -0
  30. package/src/stories/Modal.story.vue +127 -0
  31. package/src/utils/useViewportDetector.ts +2 -2
@@ -1,19 +1,19 @@
1
1
  <template>
2
2
  <Teleport to="body">
3
3
  <Transition name="fade">
4
- <div v-show="active" class="modal-backdrop">
4
+ <div v-show="active" class="modal-backdrop" :style="{ ...cssVars }">
5
5
  <div class="modal-overlay" @click.self="closeModal">
6
6
  <div class="modal-container" ref="modalContainer">
7
- <button
8
- type="button"
9
- class="btn-close"
10
- aria-label="Close"
11
- @click="closeModal"
12
- >
13
- <span class="m-cgg-icon--cross"></span>
14
- </button>
15
- <div class="modal-header" v-if="title">
16
- {{ title }}
7
+ <div class="modal-header" :class="{ 'has-title': $slots.header }">
8
+ <div class="modal-header-wrapper">
9
+ <slot name="header"></slot>
10
+ </div>
11
+ <button
12
+ type="button"
13
+ class="btn-close m-cgg-icon--cross"
14
+ aria-label="Close"
15
+ @click="closeModal"
16
+ ></button>
17
17
  </div>
18
18
  <div class="modal-body">
19
19
  <slot name="body"></slot>
@@ -29,27 +29,42 @@
29
29
  </template>
30
30
 
31
31
  <script lang="ts" setup>
32
- import { ref, watch } from "vue";
33
-
34
- defineProps<{
35
- title?: string;
36
- }>();
32
+ import { ref, watch, computed } from "vue";
33
+
34
+ const props = withDefaults(
35
+ defineProps<{
36
+ maxWidth?: {
37
+ md?: string;
38
+ lg?: string;
39
+ xl?: string;
40
+ };
41
+ }>(),
42
+ {
43
+ maxWidth: () => ({
44
+ md: "min(calc(100% - 48px), 768px)",
45
+ lg: "min(calc(100% - 48px), 1024px)",
46
+ xl: "1024px",
47
+ }),
48
+ }
49
+ );
37
50
 
38
51
  const active = ref(false);
39
52
  const modalContainer = ref();
40
53
 
54
+ const cssVars = computed(() => ({
55
+ "--md-max-width": props.maxWidth.md,
56
+ "--lg-max-width": props.maxWidth.lg,
57
+ "--xl-max-width": props.maxWidth.xl,
58
+ }));
59
+
41
60
  const emit = defineEmits(["close"]);
42
61
 
43
62
  watch(
44
63
  () => active.value,
45
64
  (active) => {
46
- if (active) {
47
- document.body.style.overflow = "hidden";
48
- document.documentElement.style.overflow = "hidden";
49
- } else {
50
- document.body.style.overflow = "";
51
- document.documentElement.style.overflow = "";
52
- }
65
+ document.querySelectorAll("html, body").forEach((el) => {
66
+ (el as HTMLElement).style.overflow = active ? "hidden" : "";
67
+ });
53
68
  }
54
69
  );
55
70
 
@@ -74,6 +89,8 @@ defineExpose({ active, openModal, closeModal, toggleModal });
74
89
  </script>
75
90
 
76
91
  <style lang="scss" scoped>
92
+ @import "../../assets/breakpoints.scss";
93
+
77
94
  .modal-backdrop {
78
95
  position: fixed;
79
96
  top: 0;
@@ -105,87 +122,83 @@ defineExpose({ active, openModal, closeModal, toggleModal });
105
122
  box-sizing: border-box;
106
123
  display: flex;
107
124
  flex-direction: column;
125
+ gap: 16px;
108
126
 
109
- @media screen and (min-width: 601px) {
127
+ @media screen and (min-width: $break-md-min) {
110
128
  top: 50%;
111
129
  left: 50%;
112
130
  transform: translateY(-50%) translateX(-50%);
113
- max-width: calc(100% - 60px);
114
- max-height: calc(100% - 40px);
131
+ max-width: var(--md-max-width);
132
+ max-height: calc(100% - 48px);
115
133
  border-radius: 8px;
116
134
  padding: 20px 40px;
117
135
  height: auto;
118
136
  }
119
137
 
120
- @media screen and (min-width: 1100px) {
121
- max-width: 1040px;
138
+ @media screen and (min-width: $break-lg-min) {
139
+ max-width: var(--lg-max-width);
140
+ }
141
+
142
+ @media screen and (min-width: $break-xl-min) {
143
+ max-width: var(--xl-max-width);
122
144
  }
123
145
 
124
146
  .modal-header {
125
- font-weight: 700;
126
- font-size: 18px;
127
- line-height: 21px;
128
- padding: 0 20px 15px 0;
129
- border-bottom: 1px solid #dedede;
130
-
131
- @media screen and (min-width: 601px) {
132
- font-size: 24px;
133
- line-height: 27px;
134
- }
147
+ display: grid;
148
+ grid-template-columns: 1fr 20px;
149
+ padding: 0;
135
150
 
136
151
  &:before,
137
152
  &:after {
138
153
  content: none;
139
154
  }
140
- }
141
-
142
- .btn-close {
143
- position: absolute;
144
- top: 20px;
145
- right: 20px;
146
- background: none;
147
- border: none;
148
- padding: 0;
149
- outline: 0;
150
- height: 20px;
151
- cursor: pointer;
152
- z-index: 2;
153
155
 
154
- @media screen and (min-width: 601px) {
155
- right: 40px;
156
+ &.has-title {
157
+ padding: 0 0 8px 0;
158
+ border-bottom: 1px solid #dedede;
156
159
  }
157
160
 
158
- span {
159
- display: block;
161
+ .modal-header-wrapper {
160
162
  font-size: 16px;
161
163
  line-height: 20px;
164
+ font-weight: 700;
162
165
  }
163
166
 
164
- &:focus {
167
+ .btn-close {
168
+ background: none;
169
+ border: none;
170
+ padding: 0;
165
171
  outline: 0;
172
+ height: 20px;
173
+ width: 20px;
174
+ cursor: pointer;
175
+ z-index: 2;
176
+ font-size: 16px;
177
+ line-height: 20px;
178
+
179
+ &:focus {
180
+ outline: 0;
181
+ }
166
182
  }
167
183
  }
168
184
 
169
185
  .modal-body {
170
- margin: 0 -15px -15px;
171
- font-size: 15px;
172
- line-height: 17px;
186
+ font-size: 14px;
187
+ line-height: 18px;
173
188
  overflow-y: auto;
174
189
  flex-grow: 1;
175
190
  height: auto;
176
- padding: 15px;
177
191
  }
178
192
 
179
193
  .modal-footer {
180
194
  border-top: 1px solid #dedede;
181
- padding: 15px 0 0;
182
- margin-top: 15px;
195
+ padding: 8px 0 0;
183
196
 
184
197
  :deep(.button-container) {
185
198
  display: flex;
186
199
  flex-direction: row;
187
200
  justify-content: flex-end;
188
- gap: 10px;
201
+ gap: 8px;
189
202
  }
190
203
  }
191
204
  }
@@ -0,0 +1,192 @@
1
+ <script setup lang="ts">
2
+ import CjaButton from "../components/structural/CjaButton.vue";
3
+ import TextInput from "../components/forms/TextInput.vue";
4
+
5
+ const initState = () => ({
6
+ content: "Button Text",
7
+ });
8
+
9
+ const initStateAllConfigs = () => ({
10
+ content: "Button Text",
11
+ type: "primary" as "primary" | "secondary" | "tertiary",
12
+ color: "blue" as "blue" | "orange" | "white",
13
+ size: "md" as "sm" | "md" | "lg",
14
+ icon: "" as string,
15
+ iconAny: "" as string,
16
+ iconPosition: "right" as "left" | "right",
17
+ loading: false as boolean,
18
+ disabled: false as boolean,
19
+ });
20
+
21
+ const initStateIcon = () => ({
22
+ type: "primary" as "primary" | "secondary" | "tertiary",
23
+ color: "blue" as "blue" | "orange" | "white",
24
+ size: "md" as "sm" | "md" | "lg",
25
+ icon: "m-cgg-icon--gears" as string,
26
+ iconAny: "" as string,
27
+ iconPosition: "right" as "left" | "right",
28
+ loading: false as boolean,
29
+ disabled: false as boolean,
30
+ });
31
+
32
+ const options = {
33
+ type: ["primary", "secondary", "tertiary"],
34
+ color: ["blue", "orange", "white"],
35
+ size: ["sm", "md", "lg"],
36
+ icon: [
37
+ { label: "None", value: "" },
38
+ { label: "Chevron Right", value: "m-cgg-icon--chevron-right" },
39
+ { label: "Chevron Down", value: "m-cgg-icon--chevron-down" },
40
+ { label: "Gears", value: "m-cgg-icon--gears" },
41
+ ],
42
+ iconPosition: ["right", "left"],
43
+ };
44
+
45
+ const inputValue = "";
46
+ </script>
47
+
48
+ <template>
49
+ <Story title="Button">
50
+ <Variant title="Default" :init-state="initState">
51
+ <template #default="{ state }">
52
+ <CjaButton>{{ state.content }}</CjaButton>
53
+ </template>
54
+
55
+ <template #controls="{ state }">
56
+ <HstText type="text" v-model="state.content" title="Content" />
57
+ </template>
58
+ </Variant>
59
+ <Variant title="All Configs" :init-state="initStateAllConfigs">
60
+ <template #default="{ state }">
61
+ <div class="form-container">
62
+ <TextInput v-model="inputValue" :size="state.size" />
63
+ <CjaButton
64
+ :type="state.type"
65
+ :color="state.color"
66
+ :size="state.size"
67
+ :icon="state.iconAny || state.icon"
68
+ :iconPosition="state.iconPosition"
69
+ :loading="state.loading"
70
+ :disabled="state.disabled"
71
+ >{{ state.content }}</CjaButton
72
+ >
73
+ </div>
74
+ </template>
75
+
76
+ <template #controls="{ state }">
77
+ <HstText type="text" v-model="state.content" title="Content" />
78
+ <HstSelect v-model="state.type" :options="options.type" title="Type" />
79
+ <HstSelect
80
+ v-model="state.color"
81
+ :options="options.color"
82
+ title="Color"
83
+ />
84
+ <HstSelect v-model="state.size" :options="options.size" title="Size" />
85
+ <HstText type="text" v-model="state.iconAny" title="Icon Any" />
86
+ <HstSelect v-model="state.icon" :options="options.icon" title="Icon" />
87
+ <HstSelect
88
+ v-model="state.iconPosition"
89
+ :options="options.iconPosition"
90
+ title="Icon Position"
91
+ />
92
+ <HstCheckbox v-model="state.loading" title="Loading" />
93
+ <HstCheckbox v-model="state.disabled" title="Disabled" />
94
+ </template>
95
+ </Variant>
96
+ <Variant title="Icon Button" :init-state="initStateIcon">
97
+ <template #default="{ state }">
98
+ <div class="form-container">
99
+ <TextInput v-model="inputValue" :size="state.size" />
100
+ <CjaButton
101
+ :type="state.type"
102
+ :color="state.color"
103
+ :size="state.size"
104
+ :icon="state.iconAny || state.icon"
105
+ :iconPosition="state.iconPosition"
106
+ :loading="state.loading"
107
+ :disabled="state.disabled"
108
+ />
109
+ </div>
110
+ </template>
111
+
112
+ <template #controls="{ state }">
113
+ <HstSelect v-model="state.type" :options="options.type" title="Type" />
114
+ <HstSelect
115
+ v-model="state.color"
116
+ :options="options.color"
117
+ title="Color"
118
+ />
119
+ <HstSelect v-model="state.size" :options="options.size" title="Size" />
120
+ <HstText type="text" v-model="state.iconAny" title="Icon Any" />
121
+ <HstSelect v-model="state.icon" :options="options.icon" title="Icon" />
122
+ <HstSelect
123
+ v-model="state.iconPosition"
124
+ :options="options.iconPosition"
125
+ title="Icon Position"
126
+ />
127
+ <HstCheckbox v-model="state.loading" title="Loading" />
128
+ <HstCheckbox v-model="state.disabled" title="Disabled" />
129
+ </template>
130
+ </Variant>
131
+ </Story>
132
+ </template>
133
+
134
+ <style lang="scss" scoped>
135
+ .form-container {
136
+ display: grid;
137
+ grid-template-columns: 200px max-content;
138
+ gap: 24px;
139
+ }
140
+ </style>
141
+
142
+ <docs lang="md">
143
+ ## Props
144
+
145
+ #### type
146
+
147
+ Defines fill and border type
148
+
149
+ - **primary** has background fill
150
+ - **secondary** is bordered without fill
151
+ - **tertiary** does not have background or border
152
+
153
+ #### color
154
+
155
+ Defines button color profile
156
+
157
+ - <b style="color: #076b9c">blue</b>
158
+ - <b style="color: #f58423">orange</b>
159
+ - <b style="color: #fff">white</b>
160
+
161
+ #### size
162
+
163
+ Defines button size, changing padding and font-size
164
+
165
+ - **sm**
166
+ - **md**
167
+ - **lg**
168
+
169
+ #### icon
170
+
171
+ An iconia class icon, inserted as a class into a span
172
+
173
+ #### iconPosition
174
+
175
+ Defines icon position relative to button text
176
+
177
+ - **left**
178
+ - **right**
179
+
180
+ #### loading
181
+
182
+ Displays a spinner when boolean is true, prevents display of button text and icon
183
+
184
+ ## Defaults
185
+
186
+ When implementing a **CjaButton** component with no props, the following props are pre-defined as default:
187
+
188
+ - **type**: primary
189
+ - **color**: <b style="color: #076b9c">blue</b>
190
+ - **size**: md
191
+ - **iconPosition**: right
192
+ </docs>
@@ -0,0 +1,59 @@
1
+ <script lang="ts" setup>
2
+ import ContentTabs from "../components/structural/ContentTabs.vue";
3
+
4
+ const initState = () => ({
5
+ tabs: ["Tab 1", "Tab 2"],
6
+ });
7
+ </script>
8
+
9
+ <template>
10
+ <Story title="Content Tabs">
11
+ <Variant title="Default" :init-state="initState">
12
+ <template #default="{ state }">
13
+ <ContentTabs :defaultTab="0" :tabs="state.tabs">
14
+ <template #[i] v-for="(tab, i) in state.tabs">
15
+ Each tab must match a numbered slot equivalent to the position on
16
+ the array, this is the slot for: {{ tab }}
17
+ </template>
18
+ </ContentTabs>
19
+ </template>
20
+
21
+ <template #controls="{ state }">
22
+ <HstJson v-model="state.tabs" title="Tabs"></HstJson>
23
+ </template>
24
+
25
+ <template #source>
26
+ <textarea v-pre>
27
+ <ContentTabs :defaultTab="0" :tabs="['Tab 1', 'Tab 2']">
28
+ <template #0> Slot content </template>
29
+ <template #1> Slot content </template>
30
+ </ContentTabs>
31
+ </textarea>
32
+ </template>
33
+ </Variant>
34
+ </Story>
35
+ </template>
36
+
37
+ <docs lang="md">
38
+ ### Props
39
+
40
+ #### defaultTab
41
+
42
+ The initial active tab when the component is first rendered.
43
+
44
+ #### tabs
45
+
46
+ An array of strings displayed on the tab controls, each one must match a numbered slot in order to display content properly.
47
+
48
+ ### Emits
49
+
50
+ #### tab:changed
51
+
52
+ Triggered when the tab controls are clicked, sends the new tab number.
53
+
54
+ ### Exposed
55
+
56
+ #### activeTab
57
+
58
+ Exposes the **activeTab** variable, used to display the current selected tab.
59
+ </docs>
@@ -0,0 +1,127 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import Modal from "../components/structural/Modal.vue";
4
+ import CjaButton from "../components/structural/CjaButton.vue";
5
+
6
+ const modal = ref();
7
+
8
+ const initState = () => ({
9
+ mdMaxWidth: "min(calc(100% - 48px), 768px)",
10
+ lgMaxWidth: "min(calc(100% - 48px), 1024px)",
11
+ xlMaxWidth: "1024px",
12
+ modalHeader: true,
13
+ modalBody: true,
14
+ modalFooter: false,
15
+ });
16
+ </script>
17
+
18
+ <template>
19
+ <Story title="Modal">
20
+ <Variant title="Default" :init-state="initState">
21
+ <template #default="{ state }">
22
+ <Modal
23
+ ref="modal"
24
+ :maxWidth="{
25
+ md: state.mdMaxWidth,
26
+ lg: state.lgMaxWidth,
27
+ xl: state.xlMaxWidth,
28
+ }"
29
+ >
30
+ <template #header v-if="state.modalHeader">
31
+ Modal Header Slot
32
+ </template>
33
+ <template #body v-if="state.modalBody">
34
+ <div class="modal-body">
35
+ <h2>Modal Body Slot</h2>
36
+ <p>
37
+ The modal body slot can host whatever component you need to use
38
+ inside
39
+ </p>
40
+ </div>
41
+ </template>
42
+ <template #footer v-if="state.modalFooter">
43
+ <div class="modal-footer">Modal Footer Slot</div>
44
+ </template>
45
+ </Modal>
46
+
47
+ <CjaButton @click="modal.openModal()">Open modal</CjaButton>
48
+ </template>
49
+
50
+ <template #controls="{ state }">
51
+ <HstText v-model="state.mdMaxWidth" title="Max Width md" />
52
+ <HstText v-model="state.lgMaxWidth" title="Max Width lg" />
53
+ <HstText v-model="state.xlMaxWidth" title="Max Width xl" />
54
+ <HstCheckbox v-model="state.modalHeader" title="Modal Header" />
55
+ <HstCheckbox v-model="state.modalBody" title="Modal Body" />
56
+ <HstCheckbox v-model="state.modalFooter" title="Modal Footer" />
57
+ </template>
58
+ </Variant>
59
+ </Story>
60
+ </template>
61
+
62
+ <style lang="scss" scoped>
63
+ h2 {
64
+ margin-top: 0;
65
+ }
66
+
67
+ p {
68
+ margin: 0;
69
+ }
70
+ </style>
71
+
72
+ <docs lang="md">
73
+ ### Props
74
+
75
+ #### maxWidth
76
+
77
+ Defines a maxWidth CSS string for the **md**, **lg** and **xl** viewports.
78
+
79
+ ### Slots
80
+
81
+ #### #header
82
+
83
+ Displays content inside the header slot space and adds a horizontal rule below it to the modal.
84
+
85
+ #### #body
86
+
87
+ Displays content inside the modal, becoming scrollable after a threshold. If you need to stylize slotted content, wrap the html within a div, it will be scoped to the vue scope you are currently working with.
88
+
89
+ #### #footer
90
+
91
+ Displays content below the modal body, separated by a horizontal ruler. Exempt from the scrollable container.
92
+
93
+ ### Emits
94
+
95
+ #### @close
96
+
97
+ Triggered when the modal is closed, through the close button or clicking outside the modal window.
98
+
99
+ ### Exposed
100
+
101
+ #### active
102
+
103
+ Exposes the modal's current **active** status, allows you to detect if the modal is opened or closed.
104
+
105
+ #### openModal()
106
+
107
+ Exposes the **openModal** method, used to display the modal, typically wrapped in a local function to perform additional changes before opening the modal.
108
+
109
+ #### closeModal()
110
+
111
+ Exposes the **closeModal** method, used to hide the modal, emits a **@close** event.
112
+
113
+ #### toggleModal()
114
+
115
+ Exposes the **toggleModal** method, used to hide or show the modal depending on the current **active** value. Emits a **@close** event if the modal was closed.
116
+
117
+ ## Defaults
118
+
119
+ - **maxWidth**:
120
+ ```
121
+ {
122
+ md: "min(calc(100% - 48px), 768px)",
123
+ lg: "min(calc(100% - 48px), 1024px)",
124
+ xl: "1024px"
125
+ }
126
+ ```
127
+ </docs>
@@ -3,8 +3,8 @@ import { Ref, onMounted, onUnmounted, ref } from "vue";
3
3
  export const useViewportDetector = (
4
4
  breakpoints: { [index: string]: number } = {
5
5
  xs: 0,
6
- s: 420,
7
- m: 768,
6
+ sm: 420,
7
+ md: 768,
8
8
  lg: 1024,
9
9
  xl: 1200,
10
10
  }