@m11g/library 0.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 (66) hide show
  1. package/.editorconfig +8 -0
  2. package/.gitattributes +1 -0
  3. package/.oxfmtrc.json +5 -0
  4. package/.oxlintrc.json +10 -0
  5. package/.stylelintrc +33 -0
  6. package/.vscode/extensions.json +8 -0
  7. package/README.md +48 -0
  8. package/env.d.ts +1 -0
  9. package/eslint.config.ts +35 -0
  10. package/index.html +13 -0
  11. package/package.json +61 -0
  12. package/postcss.config.ts +23 -0
  13. package/public/favicon.ico +0 -0
  14. package/src/App.vue +28 -0
  15. package/src/assets/css/base.css +2 -0
  16. package/src/assets/css/elements/_base.css +5 -0
  17. package/src/assets/css/elements/buttons.css +23 -0
  18. package/src/assets/css/elements/form.css +75 -0
  19. package/src/assets/css/elements/scaffolding.css +62 -0
  20. package/src/assets/css/elements/transition.css +4 -0
  21. package/src/assets/css/elements/typography.css +80 -0
  22. package/src/assets/css/media-queries/media-queries.css +3 -0
  23. package/src/assets/css/mixins/list-reset.css +5 -0
  24. package/src/assets/css/variables/_base.css +7 -0
  25. package/src/assets/css/variables/chakra.css +160 -0
  26. package/src/assets/css/variables/colors.css +205 -0
  27. package/src/assets/css/variables/grid.css +6 -0
  28. package/src/assets/css/variables/spacings.css +10 -0
  29. package/src/assets/css/variables/transitions.css +10 -0
  30. package/src/assets/css/variables/typography.css +17 -0
  31. package/src/components/AppBreadcrumb.vue +63 -0
  32. package/src/components/AppButton.vue +105 -0
  33. package/src/components/AppCarousel.vue +145 -0
  34. package/src/components/AppDialog.vue +126 -0
  35. package/src/components/AppNotification.vue +68 -0
  36. package/src/components/AppPagination.vue +127 -0
  37. package/src/components/AppPopover.vue +57 -0
  38. package/src/components/AppTabs.vue +176 -0
  39. package/src/components/CenterWrapper.vue +50 -0
  40. package/src/components/ClickableWrapper.vue +48 -0
  41. package/src/components/Forms/AppForm.vue +61 -0
  42. package/src/components/Forms/FormErrorMessage.vue +17 -0
  43. package/src/components/Forms/FormField.vue +30 -0
  44. package/src/components/Forms/FormFieldset.vue +28 -0
  45. package/src/components/Forms/RadioGroup.vue +85 -0
  46. package/src/components/Forms/SearchField.vue +38 -0
  47. package/src/components/Forms/SelectField.vue +125 -0
  48. package/src/components/Forms/TextField.vue +41 -0
  49. package/src/components/Forms/TextareaField.vue +32 -0
  50. package/src/components/ScrollSpy.vue +111 -0
  51. package/src/components/SummaryDetails.vue +83 -0
  52. package/src/composables/useTheme.ts +20 -0
  53. package/src/index.ts +48 -0
  54. package/src/main.ts +10 -0
  55. package/src/router/index.ts +26 -0
  56. package/src/types/Theme.ts +11 -0
  57. package/src/views/CarouselView.vue +27 -0
  58. package/src/views/FormView.vue +111 -0
  59. package/src/views/HomeView.vue +100 -0
  60. package/src/views/ScrollSpyView.vue +58 -0
  61. package/theme-dark +117 -0
  62. package/theme-light +191 -0
  63. package/tsconfig.app.json +18 -0
  64. package/tsconfig.json +11 -0
  65. package/tsconfig.node.json +28 -0
  66. package/vite.config.ts +26 -0
