@viibestack/ui 0.7.6 → 0.7.8
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 +2 -2
- package/src/badge.tsx +25 -4
- package/src/index.ts +1 -0
- package/src/nav.tsx +14 -12
- package/src/team.tsx +91 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viibestack/ui",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Dependency-free React UI kit -- icons, core components (Button, Card, Input, Modal, etc.), gamification primitives (PointsDisplay, StreakCounter, Achievements, Leaderboard, fireReward), a real per-app data client for browser code (listRows/listRowsPage/getRow/upsertRow/deleteRow/captureClientError) plus server-safe variants for Route Handlers/Server Components (listRowsServer/listRowsPageServer/getRowServer/upsertRowServer/deleteRowServer/resolveAppIdByHostname), a real per-app end-user auth client (signUp/logIn/logOut/getCurrentUser/reportError), and a self-gating PoweredByBadge component -- backed by ViibeStack's own platform-managed data store.",
|
|
3
|
+
"version": "0.7.8",
|
|
4
|
+
"description": "Dependency-free React UI kit -- icons, core components (Button, Card, Input, Modal, etc.), a TeamGrid/TeamMemberCard people-grid pattern, gamification primitives (PointsDisplay, StreakCounter, Achievements, Leaderboard, fireReward), a real per-app data client for browser code (listRows/listRowsPage/getRow/upsertRow/deleteRow/captureClientError) plus server-safe variants for Route Handlers/Server Components (listRowsServer/listRowsPageServer/getRowServer/upsertRowServer/deleteRowServer/resolveAppIdByHostname), a real per-app end-user auth client (signUp/logIn/logOut/getCurrentUser/reportError), and a self-gating PoweredByBadge component -- backed by ViibeStack's own platform-managed data store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"types": "./src/index.ts",
|
package/src/badge.tsx
CHANGED
|
@@ -110,22 +110,43 @@ export function PoweredByBadge() {
|
|
|
110
110
|
setDismissed(true);
|
|
111
111
|
}}
|
|
112
112
|
style={{
|
|
113
|
+
// Tap target is 2.75rem (44px, Apple's/Android's usual minimum
|
|
114
|
+
// recommendation) even though the visible glyph stays the same
|
|
115
|
+
// small size below -- was a literal 1rem (16px) box, reported as
|
|
116
|
+
// hard to hit on mobile. Negative margin cancels the extra size
|
|
117
|
+
// from the pill's own layout (this flex row centers on cross-axis
|
|
118
|
+
// with no fixed height, so a bigger child would otherwise stretch
|
|
119
|
+
// the whole badge taller) -- the button still visually reads as
|
|
120
|
+
// the same small pill, just easier to actually tap.
|
|
113
121
|
display: "flex",
|
|
114
122
|
alignItems: "center",
|
|
115
123
|
justifyContent: "center",
|
|
116
|
-
width: "
|
|
117
|
-
height: "
|
|
124
|
+
width: "2.75rem",
|
|
125
|
+
height: "2.75rem",
|
|
126
|
+
margin: "-0.875rem",
|
|
118
127
|
padding: 0,
|
|
119
128
|
border: "none",
|
|
120
129
|
borderRadius: "9999px",
|
|
121
|
-
background: "
|
|
130
|
+
background: "transparent",
|
|
122
131
|
color: "#fff",
|
|
123
132
|
fontSize: "0.7rem",
|
|
124
133
|
lineHeight: 1,
|
|
125
134
|
cursor: "pointer",
|
|
126
135
|
}}
|
|
127
136
|
>
|
|
128
|
-
|
|
137
|
+
<span
|
|
138
|
+
style={{
|
|
139
|
+
display: "flex",
|
|
140
|
+
alignItems: "center",
|
|
141
|
+
justifyContent: "center",
|
|
142
|
+
width: "1rem",
|
|
143
|
+
height: "1rem",
|
|
144
|
+
borderRadius: "9999px",
|
|
145
|
+
background: "rgba(255, 255, 255, 0.15)",
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
✕
|
|
149
|
+
</span>
|
|
129
150
|
</button>
|
|
130
151
|
</span>
|
|
131
152
|
);
|
package/src/index.ts
CHANGED
package/src/nav.tsx
CHANGED
|
@@ -115,20 +115,22 @@ export interface AppShellProps {
|
|
|
115
115
|
/** Rendered at the bottom of the sidebar/drawer -- e.g. a user menu, sign-out button. */
|
|
116
116
|
footer?: ReactNode;
|
|
117
117
|
/**
|
|
118
|
-
* Set true to
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* floating
|
|
118
|
+
* Set true to render a dark/light toggle as a normal row in the nav.
|
|
119
|
+
* Off by default -- AppShell doesn't presume every app wants this
|
|
120
|
+
* specific row in this specific place; the platform's own dark/light
|
|
121
|
+
* requirement (every app must support both and let the end user switch)
|
|
122
|
+
* is satisfied independently by the floating fallback toggle every app
|
|
123
|
+
* gets at build time (see template-patches.ts's THEME_TOGGLE_SCRIPT)
|
|
124
|
+
* unless the app supplies its own. Pass true here for a nav-integrated
|
|
125
|
+
* toggle instead of that floating button -- it shares the exact same
|
|
126
|
+
* data-vs="viibestack-theme-toggle" marker, so the fallback detects it
|
|
127
|
+
* and backs off rather than adding a second, floating, duplicate one.
|
|
126
128
|
*/
|
|
127
|
-
|
|
129
|
+
showThemeToggle?: boolean;
|
|
128
130
|
children: ReactNode;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
export function AppShell({ brand, nav, activeHref, renderLink, footer,
|
|
133
|
+
export function AppShell({ brand, nav, activeHref, renderLink, footer, showThemeToggle, children }: AppShellProps) {
|
|
132
134
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
133
135
|
const [mounted, setMounted] = useState(false);
|
|
134
136
|
|
|
@@ -196,7 +198,7 @@ export function AppShell({ brand, nav, activeHref, renderLink, footer, hideTheme
|
|
|
196
198
|
<div className="px-4 py-4 text-lg font-semibold text-gray-900 dark:text-gray-100">{brand}</div>
|
|
197
199
|
<nav className="flex flex-col gap-1 overflow-y-auto px-3 pb-3">
|
|
198
200
|
{nav.map((item, i) => renderItem(item, i))}
|
|
199
|
-
{
|
|
201
|
+
{showThemeToggle && mounted && <ThemeToggleItem />}
|
|
200
202
|
</nav>
|
|
201
203
|
{footer && <div className="mt-auto border-t border-gray-200 p-3 dark:border-gray-800">{footer}</div>}
|
|
202
204
|
</aside>
|
|
@@ -241,7 +243,7 @@ export function AppShell({ brand, nav, activeHref, renderLink, footer, hideTheme
|
|
|
241
243
|
</div>
|
|
242
244
|
<nav className="flex flex-col gap-1 px-3 pb-3">
|
|
243
245
|
{nav.map((item, i) => renderItem(item, i, () => setMobileOpen(false)))}
|
|
244
|
-
{
|
|
246
|
+
{showThemeToggle && <ThemeToggleItem />}
|
|
245
247
|
</nav>
|
|
246
248
|
{footer && <div className="mt-auto border-t border-gray-200 p-3 dark:border-gray-800">{footer}</div>}
|
|
247
249
|
</div>
|
package/src/team.tsx
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Team/people-grid building block -- TeamMemberCard, TeamGrid. A distinct,
|
|
4
|
+
// later-added vocabulary (same rationale as dashboard.tsx) for "who's on
|
|
5
|
+
// this" pages: about pages, staff directories, contributor/leaderboard-
|
|
6
|
+
// adjacent listings. Original implementation -- conceptually informed by
|
|
7
|
+
// the general "avatar + name + role + optional bio + optional links" shape
|
|
8
|
+
// common to team-showcase page sections, not copied markup/CSS from any
|
|
9
|
+
// one source (see packages/design-library's README for this platform's
|
|
10
|
+
// design-provenance conventions). Deliberately no baked-in social-network
|
|
11
|
+
// icon set -- `links` is a generic {label, href, icon?} array so this
|
|
12
|
+
// doesn't force which platforms (or how many) a member's links cover.
|
|
13
|
+
import type { ReactNode } from "react";
|
|
14
|
+
import { Avatar } from "./components";
|
|
15
|
+
|
|
16
|
+
function cx(...parts: (string | false | null | undefined)[]): string {
|
|
17
|
+
return parts.filter(Boolean).join(" ");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TeamMemberLink {
|
|
21
|
+
label: string;
|
|
22
|
+
href: string;
|
|
23
|
+
icon?: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TeamMember {
|
|
27
|
+
name: string;
|
|
28
|
+
role?: ReactNode;
|
|
29
|
+
avatarSrc?: string;
|
|
30
|
+
bio?: ReactNode;
|
|
31
|
+
links?: TeamMemberLink[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TeamMemberCardProps {
|
|
35
|
+
member: TeamMember;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function TeamMemberCard({ member, className }: TeamMemberCardProps) {
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className={cx(
|
|
43
|
+
"flex flex-col items-center gap-3 rounded-xl border border-gray-200 bg-white p-6 text-center dark:border-gray-800 dark:bg-gray-900",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<Avatar src={member.avatarSrc} name={member.name} size={72} />
|
|
48
|
+
<div>
|
|
49
|
+
<div className="text-base font-semibold text-gray-900 dark:text-gray-100">{member.name}</div>
|
|
50
|
+
{member.role && <div className="text-sm text-gray-500 dark:text-gray-400">{member.role}</div>}
|
|
51
|
+
</div>
|
|
52
|
+
{member.bio && <p className="text-sm text-gray-600 dark:text-gray-300">{member.bio}</p>}
|
|
53
|
+
{member.links && member.links.length > 0 && (
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
{member.links.map((link, i) => (
|
|
56
|
+
<a
|
|
57
|
+
key={i}
|
|
58
|
+
href={link.href}
|
|
59
|
+
target="_blank"
|
|
60
|
+
rel="noopener noreferrer"
|
|
61
|
+
aria-label={link.label}
|
|
62
|
+
title={link.label}
|
|
63
|
+
className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-100 text-gray-600 hover:bg-primary/10 hover:text-primary dark:bg-gray-800 dark:text-gray-300"
|
|
64
|
+
>
|
|
65
|
+
{link.icon ?? link.label[0]?.toUpperCase()}
|
|
66
|
+
</a>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface TeamGridProps {
|
|
75
|
+
members: TeamMember[];
|
|
76
|
+
className?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 1/2/3-column responsive grid -- the columns step follows this package's
|
|
80
|
+
// existing breakpoint convention (md for tablet, lg for desktop) rather
|
|
81
|
+
// than a 4th column, since a wider member card reads better than a
|
|
82
|
+
// cramped one on very large screens.
|
|
83
|
+
export function TeamGrid({ members, className }: TeamGridProps) {
|
|
84
|
+
return (
|
|
85
|
+
<div className={cx("grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3", className)}>
|
|
86
|
+
{members.map((member, i) => (
|
|
87
|
+
<TeamMemberCard key={i} member={member} />
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|