@proyecto-viviana/ui 0.4.1 → 0.5.0
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/dist/styles.css +0 -3
- package/dist/viviana-tokens.css +240 -171
- package/package.json +3 -57
- package/src/viviana-tokens.css +240 -171
- package/dist/CalendarCard.d.ts +0 -2
- package/dist/CalendarCard.d.ts.map +0 -1
- package/dist/CalendarCard.js +0 -96
- package/dist/CalendarCard.js.map +0 -1
- package/dist/CalendarCard.jsx +0 -50
- package/dist/CalendarCard.jsx.map +0 -1
- package/dist/Chip.d.ts +0 -2
- package/dist/Chip.d.ts.map +0 -1
- package/dist/Chip.js +0 -2
- package/dist/Chip.jsx +0 -2
- package/dist/Conversation.d.ts +0 -2
- package/dist/Conversation.d.ts.map +0 -1
- package/dist/Conversation.js +0 -176
- package/dist/Conversation.js.map +0 -1
- package/dist/Conversation.jsx +0 -106
- package/dist/Conversation.jsx.map +0 -1
- package/dist/EventCard.d.ts +0 -2
- package/dist/EventCard.d.ts.map +0 -1
- package/dist/EventCard.js +0 -171
- package/dist/EventCard.js.map +0 -1
- package/dist/EventCard.jsx +0 -94
- package/dist/EventCard.jsx.map +0 -1
- package/dist/Logo.d.ts +0 -2
- package/dist/Logo.d.ts.map +0 -1
- package/dist/Logo.js +0 -116
- package/dist/Logo.js.map +0 -1
- package/dist/Logo.jsx +0 -101
- package/dist/Logo.jsx.map +0 -1
- package/dist/PageLayout.d.ts +0 -2
- package/dist/PageLayout.d.ts.map +0 -1
- package/dist/PageLayout.js +0 -31
- package/dist/PageLayout.js.map +0 -1
- package/dist/PageLayout.jsx +0 -23
- package/dist/PageLayout.jsx.map +0 -1
- package/dist/ProfileCard.d.ts +0 -2
- package/dist/ProfileCard.d.ts.map +0 -1
- package/dist/ProfileCard.js +0 -77
- package/dist/ProfileCard.js.map +0 -1
- package/dist/ProfileCard.jsx +0 -52
- package/dist/ProfileCard.jsx.map +0 -1
- package/dist/ProjectCard.d.ts +0 -2
- package/dist/ProjectCard.d.ts.map +0 -1
- package/dist/ProjectCard.js +0 -162
- package/dist/ProjectCard.js.map +0 -1
- package/dist/ProjectCard.jsx +0 -131
- package/dist/ProjectCard.jsx.map +0 -1
- package/dist/TimelineItem.d.ts +0 -2
- package/dist/TimelineItem.d.ts.map +0 -1
- package/dist/TimelineItem.js +0 -137
- package/dist/TimelineItem.js.map +0 -1
- package/dist/TimelineItem.jsx +0 -70
- package/dist/TimelineItem.jsx.map +0 -1
- package/dist/chip2.js +0 -82
- package/dist/chip2.js.map +0 -1
- package/dist/chip2.jsx +0 -68
- package/dist/chip2.jsx.map +0 -1
- package/dist/custom/calendar-card/index.d.ts +0 -12
- package/dist/custom/calendar-card/index.d.ts.map +0 -1
- package/dist/custom/chip/index.d.ts +0 -16
- package/dist/custom/chip/index.d.ts.map +0 -1
- package/dist/custom/conversation/index.d.ts +0 -32
- package/dist/custom/conversation/index.d.ts.map +0 -1
- package/dist/custom/event-card/index.d.ts +0 -30
- package/dist/custom/event-card/index.d.ts.map +0 -1
- package/dist/custom/header/index.d.ts +0 -12
- package/dist/custom/header/index.d.ts.map +0 -1
- package/dist/custom/lateral-nav/index.d.ts +0 -32
- package/dist/custom/lateral-nav/index.d.ts.map +0 -1
- package/dist/custom/logo/index.d.ts +0 -11
- package/dist/custom/logo/index.d.ts.map +0 -1
- package/dist/custom/nav-header/index.d.ts +0 -13
- package/dist/custom/nav-header/index.d.ts.map +0 -1
- package/dist/custom/page-layout/index.d.ts +0 -7
- package/dist/custom/page-layout/index.d.ts.map +0 -1
- package/dist/custom/profile-card/index.d.ts +0 -16
- package/dist/custom/profile-card/index.d.ts.map +0 -1
- package/dist/custom/project-card/index.d.ts +0 -13
- package/dist/custom/project-card/index.d.ts.map +0 -1
- package/dist/custom/timeline-item/index.d.ts +0 -28
- package/dist/custom/timeline-item/index.d.ts.map +0 -1
- package/src/CalendarCard.ts +0 -1
- package/src/Chip.ts +0 -1
- package/src/Conversation.ts +0 -1
- package/src/EventCard.ts +0 -1
- package/src/Logo.ts +0 -1
- package/src/PageLayout.ts +0 -1
- package/src/ProfileCard.ts +0 -1
- package/src/ProjectCard.ts +0 -1
- package/src/TimelineItem.ts +0 -1
- package/src/custom/calendar-card/index.tsx +0 -124
- package/src/custom/chip/index.tsx +0 -82
- package/src/custom/conversation/index.tsx +0 -216
- package/src/custom/event-card/index.tsx +0 -253
- package/src/custom/header/index.tsx +0 -64
- package/src/custom/lateral-nav/index.tsx +0 -143
- package/src/custom/logo/index.tsx +0 -60
- package/src/custom/nav-header/index.tsx +0 -106
- package/src/custom/page-layout/index.tsx +0 -33
- package/src/custom/profile-card/index.tsx +0 -123
- package/src/custom/project-card/index.tsx +0 -76
- package/src/custom/timeline-item/index.tsx +0 -148
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "solid-js";
|
|
2
|
-
// Styled via the S2 style() macro through viviana's own seam (../../style →
|
|
3
|
-
// src/style.ts). A clean two-word wordmark: the S2 title ramp sizes it; Silapse
|
|
4
|
-
// colors paint it (primary + accent). `inverted` swaps which word takes accent.
|
|
5
|
-
import { style } from "../../style" with { type: "macro" };
|
|
6
|
-
|
|
7
|
-
export type LogoSize = "sm" | "md" | "lg" | "xl";
|
|
8
|
-
|
|
9
|
-
export interface LogoProps {
|
|
10
|
-
firstWord?: string;
|
|
11
|
-
secondWord?: string;
|
|
12
|
-
size?: LogoSize;
|
|
13
|
-
inverted?: boolean;
|
|
14
|
-
class?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const wordmark = style<{ size: LogoSize }>({
|
|
18
|
-
display: "inline-flex",
|
|
19
|
-
alignItems: "baseline",
|
|
20
|
-
columnGap: 8,
|
|
21
|
-
whiteSpace: "nowrap",
|
|
22
|
-
font: {
|
|
23
|
-
size: {
|
|
24
|
-
sm: "title-sm",
|
|
25
|
-
md: "title",
|
|
26
|
-
lg: "title-xl",
|
|
27
|
-
xl: "title-3xl",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
fontWeight: "black",
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const word = style<{ tone: "primary" | "accent" }>({
|
|
34
|
-
color: {
|
|
35
|
-
tone: {
|
|
36
|
-
primary: "[var(--color-primary-100)]",
|
|
37
|
-
// `--color-accent` (#df5c9a) is the SAME non-flipping pink in both themes;
|
|
38
|
-
// on the light `--color-bg-200` panel the wordmark reads only 1.89:1 (fails
|
|
39
|
-
// even the 3:1 large-text floor the `black`-weight `title-xl` earns). No
|
|
40
|
-
// single non-flipping shade clears a near-white AND a near-black panel, so
|
|
41
|
-
// the accent word takes the *flipping* `--color-accent-500` (dark #d84a8f /
|
|
42
|
-
// light #8a1e4a) → 3.86:1 dark / 4.91:1 light — clearing the large-text
|
|
43
|
-
// floor in both themes while keeping the two-tone wordmark identity.
|
|
44
|
-
accent: "[var(--color-accent-500)]",
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export function Logo(props: LogoProps): JSX.Element {
|
|
50
|
-
const firstWord = () => props.firstWord ?? "Proyecto";
|
|
51
|
-
const secondWord = () => props.secondWord ?? "Viviana";
|
|
52
|
-
const inverted = () => props.inverted ?? false;
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<span class={`${wordmark({ size: props.size ?? "md" })} ${props.class ?? ""}`}>
|
|
56
|
-
<span class={word({ tone: inverted() ? "accent" : "primary" })}>{firstWord()}</span>
|
|
57
|
-
<span class={word({ tone: inverted() ? "primary" : "accent" })}>{secondWord()}</span>
|
|
58
|
-
</span>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "solid-js";
|
|
2
|
-
import { Show } from "solid-js";
|
|
3
|
-
import { Button as HeadlessButton } from "@proyecto-viviana/solidaria-components";
|
|
4
|
-
// Styled via the S2 style() macro through viviana's own seam (../../style →
|
|
5
|
-
// src/style.ts). A nav bar with an accent bottom rule, a logo/wordmark slot,
|
|
6
|
-
// and trailing actions, on the S2 ramps in Silapse colors.
|
|
7
|
-
import { style } from "../../style" with { type: "macro" };
|
|
8
|
-
|
|
9
|
-
export interface NavHeaderProps {
|
|
10
|
-
logo?: string;
|
|
11
|
-
logoAlt?: string;
|
|
12
|
-
logoText?: string;
|
|
13
|
-
children?: JSX.Element;
|
|
14
|
-
menuIcon?: JSX.Element;
|
|
15
|
-
onMenuClick?: () => void;
|
|
16
|
-
menuAriaLabel?: string;
|
|
17
|
-
class?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const bar = style({
|
|
21
|
-
display: "flex",
|
|
22
|
-
alignItems: "center",
|
|
23
|
-
height: 70,
|
|
24
|
-
backgroundColor: "[var(--color-bg-400)]",
|
|
25
|
-
borderBottomWidth: 4,
|
|
26
|
-
borderStyle: "solid",
|
|
27
|
-
borderColor: "[var(--color-accent)]",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const lead = style({
|
|
31
|
-
display: "flex",
|
|
32
|
-
alignItems: "center",
|
|
33
|
-
paddingStart: 32,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const logoText = style({
|
|
37
|
-
display: "flex",
|
|
38
|
-
alignItems: "center",
|
|
39
|
-
font: "title-xl",
|
|
40
|
-
fontWeight: "normal",
|
|
41
|
-
// WCAG AA fix: the wordmark sits on the `--color-bg-400` bar. `--color-primary-700`
|
|
42
|
-
// is a non-flipping brand fill (light blue in light mode, dark blue in dark mode) —
|
|
43
|
-
// the SAME direction as the bar, so it read only 2.10:1 light / 2.92:1 dark. The
|
|
44
|
-
// rendered `title-xl` measures under 24px, so WCAG scores it as normal text (4.5:1
|
|
45
|
-
// floor, not the 3:1 large-text exception). `--color-primary-500` is the smallest
|
|
46
|
-
// ramp step that flips with the theme (dark-on-light / light-on-dark) AND clears
|
|
47
|
-
// 4.5:1, lifting the wordmark to 5.37:1 light / 7.00:1 dark while keeping a prominent
|
|
48
|
-
// brand blue.
|
|
49
|
-
color: "[var(--color-primary-500)]",
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const logoImage = style({
|
|
53
|
-
height: 42,
|
|
54
|
-
width: "auto",
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const trail = style({
|
|
58
|
-
display: "flex",
|
|
59
|
-
alignItems: "center",
|
|
60
|
-
justifyContent: "end",
|
|
61
|
-
flexGrow: 1,
|
|
62
|
-
gap: 12,
|
|
63
|
-
paddingEnd: 32,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const menuButton = style({
|
|
67
|
-
display: "flex",
|
|
68
|
-
alignItems: "center",
|
|
69
|
-
justifyContent: "center",
|
|
70
|
-
cursor: "pointer",
|
|
71
|
-
backgroundColor: "transparent",
|
|
72
|
-
borderStyle: "none",
|
|
73
|
-
color: "[var(--color-primary-200)]",
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
export function NavHeader(props: NavHeaderProps) {
|
|
77
|
-
return (
|
|
78
|
-
<nav class={`${bar} ${props.class ?? ""}`}>
|
|
79
|
-
<div class={lead}>
|
|
80
|
-
<Show
|
|
81
|
-
when={props.logo}
|
|
82
|
-
fallback={
|
|
83
|
-
<Show when={props.logoText}>
|
|
84
|
-
<span class={logoText}>{props.logoText}</span>
|
|
85
|
-
</Show>
|
|
86
|
-
}
|
|
87
|
-
>
|
|
88
|
-
<img class={logoImage} src={props.logo} alt={props.logoAlt ?? "Logo"} />
|
|
89
|
-
</Show>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<div class={trail}>
|
|
93
|
-
{props.children}
|
|
94
|
-
<Show when={props.menuIcon}>
|
|
95
|
-
<HeadlessButton
|
|
96
|
-
class={menuButton}
|
|
97
|
-
onPress={() => props.onMenuClick?.()}
|
|
98
|
-
aria-label={props.menuAriaLabel ?? "Open menu"}
|
|
99
|
-
>
|
|
100
|
-
{props.menuIcon}
|
|
101
|
-
</HeadlessButton>
|
|
102
|
-
</Show>
|
|
103
|
-
</div>
|
|
104
|
-
</nav>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { type JSX, splitProps } from "solid-js";
|
|
2
|
-
// Styled via the S2 style() macro through viviana's own seam (../../style →
|
|
3
|
-
// src/style.ts). A full-height page shell painted in Silapse surface/text
|
|
4
|
-
// colors; `withHeader` reserves top space so a fixed header doesn't overlap.
|
|
5
|
-
import { style } from "../../style" with { type: "macro" };
|
|
6
|
-
|
|
7
|
-
export interface PageLayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
8
|
-
children: JSX.Element;
|
|
9
|
-
withHeader?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const page = style<{ isWithHeader: boolean }>({
|
|
13
|
-
display: "flex",
|
|
14
|
-
flexDirection: "column",
|
|
15
|
-
minHeight: "screen",
|
|
16
|
-
width: "full",
|
|
17
|
-
backgroundColor: "[var(--color-background)]",
|
|
18
|
-
color: "[var(--color-text)]",
|
|
19
|
-
paddingTop: { isWithHeader: 64 },
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export function PageLayout(props: PageLayoutProps) {
|
|
23
|
-
const [local, rest] = splitProps(props, ["class", "withHeader"]);
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div
|
|
27
|
-
class={`${page({ isWithHeader: local.withHeader ?? false })} ${local.class ?? ""}`}
|
|
28
|
-
{...rest}
|
|
29
|
-
>
|
|
30
|
-
{props.children}
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "solid-js";
|
|
2
|
-
import { Show } from "solid-js";
|
|
3
|
-
import { Avatar } from "@proyecto-viviana/solid-spectrum";
|
|
4
|
-
// Styled via the S2 style() macro through viviana's own seam (../../style →
|
|
5
|
-
// src/style.ts). Mirrors S2's UserCard shape (avatar + title/description +
|
|
6
|
-
// footer actions) on the S2 spacing/radius/elevation ramps, in Silapse colors.
|
|
7
|
-
import { style } from "../../style" with { type: "macro" };
|
|
8
|
-
|
|
9
|
-
export interface ProfileCardProps {
|
|
10
|
-
username: string;
|
|
11
|
-
avatar?: string;
|
|
12
|
-
bio?: string;
|
|
13
|
-
followers?: number;
|
|
14
|
-
following?: number;
|
|
15
|
-
/**
|
|
16
|
-
* Actions to display below the profile.
|
|
17
|
-
* Use a function returning JSX for SSR compatibility: `actions={() => <Button>...</Button>}`
|
|
18
|
-
*/
|
|
19
|
-
actions?: JSX.Element | (() => JSX.Element);
|
|
20
|
-
class?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const card = style({
|
|
24
|
-
display: "flex",
|
|
25
|
-
flexDirection: "column",
|
|
26
|
-
gap: 16,
|
|
27
|
-
padding: 16,
|
|
28
|
-
borderRadius: "lg",
|
|
29
|
-
backgroundColor: "[var(--color-bg-200)]",
|
|
30
|
-
boxShadow: "elevated",
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const head = style({
|
|
34
|
-
display: "flex",
|
|
35
|
-
alignItems: "start",
|
|
36
|
-
gap: 16,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const info = style({
|
|
40
|
-
display: "flex",
|
|
41
|
-
flexDirection: "column",
|
|
42
|
-
gap: 4,
|
|
43
|
-
flexGrow: 1,
|
|
44
|
-
minWidth: 0,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const name = style({
|
|
48
|
-
font: "heading-sm",
|
|
49
|
-
color: "[var(--color-primary-100)]",
|
|
50
|
-
overflow: "hidden",
|
|
51
|
-
textOverflow: "ellipsis",
|
|
52
|
-
whiteSpace: "nowrap",
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const bio = style({
|
|
56
|
-
font: "ui-sm",
|
|
57
|
-
// WCAG AA fix: the bio is small text (`ui-sm`) on the `--color-bg-200` card, so
|
|
58
|
-
// the 4.5:1 floor applies. `--color-text-secondary` read only 3.84:1 on the
|
|
59
|
-
// light-blue card (5.86:1 dark); `--color-text` flips and clears 4.5:1 both
|
|
60
|
-
// ways (15.33:1 dark / 7.53:1 light). The bio stays visually secondary to the
|
|
61
|
-
// `heading-sm` name through its smaller size, not a sub-AA color.
|
|
62
|
-
color: "[var(--color-text)]",
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const stats = style({
|
|
66
|
-
display: "flex",
|
|
67
|
-
gap: 16,
|
|
68
|
-
font: "ui-sm",
|
|
69
|
-
// WCAG AA fix: the stat connector words ("seguidores" / "siguiendo") share the
|
|
70
|
-
// bio's 3.84:1-light failure on the `--color-bg-200` card and take the same
|
|
71
|
-
// flipping `--color-text`. (The stat VALUES are `--color-primary-100`, which
|
|
72
|
-
// already clears AA — 13.47:1 dark / 10.37:1 light.)
|
|
73
|
-
color: "[var(--color-text)]",
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const statValue = style({
|
|
77
|
-
fontWeight: "bold",
|
|
78
|
-
color: "[var(--color-primary-100)]",
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const actionRow = style({
|
|
82
|
-
display: "flex",
|
|
83
|
-
gap: 8,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
export function ProfileCard(props: ProfileCardProps) {
|
|
87
|
-
const formatNumber = (num: number) => {
|
|
88
|
-
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
|
|
89
|
-
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
|
|
90
|
-
return num.toString();
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<div class={`${card} ${props.class ?? ""}`}>
|
|
95
|
-
<div class={head}>
|
|
96
|
-
<Avatar src={props.avatar} alt={props.username} size="lg" />
|
|
97
|
-
<div class={info}>
|
|
98
|
-
<h3 class={name}>{props.username}</h3>
|
|
99
|
-
<Show when={props.bio}>
|
|
100
|
-
<p class={bio}>{props.bio}</p>
|
|
101
|
-
</Show>
|
|
102
|
-
<div class={stats}>
|
|
103
|
-
<Show when={props.followers !== undefined}>
|
|
104
|
-
<span>
|
|
105
|
-
<span class={statValue}>{formatNumber(props.followers!)}</span> seguidores
|
|
106
|
-
</span>
|
|
107
|
-
</Show>
|
|
108
|
-
<Show when={props.following !== undefined}>
|
|
109
|
-
<span>
|
|
110
|
-
<span class={statValue}>{formatNumber(props.following!)}</span> siguiendo
|
|
111
|
-
</span>
|
|
112
|
-
</Show>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
<Show when={props.actions}>
|
|
117
|
-
<div class={actionRow}>
|
|
118
|
-
{typeof props.actions === "function" ? props.actions() : props.actions}
|
|
119
|
-
</div>
|
|
120
|
-
</Show>
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "solid-js";
|
|
2
|
-
// Styled via the S2 style() macro through viviana's own seam (../../style →
|
|
3
|
-
// src/style.ts). A square logo/preview tile with a caption, sized by the S2
|
|
4
|
-
// spacing + type ramps and painted in Silapse colors. `inactive` dims it;
|
|
5
|
-
// `href` turns the whole card into a link.
|
|
6
|
-
import { style } from "../../style" with { type: "macro" };
|
|
7
|
-
|
|
8
|
-
export type ProjectCardSize = "sm" | "md" | "lg";
|
|
9
|
-
|
|
10
|
-
export interface ProjectCardProps {
|
|
11
|
-
name: string;
|
|
12
|
-
imageSrc: string;
|
|
13
|
-
imageAlt?: string;
|
|
14
|
-
href?: string;
|
|
15
|
-
size?: ProjectCardSize;
|
|
16
|
-
inactive?: boolean;
|
|
17
|
-
class?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const card = style<{ size: ProjectCardSize; isInactive: boolean }>({
|
|
21
|
-
display: "flex",
|
|
22
|
-
flexDirection: "column",
|
|
23
|
-
alignItems: "center",
|
|
24
|
-
gap: 8,
|
|
25
|
-
width: "full",
|
|
26
|
-
borderRadius: "lg",
|
|
27
|
-
borderWidth: 1,
|
|
28
|
-
borderStyle: "solid",
|
|
29
|
-
borderColor: "[var(--color-border)]",
|
|
30
|
-
backgroundColor: "[var(--color-bg-200)]",
|
|
31
|
-
color: "[var(--color-primary-100)]",
|
|
32
|
-
textDecoration: "none",
|
|
33
|
-
padding: { size: { sm: 12, md: 16, lg: 20 } },
|
|
34
|
-
opacity: { default: 1, isInactive: 0.5 },
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const image = style({
|
|
38
|
-
width: "full",
|
|
39
|
-
aspectRatio: "square",
|
|
40
|
-
objectFit: "contain",
|
|
41
|
-
borderRadius: "sm",
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const caption = style<{ size: ProjectCardSize }>({
|
|
45
|
-
width: "full",
|
|
46
|
-
textAlign: "center",
|
|
47
|
-
overflow: "hidden",
|
|
48
|
-
textOverflow: "ellipsis",
|
|
49
|
-
whiteSpace: "nowrap",
|
|
50
|
-
font: { size: { sm: "ui-sm", md: "ui", lg: "ui-lg" } },
|
|
51
|
-
color: "[var(--color-primary-200)]",
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
export function ProjectCard(props: ProjectCardProps): JSX.Element {
|
|
55
|
-
const size = () => props.size ?? "sm";
|
|
56
|
-
const inactive = () => props.inactive ?? false;
|
|
57
|
-
|
|
58
|
-
const cardContent = () => (
|
|
59
|
-
<>
|
|
60
|
-
<img class={image} src={props.imageSrc} alt={props.imageAlt ?? props.name} />
|
|
61
|
-
<span class={caption({ size: size() })}>{props.name}</span>
|
|
62
|
-
</>
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const classes = () => `${card({ size: size(), isInactive: inactive() })} ${props.class ?? ""}`;
|
|
66
|
-
|
|
67
|
-
if (props.href) {
|
|
68
|
-
return (
|
|
69
|
-
<a href={props.href} target="_blank" rel="noopener noreferrer" class={classes()}>
|
|
70
|
-
{cardContent()}
|
|
71
|
-
</a>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return <div class={classes()}>{cardContent()}</div>;
|
|
76
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import type { JSX } from "solid-js";
|
|
2
|
-
import { Show } from "solid-js";
|
|
3
|
-
import { Avatar } from "@proyecto-viviana/solid-spectrum";
|
|
4
|
-
// Styled via the S2 style() macro through viviana's own seam (../../style →
|
|
5
|
-
// src/style.ts). A social timeline event card (two avatars + icon + message)
|
|
6
|
-
// on the S2 spacing/radius ramps in Silapse colors.
|
|
7
|
-
import { style } from "../../style" with { type: "macro" };
|
|
8
|
-
|
|
9
|
-
export type TimelineEventType = "follow" | "like" | "comment" | "event" | "custom";
|
|
10
|
-
|
|
11
|
-
export interface TimelineItemProps {
|
|
12
|
-
type?: TimelineEventType;
|
|
13
|
-
/**
|
|
14
|
-
* Icon to display between the two avatars.
|
|
15
|
-
* Use a function returning JSX for SSR compatibility: `icon={() => <MyIcon />}`
|
|
16
|
-
* Or pass a simple string for text-based icons: `icon="👋"`
|
|
17
|
-
*/
|
|
18
|
-
icon?: string | (() => JSX.Element);
|
|
19
|
-
leftUser?: {
|
|
20
|
-
name: string;
|
|
21
|
-
avatar?: string;
|
|
22
|
-
};
|
|
23
|
-
rightUser?: {
|
|
24
|
-
name: string;
|
|
25
|
-
avatar?: string;
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Custom message content.
|
|
29
|
-
* Use a function returning JSX for SSR compatibility: `message={() => <span>...</span>}`
|
|
30
|
-
* Or pass a simple string.
|
|
31
|
-
*/
|
|
32
|
-
message?: string | (() => JSX.Element);
|
|
33
|
-
class?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const nameEmph = style({
|
|
37
|
-
fontWeight: "bold",
|
|
38
|
-
// Emphasis via weight, not color: `--color-accent` (#df5c9a) fails AA on the
|
|
39
|
-
// `--color-bg-200` card (~1.9:1 light, ~4.48:1 dark) and no accent shade clears
|
|
40
|
-
// 4.5:1 in BOTH themes. The flipping `--color-text` does (7.53 light / 15.33
|
|
41
|
-
// dark); the `bold` weight sets the names apart from the message body.
|
|
42
|
-
color: "[var(--color-text)]",
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const eventMessages: Record<TimelineEventType, (left: string, right: string) => JSX.Element> = {
|
|
46
|
-
follow: (left, right) => (
|
|
47
|
-
<>
|
|
48
|
-
<span class={nameEmph}>{left}</span>
|
|
49
|
-
{" ha empezado a seguir a "}
|
|
50
|
-
<span class={nameEmph}>{right}</span>
|
|
51
|
-
</>
|
|
52
|
-
),
|
|
53
|
-
like: (left, right) => (
|
|
54
|
-
<>
|
|
55
|
-
<span class={nameEmph}>{left}</span>
|
|
56
|
-
{" le ha dado like a "}
|
|
57
|
-
<span class={nameEmph}>{right}</span>
|
|
58
|
-
</>
|
|
59
|
-
),
|
|
60
|
-
comment: (left, right) => (
|
|
61
|
-
<>
|
|
62
|
-
<span class={nameEmph}>{left}</span>
|
|
63
|
-
{" ha comentado en "}
|
|
64
|
-
<span class={nameEmph}>{right}</span>
|
|
65
|
-
</>
|
|
66
|
-
),
|
|
67
|
-
event: (left, right) => (
|
|
68
|
-
<>
|
|
69
|
-
<span class={nameEmph}>{left}</span>
|
|
70
|
-
{" asistirá al evento de "}
|
|
71
|
-
<span class={nameEmph}>{right}</span>
|
|
72
|
-
</>
|
|
73
|
-
),
|
|
74
|
-
custom: () => null,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const card = style({
|
|
78
|
-
display: "inline-flex",
|
|
79
|
-
flexDirection: "column",
|
|
80
|
-
gap: 20,
|
|
81
|
-
padding: 20,
|
|
82
|
-
borderRadius: "xl",
|
|
83
|
-
borderWidth: 1,
|
|
84
|
-
borderStyle: "solid",
|
|
85
|
-
borderColor: "[var(--color-primary-700)]",
|
|
86
|
-
backgroundColor: "[var(--color-bg-200)]",
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const avatarsRow = style({
|
|
90
|
-
display: "flex",
|
|
91
|
-
alignItems: "center",
|
|
92
|
-
justifyContent: "center",
|
|
93
|
-
gap: 12,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const messageRow = style({
|
|
97
|
-
display: "flex",
|
|
98
|
-
alignItems: "center",
|
|
99
|
-
justifyContent: "center",
|
|
100
|
-
textAlign: "center",
|
|
101
|
-
font: "ui",
|
|
102
|
-
// `--color-text-secondary` renders 3.84:1 on the light `--color-bg-200` card
|
|
103
|
-
// (fails the 4.5:1 small-text floor); the flipping `--color-text` clears it in
|
|
104
|
-
// both themes. `color` stays after `font` (the S2 macro font-shorthand default
|
|
105
|
-
// color would otherwise win on source order).
|
|
106
|
-
color: "[var(--color-text)]",
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
export function TimelineItem(props: TimelineItemProps) {
|
|
110
|
-
const type = () => props.type ?? "follow";
|
|
111
|
-
const leftName = () => props.leftUser?.name ?? "";
|
|
112
|
-
const rightName = () => props.rightUser?.name ?? "";
|
|
113
|
-
|
|
114
|
-
const renderIcon = () => {
|
|
115
|
-
const icon = props.icon;
|
|
116
|
-
if (!icon) return null;
|
|
117
|
-
if (typeof icon === "string") return icon;
|
|
118
|
-
return icon();
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const renderMessage = () => {
|
|
122
|
-
const message = props.message;
|
|
123
|
-
if (!message) return null;
|
|
124
|
-
if (typeof message === "string") return message;
|
|
125
|
-
return message();
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<div class={`${card} ${props.class ?? ""}`}>
|
|
130
|
-
<div class={avatarsRow}>
|
|
131
|
-
<Show when={props.leftUser}>
|
|
132
|
-
<Avatar src={props.leftUser!.avatar} alt={props.leftUser!.name} />
|
|
133
|
-
</Show>
|
|
134
|
-
<Show when={props.icon}>{renderIcon()}</Show>
|
|
135
|
-
<Show when={props.rightUser}>
|
|
136
|
-
<Avatar src={props.rightUser!.avatar} alt={props.rightUser!.name} />
|
|
137
|
-
</Show>
|
|
138
|
-
</div>
|
|
139
|
-
<div class={messageRow}>
|
|
140
|
-
<span>
|
|
141
|
-
<Show when={props.message} fallback={eventMessages[type()](leftName(), rightName())}>
|
|
142
|
-
{renderMessage()}
|
|
143
|
-
</Show>
|
|
144
|
-
</span>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
);
|
|
148
|
-
}
|