@soubiran/ui 0.9.1 → 0.10.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.
package/dist/resolver.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  //#region src/resolver.ts
2
2
  const components = [
3
+ "Comments",
4
+ "Feedback",
5
+ "LoginCallback",
3
6
  "Page",
7
+ "PageTitle",
8
+ "PageHeader",
4
9
  "Header",
5
10
  "Socials",
6
11
  "Sponsors",
7
- "Feedback",
8
12
  "Container",
9
- "PageTitle",
10
- "PageHeader",
11
- "FeedbackCard",
12
13
  "ViewersCounter",
13
14
  "TableOfContents"
14
15
  ];
@@ -17,8 +18,8 @@ function resolver_default() {
17
18
  type: "component",
18
19
  resolve: (name) => {
19
20
  if (components.includes(name)) return {
20
- name: "default",
21
- from: `@soubiran/ui/components/${name}.vue`
21
+ name,
22
+ from: "@soubiran/ui"
22
23
  };
23
24
  }
24
25
  };
package/dist/style.css CHANGED
@@ -1,146 +1,11 @@
1
- .fade-enter-active, .fade-leave-active {
2
- transition: opacity .4s ease-out;
3
- }
4
-
5
- .fade-enter-from, .fade-leave-to {
6
- opacity: 0;
7
- }
8
-
9
- @media (prefers-reduced-motion: no-preference) {
10
- html:not(.no-sliding) .slide-enter, html:not(.no-sliding) .slide-enter-content > * {
11
- --enter-step: 60ms;
12
- --enter-initial: 0s;
13
- animation: 1s both slide-enter;
14
- animation-delay: calc(var(--enter-stage) * var(--enter-step));
15
- }
16
-
17
- .slide-enter-content > :first-child {
18
- --enter-stage: 1 !important;
19
- }
20
-
21
- .slide-enter-content > :nth-child(2) {
22
- --enter-stage: 2 !important;
23
- }
24
-
25
- .slide-enter-content > :nth-child(3) {
26
- --enter-stage: 3 !important;
27
- }
28
-
29
- .slide-enter-content > :nth-child(4) {
30
- --enter-stage: 4 !important;
31
- }
32
-
33
- .slide-enter-content > :nth-child(5) {
34
- --enter-stage: 5 !important;
35
- }
36
-
37
- .slide-enter-content > :nth-child(6) {
38
- --enter-stage: 6 !important;
39
- }
40
-
41
- .slide-enter-content > :nth-child(7) {
42
- --enter-stage: 7 !important;
43
- }
44
-
45
- .slide-enter-content > :nth-child(8) {
46
- --enter-stage: 8 !important;
47
- }
48
-
49
- .slide-enter-content > :nth-child(9) {
50
- --enter-stage: 9 !important;
51
- }
52
-
53
- .slide-enter-content > :nth-child(10) {
54
- --enter-stage: 10 !important;
55
- }
56
-
57
- .slide-enter-content > :nth-child(11) {
58
- --enter-stage: 11 !important;
59
- }
60
-
61
- .slide-enter-content > :nth-child(12) {
62
- --enter-stage: 12 !important;
63
- }
64
-
65
- .slide-enter-content > :nth-child(13) {
66
- --enter-stage: 13 !important;
67
- }
68
-
69
- .slide-enter-content > :nth-child(14) {
70
- --enter-stage: 14 !important;
71
- }
72
-
73
- .slide-enter-content > :nth-child(15) {
74
- --enter-stage: 15 !important;
75
- }
76
-
77
- .slide-enter-content > :nth-child(16) {
78
- --enter-stage: 16 !important;
79
- }
80
-
81
- .slide-enter-content > :nth-child(17) {
82
- --enter-stage: 17 !important;
83
- }
84
-
85
- .slide-enter-content > :nth-child(18) {
86
- --enter-stage: 18 !important;
87
- }
88
-
89
- .slide-enter-content > :nth-child(19) {
90
- --enter-stage: 19 !important;
91
- }
92
-
93
- .slide-enter-content > :nth-child(20) {
94
- --enter-stage: 20 !important;
95
- }
96
- }
97
-
98
- @keyframes slide-enter {
99
- 0% {
100
- opacity: 0;
101
- transform: translateY(10px);
102
- }
103
-
104
- to {
105
- opacity: 1;
106
- transform: translateY(0);
107
- }
108
- }
109
-
110
- @keyframes scale-up {
111
- from {
112
- opacity: 0;
113
- transform: scale(.96) translateY(8px);
114
- }
115
-
116
- to {
117
- opacity: 1;
118
- transform: scale(1) translateY(0);
119
- }
120
- }
121
-
122
- .shiki {
123
- color: var(--shiki-light);
124
- background-color: var(--ui-color-neutral-100);
125
- }
126
-
127
- .shiki .line span {
128
- color: var(--shiki-light);
129
- }
130
-
131
- .dark .shiki {
132
- color: var(--shiki-dark);
133
- background-color: var(--ui-color-neutral-800);
134
- }
135
-
136
- .dark .shiki .line span {
137
- color: var(--shiki-dark);
138
- }
1
+ @import './styles/animations.css';
2
+ @import './styles/keyframes.css';
3
+ @import './styles/shiki.css';
139
4
 
