@oddsmith/ui 0.0.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/.eleventy.cjs +14 -0
- package/LICENSE +28 -0
- package/README.md +118 -0
- package/custom-elements.json +1539 -0
- package/docs/_README/index.html +4 -0
- package/docs/api/index.html +2100 -0
- package/docs/components.bundle.js +1669 -0
- package/docs/components.bundle.js.map +1 -0
- package/docs/docs.css +162 -0
- package/docs/examples/index.html +56 -0
- package/docs/index.html +53 -0
- package/docs/install/index.html +45 -0
- package/docs/prism-okaidia.css +123 -0
- package/docs-src/.nojekyll +0 -0
- package/docs-src/_README.md +7 -0
- package/docs-src/_data/api.11tydata.js +8 -0
- package/docs-src/_includes/example.11ty.js +35 -0
- package/docs-src/_includes/footer.11ty.js +6 -0
- package/docs-src/_includes/header.11ty.js +7 -0
- package/docs-src/_includes/nav.11ty.js +11 -0
- package/docs-src/_includes/page.11ty.js +32 -0
- package/docs-src/_includes/relative-path.cjs +9 -0
- package/docs-src/api.11ty.js +85 -0
- package/docs-src/bundle.ts +9 -0
- package/docs-src/docs.css +162 -0
- package/docs-src/examples/index.md +15 -0
- package/docs-src/index.md +39 -0
- package/docs-src/install.md +28 -0
- package/docs-src/package.json +3 -0
- package/index.html +19 -0
- package/karma.conf.cjs +24 -0
- package/main.css +210 -0
- package/main.ts +124 -0
- package/package.json +86 -0
- package/previews/casino.ts +12 -0
- package/previews/catalog.ts +94 -0
- package/previews/leaderboard-v1.ts +12 -0
- package/previews/leaderboard-v2.ts +17 -0
- package/previews/sample-data.ts +101 -0
- package/previews/sf-leaderboard.ts +100 -0
- package/previews/sf-live-feed.ts +15 -0
- package/previews/streaks.ts +40 -0
- package/previews/types.ts +18 -0
- package/src/components/README.md +16 -0
- package/src/components/casino-leaderboard/casino-leaderboard.html +80 -0
- package/src/components/casino-leaderboard/casino-leaderboard.scss +585 -0
- package/src/components/casino-leaderboard/casino-leaderboard.ts +136 -0
- package/src/components/casino-leaderboard/data.ts +111 -0
- package/src/components/casino-leaderboard/index.ts +5 -0
- package/src/components/casino-leaderboard/todo.txt +2 -0
- package/src/components/casino-leaderboard/types.ts +19 -0
- package/src/components/leaderboard/components/leaderboard.ts +373 -0
- package/src/components/leaderboard/components/player-card.ts +342 -0
- package/src/components/leaderboard/components/ui.ts +452 -0
- package/src/components/leaderboard/data.ts +152 -0
- package/src/components/leaderboard/index.ts +2 -0
- package/src/components/leaderboard/main.ts +42 -0
- package/src/components/leaderboard/styles.ts +67 -0
- package/src/components/leaderboard/types.ts +28 -0
- package/src/components/leaderboard-v2/components/sf-leaderboard-player.ts +451 -0
- package/src/components/leaderboard-v2/components/sf-leaderboard-ui.ts +512 -0
- package/src/components/leaderboard-v2/components/sf-leaderboard.ts +205 -0
- package/src/components/leaderboard-v2/constants.ts +16 -0
- package/src/components/leaderboard-v2/demo/sample-data.ts +152 -0
- package/src/components/leaderboard-v2/events.ts +13 -0
- package/src/components/leaderboard-v2/icons.ts +22 -0
- package/src/components/leaderboard-v2/index.ts +23 -0
- package/src/components/leaderboard-v2/sf-leaderboard.html +1 -0
- package/src/components/leaderboard-v2/sf-leaderboard.scss +382 -0
- package/src/components/leaderboard-v2/tokens.ts +35 -0
- package/src/components/leaderboard-v2/types.ts +30 -0
- package/src/components/sf-leaderboard/index.ts +77 -0
- package/src/components/sf-leaderboard/sections/footer-section/footer-section.host.ts +3 -0
- package/src/components/sf-leaderboard/sections/footer-section/footer-section.html +3 -0
- package/src/components/sf-leaderboard/sections/footer-section/footer-section.scss +18 -0
- package/src/components/sf-leaderboard/sections/footer-section/footer-section.ts +22 -0
- package/src/components/sf-leaderboard/sections/header-section/header-section.host.ts +14 -0
- package/src/components/sf-leaderboard/sections/header-section/header-section.html +27 -0
- package/src/components/sf-leaderboard/sections/header-section/header-section.scss +189 -0
- package/src/components/sf-leaderboard/sections/header-section/header-section.ts +70 -0
- package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.host.ts +22 -0
- package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.html +38 -0
- package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.scss +99 -0
- package/src/components/sf-leaderboard/sections/ranking-section/ranking-section.ts +121 -0
- package/src/components/sf-leaderboard/sections/stats-section/stats-section.host.ts +8 -0
- package/src/components/sf-leaderboard/sections/stats-section/stats-section.html +6 -0
- package/src/components/sf-leaderboard/sections/stats-section/stats-section.scss +44 -0
- package/src/components/sf-leaderboard/sections/stats-section/stats-section.ts +41 -0
- package/src/components/sf-leaderboard/sections/table-section/table-section.host.ts +17 -0
- package/src/components/sf-leaderboard/sections/table-section/table-section.html +19 -0
- package/src/components/sf-leaderboard/sections/table-section/table-section.scss +37 -0
- package/src/components/sf-leaderboard/sections/table-section/table-section.ts +108 -0
- package/src/components/sf-leaderboard/services/index.ts +22 -0
- package/src/components/sf-leaderboard/services/sf-leaderboard-data.service.ts +54 -0
- package/src/components/sf-leaderboard/services/sf-leaderboard.state.ts +160 -0
- package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.host.ts +7 -0
- package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.html +10 -0
- package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.scss +180 -0
- package/src/components/sf-leaderboard/shared/components/activity-feed/activity-feed.ts +88 -0
- package/src/components/sf-leaderboard/shared/components/filters/filters.host.ts +12 -0
- package/src/components/sf-leaderboard/shared/components/filters/filters.html +22 -0
- package/src/components/sf-leaderboard/shared/components/filters/filters.scss +122 -0
- package/src/components/sf-leaderboard/shared/components/filters/filters.ts +75 -0
- package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.host.ts +9 -0
- package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.html +5 -0
- package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.scss +81 -0
- package/src/components/sf-leaderboard/shared/components/player-avatar/player-avatar.ts +34 -0
- package/src/components/sf-leaderboard/shared/components/podium/map-players.ts +24 -0
- package/src/components/sf-leaderboard/shared/components/podium/podium.host.ts +10 -0
- package/src/components/sf-leaderboard/shared/components/podium/podium.html +53 -0
- package/src/components/sf-leaderboard/shared/components/podium/podium.scss +580 -0
- package/src/components/sf-leaderboard/shared/components/podium/podium.ts +49 -0
- package/src/components/sf-leaderboard/shared/components/podium/podium.types.ts +9 -0
- package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.host.ts +11 -0
- package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.html +9 -0
- package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.scss +98 -0
- package/src/components/sf-leaderboard/shared/components/rank-badge/rank-badge.ts +63 -0
- package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.host.ts +9 -0
- package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.html +15 -0
- package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.scss +210 -0
- package/src/components/sf-leaderboard/shared/components/stat-card/stat-card.ts +36 -0
- package/src/components/sf-leaderboard/shared/components/table/table.host.ts +5 -0
- package/src/components/sf-leaderboard/shared/components/table/table.html +11 -0
- package/src/components/sf-leaderboard/shared/components/table/table.scss +212 -0
- package/src/components/sf-leaderboard/shared/components/table/table.ts +111 -0
- package/src/components/sf-leaderboard/shared/constants/defaults.ts +7 -0
- package/src/components/sf-leaderboard/shared/constants/filters.ts +16 -0
- package/src/components/sf-leaderboard/shared/constants/index.ts +5 -0
- package/src/components/sf-leaderboard/shared/constants/player-stats.ts +3 -0
- package/src/components/sf-leaderboard/shared/constants/stats-overview.ts +38 -0
- package/src/components/sf-leaderboard/shared/constants/tags.ts +16 -0
- package/src/components/sf-leaderboard/shared/styles/_section.scss +35 -0
- package/src/components/sf-leaderboard/shared/types/data.ts +29 -0
- package/src/components/sf-leaderboard/shared/types/events.ts +30 -0
- package/src/components/sf-leaderboard/shared/types/player-stats.ts +3 -0
- package/src/components/sf-leaderboard/shared/types/sections.ts +100 -0
- package/src/components/sf-leaderboard/shared/utils/utils.ts +17 -0
- package/src/components/sf-leaderboard/theme/THEMING.md +54 -0
- package/src/components/sf-leaderboard/theme/context.ts +16 -0
- package/src/components/sf-leaderboard/theme/default-theme.ts +4 -0
- package/src/components/sf-leaderboard/theme/hex-to-rgb.ts +25 -0
- package/src/components/sf-leaderboard/theme/index.ts +18 -0
- package/src/components/sf-leaderboard/theme/inject-theme.ts +39 -0
- package/src/components/sf-leaderboard/theme/load-theme.ts +26 -0
- package/src/components/sf-leaderboard/theme/merge-theme.ts +59 -0
- package/src/components/sf-leaderboard/theme/scss/_colors.scss +101 -0
- package/src/components/sf-leaderboard/theme/scss/shared.scss +123 -0
- package/src/components/sf-leaderboard/theme/styles.ts +6 -0
- package/src/components/sf-leaderboard/theme/theme-to-css-vars.ts +99 -0
- package/src/components/sf-leaderboard/theme/themes/fallback.json +62 -0
- package/src/components/sf-leaderboard/theme/themes/red.json +62 -0
- package/src/components/sf-leaderboard/theme/types.ts +71 -0
- package/src/components/sf-live-feed/components/avatar/avatar.host.ts +5 -0
- package/src/components/sf-live-feed/components/avatar/avatar.html +3 -0
- package/src/components/sf-live-feed/components/avatar/avatar.scss +24 -0
- package/src/components/sf-live-feed/components/avatar/avatar.ts +27 -0
- package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.host.ts +8 -0
- package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.html +10 -0
- package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.scss +177 -0
- package/src/components/sf-live-feed/components/sf-live-feed/sf-live-feed.ts +65 -0
- package/src/components/sf-live-feed/constants.ts +4 -0
- package/src/components/sf-live-feed/demo/sample-data.ts +34 -0
- package/src/components/sf-live-feed/index.ts +19 -0
- package/src/components/sf-live-feed/styles/theme.scss +19 -0
- package/src/components/sf-live-feed/styles/theme.ts +5 -0
- package/src/components/sf-live-feed/types.ts +19 -0
- package/src/components/sf-live-feed/utils.ts +17 -0
- package/src/components/streaks/constants.ts +17 -0
- package/src/components/streaks/demo/sample-steps.ts +10 -0
- package/src/components/streaks/events.ts +8 -0
- package/src/components/streaks/index.ts +16 -0
- package/src/components/streaks/sf-streaks.html +26 -0
- package/src/components/streaks/sf-streaks.scss +351 -0
- package/src/components/streaks/sf-streaks.ts +235 -0
- package/src/components/streaks/types.ts +7 -0
- package/src/lib/lit/component.ts +10 -0
- package/src/lib/lit/safe-custom-element.ts +12 -0
- package/src/lib/lit/scss.ts +6 -0
- package/src/vite-env.d.ts +18 -0
- package/styles/global.css +125 -0
- package/todo.txt +54 -0
- package/tsconfig.json +31 -0
- package/vite.config.ts +56 -0
- package/vite.docs.config.ts +33 -0
- package/vite.lit-html-plugin.ts +43 -0
package/main.css
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
body {
|
|
6
|
+
margin: 0;
|
|
7
|
+
font-family: system-ui, sans-serif;
|
|
8
|
+
background: #0f1117;
|
|
9
|
+
color: #e8eaed;
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.core-dev {
|
|
14
|
+
display: flex;
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.core-dev__sidebar {
|
|
19
|
+
width: 14rem;
|
|
20
|
+
flex-shrink: 0;
|
|
21
|
+
padding: 1rem;
|
|
22
|
+
background: #1a1d26;
|
|
23
|
+
border-right: 1px solid #2a2f3a;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: 1rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.core-dev__brand strong {
|
|
30
|
+
display: block;
|
|
31
|
+
font-size: 0.95rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.core-dev__brand span {
|
|
35
|
+
font-size: 0.75rem;
|
|
36
|
+
color: #9aa0a6;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.core-dev__nav {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: 0.25rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.core-dev__nav-link {
|
|
46
|
+
display: block;
|
|
47
|
+
padding: 0.5rem 0.65rem;
|
|
48
|
+
border-radius: 6px;
|
|
49
|
+
color: #c4c7cc;
|
|
50
|
+
text-decoration: none;
|
|
51
|
+
font-size: 0.875rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.core-dev__nav-link:hover {
|
|
55
|
+
background: #252830;
|
|
56
|
+
color: #fff;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.core-dev__nav-link.is-active {
|
|
60
|
+
background: #2d3340;
|
|
61
|
+
color: #fff;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.core-dev__main {
|
|
65
|
+
flex: 1;
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
min-width: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.core-dev__header {
|
|
72
|
+
padding: 1rem 1.25rem;
|
|
73
|
+
border-bottom: 1px solid #2a2f3a;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.core-dev__header h1 {
|
|
77
|
+
margin: 0 0 0.35rem;
|
|
78
|
+
font-size: 1.25rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.core-dev__header p {
|
|
82
|
+
margin: 0;
|
|
83
|
+
font-size: 0.875rem;
|
|
84
|
+
color: #9aa0a6;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.core-dev__stage {
|
|
88
|
+
flex: 1;
|
|
89
|
+
padding: 1.5rem;
|
|
90
|
+
display: flex;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
align-items: flex-start;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.core-dev__stage:has(> sf-leaderboard:only-child),
|
|
96
|
+
.core-dev__stage:has(.sf-leaderboard-dev > sf-leaderboard) {
|
|
97
|
+
padding: 0;
|
|
98
|
+
display: block;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.core-dev__stage > sf-leaderboard,
|
|
102
|
+
.core-dev__stage .sf-leaderboard-dev > sf-leaderboard {
|
|
103
|
+
width: 100%;
|
|
104
|
+
display: block;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.core-dev__stage:empty::after {
|
|
108
|
+
content: 'Select a component';
|
|
109
|
+
color: #6b7280;
|
|
110
|
+
font-size: 0.875rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.core-dev__error {
|
|
114
|
+
color: #f87171;
|
|
115
|
+
font-size: 0.875rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* SF Leaderboard preset bar (dev preview) */
|
|
119
|
+
.sf-leaderboard-dev {
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
width: 100%;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.playground-preset-bar {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
gap: 1rem 1.5rem;
|
|
129
|
+
padding: 0.75rem 1rem;
|
|
130
|
+
background: #1a1d26;
|
|
131
|
+
border-bottom: 1px solid #2a2f3a;
|
|
132
|
+
align-items: flex-start;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.playground-preset-bar__group {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: 0.35rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.playground-preset-bar__label {
|
|
142
|
+
font-size: 0.75rem;
|
|
143
|
+
color: #9aa0a6;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.playground-preset-bar__select {
|
|
147
|
+
font: inherit;
|
|
148
|
+
padding: 0.4rem 0.6rem;
|
|
149
|
+
border-radius: 6px;
|
|
150
|
+
border: 1px solid #3c4048;
|
|
151
|
+
background: #0f1117;
|
|
152
|
+
color: inherit;
|
|
153
|
+
min-width: 12rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.playground-preset-bar__hint {
|
|
157
|
+
margin: 0;
|
|
158
|
+
font-size: 0.75rem;
|
|
159
|
+
color: #6b7280;
|
|
160
|
+
max-width: 20rem;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.playground-preset-bar__meta {
|
|
164
|
+
flex-direction: row;
|
|
165
|
+
flex-wrap: wrap;
|
|
166
|
+
align-items: center;
|
|
167
|
+
gap: 0.5rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.playground-preset-bar__badge {
|
|
171
|
+
font-size: 0.75rem;
|
|
172
|
+
padding: 0.25rem 0.5rem;
|
|
173
|
+
border-radius: 4px;
|
|
174
|
+
background: #2d3340;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.playground-preset-bar__badge--muted {
|
|
178
|
+
background: #252830;
|
|
179
|
+
color: #9aa0a6;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.playground-preset-bar__labels {
|
|
183
|
+
border: 1px solid #2a2f3a;
|
|
184
|
+
border-radius: 6px;
|
|
185
|
+
padding: 0.5rem 0.75rem;
|
|
186
|
+
margin: 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.playground-preset-bar__labels legend {
|
|
190
|
+
font-size: 0.75rem;
|
|
191
|
+
color: #9aa0a6;
|
|
192
|
+
padding: 0 0.25rem;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.playground-preset-bar__labels label {
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
gap: 0.2rem;
|
|
199
|
+
font-size: 0.75rem;
|
|
200
|
+
margin-top: 0.35rem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.playground-preset-bar__labels input {
|
|
204
|
+
font: inherit;
|
|
205
|
+
padding: 0.3rem 0.5rem;
|
|
206
|
+
border-radius: 4px;
|
|
207
|
+
border: 1px solid #3c4048;
|
|
208
|
+
background: #0f1117;
|
|
209
|
+
color: inherit;
|
|
210
|
+
}
|
package/main.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core dev shell — runs before any component module loads.
|
|
3
|
+
* Start: npm run dev → http://localhost:5173/?component=sf-leaderboard
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
COMPONENT_CATALOG,
|
|
7
|
+
getCatalogEntry,
|
|
8
|
+
navigateToComponent,
|
|
9
|
+
persistComponentId,
|
|
10
|
+
readInitialComponentId,
|
|
11
|
+
switchingRequiresReload,
|
|
12
|
+
} from './previews/catalog.js';
|
|
13
|
+
|
|
14
|
+
const app = document.getElementById('app');
|
|
15
|
+
if (!app) {
|
|
16
|
+
throw new Error('#app not found — index.html must define the mount point before main.ts');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let activeId = readInitialComponentId();
|
|
20
|
+
let teardown: (() => void) | undefined;
|
|
21
|
+
|
|
22
|
+
const shell = document.createElement('div');
|
|
23
|
+
shell.className = 'core-dev';
|
|
24
|
+
|
|
25
|
+
const sidebar = document.createElement('aside');
|
|
26
|
+
sidebar.className = 'core-dev__sidebar';
|
|
27
|
+
|
|
28
|
+
const brand = document.createElement('div');
|
|
29
|
+
brand.className = 'core-dev__brand';
|
|
30
|
+
brand.innerHTML = '<strong>Oddsmith</strong><span>Core dev</span>';
|
|
31
|
+
|
|
32
|
+
const nav = document.createElement('nav');
|
|
33
|
+
nav.className = 'core-dev__nav';
|
|
34
|
+
nav.setAttribute('aria-label', 'Components');
|
|
35
|
+
|
|
36
|
+
const main = document.createElement('main');
|
|
37
|
+
main.className = 'core-dev__main';
|
|
38
|
+
|
|
39
|
+
const header = document.createElement('header');
|
|
40
|
+
header.className = 'core-dev__header';
|
|
41
|
+
|
|
42
|
+
const titleEl = document.createElement('h1');
|
|
43
|
+
const descEl = document.createElement('p');
|
|
44
|
+
header.append(titleEl, descEl);
|
|
45
|
+
|
|
46
|
+
const stage = document.createElement('div');
|
|
47
|
+
stage.className = 'core-dev__stage';
|
|
48
|
+
main.append(header, stage);
|
|
49
|
+
|
|
50
|
+
shell.append(sidebar, main);
|
|
51
|
+
sidebar.append(brand, nav);
|
|
52
|
+
app.appendChild(shell);
|
|
53
|
+
|
|
54
|
+
function renderNav(): void {
|
|
55
|
+
nav.replaceChildren();
|
|
56
|
+
for (const entry of COMPONENT_CATALOG) {
|
|
57
|
+
if (entry.id === 'none') continue;
|
|
58
|
+
const link = document.createElement('a');
|
|
59
|
+
link.className = 'core-dev__nav-link';
|
|
60
|
+
link.href = `?component=${entry.id}`;
|
|
61
|
+
link.textContent = entry.title;
|
|
62
|
+
link.dataset.component = entry.id;
|
|
63
|
+
if (entry.id === activeId) link.classList.add('is-active');
|
|
64
|
+
nav.appendChild(link);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function updateHeader(): void {
|
|
69
|
+
const entry = getCatalogEntry(activeId);
|
|
70
|
+
titleEl.textContent = entry?.title ?? 'Preview';
|
|
71
|
+
descEl.textContent = entry?.description ?? '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function showComponent(id: string): Promise<void> {
|
|
75
|
+
teardown?.();
|
|
76
|
+
teardown = undefined;
|
|
77
|
+
stage.replaceChildren();
|
|
78
|
+
|
|
79
|
+
const entry = getCatalogEntry(id);
|
|
80
|
+
if (!entry || entry.id === 'none' || !entry.tag) {
|
|
81
|
+
updateHeader();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const mod = await entry.load();
|
|
87
|
+
teardown = await mod.mount(stage);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(err);
|
|
90
|
+
const msg = document.createElement('p');
|
|
91
|
+
msg.className = 'core-dev__error';
|
|
92
|
+
msg.textContent = `Failed to load "${id}": ${err instanceof Error ? err.message : String(err)}`;
|
|
93
|
+
stage.appendChild(msg);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
updateHeader();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
nav.addEventListener('click', (e) => {
|
|
100
|
+
const link = (e.target as HTMLElement).closest<HTMLAnchorElement>(
|
|
101
|
+
'a[data-component]',
|
|
102
|
+
);
|
|
103
|
+
if (!link) return;
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
const nextId = link.dataset.component;
|
|
106
|
+
if (!nextId || nextId === activeId) return;
|
|
107
|
+
|
|
108
|
+
if (switchingRequiresReload(activeId, nextId)) {
|
|
109
|
+
navigateToComponent(nextId);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
activeId = nextId;
|
|
114
|
+
persistComponentId(nextId);
|
|
115
|
+
const url = new URL(location.href);
|
|
116
|
+
url.searchParams.set('component', nextId);
|
|
117
|
+
history.replaceState(null, '', url);
|
|
118
|
+
renderNav();
|
|
119
|
+
void showComponent(activeId);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
renderNav();
|
|
123
|
+
updateHeader();
|
|
124
|
+
void showComponent(activeId);
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oddsmith/ui",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Brandable web components",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"web-components",
|
|
7
|
+
"lit-element",
|
|
8
|
+
"typescript"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git@github-skinforge:skinforge-io/core.git"
|
|
13
|
+
},
|
|
14
|
+
"license": "BSD-3-Clause",
|
|
15
|
+
"author": "Oddsmith",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
"./leaderboard": "./dist/leaderboard/main.js",
|
|
19
|
+
"./leaderboard-v2": "./dist/leaderboard-v2/index.js",
|
|
20
|
+
"./sf-leaderboard": "./dist/sf-leaderboard/index.js",
|
|
21
|
+
"./casino-leaderboard": "./dist/casino-leaderboard/component.js",
|
|
22
|
+
"./streaks": "./dist/streaks/index.js",
|
|
23
|
+
"./sf-live-feed": "./dist/sf-live-feed/index.js"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/sf-leaderboard/index.js",
|
|
26
|
+
"directories": {
|
|
27
|
+
"doc": "docs",
|
|
28
|
+
"test": "test"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "vite build",
|
|
32
|
+
"build:watch": "vite build --watch",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"clean": "rm -rf node_modules package-lock.json",
|
|
35
|
+
"lint": "npm run lint:lit-analyzer && npm run lint:eslint",
|
|
36
|
+
"lint:eslint": "eslint 'src/**/*.ts'",
|
|
37
|
+
"lint:lit-analyzer": "lit-analyzer",
|
|
38
|
+
"format": "prettier src/* --write",
|
|
39
|
+
"docs": "npm run docs:clean && npm run build && npm run analyze && npm run docs:bundle && npm run docs:gen && npm run docs:assets",
|
|
40
|
+
"docs:clean": "rimraf docs",
|
|
41
|
+
"docs:bundle": "vite build --config vite.docs.config.ts",
|
|
42
|
+
"docs:gen": "eleventy --config=.eleventy.cjs",
|
|
43
|
+
"docs:gen:watch": "eleventy --config=.eleventy.cjs --watch",
|
|
44
|
+
"docs:assets": "cp node_modules/prismjs/themes/prism-okaidia.css docs/",
|
|
45
|
+
"docs:serve": "npx --yes serve docs",
|
|
46
|
+
"analyze": "wca analyze \"src/**/*.ts\" --outFile custom-elements.json",
|
|
47
|
+
"dev": "vite",
|
|
48
|
+
"serve": "vite",
|
|
49
|
+
"test": "karma start karma.conf.cjs",
|
|
50
|
+
"test:watch": "karma start karma.conf.cjs --auto-watch=true --single-run=false",
|
|
51
|
+
"test:update-snapshots": "karma start karma.conf.cjs --update-snapshots",
|
|
52
|
+
"test:prune-snapshots": "karma start karma.conf.cjs --prune-snapshots"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"immer": "^11.1.8",
|
|
56
|
+
"lit": "^3.3.3",
|
|
57
|
+
"lit-element": "^2.3.1",
|
|
58
|
+
"rxjs": "~7.8.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@11ty/eleventy": "^0.10.0",
|
|
62
|
+
"@11ty/eleventy-plugin-syntaxhighlight": "^3.0.1",
|
|
63
|
+
"@open-wc/testing": "^2.5.10",
|
|
64
|
+
"@open-wc/testing-karma": "^3.3.11",
|
|
65
|
+
"@types/chai": "^4.2.11",
|
|
66
|
+
"@types/mocha": "^7.0.2",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^2.27.0",
|
|
68
|
+
"@typescript-eslint/parser": "^2.27.0",
|
|
69
|
+
"chai": "^4.2.0",
|
|
70
|
+
"deepmerge": "^4.2.2",
|
|
71
|
+
"eslint": "^6.8.0",
|
|
72
|
+
"karma": "^4.4.1",
|
|
73
|
+
"karma-chai": "^0.1.0",
|
|
74
|
+
"karma-mocha": "^1.3.0",
|
|
75
|
+
"lit-analyzer": "^1.1.10",
|
|
76
|
+
"mocha": "^7.1.1",
|
|
77
|
+
"prettier": "^2.0.4",
|
|
78
|
+
"prismjs": "^1.30.0",
|
|
79
|
+
"rimraf": "^3.0.2",
|
|
80
|
+
"sass": "^1.97.3",
|
|
81
|
+
"typescript": "^6.0.3",
|
|
82
|
+
"vite": "^7.3.1",
|
|
83
|
+
"web-component-analyzer": "^1.0.3"
|
|
84
|
+
},
|
|
85
|
+
"module": "./dist/sf-leaderboard/index.js"
|
|
86
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentPreviewModule } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const mount: ComponentPreviewModule['mount'] = async (root) => {
|
|
4
|
+
await import('../src/components/casino-leaderboard/index.ts');
|
|
5
|
+
|
|
6
|
+
const el = document.createElement('casino-leaderboard');
|
|
7
|
+
root.appendChild(el);
|
|
8
|
+
|
|
9
|
+
return () => {
|
|
10
|
+
root.replaceChildren();
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { ComponentCatalogEntry } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const COMPONENT_CATALOG: readonly ComponentCatalogEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
id: 'none',
|
|
6
|
+
title: '— None —',
|
|
7
|
+
description: 'Empty preview stage.',
|
|
8
|
+
tag: null,
|
|
9
|
+
load: async () => ({
|
|
10
|
+
mount: async () => () => {},
|
|
11
|
+
}),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'sf-leaderboard',
|
|
15
|
+
title: 'SF Leaderboard',
|
|
16
|
+
description:
|
|
17
|
+
'Config-driven leaderboard with presets, theme, and interactive demo data.',
|
|
18
|
+
tag: 'sf-leaderboard',
|
|
19
|
+
sharedTag: 'sf-leaderboard',
|
|
20
|
+
load: () => import('./sf-leaderboard.js'),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'sf-live-feed',
|
|
24
|
+
title: 'Live Feed',
|
|
25
|
+
description: 'Activity stream with avatars and event types.',
|
|
26
|
+
tag: 'sf-live-feed',
|
|
27
|
+
load: () => import('./sf-live-feed.js'),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'streaks',
|
|
31
|
+
title: 'Streaks',
|
|
32
|
+
description: 'Step tracker with click-to-complete interaction.',
|
|
33
|
+
tag: 'sf-streaks',
|
|
34
|
+
load: () => import('./streaks.js'),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'leaderboard-v2',
|
|
38
|
+
title: 'Leaderboard v2 (legacy)',
|
|
39
|
+
description: 'Earlier leaderboard — same tag as SF Leaderboard; switch via reload.',
|
|
40
|
+
tag: 'sf-leaderboard',
|
|
41
|
+
sharedTag: 'sf-leaderboard',
|
|
42
|
+
load: () => import('./leaderboard-v2.js'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'leaderboard',
|
|
46
|
+
title: 'Leaderboard (v1)',
|
|
47
|
+
description: 'Original game leaderboard shell.',
|
|
48
|
+
tag: 'leaderboard-app',
|
|
49
|
+
load: () => import('./leaderboard-v1.js'),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'casino',
|
|
53
|
+
title: 'Casino Leaderboard',
|
|
54
|
+
description: 'Casino-style leaderboard with tabs and prize pool.',
|
|
55
|
+
tag: 'casino-leaderboard',
|
|
56
|
+
load: () => import('./casino.js'),
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const STORAGE_KEY = 'oddsmith-core-dev-component';
|
|
61
|
+
|
|
62
|
+
export function getCatalogEntry(id: string): ComponentCatalogEntry | undefined {
|
|
63
|
+
return COMPONENT_CATALOG.find((e) => e.id === id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function readInitialComponentId(): string {
|
|
67
|
+
const fromQuery = new URLSearchParams(location.search).get('component');
|
|
68
|
+
if (fromQuery && getCatalogEntry(fromQuery)) return fromQuery;
|
|
69
|
+
const fromStorage = localStorage.getItem(STORAGE_KEY);
|
|
70
|
+
if (fromStorage && getCatalogEntry(fromStorage)) return fromStorage;
|
|
71
|
+
return 'sf-leaderboard';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function persistComponentId(id: string): void {
|
|
75
|
+
localStorage.setItem(STORAGE_KEY, id);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function navigateToComponent(id: string): void {
|
|
79
|
+
persistComponentId(id);
|
|
80
|
+
const url = new URL(location.href);
|
|
81
|
+
if (id === 'none') url.searchParams.delete('component');
|
|
82
|
+
else url.searchParams.set('component', id);
|
|
83
|
+
location.assign(url);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Tags that cannot coexist without reload (duplicate custom element names). */
|
|
87
|
+
export function switchingRequiresReload(fromId: string, toId: string): boolean {
|
|
88
|
+
if (fromId === toId) return false;
|
|
89
|
+
const from = getCatalogEntry(fromId);
|
|
90
|
+
const to = getCatalogEntry(toId);
|
|
91
|
+
const fromTag = from?.sharedTag ?? from?.tag;
|
|
92
|
+
const toTag = to?.sharedTag ?? to?.tag;
|
|
93
|
+
return Boolean(fromTag && toTag && fromTag === toTag);
|
|
94
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentPreviewModule } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const mount: ComponentPreviewModule['mount'] = async (root) => {
|
|
4
|
+
await import('../src/components/leaderboard/index.ts');
|
|
5
|
+
|
|
6
|
+
const el = document.createElement('leaderboard-app');
|
|
7
|
+
root.appendChild(el);
|
|
8
|
+
|
|
9
|
+
return () => {
|
|
10
|
+
root.replaceChildren();
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SfLeaderboard } from '../src/components/leaderboard-v2/components/sf-leaderboard.js';
|
|
2
|
+
import { lbV2CurrentUser, lbV2Players, lbV2Stats } from './sample-data.js';
|
|
3
|
+
import type { ComponentPreviewModule } from './types.js';
|
|
4
|
+
|
|
5
|
+
export const mount: ComponentPreviewModule['mount'] = async (root) => {
|
|
6
|
+
await import('../src/components/leaderboard-v2/index.ts');
|
|
7
|
+
|
|
8
|
+
const el = document.createElement('sf-leaderboard') as SfLeaderboard;
|
|
9
|
+
el.players = lbV2Players;
|
|
10
|
+
el.stats = lbV2Stats;
|
|
11
|
+
el.currentUser = lbV2CurrentUser;
|
|
12
|
+
root.appendChild(el);
|
|
13
|
+
|
|
14
|
+
return () => {
|
|
15
|
+
root.replaceChildren();
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { LiveFeedActivity } from '../src/components/sf-live-feed/types.js';
|
|
2
|
+
import type { StreaksStep } from '../src/components/streaks/types.js';
|
|
3
|
+
import type {
|
|
4
|
+
LbBadge,
|
|
5
|
+
LbPlayer,
|
|
6
|
+
LbStats,
|
|
7
|
+
} from '../src/components/leaderboard-v2/types.js';
|
|
8
|
+
|
|
9
|
+
export const mockLiveFeedActivities: LiveFeedActivity[] = [
|
|
10
|
+
{
|
|
11
|
+
id: '1',
|
|
12
|
+
type: 'jackpot',
|
|
13
|
+
player: { username: 'CryptoKing', avatar: '' },
|
|
14
|
+
message: 'Hit the MEGA JACKPOT on Lightning Slots!',
|
|
15
|
+
amount: 125000,
|
|
16
|
+
timestamp: new Date(Date.now() - 30000),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: '2',
|
|
20
|
+
type: 'win',
|
|
21
|
+
player: { username: 'AceHunter', avatar: '' },
|
|
22
|
+
message: "Won a massive pot in Texas Hold'em",
|
|
23
|
+
amount: 45000,
|
|
24
|
+
timestamp: new Date(Date.now() - 120000),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: '3',
|
|
28
|
+
type: 'streak',
|
|
29
|
+
player: { username: 'LuckyDragon', avatar: '' },
|
|
30
|
+
message: 'Reached a 8-game winning streak!',
|
|
31
|
+
timestamp: new Date(Date.now() - 300000),
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const demoStreaksSteps: StreaksStep[] = [
|
|
36
|
+
{ id: '1', label: 'G1', completed: true },
|
|
37
|
+
{ id: '2', label: 'G2', completed: true },
|
|
38
|
+
{ id: '3', label: 'G3', completed: true },
|
|
39
|
+
{ id: '4', label: 'G4', completed: false },
|
|
40
|
+
{ id: '5', label: 'G5', completed: false, isReward: true },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export const lbV2Badges: LbBadge[] = [
|
|
44
|
+
{ id: '1', name: 'First Win', icon: 'trophy', rarity: 'common' },
|
|
45
|
+
{ id: '2', name: 'Streak Master', icon: 'flame', rarity: 'rare' },
|
|
46
|
+
{ id: '3', name: 'Top 10', icon: 'star', rarity: 'epic' },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export const lbV2Players: LbPlayer[] = [
|
|
50
|
+
{
|
|
51
|
+
id: '1',
|
|
52
|
+
rank: 1,
|
|
53
|
+
username: 'ShadowNinja',
|
|
54
|
+
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=shadow',
|
|
55
|
+
level: 99,
|
|
56
|
+
xp: 45000,
|
|
57
|
+
xpToNextLevel: 50000,
|
|
58
|
+
score: 2847500,
|
|
59
|
+
wins: 342,
|
|
60
|
+
streak: 15,
|
|
61
|
+
badges: [lbV2Badges[2], lbV2Badges[1]],
|
|
62
|
+
trend: 'same',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: '2',
|
|
66
|
+
rank: 2,
|
|
67
|
+
username: 'CyberPhoenix',
|
|
68
|
+
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=phoenix',
|
|
69
|
+
level: 95,
|
|
70
|
+
xp: 38000,
|
|
71
|
+
xpToNextLevel: 45000,
|
|
72
|
+
score: 2654200,
|
|
73
|
+
wins: 298,
|
|
74
|
+
streak: 8,
|
|
75
|
+
badges: [lbV2Badges[1], lbV2Badges[0]],
|
|
76
|
+
trend: 'up',
|
|
77
|
+
rankChange: 2,
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
export const lbV2Stats: LbStats = {
|
|
82
|
+
totalPlayers: 12847,
|
|
83
|
+
yourRank: 156,
|
|
84
|
+
topScore: 2847500,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const lbV2CurrentUser: LbPlayer = {
|
|
88
|
+
id: 'current',
|
|
89
|
+
rank: 156,
|
|
90
|
+
username: 'YourLbPlayer',
|
|
91
|
+
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=current',
|
|
92
|
+
level: 45,
|
|
93
|
+
xp: 12500,
|
|
94
|
+
xpToNextLevel: 20000,
|
|
95
|
+
score: 456780,
|
|
96
|
+
wins: 42,
|
|
97
|
+
streak: 3,
|
|
98
|
+
badges: [lbV2Badges[0], lbV2Badges[1]],
|
|
99
|
+
trend: 'up',
|
|
100
|
+
rankChange: 12,
|
|
101
|
+
};
|