Skip to content

Commit 7c5a09e

Browse files
committed
v1.12.2
fix: #1718
1 parent 7a43609 commit 7c5a09e

File tree

13 files changed

+548
-1
lines changed

13 files changed

+548
-1
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 1.12.2
4+
5+
### Patch Changes
6+
7+
- - feat(utils): add responsive breakpoint utilities
8+
9+
Add useMediaQuery, useBreakpoints, and useCurrentBreakpoint hooks with TypeScript support and comprehensive documentation for controlling component behavior based on screen size.
10+
311
## 1.12.1
412

513
### Patch Changes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "flowbite-svelte",
3-
"version": "1.12.1",
3+
"version": "1.12.2",
44
"description": "Flowbite components for Svelte",
55
"main": "dist/index.js",
66
"author": {

src/lib/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { default as CloseButton } from "./CloseButton.svelte";
44
export { closeButton } from "./theme";
55
export { trapFocus } from "./actions";
66
export { default as Popper } from "./Popper.svelte";
7+
export * from "./responsive.svelte";
78

89
export function cn(...inputs: ClassValue[]) {
910
return twMerge(clsx(inputs));

src/lib/utils/responsive.svelte.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { browser } from "$app/environment";
2+
3+
/**
4+
* A Svelte 5 rune-based media query hook
5+
* @param query - CSS media query string (e.g., '(min-width: 768px)')
6+
* @returns A function that returns the current match state
7+
*/
8+
export function useMediaQuery(query: string): () => boolean {
9+
let matches = $state<boolean>(false);
10+
11+
$effect(() => {
12+
if (!browser) return;
13+
14+
const mediaQuery: MediaQueryList = window.matchMedia(query);
15+
matches = mediaQuery.matches;
16+
17+
const handler = (e: MediaQueryListEvent): void => {
18+
matches = e.matches;
19+
};
20+
21+
mediaQuery.addEventListener("change", handler);
22+
23+
return (): void => {
24+
mediaQuery.removeEventListener("change", handler);
25+
};
26+
});
27+
28+
return (): boolean => matches;
29+
}
30+
31+
/**
32+
* Hook for common Tailwind CSS breakpoints
33+
* @returns Object with boolean values for each breakpoint
34+
*/
35+
export function useBreakpoints() {
36+
const sm = useMediaQuery("(min-width: 640px)");
37+
const md = useMediaQuery("(min-width: 768px)");
38+
const lg = useMediaQuery("(min-width: 1024px)");
39+
const xl = useMediaQuery("(min-width: 1280px)");
40+
const xxl = useMediaQuery("(min-width: 1536px)");
41+
42+
return {
43+
get sm() {
44+
return sm();
45+
},
46+
get md() {
47+
return md();
48+
},
49+
get lg() {
50+
return lg();
51+
},
52+
get xl() {
53+
return xl();
54+
},
55+
get "2xl"() {
56+
return xxl();
57+
},
58+
get isMobile() {
59+
return !sm();
60+
},
61+
get isTablet() {
62+
return sm() && !lg();
63+
},
64+
get isDesktop() {
65+
return lg();
66+
}
67+
};
68+
}
69+
70+
/**
71+
* Get current breakpoint name
72+
* @returns Current breakpoint as string
73+
*/
74+
export function useCurrentBreakpoint(): () => "xs" | "sm" | "md" | "lg" | "xl" | "2xl" {
75+
let currentBreakpoint = $state<"xs" | "sm" | "md" | "lg" | "xl" | "2xl">("xs");
76+
77+
$effect(() => {
78+
if (!browser) return;
79+
80+
const updateBreakpoint = (): void => {
81+
const width = window.innerWidth;
82+
if (width >= 1536) currentBreakpoint = "2xl";
83+
else if (width >= 1280) currentBreakpoint = "xl";
84+
else if (width >= 1024) currentBreakpoint = "lg";
85+
else if (width >= 768) currentBreakpoint = "md";
86+
else if (width >= 640) currentBreakpoint = "sm";
87+
else currentBreakpoint = "xs";
88+
};
89+
90+
updateBreakpoint();
91+
window.addEventListener("resize", updateBreakpoint);
92+
93+
return (): void => {
94+
window.removeEventListener("resize", updateBreakpoint);
95+
};
96+
});
97+
98+
return (): "xs" | "sm" | "md" | "lg" | "xl" | "2xl" => currentBreakpoint;
99+
}
100+
101+
// Type definitions for common breakpoint values
102+
export type BreakpointKey = "xs" | "sm" | "md" | "lg" | "xl" | "2xl";
103+
104+
export const BREAKPOINTS: Record<BreakpointKey, number> = {
105+
xs: 0,
106+
sm: 640,
107+
md: 768,
108+
lg: 1024,
109+
xl: 1280,
110+
"2xl": 1536
111+
} as const;

src/routes/api-check/components/accordion/+page.svelte

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,39 @@
112112
<HighlightCompo codeLang="ts" code={exampleModules["./examples/TransitionNone.svelte"] as string} />
113113
{/snippet}
114114
</CodeWrapper>
115+
116+
<H2>Breakpoints</H2>
117+
<CodeWrapper innerClass="space-y-4">
118+
<ExampleComponents.BpBasic />
119+
{#snippet codeblock()}
120+
<HighlightCompo codeLang="ts" code={exampleModules["./examples/BpBasic.svelte"] as string} />
121+
{/snippet}
122+
</CodeWrapper>
123+
124+
<CodeWrapper innerClass="space-y-4">
125+
<ExampleComponents.BpObject />
126+
{#snippet codeblock()}
127+
<HighlightCompo codeLang="ts" code={exampleModules["./examples/BpObject.svelte"] as string} />
128+
{/snippet}
129+
</CodeWrapper>
130+
131+
<CodeWrapper innerClass="space-y-4">
132+
<ExampleComponents.BpAdvanced />
133+
{#snippet codeblock()}
134+
<HighlightCompo codeLang="ts" code={exampleModules["./examples/BpAdvanced.svelte"] as string} />
135+
{/snippet}
136+
</CodeWrapper>
137+
138+
<CodeWrapper innerClass="space-y-4">
139+
<ExampleComponents.BpRange />
140+
{#snippet codeblock()}
141+
<HighlightCompo codeLang="ts" code={exampleModules["./examples/BpRange.svelte"] as string} />
142+
{/snippet}
143+
</CodeWrapper>
144+
145+
<CodeWrapper innerClass="space-y-4">
146+
<ExampleComponents.BpComplex />
147+
{#snippet codeblock()}
148+
<HighlightCompo codeLang="ts" code={exampleModules["./examples/BpComplex.svelte"] as string} />
149+
{/snippet}
150+
</CodeWrapper>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import { AccordionItem, useCurrentBreakpoint, useBreakpoints } from "flowbite-svelte";
3+
const breakpoints = useBreakpoints();
4+
const getCurrentBreakpoint = useCurrentBreakpoint();
5+
const currentBp = $derived(getCurrentBreakpoint());
6+
</script>
7+
8+
<AccordionItem open={breakpoints.lg}>
9+
{#snippet header()}Desktop Only (Current: {currentBp}){/snippet}
10+
<p>This opens only on large screens and above.</p>
11+
</AccordionItem>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { AccordionItem, useMediaQuery } from "flowbite-svelte";
3+
const isMdAndUp = useMediaQuery("(min-width: 768px)");
4+
</script>
5+
6+
<AccordionItem open={isMdAndUp()}>
7+
{#snippet header()}Opens on tablets and desktop{/snippet}
8+
<p>This content is visible on medium screens and larger.</p>
9+
</AccordionItem>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script lang="ts">
2+
import { Accordion, AccordionItem, P, useMediaQuery, useBreakpoints, useCurrentBreakpoint } from "flowbite-svelte";
3+
4+
// Different approaches to responsive behavior
5+
const isMdAndUp = useMediaQuery("(min-width: 768px)");
6+
const breakpoints = useBreakpoints();
7+
const getCurrentBreakpoint = useCurrentBreakpoint();
8+
9+
const currentBp = $derived(getCurrentBreakpoint());
10+
const tabletOnly = $derived(breakpoints.sm && !breakpoints.lg);
11+
const mobileOnly = $derived(!breakpoints.sm);
12+
</script>
13+
14+
<!-- Always open on medium+ screens -->
15+
<Accordion>
16+
<AccordionItem open={isMdAndUp()}>
17+
{#snippet header()}
18+
📱 Tablet & Desktop (Current: {currentBp})
19+
{/snippet}
20+
<P>Opens on tablets and larger screens. Stays closed on mobile.</P>
21+
</AccordionItem>
22+
23+
<AccordionItem>
24+
{#snippet header()}Always Interactive{/snippet}
25+
<P>This accordion item behaves normally on all screen sizes.</P>
26+
</AccordionItem>
27+
</Accordion>
28+
29+
<!-- Open only in tablet range -->
30+
<Accordion>
31+
<AccordionItem open={tabletOnly}>
32+
{#snippet header()}
33+
📱 Tablet Only (640px - 1023px)
34+
{/snippet}
35+
<P>This opens automatically on tablets but closes on mobile phones and large desktop screens.</P>
36+
</AccordionItem>
37+
</Accordion>
38+
39+
<!-- Mobile-first approach -->
40+
<Accordion>
41+
<AccordionItem open={mobileOnly}>
42+
{#snippet header()}
43+
📱 Mobile Only (below 640px)
44+
{/snippet}
45+
<P>Expanded by default on mobile for better accessibility, collapsed on larger screens to save space.</P>
46+
</AccordionItem>
47+
</Accordion>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
import { AccordionItem, useBreakpoints } from "flowbite-svelte";
3+
const breakpoints = useBreakpoints();
4+
</script>
5+
6+
<AccordionItem open={breakpoints.md}>
7+
{#snippet header()}Opens on medium screens+{/snippet}
8+
<p>Content for tablets and desktop users.</p>
9+
</AccordionItem>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script>
2+
import { AccordionItem, useMediaQuery, useBreakpoints } from "flowbite-svelte";
3+
const breakpoints = useBreakpoints();
4+
5+
// Open from sm to lg (640px - 1023px)
6+
const tabletRange = $derived(breakpoints.sm && !breakpoints.lg);
7+
8+
// Open on specific breakpoints only
9+
const specificSizes = $derived((breakpoints.sm && !breakpoints.md) || (breakpoints.lg && !breakpoints.xl));
10+
11+
// Custom pixel range
12+
const customRange = useMediaQuery("(min-width: 640px) and (max-width: 1023px)");
13+
</script>
14+
15+
<AccordionItem open={tabletRange}>
16+
{#snippet header()}Tablet Range (640px - 1023px){/snippet}
17+
<p>Open on tablets, closed on phones and large desktops.</p>
18+
</AccordionItem>
19+
20+
<AccordionItem open={specificSizes}>
21+
{#snippet header()}Small phones OR Large desktops only{/snippet}
22+
<p>Open on sm-only OR lg-only, closed on other sizes.</p>
23+
</AccordionItem>
24+
25+
<AccordionItem open={customRange()}>
26+
{#snippet header()}Custom Range{/snippet}
27+
<p>Define exact pixel ranges for precise control.</p>
28+
</AccordionItem>

0 commit comments

Comments
 (0)