140
5
  @theme {
141
- --font-sans: "DM Sans", sans-serif;
142
- --font-sofia: "Sofia sans", sans-serif;
143
- --font-mono: "DM Mono", monospace;
6
+ --font-sans: 'DM Sans', sans-serif;
7
+ --font-sofia: 'Sofia sans', sans-serif;
8
+ --font-mono: 'DM Mono', monospace;
144
9
  }
145
10
 
146
11
  html {
@@ -0,0 +1,80 @@
1
+ .fade-enter-active,
2
+ .fade-leave-active {
3
+ transition: opacity 0.4s ease-out;
4
+ }
5
+
6
+ .fade-enter-from,
7
+ .fade-leave-to {
8
+ opacity: 0;
9
+ }
10
+
11
+ @media (prefers-reduced-motion: no-preference) {
12
+ html:not(.no-sliding) .slide-enter,
13
+ html:not(.no-sliding) .slide-enter-content > * {
14
+ --enter-step: 60ms;
15
+ --enter-initial: 0ms;
16
+ animation: slide-enter 1s both 1;
17
+ animation-delay: calc(var(--enter-stage) * var(--enter-step));
18
+ }
19
+
20
+ .slide-enter-content > *:nth-child(1) {
21
+ --enter-stage: 1 !important;
22
+ }
23
+ .slide-enter-content > *:nth-child(2) {
24
+ --enter-stage: 2 !important;
25
+ }
26
+ .slide-enter-content > *:nth-child(3) {
27
+ --enter-stage: 3 !important;
28
+ }
29
+ .slide-enter-content > *:nth-child(4) {
30
+ --enter-stage: 4 !important;
31
+ }
32
+ .slide-enter-content > *:nth-child(5) {
33
+ --enter-stage: 5 !important;
34
+ }
35
+ .slide-enter-content > *:nth-child(6) {
36
+ --enter-stage: 6 !important;
37
+ }
38
+ .slide-enter-content > *:nth-child(7) {
39
+ --enter-stage: 7 !important;
40
+ }
41
+ .slide-enter-content > *:nth-child(8) {
42
+ --enter-stage: 8 !important;
43
+ }
44
+ .slide-enter-content > *:nth-child(9) {
45
+ --enter-stage: 9 !important;
46
+ }
47
+ .slide-enter-content > *:nth-child(10) {
48
+ --enter-stage: 10 !important;
49
+ }
50
+ .slide-enter-content > *:nth-child(11) {
51
+ --enter-stage: 11 !important;
52
+ }
53
+ .slide-enter-content > *:nth-child(12) {
54
+ --enter-stage: 12 !important;
55
+ }
56
+ .slide-enter-content > *:nth-child(13) {
57
+ --enter-stage: 13 !important;
58
+ }
59
+ .slide-enter-content > *:nth-child(14) {
60
+ --enter-stage: 14 !important;
61
+ }
62
+ .slide-enter-content > *:nth-child(15) {
63
+ --enter-stage: 15 !important;
64
+ }
65
+ .slide-enter-content > *:nth-child(16) {
66
+ --enter-stage: 16 !important;
67
+ }
68
+ .slide-enter-content > *:nth-child(17) {
69
+ --enter-stage: 17 !important;
70
+ }
71
+ .slide-enter-content > *:nth-child(18) {
72
+ --enter-stage: 18 !important;
73
+ }
74
+ .slide-enter-content > *:nth-child(19) {
75
+ --enter-stage: 19 !important;
76
+ }
77
+ .slide-enter-content > *:nth-child(20) {
78
+ --enter-stage: 20 !important;
79
+ }
80
+ }
@@ -0,0 +1,23 @@
1
+ @keyframes slide-enter {
2
+ 0% {
3
+ transform: translateY(10px);
4
+ opacity: 0;
5
+ }
6
+
7
+ to {
8
+ transform: translateY(0);
9
+ opacity: 1;
10
+ }
11
+ }
12
+
13
+ @keyframes scale-up {
14
+ from {
15
+ opacity: 0;
16
+ transform: scale(0.96) translateY(8px);
17
+ }
18
+
19
+ to {
20
+ opacity: 1;
21
+ transform: scale(1) translateY(0);
22
+ }
23
+ }
@@ -0,0 +1,17 @@
1
+ .shiki {
2
+ color: var(--shiki-light);
3
+ background-color: var(--ui-color-neutral-100);
4
+ }
5
+
6
+ .shiki .line span {
7
+ color: var(--shiki-light);
8
+ }
9
+
10
+ .dark .shiki {
11
+ color: var(--shiki-dark);
12
+ background-color: var(--ui-color-neutral-800);
13
+ }
14
+
15
+ .dark .shiki .line span {
16
+ color: var(--shiki-dark);
17
+ }
@@ -12,7 +12,7 @@ const prose = [
12
12
  "prose-img:rounded-lg prose-img:border prose-img:border-accented prose-img:shadow-sm",
13
13
  "prose-video:rounded-lg prose-video:border prose-video:border-accented prose-video:shadow-sm",
14
14
  "prose-figcaption:text-center prose-figcaption:py-1 prose-figcaption:m-0",
15
- "[&_:where(:not(:where([class~=\"not-prose\"],[class~=\"not-prose\"]_*)):first-child)]:mt-0 [&_:where(:not(:where([class~=\"not-prose\"],[class~=\"not-prose\"]_*)):last-child)]:mb-0"
15
+ "[&_:where(:not(:where(.not-prose,.not-prose_*)):first-child)]:mt-0 [&_:where(:not(:where(.not-prose,.not-prose_*)):last-child)]:mb-0"
16
16
  ];
17
17
  var wrapper_classes_default = ["slide-enter-content", ...prose];
18
18
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@soubiran/ui",
3
3
  "type": "module",
4
- "version": "0.9.1",
4
+ "version": "0.10.0",
5
5
  "author": "Estéban Soubiran <esteban@soubiran.dev>",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/Barbapapazes",
@@ -17,11 +17,6 @@
17
17
  "import": "./dist/index.js",
18
18
  "style": "./dist/style.css"
19
19
  },