@@ -0,0 +1,50 @@
1
+ <script lang="ts" setup>
2
+ withDefaults(
3
+ defineProps<{
4
+ size?: "full" | "md" | "lg" | "sm";
5
+ top?: boolean;
6
+ }>(),
7
+ {
8
+ top: false,
9
+ size: "lg",
10
+ },
11
+ );
12
+ </script>
13
+
14
+ <template>
15
+ <div class="notch" :class="{ top }">
16
+ <div class="wrapper" :class="`wrapper-${size}`">
17
+ <slot />
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <style lang="css" scoped>
23
+ .notch {
24
+ padding: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left);
25
+
26
+ &.top {
27
+ padding-block-start: env(safe-area-inset-top);
28
+ }
29
+ }
30
+
31
+ .wrapper-sm {
32
+ --wrapper-size: var(--container-size-sm);
33
+ }
34
+
35
+ .wrapper-md {
36
+ --wrapper-size: var(--container-size-md);
37
+ }
38
+
39
+ .wrapper-lg {
40
+ --wrapper-size: var(--container-size-lg);
41
+ }
42
+
43
+ .wrapper {
44
+ /* stylelint-disable-next-line length-zero-no-unit */
45
+ --zero: 0px;
46
+
47
+ padding-inline: var(--gutter);
48
+ margin-inline: max(var(--zero), ((100% - var(--wrapper-size)) / 2));
49
+ }
50
+ </style>
@@ -0,0 +1,48 @@
1
+ <script lang="ts" setup>
2
+ import { ref, type Ref } from "vue";
3
+ import { useRouter, type RouteLocationRaw } from "vue-router";
4
+
5
+ const props = withDefaults(
6
+ defineProps<{
7
+ to: RouteLocationRaw;
8
+ tag?: "div" | "li";
9
+ }>(),
10
+ {
11
+ tag: "li",
12
+ },
13
+ );
14
+
15
+ const down: Ref<number | null> = ref(null);
16
+
17
+ const router = useRouter();
18
+
19
+ const mouseDown = (event: MouseEvent) => {
20
+ const target = event.target as Element;
21
+ if (target.nodeName.toUpperCase() === "A" || target.parentNode?.nodeName.toUpperCase() === "A") {
22
+ down.value = null;
23
+ } else {
24
+ down.value = +new Date();
25
+ }
26
+ };
27
+ const mouseUp = async () => {
28
+ if (!down.value) {
29
+ return;
30
+ }
31
+ const up = +new Date();
32
+ if (up - down.value < 200) {
33
+ await router.push(props.to);
34
+ }
35
+ };
36
+ </script>
37
+
38
+ <template>
39
+ <component :is="tag" class="item" @mousedown.left="mouseDown" @mouseup.left="mouseUp">
40
+ <slot />
41
+ </component>
42
+ </template>
43
+
44
+ <style lang="css" scoped>
45
+ .item {
46
+ cursor: pointer;
47
+ }
48
+ </style>
@@ -0,0 +1,61 @@
1
+ <script lang="ts" setup>
2
+ import type { FormErrors } from "vee-validate";
3
+ import AppButton from "@/components/AppButton.vue";
4
+ import AppNotification from "@/components/AppNotification.vue";
5
+ import { computed, ref } from "vue";
6
+
7
+ const props = defineProps<{
8
+ buttonTitle: string;
9
+ successText: string;
10
+ status: "idle" | "pending" | "success" | "error";
11
+ errors: FormErrors<Record<string, string>>;
12
+ }>();
13
+
14
+ const emit = defineEmits<{
15
+ (event: "submit-form"): void;
16
+ }>();
17
+
18
+ const errorList = computed(() => {
19
+ return Object.values(props.errors);
20
+ });
21
+
22
+ const touched = ref(false);
23
+
24
+ const submit = () => {
25
+ touched.value = true;
26
+ emit("submit-form");
27
+ };
28
+ </script>
29
+
30
+ <template>
31
+ <app-notification v-if="status === 'success'" :title="successText" variant="success" />
32
+ <form v-else method="post" novalidate @submit.prevent="submit">
33
+ <slot />
34
+ <div aria-live="assertive">
35
+ <app-notification
36
+ v-if="errorList.length && touched"
37
+ variant="warning"
38
+ title="Het formulier is niet correct ingevuld"
39
+ >
40
+ <ul>
41
+ <li v-for="error in errors" :key="error">
42
+ {{ error }}
43
+ </li>
44
+ </ul>
45
+ </app-notification>
46
+ </div>
47
+ <app-button :title="buttonTitle" type="submit" :disabled="status === 'pending'" />
48
+ </form>
49
+ </template>
50
+
51
+ <style lang="css" scoped>
52
+ ul {
53
+ padding-inline-start: 0;
54
+ margin: 0;
55
+ list-style: none outside;
56
+ }
57
+
58
+ [aria-live="assertive"]:not(:empty) {
59
+ margin-block-start: var(--spacing-4);
60
+ }
61
+ </style>
@@ -0,0 +1,17 @@
1
+ <script lang="ts" setup>
2
+ defineProps<{
3
+ errorMessage?: string;
4
+ }>();
5
+ </script>
6
+
7
+ <template>
8
+ <div aria-live="assertive">
9
+ {{ errorMessage }}
10
+ </div>
11
+ </template>
12
+
13
+ <style lang="css" scoped>
14
+ span {
15
+ display: block;
16
+ }
17
+ </style>
@@ -0,0 +1,30 @@
1
+ <script lang="ts" setup>
2
+ import FormErrorMessage from "./FormErrorMessage.vue";
3
+
4
+ defineProps<{
5
+ id: string;
6
+ title: string;
7
+ errorMessage?: string;
8
+ }>();
9
+ </script>
10
+
11
+ <template>
12
+ <div>
13
+ <label :for="id" class="label">
14
+ {{ title }}
15
+ </label>
16
+ <slot />
17
+ <form-error-message :error-message="errorMessage" class="error-message" />
18
+ </div>
19
+ </template>
20
+
21
+ <style lang="css" scoped>
22
+ label {
23
+ margin-block-end: var(--spacing-xxs);
24
+ font-weight: var(--font-weight-bold);
25
+ }
26
+
27
+ .error-message {
28
+ margin-block-start: var(--spacing-xxs);
29
+ }
30
+ </style>
@@ -0,0 +1,28 @@
1
+ <script lang="ts" setup>
2
+ withDefaults(
3
+ defineProps<{
4
+ title: string;
5
+ columns?: number;
6
+ }>(),
7
+ {
8
+ columns: 1,
9
+ },
10
+ );
11
+ </script>
12
+
13
+ <template>
14
+ <fieldset>
15
+ <legend>
16
+ {{ title }}
17
+ </legend>
18
+ <slot />
19
+ </fieldset>
20
+ </template>
21
+
22
+ <style lang="css" scoped>
23
+ fieldset {
24
+ display: grid;
25
+ grid-template-columns: repeat(v-bind(columns), 1fr);
26
+ gap: var(--gutter);
27
+ }
28
+ </style>
@@ -0,0 +1,85 @@
1
+ <script lang="ts" setup>
2
+ import { useField } from "vee-validate";
3
+ import { toRefs, useId } from "vue";
4
+ import FormErrorMessage from "./FormErrorMessage.vue";
5
+ import useTheme from "@/composables/useTheme.ts";
6
+
7
+ const props = defineProps<{
8
+ // type: 'directors' | 'authors'
9
+ name: string;
10
+ title: string;
11
+ options: {
12
+ value: string | number;
13
+ title: string;
14
+ }[];
15
+ }>();
16
+
17
+ const id = useId();
18
+
19
+ const { name } = toRefs(props);
20
+
21
+ // we don't provide any rules here because we are using form-level validation
22
+ // https://vee-validate.logaretm.com/v4/guide/validation#form-level-validation
23
+ const { value, handleChange, errorMessage } = useField<string>(name);
24
+
25
+ const validationListeners = {
26
+ change: handleChange,
27
+ };
28
+
29
+ const { colors } = useTheme()
30
+
31
+ </script>
32
+
33
+ <template>
34
+ <fieldset>
35
+ <legend>{{ title }}</legend>
36
+ <div class="fields">
37
+ <div v-for="(option, index) in options" :key="option.value" class="field">
38
+ <input
39
+ :id="`${id}-${index}`"
40
+ type="radio"
41
+ :checked="value === option.value"
42
+ :name
43
+ :value="option.value"
44
+ v-on="validationListeners"
45
+ />
46
+ <label :for="`${id}-${index}`">
47
+ {{ option.title }}
48
+ </label>
49
+ </div>
50
+ </div>
51
+ <form-error-message :error-message="errorMessage" />
52
+ </fieldset>
53
+ </template>
54
+
55
+ <style lang="css" scoped>
56
+ .field {
57
+ display: flex;
58
+ gap: var(--spacing-2);
59
+ align-items: start;
60
+
61
+ &:not(:last-child) {
62
+ margin-block-end: var(--spacing-1);
63
+ }
64
+ }
65
+
66
+ input {
67
+ flex: 0 0 auto;
68
+ inline-size: 1.25em;
69
+ aspect-ratio: 1;
70
+ padding: 0;
71
+ margin: 0;
72
+ margin-block-start: 0.1em;
73
+ appearance: none;
74
+ background: var(--color-panel-bg);
75
+ border: 3px solid var(--color-white);
76
+ border-radius: 50%;
77
+ transition:
78
+ border var(--transition),
79
+ background var(--transition);
80
+
81
+ &:checked {
82
+ background-color: v-bind(colors.solid);
83
+ }
84
+ }
85
+ </style>
@@ -0,0 +1,38 @@
1
+ <script lang="ts" setup>
2
+ import { Icon } from "@iconify/vue";
3
+
4
+ defineProps<{
5
+ placeholder?: string;
6
+ }>();
7
+
8
+ const model = defineModel<string>({
9
+ required: true,
10
+ });
11
+ </script>
12
+
13
+ <template>
14
+ <div class="field">
15
+ <input v-model="model" :placeholder name="search" type="search" />
16
+ <icon icon="solar:minimalistic-magnifer-outline" />
17
+ </div>
18
+ </template>
19
+
20
+ <style lang="css" scoped>
21
+ .field {
22
+ position: relative;
23
+ }
24
+
25
+ input {
26
+ padding-inline-end: 3rem;
27
+ }
28
+
29
+ svg {
30
+ position: absolute;
31
+ inset-block-start: 50%;
32
+ inset-inline-end: var(--spacing-4);
33
+ inline-size: 1.5rem;
34
+ block-size: auto;
35
+ aspect-ratio: 1;
36
+ translate: 0 -0.75rem;
37
+ }
38
+ </style>
@@ -0,0 +1,125 @@
1
+ <script lang="ts" setup>
2
+ import { useField } from "vee-validate";
3
+ import { toRefs, useId } from "vue";
4
+ import FormField from "./FormField.vue";
5
+ import { Icon } from "@iconify/vue";
6
+ import useTheme from "@/composables/useTheme.ts";
7
+
8
+ const props = defineProps<{
9
+ // type: 'directors' | 'authors'
10
+ name: string;
11
+ title: string;
12
+ options: {
13
+ value: string | number;
14
+ title: string;
15
+ }[];
16
+ }>();
17
+
18
+ const id = useId();
19
+
20
+ const { name } = toRefs(props);
21
+
22
+ // we don't provide any rules here because we are using form-level validation
23
+ // https://vee-validate.logaretm.com/v4/guide/validation#form-level-validation
24
+ const { value, handleChange, errorMessage } = useField<string>(name, undefined, {
25
+ validateOnValueUpdate: false,
26
+ });
27
+
28
+ const validationListeners = {
29
+ change: handleChange,
30
+ };
31
+
32
+ const { colors } = useTheme()
33
+ </script>
34
+
35
+ <template>
36
+ <form-field :id :title :error-message>
37
+ <select :id :value :name v-on="validationListeners">
38
+ <button>
39
+ <selectedcontent />
40
+ <icon icon="solar:alt-arrow-down-outline" class="icon" />
41
+ </button>
42
+ <option v-for="option in options" :key="option.value" :value="option.value">
43
+ {{ option.title }}
44
+ </option>
45
+ </select>
46
+ </form-field>
47
+ </template>
48
+
49
+ <style lang="css" scoped>
50
+ .icon {
51
+ transition: rotate var(--transition);
52
+ }
53
+
54
+ select {
55
+ appearance: base-select;
56
+ cursor: pointer;
57
+
58
+ &:open {
59
+ .icon {
60
+ rotate: -180deg;
61
+ }
62
+ }
63
+ }
64
+
65
+ button {
66
+ display: flex;
67
+ gap: var(--spacing-2);
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ inline-size: 100%;
71
+ }
72
+
73
+ ::checkmark {
74
+ display: none;
75
+ }
76
+
77
+ ::picker-icon {
78
+ display: none;
79
+ }
80
+
81
+ ::picker(select) {
82
+ margin-block-start: var(--spacing-1);
83
+ appearance: base-select;
84
+ border: 2px solid var(--color-white);
85
+ opacity: 0;
86
+ translate: 0 calc(-1 * var(--spacing-4));
87
+ transition:
88
+ translate var(--transition),
89
+ opacity var(--transition),
90
+ overlay var(--transition) allow-discrete,
91
+ display var(--transition) allow-discrete;
92
+ }
93
+
94
+ :open::picker(select) {
95
+ opacity: 1;
96
+ translate: 0 0;
97
+ }
98
+
99
+ @starting-style {
100
+ :open::picker(select) {
101
+ opacity: 0;
102
+ translate: 0 calc(-1 * var(--spacing-4));
103
+ }
104
+ }
105
+
106
+ option {
107
+ display: flex;
108
+ gap: var(--spacing-2);
109
+ padding: var(--spacing-2);
110
+ color: var(--color-fg);
111
+ background-color: var(--color-body-background);
112
+ border-inline-start: 3px solid transparent;
113
+
114
+ &:hover {
115
+ color: v-bind(colors.fg);
116
+ background-color: var(--color-primary-hover);
117
+ }
118
+
119
+ &:checked {
120
+ font-weight: var(--font-weight-bold);
121
+ color: v-bind(colors.fg);
122
+ background-color: var(--color-primary);
123
+ }
124
+ }
125
+ </style>
@@ -0,0 +1,41 @@
1
+ <script lang="ts" setup>
2
+ import { useField } from "vee-validate";
3
+ import { toRefs, useId, type InputTypeHTMLAttribute } from "vue";
4
+ import FormField from "./FormField.vue";
5
+
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ type?: InputTypeHTMLAttribute;
9
+ autocomplete?: AutoFillField;
10
+ title: string;
11
+ name: string;
12
+ placeholder?: string;
13
+ }>(),
14
+ {
15
+ autocomplete: undefined,
16
+ type: "text",
17
+ placeholder: undefined,
18
+ },
19
+ );
20
+
21
+ const id = useId();
22
+ const { name } = toRefs(props);
23
+
24
+ // we don't provide any rules here because we are using form-level validation
25
+ // https://vee-validate.logaretm.com/v4/guide/validation#form-level-validation
26
+ const { value, handleBlur, handleChange, errorMessage } = useField<string>(name, undefined, {
27
+ validateOnValueUpdate: false,
28
+ });
29
+
30
+ const validationListeners = {
31
+ blur: (event: InputEvent) => handleBlur(event, true),
32
+ change: handleChange,
33
+ input: (event: InputEvent) => handleChange(event, !!errorMessage.value),
34
+ };
35
+ </script>
36
+
37
+ <template>
38
+ <form-field :id :title :error-message>
39
+ <input :id :name :type :autocomplete :value :placeholder v-on="validationListeners" />
40
+ </form-field>
41
+ </template>
@@ -0,0 +1,32 @@
1
+ <script lang="ts" setup>
2
+ import { useField } from "vee-validate";
3
+ import { toRefs, useId } from "vue";
4
+ import FormField from "./FormField.vue";
5
+
6
+ const props = defineProps<{
7
+ title: string;
8
+ name: string;
9
+ placeholder?: string;
10
+ }>();
11
+
12
+ const id = useId();
13
+ const { name } = toRefs(props);
14
+
15
+ // we don't provide any rules here because we are using form-level validation
16
+ // https://vee-validate.logaretm.com/v4/guide/validation#form-level-validation
17
+ const { value, handleBlur, handleChange, errorMessage } = useField<string>(name, undefined, {
18
+ validateOnValueUpdate: false,
19
+ });
20
+
21
+ const validationListeners = {
22
+ blur: (event: InputEvent) => handleBlur(event, true),
23
+ change: handleChange,
24
+ input: (event: InputEvent) => handleChange(event, !!errorMessage.value),
25
+ };
26
+ </script>
27
+
28
+ <template>
29
+ <form-field :id :title :name :error-message>
30
+ <textarea :id :value :name :placeholder v-on="validationListeners" />
31
+ </form-field>
32
+ </template>
@@ -0,0 +1,111 @@
1
+ <script lang="ts" setup>
2
+ import useTheme from "@/composables/useTheme";
3
+ import { computed, useId } from "vue";
4
+
5
+ defineProps<{
6
+ pages: {
7
+ id: string;
8
+ title: string;
9
+ }[];
10
+ }>();
11
+
12
+ const id = useId();
13
+
14
+ const anchor = computed(() => {
15
+ return `--${id}`;
16
+ });
17
+
18
+ const { colors} = useTheme()
19
+ </script>
20
+
21
+ <template>
22
+ <div class="content">
23
+ <div>
24
+ <section v-for="page in pages" :key="page.title">
25
+ <span :id="page.id" />
26
+ <h2>{{ page.title }}</h2>
27
+ <slot :name="page.id" />
28
+ </section>
29
+ </div>
30
+ <nav>
31
+ <ol>
32
+ <li v-for="item in pages" :id="`link-${item.title}`" :key="item.title">
33
+ <a :href="`#${item.id}`">
34
+ {{ item.title }}
35
+ </a>
36
+ </li>
37
+ </ol>
38
+ </nav>
39
+ </div>
40
+ </template>
41
+
42
+ <style lang="css" scoped>
43
+ .content {
44
+ position: relative;
45
+ display: grid;
46
+ gap: var(--gutter);
47
+ align-items: start;
48
+
49
+ @media (--md) {
50
+ grid-template-columns: auto 12rem;
51
+ }
52
+ }
53
+
54
+ span {
55
+ display: block;
56
+ translate: 0 calc(var(--spacing-4) * -1);
57
+ }
58
+
59
+ nav {
60
+ position: sticky;
61
+ inset-block-start: var(--spacing-4);
62
+ display: none;
63
+ order: -1;
64
+ background: var(--color-bg-panel);
65
+ scroll-target-group: auto;
66
+
67
+ @media (--md) {
68
+ display: block;
69
+ order: 1;
70
+ }
71
+ }
72
+
73
+ ol {
74
+ padding: 0;
75
+ margin: 0;
76
+ list-style: none outside;
77
+
78
+ &::before {
79
+ position: absolute;
80
+ inset-block-start: anchor(top);
81
+ inset-inline-start: 0;
82
+ display: block;
83
+ inline-size: 0.25em;
84
+ /* stylelint-disable-next-line declaration-property-value-no-unknown */
85
+ block-size: anchor-size(block);
86
+ position-anchor: v-bind(anchor);
87
+ content: "";
88
+ background: v-bind(colors.solid);
89
+ transition:
90
+ top var(--transition),
91
+ height var(--transition);
92
+ }
93
+ }
94
+
95
+ a {
96
+ display: block;
97
+ padding-block: var(--spacing-1);
98
+ padding-inline-start: var(--spacing-3);
99
+ text-decoration: none;
100
+ border-inline-start: 0.25em solid v-bind(colors.subtle);
101
+
102
+ &:target-current {
103
+ anchor-name: v-bind(anchor);
104
+ color: v-bind(colors.solid);
105
+ }
106
+
107
+ &:hover {
108
+ border-color: v-bind(colors.solid);
109
+ }
110
+ }
111
+ </style>