@adarsh.nishad/activityual-ui 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,106 @@
1
- # Temporary Holding Version
1
+ # Activityual UI Component Library (`@adarsh.nishad/activityual-ui`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A reusable, accessible web component library built with **Lit** for the **Activityual** cloud-native personal habit tracker.
4
+
5
+ ---
6
+
7
+ ## Components
8
+
9
+ | Component | Custom Element Tag | Description |
10
+ | :--- | :--- | :--- |
11
+ | **Button** | `<activityual-button>` | Interactive button (`primary`, `secondary`, `amber`, `charcoal`, `ghost`, `danger`) with loading states |
12
+ | **Card** | `<activityual-card>` | Obsidian glass container card with subtle borders and glow |
13
+ | **Activity Card** | `<activityual-activity-card>` | Core habit/activity card with streak badges, actions, and progress |
14
+ | **Stat Card** | `<activityual-stat-card>` | Metrics and KPI display card with sparkline and delta indicators |
15
+ | **Progress Ring** | `<activityual-progress-ring>` | Circular SVG progress ring with animated stroke and target values |
16
+ | **Streak Badge** | `<activityual-streak-badge>` | Visual flame streak badge displaying active day streaks |
17
+ | **Status Pill** | `<activityual-status-pill>` | Activity status indicators (`active`, `paused`, `archived`, `completed`) |
18
+ | **Chart Panel** | `<activityual-chart-panel>` | Analytics and visualization container panel with filters |
19
+ | **Recommendation Banner**| `<activityual-recommendation-banner>` | AI-driven habit suggestion and nudge banner |
20
+ | **Empty State** | `<activityual-empty-state>` | Placeholder presentation for empty lists and initial states |
21
+ | **Confirm Dialog** | `<activityual-confirm-dialog>` | Modal confirmation dialog with backdrop blur |
22
+ | **Form Field** | `<activityual-form-field>` | Styled input / select / textarea wrapper with validation |
23
+
24
+ ---
25
+
26
+ ## Installation & Consumption
27
+
28
+ ### In SvelteKit / Web Applications
29
+
30
+ Install the package:
31
+
32
+ ```bash
33
+ npm install @adarsh.nishad/activityual-ui
34
+ ```
35
+
36
+ Register all custom elements in your root layout (`src/routes/+layout.svelte`):
37
+
38
+ ```typescript
39
+ import '@adarsh.nishad/activityual-ui';
40
+ ```
41
+
42
+ Or import design tokens directly:
43
+
44
+ ```typescript
45
+ import { tokens } from '@adarsh.nishad/activityual-ui';
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Building & Publishing to npm
51
+
52
+ Follow these steps whenever you make changes to `packages/activityual-ui` and want to publish a new release:
53
+
54
+ ### Step 1: Verify npm Authentication
55
+
56
+ ```bash
57
+ npm whoami
58
+ ```
59
+
60
+ If not logged in, run:
61
+
62
+ ```bash
63
+ npm login
64
+ ```
65
+
66
+ ### Step 2: Build the Library
67
+
68
+ Compile all custom elements and TypeScript type definitions into `dist/`:
69
+
70
+ ```bash
71
+ # From repository root:
72
+ npm run build:ui
73
+
74
+ # Or directly in this directory:
75
+ npm run build
76
+ ```
77
+
78
+ ### Step 3: Bump Version
79
+
80
+ Bump the semantic version (`patch`, `minor`, or `major`):
81
+
82
+ ```bash
83
+ # From repository root:
84
+ npm run version:ui:patch # e.g. 0.1.0 -> 0.1.1
85
+ # or
86
+ npm run version:ui:minor # e.g. 0.1.0 -> 0.2.0
87
+ # or
88
+ npm run version:ui:major # e.g. 0.1.0 -> 1.0.0
89
+
90
+ # Or navigate directly:
91
+ cd packages/activityual-ui
92
+ npm version patch # or minor / major
93
+ ```
94
+
95
+ ### Step 4: Publish to npm
96
+
97
+ Since it is a scoped package (`@adarsh.nishad/...`), publish with public access:
98
+
99
+ ```bash
100
+ # From repository root:
101
+ npm run publish:ui
102
+
103
+ # Or navigate directly:
104
+ cd packages/activityual-ui
105
+ npm publish --access public
106
+ ```
@@ -0,0 +1,43 @@
1
+ import { LitElement } from 'lit';
2
+ import './status-pill.js';
3
+ import './button.js';
4
+ export declare class ActivityualActivityCard extends LitElement {
5
+ static properties: {
6
+ activityId: {
7
+ type: StringConstructor;
8
+ };
9
+ title: {
10
+ type: StringConstructor;
11
+ };
12
+ category: {
13
+ type: StringConstructor;
14
+ };
15
+ frequency: {
16
+ type: StringConstructor;
17
+ };
18
+ status: {
19
+ type: StringConstructor;
20
+ };
21
+ streak: {
22
+ type: NumberConstructor;
23
+ };
24
+ notes: {
25
+ type: StringConstructor;
26
+ };
27
+ readOnly: {
28
+ type: BooleanConstructor;
29
+ };
30
+ };
31
+ activityId: string;
32
+ title: string;
33
+ category: string;
34
+ frequency: string;
35
+ status: 'done' | 'missed' | 'pending';
36
+ streak: number;
37
+ notes: string;
38
+ readOnly: boolean;
39
+ static styles: import("lit").CSSResult;
40
+ getCategoryIcon(cat: string): string;
41
+ handleAction(status: 'done' | 'missed'): void;
42
+ render(): import("lit-html").TemplateResult<1>;
43
+ }
@@ -0,0 +1,302 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import './status-pill.js';
3
+ import './button.js';
4
+ export class ActivityualActivityCard extends LitElement {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.activityId = '';
8
+ this.title = '';
9
+ this.category = 'General';
10
+ this.frequency = 'Daily';
11
+ this.status = 'pending';
12
+ this.streak = 0;
13
+ this.notes = '';
14
+ this.readOnly = false;
15
+ }
16
+ static { this.properties = {
17
+ activityId: { type: String },
18
+ title: { type: String },
19
+ category: { type: String },
20
+ frequency: { type: String },
21
+ status: { type: String },
22
+ streak: { type: Number },
23
+ notes: { type: String },
24
+ readOnly: { type: Boolean }
25
+ }; }
26
+ static { this.styles = css `
27
+ :host {
28
+ display: block;
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ .card {
33
+ background: linear-gradient(145deg, rgba(42, 42, 42, 0.65) 0%, rgba(24, 24, 24, 0.8) 100%);
34
+ backdrop-filter: blur(20px);
35
+ -webkit-backdrop-filter: blur(20px);
36
+ border: 1px solid rgba(255, 255, 255, 0.05);
37
+ border-radius: 14px;
38
+ padding: 1rem 1.25rem;
39
+ box-shadow:
40
+ 0 8px 24px -4px rgba(0, 0, 0, 0.45),
41
+ inset 0 1px 0 rgba(255, 255, 255, 0.08);
42
+ font-family: Inter, 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: space-between;
46
+ gap: 1rem;
47
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
48
+ box-sizing: border-box;
49
+ }
50
+
51
+ .card:hover {
52
+ background: linear-gradient(145deg, rgba(48, 48, 48, 0.75) 0%, rgba(28, 28, 28, 0.88) 100%);
53
+ transform: translateY(-1px);
54
+ border-color: rgba(255, 255, 255, 0.1);
55
+ box-shadow:
56
+ 0 14px 32px -6px rgba(0, 0, 0, 0.55),
57
+ inset 0 1px 0 rgba(255, 255, 255, 0.12);
58
+ }
59
+
60
+ .left-cluster {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 0.85rem;
64
+ min-width: 0;
65
+ flex: 1;
66
+ }
67
+
68
+ .category-icon {
69
+ width: 38px;
70
+ height: 38px;
71
+ border-radius: 10px;
72
+ background: rgba(255, 67, 5, 0.1);
73
+ border: 1px solid rgba(255, 67, 5, 0.2);
74
+ color: #ff4305;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ font-size: 16px;
79
+ flex-shrink: 0;
80
+ box-shadow: 0 0 10px rgba(255, 67, 5, 0.15);
81
+ }
82
+
83
+ .info {
84
+ display: flex;
85
+ flex-direction: column;
86
+ gap: 0.2rem;
87
+ min-width: 0;
88
+ }
89
+
90
+ .title-row {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 0.5rem;
94
+ flex-wrap: wrap;
95
+ }
96
+
97
+ .title {
98
+ font-size: 13.5px;
99
+ font-weight: 600;
100
+ color: #f5f5f5;
101
+ margin: 0;
102
+ white-space: nowrap;
103
+ overflow: hidden;
104
+ text-overflow: ellipsis;
105
+ letter-spacing: -0.01em;
106
+ }
107
+
108
+ .meta {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 0.55rem;
112
+ font-size: 11px;
113
+ color: #a5a5a5;
114
+ font-family: 'JetBrains Mono', monospace;
115
+ }
116
+
117
+ .meta-tag {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: 0.25rem;
121
+ }
122
+
123
+ .streak-tag {
124
+ color: #ff4305;
125
+ font-weight: 600;
126
+ }
127
+
128
+ .notes {
129
+ font-size: 11.5px;
130
+ color: #707070;
131
+ margin: 0.2rem 0 0 0;
132
+ display: -webkit-box;
133
+ -webkit-line-clamp: 1;
134
+ -webkit-box-orient: vertical;
135
+ overflow: hidden;
136
+ }
137
+
138
+ .dot-trail {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 4px;
142
+ margin-top: 4px;
143
+ }
144
+
145
+ .dot {
146
+ width: 6px;
147
+ height: 6px;
148
+ border-radius: 50%;
149
+ background: rgba(255, 255, 255, 0.12);
150
+ }
151
+
152
+ .dot.done {
153
+ background: #25d366;
154
+ box-shadow: 0 0 6px rgba(37, 211, 102, 0.4);
155
+ }
156
+
157
+ .dot.missed {
158
+ background: #ff3434;
159
+ box-shadow: 0 0 6px rgba(255, 52, 52, 0.4);
160
+ }
161
+
162
+ .right-cluster {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 0.65rem;
166
+ flex-shrink: 0;
167
+ }
168
+
169
+ .action-btn {
170
+ width: 32px;
171
+ height: 32px;
172
+ border-radius: 9999px;
173
+ border: 1px solid rgba(255, 255, 255, 0.08);
174
+ background: rgba(255, 255, 255, 0.04);
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ cursor: pointer;
179
+ font-size: 13px;
180
+ font-weight: 700;
181
+ color: #a5a5a5;
182
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
183
+ outline: none;
184
+ }
185
+
186
+ .action-btn:focus-visible {
187
+ box-shadow: 0 0 0 2px rgba(255, 67, 5, 0.4);
188
+ }
189
+
190
+ .action-done:hover {
191
+ background: rgba(37, 211, 102, 0.2);
192
+ border-color: #25d366;
193
+ color: #25d366;
194
+ box-shadow: 0 0 10px rgba(37, 211, 102, 0.3);
195
+ transform: scale(1.05);
196
+ }
197
+
198
+ .action-done.active {
199
+ background: #25d366;
200
+ border-color: #25d366;
201
+ color: #ffffff;
202
+ box-shadow: 0 0 12px rgba(37, 211, 102, 0.4);
203
+ }
204
+
205
+ .action-missed:hover {
206
+ background: rgba(255, 52, 52, 0.2);
207
+ border-color: #ff3434;
208
+ color: #ff3434;
209
+ box-shadow: 0 0 10px rgba(255, 52, 52, 0.3);
210
+ transform: scale(1.05);
211
+ }
212
+
213
+ .action-missed.active {
214
+ background: #ff3434;
215
+ border-color: #ff3434;
216
+ color: #ffffff;
217
+ box-shadow: 0 0 12px rgba(255, 52, 52, 0.4);
218
+ }
219
+ `; }
220
+ getCategoryIcon(cat) {
221
+ const lower = cat.toLowerCase();
222
+ if (lower.includes('health') || lower.includes('hydration') || lower.includes('water'))
223
+ return '💧';
224
+ if (lower.includes('fit') || lower.includes('workout') || lower.includes('sport'))
225
+ return '⚡';
226
+ if (lower.includes('mind') || lower.includes('read') || lower.includes('meditat'))
227
+ return '📖';
228
+ if (lower.includes('code') || lower.includes('deep') || lower.includes('work'))
229
+ return '🎯';
230
+ return '✨';
231
+ }
232
+ handleAction(status) {
233
+ this.dispatchEvent(new CustomEvent('status-change', {
234
+ detail: { activityId: this.activityId, status },
235
+ bubbles: true,
236
+ composed: true
237
+ }));
238
+ }
239
+ render() {
240
+ return html `
241
+ <div class="card">
242
+ <div class="left-cluster">
243
+ <div class="category-icon" aria-hidden="true">
244
+ ${this.getCategoryIcon(this.category)}
245
+ </div>
246
+ <div class="info">
247
+ <div class="title-row">
248
+ <h3 class="title">${this.title}</h3>
249
+ </div>
250
+ <div class="meta">
251
+ <span class="meta-tag">${this.category}</span>
252
+ <span>•</span>
253
+ <span class="meta-tag">${this.frequency}</span>
254
+ ${this.streak > 0
255
+ ? html `
256
+ <span>•</span>
257
+ <span class="meta-tag streak-tag">🔥 ${this.streak}d streak</span>
258
+ `
259
+ : ''}
260
+ </div>
261
+ ${this.notes ? html `<p class="notes">${this.notes}</p>` : ''}
262
+ <div class="dot-trail" aria-label="Recent consistency">
263
+ <span class="dot done"></span>
264
+ <span class="dot done"></span>
265
+ <span class="dot done"></span>
266
+ <span class="dot done"></span>
267
+ <span class="dot ${this.status === 'done' ? 'done' : this.status === 'missed' ? 'missed' : ''}"></span>
268
+ </div>
269
+ </div>
270
+ </div>
271
+
272
+ <div class="right-cluster">
273
+ <activityual-status-pill .status="${this.status}" size="sm"></activityual-status-pill>
274
+
275
+ ${!this.readOnly
276
+ ? html `
277
+ <button
278
+ class="action-btn action-done ${this.status === 'done' ? 'active' : ''}"
279
+ title="Mark Done"
280
+ aria-label="Mark ${this.title} Done"
281
+ @click="${() => this.handleAction('done')}"
282
+ >
283
+ ✓
284
+ </button>
285
+ <button
286
+ class="action-btn action-missed ${this.status === 'missed' ? 'active' : ''}"
287
+ title="Mark Missed"
288
+ aria-label="Mark ${this.title} Missed"
289
+ @click="${() => this.handleAction('missed')}"
290
+ >
291
+ ✕
292
+ </button>
293
+ `
294
+ : ''}
295
+ </div>
296
+ </div>
297
+ `;
298
+ }
299
+ }
300
+ if (!customElements.get('activityual-activity-card')) {
301
+ customElements.define('activityual-activity-card', ActivityualActivityCard);
302
+ }
@@ -0,0 +1,32 @@
1
+ import { LitElement } from 'lit';
2
+ export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'charcoal' | 'amber';
3
+ export type ButtonSize = 'sm' | 'md' | 'lg';
4
+ export declare class ActivityualButton extends LitElement {
5
+ static properties: {
6
+ variant: {
7
+ type: StringConstructor;
8
+ };
9
+ size: {
10
+ type: StringConstructor;
11
+ };
12
+ disabled: {
13
+ type: BooleanConstructor;
14
+ reflect: boolean;
15
+ };
16
+ loading: {
17
+ type: BooleanConstructor;
18
+ reflect: boolean;
19
+ };
20
+ type: {
21
+ type: StringConstructor;
22
+ };
23
+ };
24
+ variant: ButtonVariant;
25
+ size: ButtonSize;
26
+ disabled: boolean;
27
+ loading: boolean;
28
+ type: string;
29
+ static styles: import("lit").CSSResult;
30
+ handleClick(e: MouseEvent): void;
31
+ render(): import("lit-html").TemplateResult<1>;
32
+ }
package/dist/button.js ADDED
@@ -0,0 +1,184 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ export class ActivityualButton extends LitElement {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.variant = 'primary';
6
+ this.size = 'md';
7
+ this.disabled = false;
8
+ this.loading = false;
9
+ this.type = 'button';
10
+ }
11
+ static { this.properties = {
12
+ variant: { type: String },
13
+ size: { type: String },
14
+ disabled: { type: Boolean, reflect: true },
15
+ loading: { type: Boolean, reflect: true },
16
+ type: { type: String }
17
+ }; }
18
+ static { this.styles = css `
19
+ :host {
20
+ display: inline-block;
21
+ vertical-align: middle;
22
+ }
23
+
24
+ button {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ gap: 0.5rem;
29
+ font-family: Inter, 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
30
+ font-weight: 600;
31
+ letter-spacing: -0.01em;
32
+ border-radius: 9999px;
33
+ cursor: pointer;
34
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
35
+ box-sizing: border-box;
36
+ width: 100%;
37
+ text-decoration: none;
38
+ outline: none;
39
+ user-select: none;
40
+ }
41
+
42
+ button:focus-visible {
43
+ box-shadow: 0 0 0 2px rgba(255, 67, 5, 0.4);
44
+ }
45
+
46
+ /* Sizes */
47
+ button.sm {
48
+ height: 30px;
49
+ padding: 0 12px;
50
+ font-size: 11px;
51
+ }
52
+
53
+ button.md {
54
+ height: 36px;
55
+ padding: 0 16px;
56
+ font-size: 12px;
57
+ }
58
+
59
+ button.lg {
60
+ height: 42px;
61
+ padding: 0 22px;
62
+ font-size: 14px;
63
+ }
64
+
65
+ /* Primary: Vaulto solid vibrant orange */
66
+ button.primary,
67
+ button.amber {
68
+ background: #ff4305;
69
+ color: #ffffff;
70
+ border: 1px solid rgba(255, 255, 255, 0.15);
71
+ box-shadow:
72
+ 0 4px 14px rgba(255, 67, 5, 0.35),
73
+ inset 0 1px 1px rgba(255, 255, 255, 0.2);
74
+ }
75
+ button.primary:hover:not(:disabled),
76
+ button.amber:hover:not(:disabled) {
77
+ background: #e93b00;
78
+ transform: translateY(-1px);
79
+ box-shadow:
80
+ 0 6px 18px rgba(255, 67, 5, 0.45),
81
+ inset 0 1px 1px rgba(255, 255, 255, 0.3);
82
+ }
83
+ button.primary:active:not(:disabled),
84
+ button.amber:active:not(:disabled) {
85
+ transform: translateY(1px);
86
+ }
87
+
88
+ /* Secondary: Vaulto dark translucent obsidian */
89
+ button.secondary,
90
+ button.charcoal {
91
+ background: rgba(255, 255, 255, 0.05);
92
+ color: #f5f5f5;
93
+ border: 1px solid rgba(255, 255, 255, 0.08);
94
+ backdrop-filter: blur(12px);
95
+ -webkit-backdrop-filter: blur(12px);
96
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
97
+ }
98
+ button.secondary:hover:not(:disabled),
99
+ button.charcoal:hover:not(:disabled) {
100
+ background: rgba(255, 255, 255, 0.09);
101
+ border-color: rgba(255, 255, 255, 0.14);
102
+ transform: translateY(-1px);
103
+ }
104
+ button.secondary:active:not(:disabled),
105
+ button.charcoal:active:not(:disabled) {
106
+ transform: translateY(1px);
107
+ }
108
+
109
+ /* Ghost */
110
+ button.ghost {
111
+ background: transparent;
112
+ color: #a5a5a5;
113
+ border: 1px solid transparent;
114
+ }
115
+ button.ghost:hover:not(:disabled) {
116
+ background: rgba(255, 255, 255, 0.05);
117
+ color: #ffffff;
118
+ }
119
+
120
+ /* Danger */
121
+ button.danger {
122
+ background: #ff3434;
123
+ color: #ffffff;
124
+ border: 1px solid rgba(255, 255, 255, 0.15);
125
+ box-shadow: 0 4px 14px rgba(255, 52, 52, 0.35);
126
+ }
127
+ button.danger:hover:not(:disabled) {
128
+ background: #e02020;
129
+ transform: translateY(-1px);
130
+ box-shadow: 0 6px 18px rgba(255, 52, 52, 0.45);
131
+ }
132
+
133
+ button:disabled {
134
+ opacity: 0.35;
135
+ cursor: not-allowed;
136
+ transform: none !important;
137
+ box-shadow: none !important;
138
+ }
139
+
140
+ .spinner {
141
+ width: 14px;
142
+ height: 14px;
143
+ border: 2px solid rgba(255, 255, 255, 0.3);
144
+ border-radius: 50%;
145
+ border-top-color: currentColor;
146
+ animation: spin 0.8s linear infinite;
147
+ }
148
+
149
+ @keyframes spin {
150
+ to {
151
+ transform: rotate(360deg);
152
+ }
153
+ }
154
+ `; }
155
+ handleClick(e) {
156
+ if (this.disabled || this.loading) {
157
+ e.preventDefault();
158
+ e.stopPropagation();
159
+ return;
160
+ }
161
+ if (this.type === 'submit') {
162
+ const form = this.closest('form');
163
+ if (form) {
164
+ form.requestSubmit();
165
+ }
166
+ }
167
+ }
168
+ render() {
169
+ return html `
170
+ <button
171
+ class="${this.variant} ${this.size}"
172
+ ?disabled="${this.disabled || this.loading}"
173
+ type="${this.type}"
174
+ @click="${this.handleClick}"
175
+ >
176
+ ${this.loading ? html `<span class="spinner" aria-hidden="true"></span>` : ''}
177
+ <slot></slot>
178
+ </button>
179
+ `;
180
+ }
181
+ }
182
+ if (!customElements.get('activityual-button')) {
183
+ customElements.define('activityual-button', ActivityualButton);
184
+ }
package/dist/card.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { LitElement } from 'lit';
2
+ export type CardVariant = 'default' | 'amber' | 'inset' | 'shell';
3
+ export declare class ActivityualCard extends LitElement {
4
+ static properties: {
5
+ variant: {
6
+ type: StringConstructor;
7
+ };
8
+ padding: {
9
+ type: StringConstructor;
10
+ };
11
+ };
12
+ variant: CardVariant;
13
+ padding: 'none' | 'sm' | 'md' | 'lg';
14
+ static styles: import("lit").CSSResult;
15
+ render(): import("lit-html").TemplateResult<1>;
16
+ }