20
- "./components/*": "./dist/components/*",
21
- "./composables/*": {
22
- "types": "./dist/composables/*.d.ts",
23
- "import": "./dist/composables/*.js"
24
- },
25
20
  "./imports": {
26
21
  "types": "./dist/imports.d.mts",
27
22
  "import": "./dist/imports.mjs"
@@ -48,35 +43,40 @@
48
43
  "dist",
49
44
  "umami.d.ts"
50
45
  ],
51
- "dependencies": {
46
+ "peerDependencies": {
52
47
  "@nuxt/ui": "^4.7.0",
53
48
  "@pinia/colada": "^1.2.1",
54
- "@vue/compiler-sfc": "^3.5.33",
49
+ "pinia": "^3.0.4",
50
+ "vue": "^3.5.35"
51
+ },
52
+ "dependencies": {
55
53
  "@vueuse/core": "^14.3.0",
56
- "markdown-it-github-alerts": "^1.0.1",
57
54
  "motion-v": "^2.2.1",
58
55
  "ofetch": "^1.5.1",
59
56
  "partysocket": "^1.1.18",
60
- "pinia": "^3.0.4",
61
57
  "reka-ui": "^2.9.6",
62
58
  "tailwind-variants": "^3.2.2",
63
- "unplugin-vue-components": "^32.0.0",
64
- "vite": "^8.0.10",
65
- "vue": "^3.5.33",
66
- "vue-router": "^5.0.6"
59
+ "unplugin-vue-components": "^32.0.0"
67
60
  },
68
61
  "devDependencies": {
69
62
  "@iconify-json/ph": "^1.2.2",
70
63
  "@iconify-json/simple-icons": "^1.2.79",
71
- "@tsdown/css": "^0.21.10",
64
+ "@nuxt/ui": "^4.7.0",
65
+ "@pinia/colada": "^1.2.1",
66
+ "@tailwindcss/typography": "^0.5.19",
72
67
  "@types/node": "^24.12.2",
73
68
  "@vitejs/plugin-vue": "^6.0.6",
74
- "@vue/language-core": "^3.2.7",
69
+ "@vue/compiler-sfc": "^3.5.35",
75
70
  "@vue/tsconfig": "^0.9.1",
71
+ "pinia": "^3.0.4",
72
+ "tailwindcss": "^4.3.3",
76
73
  "tsdown": "^0.21.10",
77
74
  "typescript": "~5.9.3",
78
75
  "unplugin-icons": "^23.0.1",
79
76
  "unplugin-vue": "^7.2.0",
77
+ "vite": "^8.0.10",
78
+ "vue": "^3.5.35",
79
+ "vue-router": "^5.0.6",
80
80
  "vue-tsc": "^3.2.7"
81
81
  },
82
82
  "scripts": {
@@ -1,36 +0,0 @@
1
- <script lang="ts">
2
- import { tv } from 'tailwind-variants'
3
- import { computed } from 'vue'
4
-
5
- const container = tv({
6
- slots: {
7
- base: 'px-4 sm:px-6 lg:px-8',
8
- inner: 'w-full max-w-7xl mx-auto',
9
- },
10
- })
11
-
12
- export interface ContainerProps {
13
- class?: any
14
- ui?: Partial<typeof container.slots>
15
- }
16
- export interface ContainerEmits {}
17
- export interface ContainerSlots {
18
- default: (props: any) => any
19
- }
20
- </script>
21
-
22
- <script lang="ts" setup>
23
- const props = defineProps<ContainerProps>()
24
- defineEmits<ContainerEmits>()
25
- defineSlots<ContainerSlots>()
26
-
27
- const ui = computed(() => container())
28
- </script>
29
-
30
- <template>
31
- <div :class="ui.base({ class: [props.ui?.base, props.class] })">
32
- <div :class="ui.inner({ class: props.ui?.inner })">
33
- <slot />
34
- </div>
35
- </div>
36
- </template>
@@ -1,65 +0,0 @@
1
- <script lang="ts">
2
- import { tv } from 'tailwind-variants'
3
- import { computed, ref } from 'vue'
4
- import thumbsUp from '~icons/ph/thumbs-up'
5
- import useUmami from '../composables/useUmami'
6
- import FeedbackCard from './FeedbackCard.vue'
7
-
8
- const feedback = tv({
9
- slots: {
10
- base: 'px-0 py-1 text-dimmed text-sm',
11
- },
12
- })
13
-
14
- export interface FeedbackProps {
15
- id: string
16
- class?: any
17
- ui?: Partial<typeof feedback.slots>
18
- }
19
- export interface FeedbackEmits {}
20
- export interface FeedbackSlots {}
21
- </script>
22
-
23
- <script lang="ts" setup>
24
- const props = defineProps<FeedbackProps>()
25
- defineEmits<FeedbackEmits>()
26
- defineSlots<FeedbackSlots>()
27
-
28
- const { track } = useUmami()
29
- function onClick() {
30
- track('feedback_click')
31
- }
32
-
33
- const content = ref('')
34
- const rating = ref('')
35
-
36
- function onSuccess() {
37
- content.value = ''
38
- rating.value = ''
39
- }
40
-
41
- const ui = computed(() => feedback())
42
- </script>
43
-
44
- <template>
45
- <UPopover :ui="{ content: 'ring-0 data-[state=open]:animate-[scale-up_100ms_ease-out]' }">
46
- <UButton
47
- variant="link"
48
- color="neutral"
49
- label="Give feedback"
50
- size="sm"
51
- :icon="thumbsUp"
52
- :class="ui.base({ class: [props.ui?.base, props.class] })"
53
- @click="onClick"
54
- />
55
-
56
- <template #content>
57
- <FeedbackCard
58
- :id="props.id"
59
- v-model:rating="rating"
60
- v-model:content="content"
61
- @success="onSuccess"
62
- />
63
- </template>
64
- </UPopover>
65
- </template>
@@ -1,168 +0,0 @@
1
- <script lang="ts">
2
- import { useMutation } from '@pinia/colada'
3
- import { motion } from 'motion-v'
4
- import { ofetch } from 'ofetch'
5
- import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from 'reka-ui'
6
- import { tv } from 'tailwind-variants'
7
- import { computed, ref } from 'vue'
8
- import checkCircle from '~icons/ph/check-circle'
9
- import useUmami from '../composables/useUmami'
10
-
11
- const feedbackCard = tv({
12
- slots: {
13
- base: 'relative w-64',
14
- successfullySentOverlay: 'absolute z-10 inset-0 bg-default',
15
- successfullySentContent: 'absolute z-20 inset-0 flex flex-col justify-center items-center text-muted text-xs',
16
- successfullySentIcon: 'size-5',
17
- input: 'w-full',
18
- radioGroup: 'flex flex-row gap-2 text-lg',
19
- radioGroupItem: 'peer',
20
- radioGroupLabel: 'grayscale-100 hover:grayscale-0 peer-data-[state=checked]:grayscale-0',
21
- },
22
- })
23
-
24
- export interface FeedbackCardProps {
25
- id: string
26
- class?: any
27
- ui?: Partial<typeof feedbackCard.slots>
28
- }
29
- export interface FeedbackCardEmits {
30
- success: [void]
31
- }
32
- export interface FeedbackCardSlots {}
33
- </script>
34
-
35
- <script lang="ts" setup>
36
- const props = defineProps<FeedbackCardProps>()
37
- const emits = defineEmits<FeedbackCardEmits>()
38
- defineSlots<FeedbackCardSlots>()
39
-
40
- const content = defineModel<string>('content', { required: true })
41
- const rating = defineModel<string>('rating', { required: true })
42
-
43
- const ratings = [
44
- {
45
- label: '😭',
46
- value: 'Hate it',
47
- },
48
- {
49
- label: '🙁',
50
- value: 'Not great',
51
- },
52
- {
53
- label: '🙂',
54
- value: 'It\'s ok',
55
- },
56
- {
57
- label: '🤩',
58
- value: 'Love it',
59
- },
60
- ]
61
-
62
- const successfullySent = ref(false)
63
-
64
- const { track } = useUmami()
65
- const { mutate, isLoading, error } = useMutation<
66
- void,
67
- { rating: string, content: string },
68
- { status: number, data: { errors?: { content?: string[], rating?: string[] } } }
69
- >({
70
- mutation: ({ rating, content }) => ofetch(`/api/pages/${props.id}/feedback`, {
71
- method: 'POST',
72
- body: { rating, content },
73
- baseURL: import.meta.env.VITE_API_BASE_URL,
74
- }),
75
- onSuccess: () => {
76
- successfullySent.value = true
77
-
78
- track('feedback_submit')
79
-
80
- // Wait for the animation to finish
81
- setTimeout(emits, 200, 'success')
82
- },
83
- })
84
-
85
- function sendFeedback() {
86
- mutate({ rating: rating.value, content: content.value })
87
- }
88
-
89
- const formattedError = computed<string | undefined>(() => {
90
- if (!error.value)
91
- return undefined
92
-
93
- if (error.value.data?.errors) {
94
- return error.value.data.errors.content?.[0] || error.value.data.errors.rating?.[0]
95
- }
96
-
97
- if (error.value.status === 404)
98
- return 'Page not found. Cannot send feedback. It\'s us, not you!'
99
-
100
- if (error.value.status === 503)
101
- return 'Service is currently unavailable. Please try again later.'
102
-
103
- return 'An unexpected error occurred.'
104
- })
105
-
106
- const ui = computed(() => feedbackCard())
107
- </script>
108
-
109
- <template>
110
- <UCard :class="ui.base({ class: [props.ui?.base, props.class] })" :ui="{ body: 'p-2 sm:p-2', footer: 'p-2 sm:p-2 flex items-center justify-between' }">
111
- <template v-if="successfullySent">
112
- <motion.div
113
- :initial="{ opacity: 0 }"
114
- :animate="{ opacity: 1, transition: { duration: 0.2 } }"
115
- :class="ui.successfullySentOverlay({ class: props.ui?.successfullySentOverlay })"
116
- />
117
- <div :class="ui.successfullySentContent({ class: props.ui?.successfullySentContent })">
118
- <motion.div
119
- :initial="{ opacity: 0, transform: 'translateY(4px)' }"
120
- :animate="{ opacity: 1, transform: 'translateY(0)', transition: { delay: 0.1, duration: 0.3 } }"
121
- >
122
- <UIcon :name="checkCircle" :class="ui.successfullySentIcon({ class: props.ui?.successfullySentIcon })" />
123
- </motion.div>
124
- <motion.p
125
- :initial="{ opacity: 0, transform: 'translateY(4px)' }"
126
- :animate="{ opacity: 1, transform: 'translateY(0)', transition: { delay: 0.2, duration: 0.3 } }"
127
- class="mt-3"
128
- >
129
- Your feedback has been received.
130
- </motion.p>
131
- <motion.p
132
- :initial="{ opacity: 0, transform: 'translateY(4px)' }"
133
- :animate="{ opacity: 1, transform: 'translateY(0)', transition: { delay: 0.3, duration: 0.3 } }"
134
- class="mt-1"
135
- >
136
- Thanks for your help!
137
- </motion.p>
138
- </div>
139
- </template>
140
-
141
- <UFormField :error="formattedError">
142
- <UTextarea v-model="content" placeholder="Your feedback..." variant="soft" :class="ui.input({ class: props.ui?.input })" />
143
- <template #error="{ error: formFieldError }">
144
- <motion.div
145
- v-if="formFieldError"
146
- :initial="{ height: 0 }"
147
- :animate="{ height: 'auto' }"
148
- class="text-sm"
149
- >
150
- {{ formFieldError }}
151
- </motion.div>
152
- </template>
153
- </UFormField>
154
- <template #footer>
155
- <RadioGroupRoot v-model="rating" :class="ui.radioGroup({ class: props.ui?.radioGroup })">
156
- <div v-for="item in ratings" :key="item.value">
157
- <RadioGroupItem :id="item.value" :value="item.value" :class="ui.radioGroupItem({ class: props.ui?.radioGroupItem })">
158
- <RadioGroupIndicator />
159
- </RadioGroupItem>
160
- <UTooltip :text="item.value">
161
- <label :for="item.value" :class="ui.radioGroupLabel({ class: props.ui?.radioGroupLabel })">{{ item.label }}</label>
162
- </UTooltip>
163
- </div>
164
- </RadioGroupRoot>
165
- <UButton size="sm" label="Send" :loading="isLoading" @click="sendFeedback" />
166
- </template>
167
- </UCard>
168
- </template>