@inriver/inri 1.0.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 (114) hide show
  1. package/.eslintrc.js +5 -0
  2. package/componentProperties.d.ts +8 -0
  3. package/configs/base-eslint-config.js +36 -0
  4. package/configs/base-tailwind-config.js +149 -0
  5. package/i18n.ts +11 -0
  6. package/index.ts +25 -0
  7. package/package.json +36 -0
  8. package/src/components/CAutoBreadcrumbs.vue +72 -0
  9. package/src/components/CBtn.vue +98 -0
  10. package/src/components/CColorPicker.vue +55 -0
  11. package/src/components/CContextMenu.vue +29 -0
  12. package/src/components/CDatePicker.vue +51 -0
  13. package/src/components/CDeleteTileBtn.vue +52 -0
  14. package/src/components/CDialog.vue +253 -0
  15. package/src/components/CDialogCloseBtn.vue +9 -0
  16. package/src/components/CDirtyCheck.vue +68 -0
  17. package/src/components/CDrawer.vue +44 -0
  18. package/src/components/CDrawerButton.vue +91 -0
  19. package/src/components/CExpansionItem.vue +57 -0
  20. package/src/components/CExpansionItemWithCheckbox.vue +41 -0
  21. package/src/components/CFileDropzone.vue +114 -0
  22. package/src/components/CGrid.vue +101 -0
  23. package/src/components/CHeader.vue +10 -0
  24. package/src/components/CHeaderLinks.vue +61 -0
  25. package/src/components/CHeaderMenuLink.vue +51 -0
  26. package/src/components/CHeaderSearch.vue +142 -0
  27. package/src/components/CIconBtn.vue +70 -0
  28. package/src/components/CMenuItem.vue +66 -0
  29. package/src/components/CPageContainer.vue +11 -0
  30. package/src/components/CPageError.vue +24 -0
  31. package/src/components/CPageSpinner.vue +18 -0
  32. package/src/components/CSearch.vue +45 -0
  33. package/src/components/CSection.vue +29 -0
  34. package/src/components/CSectionTitle.vue +8 -0
  35. package/src/components/CSelect.vue +208 -0
  36. package/src/components/CSlideEffect.vue +62 -0
  37. package/src/components/CSpinner.vue +107 -0
  38. package/src/components/CTableActions.vue +16 -0
  39. package/src/components/CTileBtn.vue +178 -0
  40. package/src/components/CTimePicker.vue +42 -0
  41. package/src/components/CToggleGridView.vue +26 -0
  42. package/src/components/CToggleSidebarBtn.vue +19 -0
  43. package/src/components/CTooltip.vue +44 -0
  44. package/src/components/CTreeNode.vue +39 -0
  45. package/src/components/cards/CBannerCard.vue +103 -0
  46. package/src/components/cards/CSmallCard.vue +149 -0
  47. package/src/components/internal-components/ConfirmDialog.vue +78 -0
  48. package/src/composables/useDialog.ts +47 -0
  49. package/src/composables/useDirtyCheck.ts +25 -0
  50. package/src/composables/useDoubleClickListener.ts +28 -0
  51. package/src/composables/useSelectable.ts +62 -0
  52. package/src/composables/useTruncateChips.ts +46 -0
  53. package/src/i18n/de/index.ts +17 -0
  54. package/src/i18n/en/index.ts +84 -0
  55. package/src/i18n/es/index.ts +7 -0
  56. package/src/i18n/fr/index.ts +7 -0
  57. package/src/init/initInriFramework.ts +25 -0
  58. package/src/init/useInriApp.ts +55 -0
  59. package/src/layouts/CLayoutBlank.vue +7 -0
  60. package/src/layouts/CLayoutWithBackButton.vue +50 -0
  61. package/src/layouts/CLayoutWithSidebars.vue +46 -0
  62. package/src/layouts/layout-fragments/CPageAutoBreadcrumbs.vue +6 -0
  63. package/src/layouts/layout-fragments/CPageBackButton.vue +18 -0
  64. package/src/layouts/layout-fragments/CPageBlank.vue +20 -0
  65. package/src/layouts/layout-fragments/CPageFlexCol.vue +5 -0
  66. package/src/layouts/layout-fragments/CPageLeftSidebar.vue +5 -0
  67. package/src/layouts/layout-fragments/CPageMainContent.vue +5 -0
  68. package/src/layouts/layout-fragments/CPageOuterWrapper.vue +5 -0
  69. package/src/layouts/layout-fragments/CPageRightSidebar.vue +5 -0
  70. package/src/layouts/layout-fragments/CPageTitle.vue +34 -0
  71. package/src/plugins/dialog.ts +45 -0
  72. package/src/plugins/errors/ErrorFactories.ts +59 -0
  73. package/src/plugins/errors/Errors.ts +156 -0
  74. package/src/plugins/errors/Exception.ts +6 -0
  75. package/src/plugins/errors/globalErrorHandler.ts +21 -0
  76. package/src/plugins/errors/handleError.ts +34 -0
  77. package/src/plugins/errors/logger.ts +16 -0
  78. package/src/plugins/errors/vueErrorHandler.ts +26 -0
  79. package/src/plugins/globalAppConfig.ts +25 -0
  80. package/src/plugins/i18n.ts +46 -0
  81. package/src/plugins/inriIcons.ts +58 -0
  82. package/src/plugins/inriverQuasarComponentConfigurations.ts +67 -0
  83. package/src/plugins/library.ts +119 -0
  84. package/src/plugins/notify.ts +153 -0
  85. package/src/plugins/quasar-user-options.ts +19 -0
  86. package/src/plugins/validationRules.ts +292 -0
  87. package/src/shims-vue.d.ts +6 -0
  88. package/src/stores/inriStore.ts +54 -0
  89. package/src/stores/interfaceLanguageStore.ts +27 -0
  90. package/src/styles/_css-variables.scss +101 -0
  91. package/src/styles/_reset.scss +67 -0
  92. package/src/styles/_transitions.scss +9 -0
  93. package/src/styles/_utils.scss +15 -0
  94. package/src/styles/components/_breadcrumbs.scss +3 -0
  95. package/src/styles/components/_checkboxes.scss +17 -0
  96. package/src/styles/components/_chips.scss +17 -0
  97. package/src/styles/components/_forms.scss +203 -0
  98. package/src/styles/components/_menu.scss +12 -0
  99. package/src/styles/components/_notifications.scss +24 -0
  100. package/src/styles/components/_q-editor.scss +14 -0
  101. package/src/styles/components/_radio-buttons.scss +17 -0
  102. package/src/styles/components/_tables.scss +92 -0
  103. package/src/styles/components/_tooltips.scss +5 -0
  104. package/src/styles/components/_tree.scss +86 -0
  105. package/src/styles/components/_truncated-chips.scss +39 -0
  106. package/src/styles/main.scss +45 -0
  107. package/src/styles/quasar.variables.scss +50 -0
  108. package/src/typings.d.ts +70 -0
  109. package/src/utils/colorUtils.ts +73 -0
  110. package/src/utils/errorUtils.ts +37 -0
  111. package/src/utils/fileUtils.ts +57 -0
  112. package/src/utils/helpers.ts +6 -0
  113. package/src/utils/routeUtils.ts +23 -0
  114. package/tsconfig.json +37 -0
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <div
3
+ class="grid"
4
+ :style="{
5
+ columnGap: `${colGap}px`,
6
+ rowGap: `${rowGap}px`,
7
+ gridTemplateColumns: computedColumns,
8
+ gridTemplateRows: computedRows,
9
+ }"
10
+ >
11
+ <slot />
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { computed } from 'vue'
17
+ import { useQuasar } from 'quasar'
18
+
19
+ const $q = useQuasar()
20
+
21
+ const props = defineProps({
22
+ cols: {
23
+ type: [String, Number, Object],
24
+ default: undefined,
25
+ },
26
+ rows: {
27
+ type: [String, Number, Object],
28
+ default: undefined,
29
+ },
30
+ colGap: {
31
+ type: [Number, String],
32
+ default: 24,
33
+ },
34
+ rowGap: {
35
+ type: [Number, String],
36
+ default: 24,
37
+ },
38
+ })
39
+
40
+ // Computed
41
+ const computedColumns = computed(() => {
42
+ if (typeof props.cols === 'object') {
43
+ if ($q.screen.xl && props.cols.xl) {
44
+ return generateColumnsTemplate(props.cols.xl)
45
+ } if ($q.screen.gt.md && props.cols.lg) {
46
+ return generateColumnsTemplate(props.cols.lg)
47
+ } else if ($q.screen.gt.sm && props.cols.md) {
48
+ return generateColumnsTemplate(props.cols.md)
49
+ } else if ($q.screen.gt.xs && props.cols.sm) {
50
+ return generateColumnsTemplate(props.cols.sm)
51
+ } else if (props.cols.xs) {
52
+ return generateColumnsTemplate(props.cols.xs)
53
+ }
54
+
55
+ return null
56
+ }
57
+
58
+ return generateColumnsTemplate(props.cols)
59
+ })
60
+
61
+ const computedRows = computed(() => {
62
+ if (typeof props.rows === 'object') {
63
+ if ($q.screen.xl && props.rows.xl) {
64
+ return generateRowsTemplate(props.rows.xl)
65
+ } if ($q.screen.gt.md && props.rows.lg) {
66
+ return generateRowsTemplate(props.rows.lg)
67
+ } else if ($q.screen.gt.sm && props.rows.md) {
68
+ return generateRowsTemplate(props.rows.md)
69
+ } else if ($q.screen.gt.xs && props.rows.sm) {
70
+ return generateRowsTemplate(props.rows.sm)
71
+ } else if (props.rows.xs) {
72
+ return generateRowsTemplate(props.rows.xs)
73
+ }
74
+
75
+ return null
76
+ }
77
+
78
+ return generateRowsTemplate(props.rows)
79
+ })
80
+
81
+ // Methods
82
+ function isNumeric (num) {
83
+ return !isNaN(num)
84
+ }
85
+
86
+ function generateColumnsTemplate (template) {
87
+ if (isNumeric(template)) {
88
+ return `repeat(${template}, minmax(0, 1fr))`
89
+ }
90
+
91
+ return template
92
+ }
93
+
94
+ function generateRowsTemplate (template) {
95
+ if (isNumeric(template)) {
96
+ return `repeat(${template}, minmax(0, 1fr))`
97
+ }
98
+
99
+ return template
100
+ }
101
+ </script>
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <q-header
3
+ class="bg-surface"
4
+ data-id="site-header"
5
+ >
6
+ <q-toolbar class="p-0 h-15">
7
+ <slot />
8
+ </q-toolbar>
9
+ </q-header>
10
+ </template>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <div
3
+ v-if="showExtendedMenu"
4
+ class="header-links gt-sm"
5
+ >
6
+ <c-header-menu-link
7
+ v-for="link in links"
8
+ :key="link.href"
9
+ :text="link.text"
10
+ :href="link.href"
11
+ :is-active="link.isActive"
12
+ />
13
+ </div>
14
+
15
+ <!-- Toggle menu items with button on small devices-->
16
+ <c-tile-btn
17
+ icon="mdi-menu"
18
+ :class="showExtendedMenu ? 'lt-md' : ''"
19
+ >
20
+ <q-menu>
21
+ <q-list>
22
+ <c-menu-item
23
+ v-for="link in links"
24
+ :key="link.href"
25
+ :text="link.text"
26
+ :href="link.href"
27
+ />
28
+ </q-list>
29
+ </q-menu>
30
+ </c-tile-btn>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { computed, withDefaults } from 'vue'
35
+
36
+ interface Link {
37
+ text: string,
38
+ href: string,
39
+ isActive?: boolean,
40
+ }
41
+
42
+ interface Props {
43
+ maxVisible?: number,
44
+ links: Link[]
45
+ }
46
+
47
+ const props = withDefaults(defineProps<Props>(), {
48
+ maxVisible: 12,
49
+ })
50
+
51
+ const showExtendedMenu = computed(() => props.maxVisible >= props.links.length)
52
+ </script>
53
+
54
+ <style scoped>
55
+ .header-links {
56
+ margin-right: 32px;
57
+ height: 100%;
58
+ display: flex;
59
+ overflow: hidden;
60
+ }
61
+ </style>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <a
3
+ class="link"
4
+ :class="{ 'is-active': isActive }"
5
+ :href="href"
6
+ >
7
+ <span class="truncate">
8
+ {{ text }}
9
+ </span>
10
+ </a>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ interface Props {
15
+ text: string,
16
+ href: string,
17
+ isActive?: boolean
18
+ }
19
+
20
+ defineProps<Props>()
21
+ </script>
22
+
23
+ <style scoped>
24
+ .link {
25
+ display: flex;
26
+ align-items: center;
27
+ font-size: 16px;
28
+ font-weight: 500;
29
+ padding: 0 20px 4px;
30
+ position: relative;
31
+ min-width: 0;
32
+ }
33
+
34
+ .link:hover {
35
+ text-decoration: underline;
36
+ }
37
+
38
+ .is-active {
39
+ font-weight: 600;
40
+ }
41
+
42
+ .is-active::before {
43
+ content: '';
44
+ width: 8px;
45
+ height: 8px;
46
+ background: var(--q-accent);
47
+ position: absolute;
48
+ top: calc(50% - 4px);
49
+ left: 8px;
50
+ }
51
+ </style>
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <c-tile-btn
3
+ icon="inri-search"
4
+ @click="toggleSearch"
5
+ />
6
+ <div class="bg-grey-lighter h-full flex content-center ">
7
+ <transition name="search-transition">
8
+ <div
9
+ v-if="showSearch"
10
+ class="pr-5"
11
+ >
12
+ <q-select
13
+ ref="selectRef"
14
+ v-model="selectedVal"
15
+ v-bind="$inri.inputDense"
16
+ class="w-74"
17
+ autofocus
18
+ use-input
19
+ hide-selected
20
+ hide-dropdown-icon
21
+ :options="options"
22
+ :loading="isLoading"
23
+ @filter="filter"
24
+ @blur="clearSearch"
25
+ @popup-show="onOpen"
26
+ @update:model-value="selectItem"
27
+ >
28
+ <template #append>
29
+ <q-icon
30
+ v-if="modelValue.length && !isLoading"
31
+ name="mdi-close"
32
+ color="grey-darker"
33
+ class="cursor-pointer"
34
+ size="18px"
35
+ @click.stop="clearSearch"
36
+ />
37
+ <q-icon
38
+ v-else-if="!isLoading"
39
+ name="inri-search"
40
+ color="grey-dark"
41
+ size="18px"
42
+ />
43
+ </template>
44
+
45
+ <template
46
+ v-if="modelValue.length && !isLoading"
47
+ #no-option
48
+ >
49
+ <q-item>
50
+ <q-item-section>
51
+ No results
52
+ </q-item-section>
53
+ </q-item>
54
+ </template>
55
+
56
+ <template #option="{ itemProps, opt }">
57
+ <q-item
58
+ v-bind="itemProps"
59
+ :to="opt"
60
+ >
61
+ <q-item-section>
62
+ {{ opt.meta.title }}
63
+ </q-item-section>
64
+ </q-item>
65
+ </template>
66
+ </q-select>
67
+ </div>
68
+ </transition>
69
+ </div>
70
+ </template>
71
+
72
+ <script setup lang="ts">
73
+ import { ref, watch } from 'vue'
74
+ import { useRouter } from 'vue-router'
75
+
76
+ const emits = defineEmits(['update:modelValue'])
77
+
78
+ const props = defineProps({
79
+ modelValue: {
80
+ type: String,
81
+ default: '',
82
+ },
83
+ options: {
84
+ type: Array,
85
+ default: undefined,
86
+ },
87
+ isLoading: Boolean,
88
+ })
89
+
90
+ const showSearch = ref(false)
91
+ const selectedVal = ref(null)
92
+ const selectRef = ref()
93
+ const router = useRouter()
94
+
95
+ watch(() => props.options, () => {
96
+ selectRef.value.refresh()
97
+ })
98
+
99
+ function onOpen () {
100
+ // Set first item as selected
101
+ selectRef.value.setOptionIndex(-1)
102
+ selectRef.value.moveOptionSelection(1, true)
103
+ }
104
+
105
+ function toggleSearch () {
106
+ showSearch.value = !showSearch.value
107
+ }
108
+
109
+ function clearSearch () {
110
+ selectRef.value.updateInputValue('', true, true)
111
+ emits('update:modelValue', '')
112
+ }
113
+
114
+ function filter (val, update) {
115
+ update(
116
+ () => {
117
+ emits('update:modelValue', val)
118
+ },
119
+ )
120
+ }
121
+
122
+ function selectItem () {
123
+ if (selectedVal.value) {
124
+ clearSearch()
125
+ showSearch.value = false
126
+ router.push(selectedVal.value)
127
+ }
128
+ }
129
+ </script>
130
+
131
+ <style>
132
+ .search-transition-enter-active,
133
+ .search-transition-leave-active {
134
+ margin-right: 0;
135
+ transition: all 0.2s ease-in-out;
136
+ }
137
+
138
+ .search-transition-enter-from,
139
+ .search-transition-leave-to {
140
+ margin-right: -320px;
141
+ }
142
+ </style>
@@ -0,0 +1,70 @@
1
+ <template>
2
+ <q-btn
3
+ class="c-icon-btn"
4
+ flat
5
+ round
6
+ :color="color"
7
+ :to="to"
8
+ :icon="icon"
9
+ :aria-label="tooltip || tooltipLeft || tooltipRight"
10
+ >
11
+ <c-tooltip v-if="tooltip">
12
+ {{ tooltip }}
13
+ </c-tooltip>
14
+
15
+ <c-tooltip
16
+ v-if="tooltipLeft"
17
+ left
18
+ >
19
+ {{ tooltipLeft }}
20
+ </c-tooltip>
21
+
22
+ <c-tooltip
23
+ v-if="tooltipRight"
24
+ right
25
+ >
26
+ {{ tooltipRight }}
27
+ </c-tooltip>
28
+ <slot />
29
+ </q-btn>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ defineProps({
34
+ color: {
35
+ type: String,
36
+ default: undefined,
37
+ },
38
+ to: {
39
+ type: [String, Object],
40
+ default: undefined,
41
+ },
42
+ icon: {
43
+ type: String,
44
+ default: undefined,
45
+ },
46
+ tooltip: {
47
+ type: String,
48
+ default: undefined,
49
+ },
50
+ tooltipLeft: {
51
+ type: String,
52
+ default: undefined,
53
+ },
54
+ tooltipRight: {
55
+ type: String,
56
+ default: undefined,
57
+ },
58
+ })
59
+ </script>
60
+
61
+ <style scoped lang="scss">
62
+ $size: 32px;
63
+
64
+ .c-icon-btn {
65
+ height: $size;
66
+ width: $size;
67
+ min-height: $size;
68
+ min-width: $size;
69
+ }
70
+ </style>
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <q-item
3
+ v-close-popup
4
+ :clickable="!disable && !isTitle"
5
+ :disable="disable"
6
+ :to="to"
7
+ :class="{
8
+ 'is-delete': isDelete,
9
+ 'font-weight-bold': isTitle
10
+ }"
11
+ @click="triggerClick"
12
+ >
13
+ <q-item-section
14
+ v-if="icon && icon.length"
15
+ avatar
16
+ class="min-w-8"
17
+ >
18
+ <q-icon
19
+ class="inherit-text"
20
+ :name="icon"
21
+ />
22
+ </q-item-section>
23
+ <q-item-section>
24
+ <template v-if="text">
25
+ {{ text }}
26
+ </template>
27
+ <slot v-else />
28
+ </q-item-section>
29
+ </q-item>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ defineProps({
34
+ icon: {
35
+ type: String,
36
+ default: undefined,
37
+ },
38
+ text: {
39
+ type: String,
40
+ default: undefined,
41
+ },
42
+ to: {
43
+ type: String,
44
+ default: undefined,
45
+ },
46
+ isDelete: Boolean,
47
+ isTitle: Boolean,
48
+ disable: Boolean,
49
+ })
50
+
51
+ const emit = defineEmits(['click'])
52
+
53
+ function triggerClick (e) {
54
+ emit('click', e)
55
+ }
56
+ </script>
57
+
58
+ <style scoped lang="scss">
59
+ .is-delete:hover {
60
+ background: var(--color-red);
61
+
62
+ > * {
63
+ color: white;
64
+ }
65
+ }
66
+ </style>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <q-page-container>
3
+ <div class="relative">
4
+ <q-page class="overflow-hidden">
5
+ <router-view v-slot="{ Component }">
6
+ <component :is="Component" />
7
+ </router-view>
8
+ </q-page>
9
+ </div>
10
+ </q-page-container>
11
+ </template>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <div class="h-full flex items-center justify-center">
3
+ <h1 class="text-primary text-center mb-20">
4
+ {{ error.title }}
5
+ <br>
6
+ {{ error.msg }}
7
+ <br>
8
+ <span
9
+ v-if="error.isAxiosError"
10
+ class="text-grey-dark text-body1"
11
+ >
12
+ {{ $t('inri.error.error_code', { code: error.status }) }}
13
+ </span>
14
+ </h1>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { BaseError } from '../plugins/errors/Errors'
20
+
21
+ defineProps <{
22
+ error: BaseError
23
+ }>()
24
+ </script>
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <q-inner-loading
3
+ :showing="showing || inriStore.state.showPageSpinner"
4
+ class="z-max"
5
+ >
6
+ <c-spinner />
7
+ </q-inner-loading>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { useInriStore } from '../stores/inriStore'
12
+
13
+ defineProps({
14
+ showing: Boolean,
15
+ })
16
+
17
+ const inriStore = useInriStore()
18
+ </script>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <q-input
3
+ v-bind="dense ? $inri.inputDense : $inri.input"
4
+ :model-value="modelValue"
5
+ :label="label"
6
+ :placeholder="placeholder"
7
+ @update:model-value="$emit('update:model-value', $event)"
8
+ >
9
+ <template #append>
10
+ <q-icon
11
+ v-if="modelValue?.length"
12
+ name="mdi-close"
13
+ color="grey-darker"
14
+ class="cursor-pointer"
15
+ size="18px"
16
+ @click.stop="$emit('update:model-value', '')"
17
+ />
18
+ <q-icon
19
+ v-else
20
+ name="inri-search"
21
+ color="grey-dark"
22
+ size="18px"
23
+ />
24
+ </template>
25
+ </q-input>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ import type { QInputProps } from 'quasar'
30
+
31
+ defineEmits(['update:model-value'])
32
+
33
+ interface Props extends QInputProps {
34
+ modelValue: string,
35
+ label: string,
36
+ placeholder?: string,
37
+ dense?: boolean,
38
+ }
39
+
40
+ withDefaults(defineProps<Props>(), {
41
+ modelValue: undefined,
42
+ label: undefined,
43
+ placeholder: undefined,
44
+ })
45
+ </script>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <section
3
+ class="c-section"
4
+ :class="{
5
+ 'overflow-y-overlay': scroll || main,
6
+ 'flex-1': flex || main,
7
+ 'border-t': borderT,
8
+ 'border-b': borderB,
9
+ }"
10
+ >
11
+ <slot />
12
+ </section>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ defineProps({
17
+ scroll: Boolean,
18
+ flex: Boolean,
19
+ main: Boolean,
20
+ borderT: Boolean,
21
+ borderB: Boolean,
22
+ })
23
+ </script>
24
+
25
+ <style>
26
+ .c-section {
27
+ @apply py-7 px-6;
28
+ }
29
+ </style>
@@ -0,0 +1,8 @@
1
+ <template>
2
+ <div
3
+ class="text-h3 mb-4"
4
+ data-id="section-title"
5
+ >
6
+ <slot />
7
+ </div>
8
+ </template>