@ansiversa/components 0.0.141 → 0.0.142

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/index.ts CHANGED
@@ -40,6 +40,7 @@ export { default as PortfolioCreatorSummary } from './src/Summary/PortfolioCreat
40
40
  export { default as AvImageUploader } from "./src/components/media/AvImageUploader.astro";
41
41
  export { default as AvAiAssist } from "./src/components/Ai/AvAiAssist.astro";
42
42
  export { default as FaqManager } from "./src/components/Admin/FaqManager.astro";
43
+ export { AvBookmarkButton, AvBookmarksEmpty, AvBookmarksList } from "./src/components/Bookmarks";
43
44
  export { AppLogo } from "./src/Logo";
44
45
  export type { AppLogoProps } from "./src/Logo";
45
46
  export { default as ResumeBuilderShell } from './src/resume-templates/ResumeBuilderShell.astro';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansiversa/components",
3
- "version": "0.0.141",
3
+ "version": "0.0.142",
4
4
  "description": "Shared UI components and layouts for the Ansiversa ecosystem",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,93 @@
1
+ ---
2
+ type Size = "sm" | "md";
3
+
4
+ interface Props {
5
+ active: boolean;
6
+ activeExpr?: string;
7
+ title?: string;
8
+ activeTitle?: string;
9
+ size?: Size;
10
+ onClick?: string;
11
+ }
12
+
13
+ const {
14
+ active,
15
+ activeExpr,
16
+ title = "Save bookmark",
17
+ activeTitle = "Remove bookmark",
18
+ size = "md",
19
+ onClick,
20
+ } = Astro.props as Props;
21
+
22
+ const iconPx = size === "sm" ? 16 : 20;
23
+ const checkPx = size === "sm" ? 10 : 12;
24
+ const resolvedActiveExpr = activeExpr ?? (active ? "true" : "false");
25
+ const escapeSingleQuotes = (value: string) => value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
26
+ const tooltipExpr = `(${resolvedActiveExpr}) ? '${escapeSingleQuotes(activeTitle)}' : '${escapeSingleQuotes(title)}'`;
27
+ ---
28
+
29
+ <button
30
+ type="button"
31
+ class="av-bookmark-btn"
32
+ :class={`(${resolvedActiveExpr}) ? 'is-active' : ''`}
33
+ x-on:click.stop.prevent={onClick}
34
+ title={active ? activeTitle : title}
35
+ :title={tooltipExpr}
36
+ aria-label={title}
37
+ :aria-label={`(${resolvedActiveExpr}) ? ${JSON.stringify(activeTitle)} : ${JSON.stringify(title)}`}
38
+ :aria-pressed={resolvedActiveExpr}
39
+ >
40
+ <span class="av-bookmark-btn__icon" :class={`(${resolvedActiveExpr}) ? 'is-hidden' : ''`} aria-hidden="true">
41
+ <svg width={iconPx} height={iconPx} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
42
+ <path d="M19 21 12 16 5 21V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
43
+ </svg>
44
+ </span>
45
+ <span class="av-bookmark-btn__icon" :class={`(${resolvedActiveExpr}) ? '' : 'is-hidden'`} aria-hidden="true">
46
+ <svg width={iconPx} height={iconPx} viewBox="0 0 24 24" fill="currentColor" stroke="none">
47
+ <path d="M19 21 12 16 5 21V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
48
+ </svg>
49
+ <svg class="av-bookmark-btn__check" width={checkPx} height={checkPx} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
50
+ <path d="m5 13 4 4L19 7" />
51
+ </svg>
52
+ </span>
53
+ </button>
54
+
55
+ <style>
56
+ .av-bookmark-btn {
57
+ position: relative;
58
+ display: inline-flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ width: 2rem;
62
+ height: 2rem;
63
+ border: 1px solid rgba(148, 163, 184, 0.35);
64
+ border-radius: 999px;
65
+ background: rgba(15, 23, 42, 0.65);
66
+ color: #94a3b8;
67
+ transition: all 0.2s ease;
68
+ }
69
+ .av-bookmark-btn:hover {
70
+ border-color: rgba(14, 165, 233, 0.6);
71
+ color: #7dd3fc;
72
+ }
73
+ .av-bookmark-btn.is-active {
74
+ border-color: rgba(14, 165, 233, 0.8);
75
+ color: #38bdf8;
76
+ background: rgba(14, 116, 144, 0.25);
77
+ }
78
+ .av-bookmark-btn__icon {
79
+ position: absolute;
80
+ inset: 0;
81
+ display: inline-flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ transition: opacity 0.15s ease;
85
+ }
86
+ .av-bookmark-btn__icon.is-hidden {
87
+ opacity: 0;
88
+ }
89
+ .av-bookmark-btn__check {
90
+ position: absolute;
91
+ color: #0f172a;
92
+ }
93
+ </style>
@@ -0,0 +1,5 @@
1
+ ---
2
+ import AvEmptyState from "../../AvEmptyState.astro";
3
+ ---
4
+
5
+ <AvEmptyState headline="No bookmarks yet" description="Save items to find them quickly here." />
@@ -0,0 +1,83 @@
1
+ ---
2
+ import AvCard from "../../AvCard.astro";
3
+ import AvBookmarkButton from "./AvBookmarkButton.astro";
4
+
5
+ type RightSlot = {
6
+ active: boolean;
7
+ activeExpr?: string;
8
+ title?: string;
9
+ activeTitle?: string;
10
+ size?: "sm" | "md";
11
+ onClick?: string;
12
+ };
13
+
14
+ type Item = {
15
+ title: string;
16
+ subtitle?: string;
17
+ description?: string;
18
+ href?: string;
19
+ rightSlot?: RightSlot;
20
+ };
21
+
22
+ interface Props {
23
+ items: Item[];
24
+ }
25
+
26
+ const { items } = Astro.props as Props;
27
+ ---
28
+
29
+ <div class="av-bookmarks-list">
30
+ {items.map((item) => (
31
+ <AvCard className="av-bookmarks-list__card">
32
+ <div class="av-bookmarks-list__row">
33
+ <div>
34
+ {item.href ? (
35
+ <a class="av-bookmarks-list__title" href={item.href}>{item.title}</a>
36
+ ) : (
37
+ <p class="av-bookmarks-list__title">{item.title}</p>
38
+ )}
39
+ {item.subtitle ? <p class="av-bookmarks-list__meta">{item.subtitle}</p> : null}
40
+ {item.description ? <p class="av-bookmarks-list__desc">{item.description}</p> : null}
41
+ </div>
42
+ {item.rightSlot ? (
43
+ <AvBookmarkButton
44
+ active={item.rightSlot.active}
45
+ activeExpr={item.rightSlot.activeExpr}
46
+ title={item.rightSlot.title}
47
+ activeTitle={item.rightSlot.activeTitle}
48
+ size={item.rightSlot.size ?? "sm"}
49
+ onClick={item.rightSlot.onClick}
50
+ />
51
+ ) : null}
52
+ </div>
53
+ </AvCard>
54
+ ))}
55
+ </div>
56
+
57
+ <style>
58
+ .av-bookmarks-list {
59
+ display: grid;
60
+ gap: 0.85rem;
61
+ }
62
+ .av-bookmarks-list__row {
63
+ display: flex;
64
+ justify-content: space-between;
65
+ align-items: flex-start;
66
+ gap: 0.75rem;
67
+ }
68
+ .av-bookmarks-list__title {
69
+ color: #e2e8f0;
70
+ font-weight: 600;
71
+ text-decoration: none;
72
+ }
73
+ .av-bookmarks-list__meta {
74
+ font-size: 0.75rem;
75
+ color: #7dd3fc;
76
+ margin-top: 0.35rem;
77
+ }
78
+ .av-bookmarks-list__desc {
79
+ margin-top: 0.4rem;
80
+ color: #94a3b8;
81
+ font-size: 0.82rem;
82
+ }
83
+ </style>
@@ -0,0 +1,3 @@
1
+ export { default as AvBookmarkButton } from "./AvBookmarkButton.astro";
2
+ export { default as AvBookmarksEmpty } from "./AvBookmarksEmpty.astro";
3
+ export { default as AvBookmarksList } from "./AvBookmarksList.astro";
@@ -6,9 +6,10 @@ import { MINI_APP_REGISTRY } from "./miniAppRegistry";
6
6
  interface Props {
7
7
  appKey: string;
8
8
  links?: MiniAppLink[];
9
+ bookmarksHref?: string;
9
10
  }
