@retalia/pos-components 0.0.4 → 0.0.5

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.
Files changed (45) hide show
  1. package/fesm2022/retalia-pos-components.mjs +1089 -0
  2. package/fesm2022/retalia-pos-components.mjs.map +1 -0
  3. package/package.json +39 -17
  4. package/types/retalia-pos-components.d.ts +300 -0
  5. package/eslint.config.js +0 -32
  6. package/ng-package.json +0 -14
  7. package/src/lib/basket/pos-basket.html +0 -124
  8. package/src/lib/basket/pos-basket.scss +0 -285
  9. package/src/lib/basket/pos-basket.spec.ts +0 -198
  10. package/src/lib/basket/pos-basket.stories.ts +0 -114
  11. package/src/lib/basket/pos-basket.ts +0 -142
  12. package/src/lib/item-entry/pos-item-entry.html +0 -41
  13. package/src/lib/item-entry/pos-item-entry.scss +0 -145
  14. package/src/lib/item-entry/pos-item-entry.spec.ts +0 -123
  15. package/src/lib/item-entry/pos-item-entry.stories.ts +0 -31
  16. package/src/lib/item-entry/pos-item-entry.ts +0 -81
  17. package/src/lib/login/pos-login.html +0 -82
  18. package/src/lib/login/pos-login.scss +0 -238
  19. package/src/lib/login/pos-login.spec.ts +0 -89
  20. package/src/lib/login/pos-login.stories.ts +0 -36
  21. package/src/lib/login/pos-login.ts +0 -150
  22. package/src/lib/receipt/pos-receipt.html +0 -116
  23. package/src/lib/receipt/pos-receipt.scss +0 -367
  24. package/src/lib/receipt/pos-receipt.spec.ts +0 -188
  25. package/src/lib/receipt/pos-receipt.stories.ts +0 -163
  26. package/src/lib/receipt/pos-receipt.ts +0 -129
  27. package/src/lib/tender/pos-tender.html +0 -79
  28. package/src/lib/tender/pos-tender.scss +0 -318
  29. package/src/lib/tender/pos-tender.spec.ts +0 -150
  30. package/src/lib/tender/pos-tender.stories.ts +0 -47
  31. package/src/lib/tender/pos-tender.ts +0 -154
  32. package/src/lib/tokens/pos-theme-css.spec.ts +0 -75
  33. package/src/lib/tokens/pos-theme-css.ts +0 -123
  34. package/src/lib/tokens/pos-token-catalog.ts +0 -514
  35. package/src/lib/tokens/pos-tokens.html +0 -351
  36. package/src/lib/tokens/pos-tokens.scss +0 -437
  37. package/src/lib/tokens/pos-tokens.spec.ts +0 -164
  38. package/src/lib/tokens/pos-tokens.stories.ts +0 -22
  39. package/src/lib/tokens/pos-tokens.ts +0 -208
  40. package/src/public-api.ts +0 -37
  41. package/tsconfig.lib.json +0 -14
  42. package/tsconfig.lib.prod.json +0 -11
  43. package/tsconfig.spec.json +0 -11
  44. /package/{src/styles → styles}/styles.css +0 -0
  45. /package/{src/styles → styles}/tokens.css +0 -0
