@askrjs/themes 0.0.2 → 0.0.3

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.
@@ -7,6 +7,28 @@ import { Link } from "@askrjs/askr/router";
7
7
  function navItemClasses(variant, className) {
8
8
  return classes("navbar-item", variant === "icon" && "navbar-item-icon", className);
9
9
  }
10
+ function normalizePathname(pathname) {
11
+ return pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
12
+ }
13
+ function getCurrentPathname() {
14
+ if (typeof window === "undefined") return null;
15
+ return normalizePathname(window.location.pathname || "/");
16
+ }
17
+ function resolveNavLinkPathname(href) {
18
+ const baseHref = typeof window !== "undefined" ? window.location.href : "http://localhost/";
19
+ const baseOrigin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
20
+ try {
21
+ const target = new URL(href, baseHref);
22
+ if (target.origin !== baseOrigin) return null;
23
+ return normalizePathname(target.pathname);
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ function isActiveNavLink(currentPathname, targetPathname) {
29
+ if (targetPathname === "/") return currentPathname === "/";
30
+ return currentPathname === targetPathname || currentPathname.startsWith(`${targetPathname}/`);
31
+ }
10
32
  function Navbar(props) {
11
33
  const { children, ref, class: className, ...rest } = props;
12
34
  return /* @__PURE__ */ jsx("nav", {
@@ -56,14 +78,22 @@ function NavItem(props) {
56
78
  });
57
79
  }
58
80
  function NavLink(props) {
59
- const { children, ref, class: className, variant = "default", ...rest } = props;
81
+ const { children, href, ref, class: className, variant = "default", ...rest } = props;
82
+ const currentPathname = getCurrentPathname();
83
+ const targetPathname = resolveNavLinkPathname(href);
84
+ const isActive = currentPathname !== null && targetPathname !== null && isActiveNavLink(currentPathname, targetPathname);
85
+ const linkProps = mergeProps(rest, {
86
+ ref,
87
+ class: navItemClasses(variant, className),
88
+ "data-slot": "nav-link",
89
+ "data-variant": variant
90
+ });
60
91
  return /* @__PURE__ */ jsx(Link, {
61
- ...mergeProps(rest, {
62
- ref,
63
- class: navItemClasses(variant, className),
64
- "data-slot": "nav-link",
65
- "data-variant": variant
66
- }),
92
+ href,
93
+ ...isActive ? mergeProps({
94
+ "aria-current": "page",
95
+ "data-active": "true"
96
+ }, linkProps) : linkProps,
67
97
  children
68
98
  });
69
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"navbar.js","names":[],"sources":["../../src/components/navbar/navbar.tsx"],"sourcesContent":["import { Link } from \"@askrjs/askr/router\";\nimport { Slot } from \"@askrjs/askr/foundations\";\nimport { mergeProps } from \"../_internal/merge-props\";\nimport type {\n NavBrandProps,\n NavGroupProps,\n NavItemAsChildProps,\n NavItemProps,\n NavItemVariant,\n NavLinkProps,\n NavbarProps,\n} from \"./navbar.types\";\nimport { classes } from \"../_internal/classes\";\n\nfunction navItemClasses(variant: NavItemVariant, className: unknown): string | undefined {\n return classes(\"navbar-item\", variant === \"icon\" && \"navbar-item-icon\", className);\n}\n\nexport function Navbar(props: NavbarProps): JSX.Element {\n const { children, ref, class: className, ...rest } = props;\n\n return (\n <nav {...rest} ref={ref} class={classes(\"navbar\", className)} data-slot=\"navbar\">\n {children}\n </nav>\n );\n}\n\nexport function NavBrand(props: NavBrandProps): JSX.Element {\n const { children, ref, class: className, ...rest } = props;\n\n return (\n <div {...rest} ref={ref} class={classes(\"navbar-brand\", className)} data-slot=\"navbar-brand\">\n {children}\n </div>\n );\n}\n\nexport function NavGroup(props: NavGroupProps): JSX.Element {\n const { children, ref, class: className, ...rest } = props;\n\n return (\n <div {...rest} ref={ref} class={classes(\"navbar-group\", className)} data-slot=\"navbar-group\">\n {children}\n </div>\n );\n}\n\nexport function NavItem(props: NavItemProps): JSX.Element;\nexport function NavItem(props: NavItemAsChildProps): JSX.Element;\nexport function NavItem(props: NavItemProps | NavItemAsChildProps): JSX.Element {\n const { asChild, children, ref, class: className, variant = \"default\", ...rest } = props;\n\n const finalProps = mergeProps(rest, {\n ref,\n class: navItemClasses(variant, className),\n \"data-slot\": \"nav-item\",\n \"data-variant\": variant,\n });\n\n if (asChild) {\n return <Slot asChild {...finalProps} children={children} />;\n }\n\n return <a {...finalProps}>{children}</a>;\n}\n\nexport function NavLink(props: NavLinkProps): JSX.Element {\n const { children, ref, class: className, variant = \"default\", ...rest } = props;\n\n const finalProps = mergeProps(rest, {\n ref,\n class: navItemClasses(variant, className),\n \"data-slot\": \"nav-link\",\n \"data-variant\": variant,\n });\n\n return <Link {...finalProps}>{children}</Link>;\n}\n"],"mappings":";;;;;;AAcA,SAAS,eAAe,SAAyB,WAAwC;AACvF,QAAO,QAAQ,eAAe,YAAY,UAAU,oBAAoB,UAAU;;AAGpF,SAAgB,OAAO,OAAiC;CACtD,MAAM,EAAE,UAAU,KAAK,OAAO,WAAW,GAAG,SAAS;AAErD,QACE,oBAAC,OAAD;EAAK,GAAI;EAAW;EAAK,OAAO,QAAQ,UAAU,UAAU;EAAE,aAAU;EACrE;EACG,CAAA;;AAIV,SAAgB,SAAS,OAAmC;CAC1D,MAAM,EAAE,UAAU,KAAK,OAAO,WAAW,GAAG,SAAS;AAErD,QACE,oBAAC,OAAD;EAAK,GAAI;EAAW;EAAK,OAAO,QAAQ,gBAAgB,UAAU;EAAE,aAAU;EAC3E;EACG,CAAA;;AAIV,SAAgB,SAAS,OAAmC;CAC1D,MAAM,EAAE,UAAU,KAAK,OAAO,WAAW,GAAG,SAAS;AAErD,QACE,oBAAC,OAAD;EAAK,GAAI;EAAW;EAAK,OAAO,QAAQ,gBAAgB,UAAU;EAAE,aAAU;EAC3E;EACG,CAAA;;AAMV,SAAgB,QAAQ,OAAwD;CAC9E,MAAM,EAAE,SAAS,UAAU,KAAK,OAAO,WAAW,UAAU,WAAW,GAAG,SAAS;CAEnF,MAAM,aAAa,WAAW,MAAM;EAClC;EACA,OAAO,eAAe,SAAS,UAAU;EACzC,aAAa;EACb,gBAAgB;EACjB,CAAC;AAEF,KAAI,QACF,QAAO,oBAAC,MAAD;EAAM,SAAA;EAAQ,GAAI;EAAsB;EAAY,CAAA;AAG7D,QAAO,oBAAC,KAAD;EAAG,GAAI;EAAa;EAAa,CAAA;;AAG1C,SAAgB,QAAQ,OAAkC;CACxD,MAAM,EAAE,UAAU,KAAK,OAAO,WAAW,UAAU,WAAW,GAAG,SAAS;AAS1E,QAAO,oBAAC,MAAD;EAAM,GAPM,WAAW,MAAM;GAClC;GACA,OAAO,eAAe,SAAS,UAAU;GACzC,aAAa;GACb,gBAAgB;GACjB,CAE0B;EAAG;EAAgB,CAAA"}
1
+ {"version":3,"file":"navbar.js","names":[],"sources":["../../src/components/navbar/navbar.tsx"],"sourcesContent":["import { Link } from \"@askrjs/askr/router\";\nimport { Slot } from \"@askrjs/askr/foundations\";\nimport { mergeProps } from \"../_internal/merge-props\";\nimport type {\n NavBrandProps,\n NavGroupProps,\n NavItemAsChildProps,\n NavItemProps,\n NavItemVariant,\n NavLinkProps,\n NavbarProps,\n} from \"./navbar.types\";\nimport { classes } from \"../_internal/classes\";\n\nfunction navItemClasses(variant: NavItemVariant, className: unknown): string | undefined {\n return classes(\"navbar-item\", variant === \"icon\" && \"navbar-item-icon\", className);\n}\n\nfunction normalizePathname(pathname: string): string {\n return pathname.endsWith(\"/\") && pathname !== \"/\" ? pathname.slice(0, -1) : pathname;\n}\n\nfunction getCurrentPathname(): string | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n return normalizePathname(window.location.pathname || \"/\");\n}\n\nfunction resolveNavLinkPathname(href: string): string | null {\n const baseHref = typeof window !== \"undefined\" ? window.location.href : \"http://localhost/\";\n const baseOrigin = typeof window !== \"undefined\" ? window.location.origin : \"http://localhost\";\n\n try {\n const target = new URL(href, baseHref);\n if (target.origin !== baseOrigin) {\n return null;\n }\n\n return normalizePathname(target.pathname);\n } catch {\n return null;\n }\n}\n\nfunction isActiveNavLink(currentPathname: string, targetPathname: string): boolean {\n if (targetPathname === \"/\") {\n return currentPathname === \"/\";\n }\n\n return currentPathname === targetPathname || currentPathname.startsWith(`${targetPathname}/`);\n}\n\nexport function Navbar(props: NavbarProps): JSX.Element {\n const { children, ref, class: className, ...rest } = props;\n\n return (\n <nav {...rest} ref={ref} class={classes(\"navbar\", className)} data-slot=\"navbar\">\n {children}\n </nav>\n );\n}\n\nexport function NavBrand(props: NavBrandProps): JSX.Element {\n const { children, ref, class: className, ...rest } = props;\n\n return (\n <div {...rest} ref={ref} class={classes(\"navbar-brand\", className)} data-slot=\"navbar-brand\">\n {children}\n </div>\n );\n}\n\nexport function NavGroup(props: NavGroupProps): JSX.Element {\n const { children, ref, class: className, ...rest } = props;\n\n return (\n <div {...rest} ref={ref} class={classes(\"navbar-group\", className)} data-slot=\"navbar-group\">\n {children}\n </div>\n );\n}\n\nexport function NavItem(props: NavItemProps): JSX.Element;\nexport function NavItem(props: NavItemAsChildProps): JSX.Element;\nexport function NavItem(props: NavItemProps | NavItemAsChildProps): JSX.Element {\n const { asChild, children, ref, class: className, variant = \"default\", ...rest } = props;\n\n const finalProps = mergeProps(rest, {\n ref,\n class: navItemClasses(variant, className),\n \"data-slot\": \"nav-item\",\n \"data-variant\": variant,\n });\n\n if (asChild) {\n return <Slot asChild {...finalProps} children={children} />;\n }\n\n return <a {...finalProps}>{children}</a>;\n}\n\nexport function NavLink(props: NavLinkProps): JSX.Element {\n const { children, href, ref, class: className, variant = \"default\", ...rest } = props;\n const currentPathname = getCurrentPathname();\n const targetPathname = resolveNavLinkPathname(href);\n const isActive =\n currentPathname !== null &&\n targetPathname !== null &&\n isActiveNavLink(currentPathname, targetPathname);\n\n const linkProps = mergeProps(rest, {\n ref,\n class: navItemClasses(variant, className),\n \"data-slot\": \"nav-link\",\n \"data-variant\": variant,\n });\n\n const finalProps = isActive\n ? mergeProps(\n {\n \"aria-current\": \"page\",\n \"data-active\": \"true\",\n },\n linkProps,\n )\n : linkProps;\n\n return (\n <Link href={href} {...finalProps}>\n {children}\n </Link>\n );\n}\n"],"mappings":";;;;;;AAcA,SAAS,eAAe,SAAyB,WAAwC;AACvF,QAAO,QAAQ,eAAe,YAAY,UAAU,oBAAoB,UAAU;;AAGpF,SAAS,kBAAkB,UAA0B;AACnD,QAAO,SAAS,SAAS,IAAI,IAAI,aAAa,MAAM,SAAS,MAAM,GAAG,GAAG,GAAG;;AAG9E,SAAS,qBAAoC;AAC3C,KAAI,OAAO,WAAW,YACpB,QAAO;AAGT,QAAO,kBAAkB,OAAO,SAAS,YAAY,IAAI;;AAG3D,SAAS,uBAAuB,MAA6B;CAC3D,MAAM,WAAW,OAAO,WAAW,cAAc,OAAO,SAAS,OAAO;CACxE,MAAM,aAAa,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS;AAE5E,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,MAAM,SAAS;AACtC,MAAI,OAAO,WAAW,WACpB,QAAO;AAGT,SAAO,kBAAkB,OAAO,SAAS;SACnC;AACN,SAAO;;;AAIX,SAAS,gBAAgB,iBAAyB,gBAAiC;AACjF,KAAI,mBAAmB,IACrB,QAAO,oBAAoB;AAG7B,QAAO,oBAAoB,kBAAkB,gBAAgB,WAAW,GAAG,eAAe,GAAG;;AAG/F,SAAgB,OAAO,OAAiC;CACtD,MAAM,EAAE,UAAU,KAAK,OAAO,WAAW,GAAG,SAAS;AAErD,QACE,oBAAC,OAAD;EAAK,GAAI;EAAW;EAAK,OAAO,QAAQ,UAAU,UAAU;EAAE,aAAU;EACrE;EACG,CAAA;;AAIV,SAAgB,SAAS,OAAmC;CAC1D,MAAM,EAAE,UAAU,KAAK,OAAO,WAAW,GAAG,SAAS;AAErD,QACE,oBAAC,OAAD;EAAK,GAAI;EAAW;EAAK,OAAO,QAAQ,gBAAgB,UAAU;EAAE,aAAU;EAC3E;EACG,CAAA;;AAIV,SAAgB,SAAS,OAAmC;CAC1D,MAAM,EAAE,UAAU,KAAK,OAAO,WAAW,GAAG,SAAS;AAErD,QACE,oBAAC,OAAD;EAAK,GAAI;EAAW;EAAK,OAAO,QAAQ,gBAAgB,UAAU;EAAE,aAAU;EAC3E;EACG,CAAA;;AAMV,SAAgB,QAAQ,OAAwD;CAC9E,MAAM,EAAE,SAAS,UAAU,KAAK,OAAO,WAAW,UAAU,WAAW,GAAG,SAAS;CAEnF,MAAM,aAAa,WAAW,MAAM;EAClC;EACA,OAAO,eAAe,SAAS,UAAU;EACzC,aAAa;EACb,gBAAgB;EACjB,CAAC;AAEF,KAAI,QACF,QAAO,oBAAC,MAAD;EAAM,SAAA;EAAQ,GAAI;EAAsB;EAAY,CAAA;AAG7D,QAAO,oBAAC,KAAD;EAAG,GAAI;EAAa;EAAa,CAAA;;AAG1C,SAAgB,QAAQ,OAAkC;CACxD,MAAM,EAAE,UAAU,MAAM,KAAK,OAAO,WAAW,UAAU,WAAW,GAAG,SAAS;CAChF,MAAM,kBAAkB,oBAAoB;CAC5C,MAAM,iBAAiB,uBAAuB,KAAK;CACnD,MAAM,WACJ,oBAAoB,QACpB,mBAAmB,QACnB,gBAAgB,iBAAiB,eAAe;CAElD,MAAM,YAAY,WAAW,MAAM;EACjC;EACA,OAAO,eAAe,SAAS,UAAU;EACzC,aAAa;EACb,gBAAgB;EACjB,CAAC;AAYF,QACE,oBAAC,MAAD;EAAY;EAAM,GAXD,WACf,WACE;GACE,gBAAgB;GAChB,eAAe;GAChB,EACD,UACD,GACD;EAIC;EACI,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/themes",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Default theme tokens, styles, and themed layout wrappers for Askr apps.",
5
5
  "keywords": [
6
6
  "askr",
@@ -91,8 +91,9 @@
91
91
  "verify:token-contract": "node ./scripts/verify-token-contract.js",
92
92
  "verify:responsive": "node ./scripts/verify-responsive-theme.js",
93
93
  "verify:slots": "node ./scripts/verify-slot-coverage.js",
94
- "test": "npm run test:unit && npm run test:browser",
95
- "test:unit": "vp test run -c vitest.config.ts",
94
+ "test": "npm run test:unit && npm run test:jsdom && npm run test:browser",
95
+ "test:unit": "vp test run -c vitest.node.config.ts",
96
+ "test:jsdom": "vp test run -c vitest.jsdom.config.ts",
96
97
  "test:browser": "vp test run -c vitest.browser.config.ts",
97
98
  "fmt": "vp fmt .",
98
99
  "lint": "vp lint .",
@@ -101,10 +102,10 @@
101
102
  "prepublishOnly": "npm run build"
102
103
  },
103
104
  "devDependencies": {
104
- "@askrjs/askr": "^0.0.33",
105
- "@askrjs/ui": "^0.0.4",
105
+ "@askrjs/askr": "^0.0.34",
106
+ "@askrjs/ui": "^0.0.5",
106
107
  "@askrjs/vite": "^0.0.2",
107
- "@tsdown/css": "0.21.10",
108
+ "@tsdown/css": "0.22.0",
108
109
  "@types/node": "^25.6.0",
109
110
  "jsdom": "^29.1.1",
110
111
  "playwright": "^1.59.1",
@@ -16,6 +16,42 @@ function navItemClasses(variant: NavItemVariant, className: unknown): string | u
16
16
  return classes("navbar-item", variant === "icon" && "navbar-item-icon", className);
17
17
  }
18
18
 
19
+ function normalizePathname(pathname: string): string {
20
+ return pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;
21
+ }
22
+
23
+ function getCurrentPathname(): string | null {
24
+ if (typeof window === "undefined") {
25
+ return null;
26
+ }
27
+
28
+ return normalizePathname(window.location.pathname || "/");
29
+ }
30
+
31
+ function resolveNavLinkPathname(href: string): string | null {
32
+ const baseHref = typeof window !== "undefined" ? window.location.href : "http://localhost/";
33
+ const baseOrigin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
34
+
35
+ try {
36
+ const target = new URL(href, baseHref);
37
+ if (target.origin !== baseOrigin) {
38
+ return null;
39
+ }
40
+
41
+ return normalizePathname(target.pathname);
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ function isActiveNavLink(currentPathname: string, targetPathname: string): boolean {
48
+ if (targetPathname === "/") {
49
+ return currentPathname === "/";
50
+ }
51
+
52
+ return currentPathname === targetPathname || currentPathname.startsWith(`${targetPathname}/`);
53
+ }
54
+
19
55
  export function Navbar(props: NavbarProps): JSX.Element {
20
56
  const { children, ref, class: className, ...rest } = props;
21
57
 
@@ -66,14 +102,34 @@ export function NavItem(props: NavItemProps | NavItemAsChildProps): JSX.Element
66
102
  }
67
103
 
68
104
  export function NavLink(props: NavLinkProps): JSX.Element {
69
- const { children, ref, class: className, variant = "default", ...rest } = props;
105
+ const { children, href, ref, class: className, variant = "default", ...rest } = props;
106
+ const currentPathname = getCurrentPathname();
107
+ const targetPathname = resolveNavLinkPathname(href);
108
+ const isActive =
109
+ currentPathname !== null &&
110
+ targetPathname !== null &&
111
+ isActiveNavLink(currentPathname, targetPathname);
70
112
 
71
- const finalProps = mergeProps(rest, {
113
+ const linkProps = mergeProps(rest, {
72
114
  ref,
73
115
  class: navItemClasses(variant, className),
74
116
  "data-slot": "nav-link",
75
117
  "data-variant": variant,
76
118
  });
77
119
 
78
- return <Link {...finalProps}>{children}</Link>;
120
+ const finalProps = isActive
121
+ ? mergeProps(
122
+ {
123
+ "aria-current": "page",
124
+ "data-active": "true",
125
+ },
126
+ linkProps,
127
+ )
128
+ : linkProps;
129
+
130
+ return (
131
+ <Link href={href} {...finalProps}>
132
+ {children}
133
+ </Link>
134
+ );
79
135
  }