10
11
 
11
- const { appKey, links } = Astro.props as Props;
12
+ const { appKey, links, bookmarksHref } = Astro.props as Props;
12
13
 
13
14
  const normalizedAppKey = typeof appKey === "string" ? appKey.trim() : "";
14
15
  const meta = normalizedAppKey ? MINI_APP_REGISTRY[normalizedAppKey] : undefined;
@@ -19,6 +20,16 @@ let menuLinks = links ?? meta?.links ?? [];
19
20
  if (!menuLinks.length) {
20
21
  menuLinks = [{ label: "Home", href: "/" }];
21
22
  }
23
+
24
+ const normalizedBookmarksHref = typeof bookmarksHref === "string" ? bookmarksHref.trim() : "";
25
+ if (normalizedBookmarksHref.length > 0) {
26
+ const hasBookmarksItem = menuLinks.some(
27
+ (item) => item.href === normalizedBookmarksHref || item.label.toLowerCase() === "bookmarks",
28
+ );
29
+ if (!hasBookmarksItem) {
30
+ menuLinks = [...menuLinks, { label: "Bookmarks", href: normalizedBookmarksHref }];
31
+ }
32
+ }
22
33
  ---
23
34
 
24
35
  <div class="av-mini-app-bar">
@@ -11,6 +11,7 @@ interface Props {
11
11
  notificationUnreadCount?: number;
12
12
  miniAppKey?: string;
13
13
  links?: { label: string; href: string }[];
14
+ bookmarksHref?: string;
14
15
  }
15
16
 
16
17
  const {
@@ -19,6 +20,7 @@ const {
19
20
  notificationUnreadCount = 0,
20
21
  miniAppKey,
21
22
  links,
23
+ bookmarksHref,
22
24
  } = Astro.props;
23
25
 
24
26
  const user = (Astro.locals as { user?: {
@@ -87,7 +89,7 @@ const ROOT_URL = rawDomain.match(/^https?:\/\//i)
87
89
  <AvNavbarActions user={user} notificationUnreadCount={notificationUnreadCount} />
88
90
  </AvNavbar>
89
91
 
90
- {miniAppKey && <AvMiniAppBar appKey={miniAppKey} links={links} />}
92
+ {miniAppKey && <AvMiniAppBar appKey={miniAppKey} links={links} bookmarksHref={bookmarksHref} />}
91
93
 
92
94
  <!-- Main content -->
93
95
  <main class="av-main">