@@ -1,285 +0,0 @@
1
- :host {
2
- display: flex;
3
- flex-direction: column;
4
- min-height: 0;
5
- width: 100%;
6
- height: 100%;
7
- color: var(--pos-color-text);
8
- font-family: var(--pos-font-family);
9
- font-size: var(--pos-font-size-md);
10
- }
11
-
12
- .pos-basket__panel {
13
- display: flex;
14
- flex-direction: column;
15
- min-height: 0;
16
- height: 100%;
17
- padding: var(--pos-space-lg);
18
- border: 1px solid var(--pos-color-border);
19
- border-radius: var(--pos-radius-lg);
20
- background: var(--pos-color-panel);
21
- }
22
-
23
- .pos-basket__header {
24
- display: flex;
25
- align-items: baseline;
26
- justify-content: space-between;
27
- gap: var(--pos-space-md);
28
- margin-bottom: var(--pos-space-md);
29
- }
30
-
31
- .pos-basket__title {
32
- margin: 0;
33
- font-family: var(--pos-font-family-display);
34
- font-size: var(--pos-font-size-xl);
35
- font-weight: var(--pos-font-weight-bold);
36
- letter-spacing: -0.03em;
37
- }
38
-
39
- .pos-basket__count {
40
- margin: 0;
41
- color: var(--pos-color-text-muted);
42
- font-size: var(--pos-font-size-sm);
43
- font-weight: var(--pos-font-weight-semibold);
44
- }
45
-
46
- .pos-basket__lines {
47
- display: flex;
48
- flex: 1 1 auto;
49
- flex-direction: column;
50
- gap: var(--pos-space-sm);
51
- min-height: 8rem;
52
- margin: 0;
53
- padding: 0;
54
- overflow-y: auto;
55
- list-style: none;
56
- }
57
-
58
- .pos-basket__empty {
59
- margin: auto 0;
60
- padding: var(--pos-space-2xl) var(--pos-space-md);
61
- color: var(--pos-color-text-muted);
62
- text-align: center;
63
- }
64
-
65
- .pos-basket__line {
66
- display: grid;
67
- grid-template-columns: minmax(0, 1fr) auto;
68
- grid-template-areas:
69
- 'info void'
70
- 'stepper prices';
71
- align-items: center;
72
- gap: var(--pos-space-sm) var(--pos-space-md);
73
- padding: var(--pos-space-md);
74
- border: 1px solid var(--pos-color-border);
75
- border-radius: var(--pos-radius-md);
76
- background: var(--pos-color-panel-elevated);
77
- }
78
-
79
- .pos-basket__line-info {
80
- grid-area: info;
81
- min-width: 0;
82
- }
83
-
84
- .pos-basket__line-name {
85
- margin: 0;
86
- font-size: var(--pos-font-size-md);
87
- font-weight: var(--pos-font-weight-semibold);
88
- overflow: hidden;
89
- text-overflow: ellipsis;
90
- white-space: nowrap;
91
- }
92
-
93
- .pos-basket__line-sku,
94
- .pos-basket__line-voided {
95
- margin: var(--pos-space-xs) 0 0;
96
- font-size: var(--pos-font-size-sm);
97
- }
98
-
99
- .pos-basket__line-sku {
100
- color: var(--pos-color-text-muted);
101
- }
102
-
103
- .pos-basket__line-voided {
104
- color: var(--pos-color-danger);
105
- font-weight: var(--pos-font-weight-semibold);
106
- }
107
-
108
- .pos-basket__stepper {
109
- grid-area: stepper;
110
- display: inline-flex;
111
- align-items: center;
112
- gap: var(--pos-space-xs);
113
- width: fit-content;
114
- padding: var(--pos-space-xs);
115
- border: 1px solid var(--pos-color-border);
116
- border-radius: var(--pos-radius-md);
117
- background: var(--pos-color-surface);
118
- }
119
-
120
- .pos-basket__qty-btn,
121
- .pos-basket__void {
122
- display: inline-flex;
123
- align-items: center;
124
- justify-content: center;
125
- margin: 0;
126
- border: 0;
127
- background: transparent;
128
- color: var(--pos-color-text);
129
- font: inherit;
130
- cursor: pointer;
131
- -webkit-tap-highlight-color: transparent;
132
- }
133
-
134
- .pos-basket__qty-btn {
135
- min-width: 2.75rem;
136
- min-height: 2.75rem;
137
- border-radius: var(--pos-radius-sm);
138
- font-size: var(--pos-font-size-lg);
139
- font-weight: var(--pos-font-weight-bold);
140
- }
141
-
142
- .pos-basket__qty-btn:not(:disabled):active,
143
- .pos-basket__void:not(:disabled):active {
144
- background: var(--pos-color-key-active);
145
- color: var(--pos-color-on-key-active);
146
- }
147
-
148
- .pos-basket__qty {
149
- min-width: 1.75rem;
150
- font-family: var(--pos-font-family-display);
151
- font-weight: var(--pos-font-weight-bold);
152
- text-align: center;
153
- }
154
-
155
- .pos-basket__line-prices {
156
- grid-area: prices;
157
- display: flex;
158
- flex-direction: column;
159
- align-items: flex-end;
160
- gap: var(--pos-space-xs);
161
- }
162
-
163
- .pos-basket__unit-price {
164
- margin: 0;
165
- color: var(--pos-color-text-muted);
166
- font-size: var(--pos-font-size-sm);
167
- }
168
-
169
- .pos-basket__line-total {
170
- margin: 0;
171
- font-family: var(--pos-font-family-display);
172
- font-weight: var(--pos-font-weight-bold);
173
- }
174
-
175
- .pos-basket__void {
176
- grid-area: void;
177
- justify-self: end;
178
- min-width: 2.75rem;
179
- min-height: 2.75rem;
180
- border: 1px solid var(--pos-color-border);
181
- border-radius: var(--pos-radius-md);
182
- color: var(--pos-color-text-muted);
183
- }
184
-
185
- .pos-basket__qty-btn:disabled,
186
- .pos-basket__void:disabled,
187
- .pos-basket__clear:disabled {
188
- cursor: not-allowed;
189
- opacity: 0.55;
190
- }
191
-
192
- .pos-basket__qty-btn:focus-visible,
193
- .pos-basket__void:focus-visible,
194
- .pos-basket__clear:focus-visible {
195
- outline: 2px solid var(--pos-color-primary);
196
- outline-offset: 2px;
197
- }
198
-
199
- .pos-basket__line--voided .pos-basket__line-name,
200
- .pos-basket__line--voided .pos-basket__line-sku,
201
- .pos-basket__line--voided .pos-basket__line-prices {
202
- color: var(--pos-color-text-muted);
203
- text-decoration: line-through;
204
- }
205
-
206
- .pos-basket__footer {
207
- display: flex;
208
- flex-direction: column;
209
- gap: var(--pos-space-md);
210
- margin-top: var(--pos-space-md);
211
- padding-top: var(--pos-space-md);
212
- border-top: 1px solid var(--pos-color-border);
213
- }
214
-
215
- .pos-basket__totals {
216
- display: flex;
217
- flex-direction: column;
218
- gap: var(--pos-space-sm);
219
- margin: 0;
220
- }
221
-
222
- .pos-basket__total-row {
223
- display: flex;
224
- justify-content: space-between;
225
- gap: var(--pos-space-md);
226
- margin: 0;
227
- color: var(--pos-color-text-muted);
228
- font-size: var(--pos-font-size-sm);
229
- }
230
-
231
- .pos-basket__total-row dt,
232
- .pos-basket__total-row dd {
233
- margin: 0;
234
- }
235
-
236
- .pos-basket__total-row--due {
237
- padding-top: var(--pos-space-sm);
238
- border-top: 1px solid var(--pos-color-border);
239
- color: var(--pos-color-text);
240
- font-family: var(--pos-font-family-display);
241
- font-size: var(--pos-font-size-lg);
242
- font-weight: var(--pos-font-weight-bold);
243
- }
244
-
245
- .pos-basket__clear {
246
- min-height: var(--pos-button-min-height);
247
- border: 0;
248
- border-radius: var(--pos-radius-md);
249
- background: var(--pos-color-danger);
250
- color: var(--pos-color-on-danger);
251
- font: inherit;
252
- font-weight: var(--pos-font-weight-bold);
253
- letter-spacing: 0.04em;
254
- text-transform: uppercase;
255
- cursor: pointer;
256
- -webkit-tap-highlight-color: transparent;
257
- }
258
-
259
- .pos-basket__clear:not(:disabled):active {
260
- transform: translateY(1px);
261
- }
262
-
263
- .pos-basket__clear:disabled {
264
- background: var(--pos-color-disabled);
265
- color: var(--pos-color-on-disabled);
266
- transform: none;
267
- }
268
-
269
- @media (min-width: 768px) {
270
- .pos-basket__panel {
271
- padding: var(--pos-space-xl);
272
- }
273
-
274
- .pos-basket__line {
275
- grid-template-columns: minmax(0, 1fr) auto auto auto;
276
- grid-template-areas: 'info stepper prices void';
277
- gap: var(--pos-space-md);
278
- }
279
- }
280
-
281
- @media (prefers-reduced-motion: reduce) {
282
- .pos-basket__clear {
283
- transform: none;
284
- }
285
- }
@@ -1,198 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
-
3
- import {
4
- PosBasket,
5
- PosBasketIntent,
6
- PosBasketLine,
7
- PosBasketTotals,
8
- } from './pos-basket';
9
-
10
- const COFFEE_LINE: PosBasketLine = {
11
- lineId: 1,
12
- sku: 'SKU-001',
13
- name: 'Coffee 250g',
14
- qty: 2,
15
- unitPrice: 6,
16
- lineTotal: 12,
17
- vat: { rate: 0.25, amount: 2.4, included: true },
18
- struckThrough: false,
19
- };
20
-
21
- const MID_SALE_TOTALS: PosBasketTotals = {
22
- gross: 12,
23
- net: 9.6,
24
- vat: 2.4,
25
- discount: 0,
26
- due: 12,
27
- currency: 'EUR',
28
- };
29
-
30
- describe('PosBasket', () => {
31
- let fixture: ComponentFixture<PosBasket>;
32
- let component: PosBasket;
33
-
34
- beforeEach(async () => {
35
- await TestBed.configureTestingModule({
36
- imports: [PosBasket],
37
- }).compileComponents();
38
-
39
- fixture = TestBed.createComponent(PosBasket);
40
- component = fixture.componentInstance;
41
- await fixture.whenStable();
42
- });
43
-
44
- it('should render the empty basket state by default', () => {
45
- const compiled = fixture.nativeElement as HTMLElement;
46
- expect(compiled.querySelector('.pos-basket__title')?.textContent).toContain(
47
- 'Basket',
48
- );
49
- expect(compiled.querySelector('.pos-basket__empty')?.textContent).toContain(
50
- 'Basket is empty',
51
- );
52
- });
53
-
54
- it('should render lines and totals exactly as received without calculating', async () => {
55
- const mismatched: PosBasketLine = {
56
- ...COFFEE_LINE,
57
- qty: 2,
58
- unitPrice: 6,
59
- lineTotal: 99,
60
- };
61
- const totals: PosBasketTotals = {
62
- ...MID_SALE_TOTALS,
63
- gross: 50,
64
- due: 40,
65
- };
66
-
67
- fixture.componentRef.setInput('lines', [mismatched]);
68
- fixture.componentRef.setInput('totals', totals);
69
- await fixture.whenStable();
70
-
71
- const compiled = fixture.nativeElement as HTMLElement;
72
- expect(
73
- compiled.querySelector('.pos-basket__line-name')?.textContent,
74
- ).toContain('Coffee 250g');
75
- expect(
76
- compiled.querySelector('.pos-basket__line-sku')?.textContent,
77
- ).toContain('SKU-001');
78
- expect(compiled.querySelector('.pos-basket__qty')?.textContent?.trim()).toBe(
79
- '2',
80
- );
81
- expect(
82
- compiled.querySelector('.pos-basket__line-total')?.textContent,
83
- ).toContain('99');
84
- expect(
85
- compiled.querySelector('.pos-basket__line-total')?.textContent,
86
- ).not.toContain('12');
87
-
88
- const due = compiled.querySelector('.pos-basket__total-row--due dd');
89
- expect(due?.textContent).toContain('40');
90
- expect(due?.textContent).not.toContain('50');
91
- });
92
-
93
- it('should emit change-quantity when the cashier increments a line', async () => {
94
- const intents: PosBasketIntent[] = [];
95
- component.action.subscribe((intent) => intents.push(intent));
96
-
97
- fixture.componentRef.setInput('lines', [COFFEE_LINE]);
98
- fixture.componentRef.setInput('totals', MID_SALE_TOTALS);
99
- await fixture.whenStable();
100
-
101
- const increase = (
102
- fixture.nativeElement as HTMLElement
103
- ).querySelectorAll('.pos-basket__qty-btn')[1] as HTMLButtonElement;
104
- increase.click();
105
- await fixture.whenStable();
106
-
107
- expect(intents).toEqual([
108
- { type: 'basket.changeQuantity', lineId: 1, qty: 3 },
109
- ]);
110
- });
111
-
112
- it('should emit change-quantity when the cashier decrements a line', async () => {
113
- const intents: PosBasketIntent[] = [];
114
- component.action.subscribe((intent) => intents.push(intent));
115
-
116
- fixture.componentRef.setInput('lines', [COFFEE_LINE]);
117
- fixture.componentRef.setInput('totals', MID_SALE_TOTALS);
118
- await fixture.whenStable();
119
-
120
- const decrease = (
121
- fixture.nativeElement as HTMLElement
122
- ).querySelector('.pos-basket__qty-btn') as HTMLButtonElement;
123
- decrease.click();
124
- await fixture.whenStable();
125
-
126
- expect(intents).toEqual([
127
- { type: 'basket.changeQuantity', lineId: 1, qty: 1 },
128
- ]);
129
- });
130
-
131
- it('should emit void-line for the selected line', async () => {
132
- const intents: PosBasketIntent[] = [];
133
- component.action.subscribe((intent) => intents.push(intent));
134
-
135
- fixture.componentRef.setInput('lines', [COFFEE_LINE]);
136
- fixture.componentRef.setInput('totals', MID_SALE_TOTALS);
137
- await fixture.whenStable();
138
-
139
- const voidBtn = (fixture.nativeElement as HTMLElement).querySelector(
140
- '.pos-basket__void',
141
- ) as HTMLButtonElement;
142
- voidBtn.click();
143
- await fixture.whenStable();
144
-
145
- expect(intents).toEqual([{ type: 'basket.voidLine', lineId: 1 }]);
146
- });
147
-
148
- it('should emit clear-basket when the cashier clears', async () => {
149
- const intents: PosBasketIntent[] = [];
150
- component.action.subscribe((intent) => intents.push(intent));
151
-
152
- fixture.componentRef.setInput('lines', [COFFEE_LINE]);
153
- fixture.componentRef.setInput('totals', MID_SALE_TOTALS);
154
- await fixture.whenStable();
155
-
156
- const clear = (fixture.nativeElement as HTMLElement).querySelector(
157
- '.pos-basket__clear',
158
- ) as HTMLButtonElement;
159
- clear.click();
160
- await fixture.whenStable();
161
-
162
- expect(intents).toEqual([{ type: 'basket.clear' }]);
163
- });
164
-
165
- it('should not emit while disabled, and should disable void on struck-through lines', async () => {
166
- const intents: PosBasketIntent[] = [];
167
- component.action.subscribe((intent) => intents.push(intent));
168
-
169
- fixture.componentRef.setInput('lines', [
170
- { ...COFFEE_LINE, struckThrough: true },
171
- ]);
172
- fixture.componentRef.setInput('totals', MID_SALE_TOTALS);
173
- fixture.componentRef.setInput('disabled', true);
174
- await fixture.whenStable();
175
-
176
- const compiled = fixture.nativeElement as HTMLElement;
177
- const increase = compiled.querySelectorAll(
178
- '.pos-basket__qty-btn',
179
- )[1] as HTMLButtonElement;
180
- const voidBtn = compiled.querySelector(
181
- '.pos-basket__void',
182
- ) as HTMLButtonElement;
183
- const clear = compiled.querySelector(
184
- '.pos-basket__clear',
185
- ) as HTMLButtonElement;
186
-
187
- increase.click();
188
- voidBtn.click();
189
- clear.click();
190
- await fixture.whenStable();
191
-
192
- expect(increase.disabled).toBe(true);
193
- expect(voidBtn.disabled).toBe(true);
194
- expect(clear.disabled).toBe(true);
195
- expect(intents).toEqual([]);
196
- expect(compiled.querySelector('.pos-basket__line--voided')).toBeTruthy();
197
- });
198
- });
@@ -1,114 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/angular-vite';
2
-
3
- import { PosBasket, PosBasketLine, PosBasketTotals } from './pos-basket';
4
-
5
- const coffee: PosBasketLine = {
6
- lineId: 1,
7
- sku: 'SKU-001',
8
- name: 'Coffee 250g',
9
- qty: 2,
10
- unitPrice: 6,
11
- lineTotal: 12,
12
- vat: { rate: 0.25, amount: 2.4, included: true },
13
- struckThrough: false,
14
- };
15
-
16
- const pastry: PosBasketLine = {
17
- lineId: 2,
18
- sku: 'SKU-014',
19
- name: 'Almond croissant',
20
- qty: 1,
21
- unitPrice: 3.5,
22
- lineTotal: 3.5,
23
- vat: { rate: 0.2, amount: 0.58, included: true },
24
- struckThrough: false,
25
- };
26
-
27
- const midSaleTotals: PosBasketTotals = {
28
- gross: 12,
29
- net: 9.6,
30
- vat: 2.4,
31
- discount: 0,
32
- due: 12,
33
- currency: 'EUR',
34
- };
35
-
36
- const meta: Meta<PosBasket> = {
37
- title: 'Basket',
38
- component: PosBasket,
39
- args: {
40
- lines: [],
41
- totals: {
42
- gross: 0,
43
- net: 0,
44
- vat: 0,
45
- discount: 0,
46
- due: 0,
47
- currency: 'EUR',
48
- },
49
- disabled: false,
50
- },
51
- };
52
-
53
- export default meta;
54
-
55
- type Story = StoryObj<PosBasket>;
56
-
57
- export const Empty: Story = {};
58
-
59
- export const MidSale: Story = {
60
- args: {
61
- lines: [coffee],
62
- totals: midSaleTotals,
63
- },
64
- };
65
-
66
- export const MultipleLines: Story = {
67
- args: {
68
- lines: [coffee, pastry],
69
- totals: {
70
- gross: 15.5,
71
- net: 12.52,
72
- vat: 2.98,
73
- discount: 0,
74
- due: 15.5,
75
- currency: 'EUR',
76
- },
77
- },
78
- };
79
-
80
- export const VoidedLine: Story = {
81
- args: {
82
- lines: [{ ...coffee, struckThrough: true }, pastry],
83
- totals: {
84
- gross: 3.5,
85
- net: 2.92,
86
- vat: 0.58,
87
- discount: 0,
88
- due: 3.5,
89
- currency: 'EUR',
90
- },
91
- },
92
- };
93
-
94
- export const AsReceivedNotCalculated: Story = {
95
- args: {
96
- lines: [{ ...coffee, qty: 2, unitPrice: 6, lineTotal: 99 }],
97
- totals: {
98
- gross: 50,
99
- net: 40,
100
- vat: 10,
101
- discount: 5,
102
- due: 45,
103
- currency: 'EUR',
104
- },
105
- },
106
- };
107
-
108
- export const Disabled: Story = {
109
- args: {
110
- lines: [coffee],
111
- totals: midSaleTotals,
112
- disabled: true,
113
- },
114
- };