Skip to content

Commit 13cf41d

Browse files
Merge pull request #642 from vuelessjs/beta
Beta
2 parents 27a1cfd + b40ca1c commit 13cf41d

38 files changed

Lines changed: 686 additions & 74 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ A UI library with Open Architecture for Vue.js 3 and Nuxt.js 3 / 4, powered by [
2828
- 🖼️ Inline SVG icons
2929
- 🪄 Auto component imports (as you use them)
3030
- 🧿 Uncompiled source in npm for better DX
31-
- 🧪️ 1300+ unit tests ensuring consistent logic
31+
- 🧪️ 1400+ unit tests ensuring consistent logic
3232
- 🛡️ Full TypeScript support with type safety
3333

3434
## Built-In Storybook

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vueless",
3-
"version": "1.3.4",
3+
"version": "1.3.5-beta.3",
44
"description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
55
"author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
66
"homepage": "https://vueless.com",

src/components.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export { default as UNotify } from "./ui.text-notify/UNotify.vue";
3636
export { default as UNumber } from "./ui.text-number/UNumber.vue";
3737
export { default as UFile } from "./ui.text-file/UFile.vue";
3838
export { default as UFiles } from "./ui.text-files/UFiles.vue";
39-
export { default as UEmpty } from "./ui.text-empty/UEmpty.vue";
4039
export { default as UBadge } from "./ui.text-badge/UBadge.vue";
4140
/* Containers */
4241
export { default as UDivider } from "./ui.container-divider/UDivider.vue";
@@ -46,6 +45,8 @@ export { default as UGroup } from "./ui.container-group/UGroup.vue";
4645
export { default as UGroups } from "./ui.container-groups/UGroups.vue";
4746
export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
4847
export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
48+
export { default as UEmpty } from "./ui.container-empty/UEmpty.vue";
49+
export { default as UPlaceholder } from "./ui.container-placeholder/UPlaceholder.vue";
4950
export { default as UCard } from "./ui.container-card/UCard.vue";
5051
export { default as UModal } from "./ui.container-modal/UModal.vue";
5152
export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";

src/components.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export { default as UNotify } from "./ui.text-notify/UNotify.vue";
3636
export { default as UNumber } from "./ui.text-number/UNumber.vue";
3737
export { default as UFile } from "./ui.text-file/UFile.vue";
3838
export { default as UFiles } from "./ui.text-files/UFiles.vue";
39-
export { default as UEmpty } from "./ui.text-empty/UEmpty.vue";
4039
export { default as UBadge } from "./ui.text-badge/UBadge.vue";
4140
/* Containers */
4241
export { default as UDivider } from "./ui.container-divider/UDivider.vue";
@@ -46,6 +45,8 @@ export { default as UGroup } from "./ui.container-group/UGroup.vue";
4645
export { default as UGroups } from "./ui.container-groups/UGroups.vue";
4746
export { default as UAccordion } from "./ui.container-accordion/UAccordion.vue";
4847
export { default as UAccordionItem } from "./ui.container-accordion-item/UAccordionItem.vue";
48+
export { default as UEmpty } from "./ui.container-empty/UEmpty.vue";
49+
export { default as UPlaceholder } from "./ui.container-placeholder/UPlaceholder.vue";
4950
export { default as UCard } from "./ui.container-card/UCard.vue";
5051
export { default as UModal } from "./ui.container-modal/UModal.vue";
5152
export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";

src/composables/useBreakpoint.ts

Lines changed: 97 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { onMounted, ref, watch, computed, onBeforeUnmount } from "vue";
22
import { isSSR } from "../utils/helper";
33

4-
import type { Ref } from "vue";
4+
type ResponsiveConfig<T> = Partial<Record<BreakpointName, T>>;
55

66
enum BreakpointName {
77
Xs = "xs",
@@ -21,12 +21,16 @@ enum BreakpointWidth {
2121
"2xl" = 1536,
2222
}
2323

24-
export function useBreakpoint() {
25-
let animationId: number | undefined;
24+
let isInitialized = false;
25+
let animationId: number | undefined;
26+
27+
const windowWidth = ref(0);
28+
const currentBreakpoint = ref(BreakpointName.Xs);
29+
const BREAKPOINT_KEYS = Object.keys(BreakpointName) as (keyof typeof BreakpointName)[];
2630

27-
const windowWidth = ref(0);
28-
const currentBreakpoint: Ref<BreakpointName> = ref(BreakpointName.Xs);
31+
watch(windowWidth, setBreakpoint, { immediate: true });
2932

33+
export function useBreakpoint() {
3034
const isPhone = computed(() => {
3135
return currentBreakpoint.value === BreakpointName.Xs;
3236
});
@@ -63,50 +67,14 @@ export function useBreakpoint() {
6367
return isDesktop.value || isLargeDesktop.value;
6468
});
6569

66-
watch(windowWidth, setBreakpoint, { immediate: true });
67-
6870
onMounted(() => {
69-
if (isSSR) return;
70-
71-
windowWidth.value = window.innerWidth;
72-
73-
window.addEventListener("resize", resizeListener, { passive: true });
71+
initBreakpointListener();
7472
});
7573

7674
onBeforeUnmount(() => {
77-
if (isSSR) return;
78-
7975
window.removeEventListener("resize", resizeListener);
8076
});
8177

82-
function resizeListener() {
83-
if (isSSR) return;
84-
85-
if (animationId) {
86-
window.cancelAnimationFrame(animationId);
87-
}
88-
89-
animationId = window.requestAnimationFrame(() => {
90-
windowWidth.value = window.innerWidth;
91-
});
92-
}
93-
94-
function setBreakpoint(newWindowWidth: number) {
95-
if (newWindowWidth === undefined) return;
96-
97-
const breakpoints = [
98-
{ width: BreakpointWidth["2xl"], name: BreakpointName["2xl"] },
99-
{ width: BreakpointWidth.Xl, name: BreakpointName.Xl },
100-
{ width: BreakpointWidth.Lg, name: BreakpointName.Lg },
101-
{ width: BreakpointWidth.Md, name: BreakpointName.Md },
102-
{ width: BreakpointWidth.Sm, name: BreakpointName.Sm },
103-
];
104-
105-
currentBreakpoint.value =
106-
breakpoints.find((breakpoint) => newWindowWidth >= breakpoint.width)?.name ||
107-
BreakpointName.Xs;
108-
}
109-
11078
return {
11179
isPhone,
11280
isLargePhone,
@@ -120,3 +88,90 @@ export function useBreakpoint() {
12088
breakpoint: currentBreakpoint,
12189
};
12290
}
91+
92+
/**
93+
* Shorthand function that can be used directly in templates.
94+
* Returns the appropriate value based on the current breakpoint.
95+
* Vue will track the reactive dependency and re-render when the breakpoint changes.
96+
*
97+
* @example
98+
* ```vue
99+
* <template>
100+
* <UButton :size="r({ sm: 'sm', md: 'md' })">Click me</UButton>
101+
* </template>
102+
*
103+
* <script setup>
104+
* import { r } from "vueless";
105+
* </script>
106+
* ```
107+
*/
108+
export function r<T>(config: ResponsiveConfig<T>): T {
109+
initBreakpointListener();
110+
111+
const definedKeys = BREAKPOINT_KEYS.filter((key) => BreakpointName[key] in config);
112+
113+
if (!definedKeys.length) {
114+
return undefined as T;
115+
}
116+
117+
const currentIndex = BREAKPOINT_KEYS.findIndex(
118+
(key) => BreakpointName[key] === currentBreakpoint.value,
119+
);
120+
const smallestDefinedIndex = BREAKPOINT_KEYS.indexOf(definedKeys[0]);
121+
const largestDefinedIndex = BREAKPOINT_KEYS.indexOf(definedKeys[definedKeys.length - 1]);
122+
123+
if (currentIndex <= smallestDefinedIndex) {
124+
return config[BreakpointName[definedKeys[0]]] as T;
125+
}
126+
127+
if (currentIndex >= largestDefinedIndex) {
128+
return config[BreakpointName[definedKeys[definedKeys.length - 1]]] as T;
129+
}
130+
131+
for (let i = currentIndex; i >= 0; i--) {
132+
const bp = BreakpointName[BREAKPOINT_KEYS[i]];
133+
134+
if (bp in config) {
135+
return config[bp] as T;
136+
}
137+
}
138+
139+
return config[BreakpointName[definedKeys[0]]] as T;
140+
}
141+
142+
function setBreakpoint(newWindowWidth: number) {
143+
if (newWindowWidth === undefined) return;
144+
145+
for (let i = BREAKPOINT_KEYS.length - 1; i >= 0; i--) {
146+
const key = BREAKPOINT_KEYS[i];
147+
148+
if (newWindowWidth >= BreakpointWidth[key]) {
149+
currentBreakpoint.value = BreakpointName[key];
150+
151+
return;
152+
}
153+
}
154+
155+
currentBreakpoint.value = BreakpointName.Xs;
156+
}
157+
158+
function resizeListener() {
159+
if (isSSR) return;
160+
161+
if (animationId) {
162+
window.cancelAnimationFrame(animationId);
163+
}
164+
165+
animationId = window.requestAnimationFrame(() => {
166+
windowWidth.value = window.innerWidth;
167+
});
168+
}
169+
170+
function initBreakpointListener() {
171+
if (isInitialized || isSSR) return;
172+
173+
isInitialized = true;
174+
windowWidth.value = window.innerWidth;
175+
176+
window.addEventListener("resize", resizeListener, { passive: true });
177+
}

src/constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,6 @@ export const COMPONENTS = {
284284
UNumber: "ui.text-number",
285285
UFile: "ui.text-file",
286286
UFiles: "ui.text-files",
287-
UEmpty: "ui.text-empty",
288287
UBadge: "ui.text-badge",
289288

290289
/* Containers */
@@ -295,6 +294,8 @@ export const COMPONENTS = {
295294
UGroups: "ui.container-groups",
296295
UAccordion: "ui.container-accordion",
297296
UAccordionItem: "ui.container-accordion-item",
297+
UEmpty: "ui.container-empty",
298+
UPlaceholder: "ui.container-placeholder",
298299
UCard: "ui.container-card",
299300
UModal: "ui.container-modal",
300301
UModalConfirm: "ui.container-modal-confirm",

src/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export { useLocale } from "./composables/useLocale";
2929
export { useUI } from "./composables/useUI";
3030
export { useDarkMode } from "./composables/useDarkMode";
3131
export { useRequestQueue } from "./composables/useRequestQueue";
32-
export { useBreakpoint } from "./composables/useBreakpoint";
32+
export { useBreakpoint, r } from "./composables/useBreakpoint";
3333
export { useLoaderOverlay } from "./ui.loader-overlay/useLoaderOverlay";
3434
export { useLoaderProgress } from "./ui.loader-progress/useLoaderProgress";
3535
export { useMutationObserver } from "./composables/useMutationObserver";

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export { useLocale } from "./composables/useLocale";
3535
export { useUI } from "./composables/useUI";
3636
export { useDarkMode } from "./composables/useDarkMode";
3737
export { useRequestQueue } from "./composables/useRequestQueue";
38-
export { useBreakpoint } from "./composables/useBreakpoint";
38+
export { useBreakpoint, r } from "./composables/useBreakpoint";
3939
export { useLoaderOverlay } from "./ui.loader-overlay/useLoaderOverlay";
4040
export { useLoaderProgress } from "./ui.loader-progress/useLoaderProgress";
4141
export { useMutationObserver } from "./composables/useMutationObserver";

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import UTextDefaultConfig from "./ui.text-block/config";
22
import UAlertDefaultConfig from "./ui.text-alert/config";
3-
import UEmptyDefaultConfig from "./ui.text-empty/config";
3+
import UEmptyDefaultConfig from "./ui.container-empty/config";
44
import UFileDefaultConfig from "./ui.text-file/config";
55
import UFilesDefaultConfig from "./ui.text-files/config";
66
import UHeaderDefaultConfig from "./ui.text-header/config";

0 commit comments

Comments
 (0)