@latte-macchiat-io/latte-vanilla-components 0.0.247 → 0.0.249

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latte-macchiat-io/latte-vanilla-components",
3
- "version": "0.0.247",
3
+ "version": "0.0.249",
4
4
  "description": "Beautiful components for amazing projects, with a touch of Vanilla 🥤",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -3,15 +3,15 @@ import { clsx } from 'clsx';
3
3
  import { ReactNode } from 'react';
4
4
  import { bar, toggleNavButton } from './styles.css';
5
5
 
6
- export type HeaderToggleNavProps = {
6
+ export type HeaderToggleNavProps = React.HTMLAttributes<HTMLDivElement> & {
7
7
  isOpen?: boolean;
8
8
  onClick: () => void;
9
9
  children?: ReactNode;
10
10
  };
11
11
 
12
- export const HeaderToggleNav = ({ isOpen, onClick, children }: HeaderToggleNavProps) => {
12
+ export const HeaderToggleNav = ({ isOpen, onClick, children, className }: HeaderToggleNavProps) => {
13
13
  return (
14
- <button onClick={onClick} aria-label="Toggle navigation" className={toggleNavButton()}>
14
+ <button onClick={onClick} aria-label="Toggle navigation" className={clsx(toggleNavButton(), className)}>
15
15
  {children || (
16
16
  <>
17
17
  <span className={clsx(bar, isOpen && 'open')} />
@@ -8,7 +8,9 @@ export type SectionProps = React.HTMLAttributes<HTMLDivElement> &
8
8
  };
9
9
 
10
10
  export const Section = ({ align, isDark, variant, isFullHeight, withPaddingTop, withPaddingBottom, className, children, style }: SectionProps) => (
11
- <section style={style} className={clsx(sectionRecipe({ align, isDark, variant, isFullHeight, withPaddingTop, withPaddingBottom }), className)}>
11
+ <section
12
+ style={style}
13
+ className={clsx(sectionRecipe({ align, isDark, variant, isFullHeight, withPaddingTop, withPaddingBottom, isHeaderFixed: true }), className)}>
12
14
  <div className={sectionContent}>{children}</div>
13
15
  </section>
14
16
  );
@@ -1,9 +1,8 @@
1
1
  import { style } from '@vanilla-extract/css';
2
- import { calc } from '@vanilla-extract/css-utils';
3
2
  import { recipe, type RecipeVariants } from '@vanilla-extract/recipes';
4
3
 
5
4
  import { themeContract } from '../../theme/contract.css';
6
- import { generateResponsiveMedia } from '../../utils/generateResponsiveMedia';
5
+ import { calcResponsiveSubtract, generateResponsiveMedia } from '../../utils/generateResponsiveMedia';
7
6
 
8
7
  export const sectionRecipe = recipe(
9
8
  {
@@ -71,7 +70,11 @@ export const sectionRecipe = recipe(
71
70
  },
72
71
  isHeaderFixed: {
73
72
  true: {
74
- // minHeight: calc.subtract('100vh', themeContract.header.height),
73
+ '@media': {
74
+ ...generateResponsiveMedia({
75
+ minHeight: calcResponsiveSubtract('100vh', themeContract.header.height),
76
+ }),
77
+ },
75
78
  },
76
79
  false: {
77
80
  minHeight: '100vh',
@@ -1,19 +1,102 @@
1
- import { Breakpoints, queries } from '../styles/mediaqueries';
1
+ // utils/generateResponsiveMedia.ts
2
+ import { queries } from '../styles/mediaqueries';
2
3
 
3
- type ResponsiveProps = Record<string, Record<Breakpoints, string>>;
4
+ /**
5
+ * Breakpoint keys you support (should match your queries object).
6
+ */
7
+ type BreakpointKey = 'mobile' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
8
+ const BPS: BreakpointKey[] = ['mobile', 'sm', 'md', 'lg', 'xl', '2xl'];
4
9
 
5
- export function generateResponsiveMedia(properties: ResponsiveProps) {
10
+ /**
11
+ * Responsive values can be a plain string/number or anything with a toString().
12
+ */
13
+ type ResponsiveValue = string | number | { toString(): string };
14
+ type BreakpointMap = Partial<Record<BreakpointKey, ResponsiveValue>>;
15
+
16
+ /**
17
+ * Generate responsive media queries for a set of CSS properties.
18
+ *
19
+ * Example:
20
+ * generateResponsiveMedia({
21
+ * width: { mobile: '100%', md: '50%' },
22
+ * padding: '20px'
23
+ * })
24
+ *
25
+ * => {
26
+ * '@media (min-width:...)': { width: '50%', padding: '20px' },
27
+ * ...
28
+ * }
29
+ */
30
+ export function generateResponsiveMedia(properties: Record<string, ResponsiveValue | BreakpointMap>) {
6
31
  const result: Record<string, Record<string, string>> = {};
7
32
 
8
- Object.entries(properties).forEach(([cssProp, breakpointsMap]) => {
9
- Object.entries(breakpointsMap).forEach(([bp, token]) => {
10
- const key = bp as Breakpoints;
11
- const mediaQuery = queries[key];
33
+ const toCssValue = (v: ResponsiveValue) => {
34
+ const s = String(v);
35
+ // if it's a CSS variable token, wrap with var(...)
36
+ return s.startsWith('--') ? `var(${s})` : s;
37
+ };
38
+
39
+ for (const [cssProp, valOrMap] of Object.entries(properties)) {
40
+ const isMapLike =
41
+ valOrMap && typeof valOrMap === 'object' && !Array.isArray(valOrMap) && Object.keys(valOrMap).some((k) => BPS.includes(k as BreakpointKey));
12
42
 
13
- if (!result[mediaQuery]) result[mediaQuery] = {};
14
- result[mediaQuery][cssProp] = token.startsWith('--') ? `var(${token})` : token;
15
- });
16
- });
43
+ if (isMapLike) {
44
+ const map = valOrMap as BreakpointMap;
45
+ for (const bp of BPS) {
46
+ const token = map[bp];
47
+ if (token === undefined) continue;
48
+ const media = queries[bp as keyof typeof queries];
49
+ if (!result[media]) result[media] = {};
50
+ result[media][cssProp] = toCssValue(token);
51
+ }
52
+ } else {
53
+ // single value => apply to all breakpoints
54
+ const token = valOrMap as ResponsiveValue;
55
+ for (const bp of BPS) {
56
+ const media = queries[bp as keyof typeof queries];
57
+ if (!result[media]) result[media] = {};
58
+ result[media][cssProp] = toCssValue(token);
59
+ }
60
+ }
61
+ }
17
62
 
18
63
  return result;
19
64
  }
65
+
66
+ /**
67
+ * Generate a responsive calc() subtraction: calc(base - other).
68
+ *
69
+ * - base: static value (string/number), e.g. "100vh"
70
+ * - other: either a single value or a BreakpointMap
71
+ *
72
+ * Example:
73
+ * calcResponsiveSubtract('100vh', { mobile: '50px', md: '80px' })
74
+ *
75
+ * => {
76
+ * mobile: "calc(100vh - 50px)",
77
+ * md: "calc(100vh - 80px)"
78
+ * }
79
+ */
80
+ export function calcResponsiveSubtract(base: ResponsiveValue, other: ResponsiveValue | BreakpointMap): BreakpointMap {
81
+ const build = (otherVal: ResponsiveValue) => `calc(${String(base)} - ${String(otherVal)})`;
82
+
83
+ const out: BreakpointMap = {};
84
+
85
+ const isMapLike = other && typeof other === 'object' && !Array.isArray(other) && Object.keys(other).some((k) => BPS.includes(k as BreakpointKey));
86
+
87
+ if (isMapLike) {
88
+ const map = other as BreakpointMap;
89
+ for (const bp of BPS) {
90
+ const token = map[bp];
91
+ if (token === undefined) continue;
92
+ out[bp] = build(token);
93
+ }
94
+ } else {
95
+ const token = other as ResponsiveValue;
96
+ for (const bp of BPS) {
97
+ out[bp] = build(token);
98
+ }
99
+ }
100
+
101
+ return out;
102
+ }