commons-shared-web-ui 0.0.53 → 0.0.55

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 (26) hide show
  1. package/documentation/smart-form.md +236 -3
  2. package/documentation/time-picker.md +274 -0
  3. package/ng-package.json +3 -1
  4. package/package.json +1 -1
  5. package/src/lib/modules/form-components/form-components.module.ts +2 -1
  6. package/src/lib/modules/form-field-configuration/services/config-schema-tree.service.ts +13 -2
  7. package/src/lib/modules/side-nav/components/side-nav/side-nav.component.scss +1 -1
  8. package/src/lib/modules/smart-form/components/form-field/form-field.component.html +145 -11
  9. package/src/lib/modules/smart-form/components/form-field/form-field.component.scss +98 -0
  10. package/src/lib/modules/smart-form/components/form-field/form-field.component.ts +225 -1
  11. package/src/lib/modules/smart-form/components/form-section/form-section.component.scss +2 -2
  12. package/src/lib/modules/smart-form/components/smart-form/smart-form.component.scss +2 -2
  13. package/src/lib/modules/smart-form/models/form-schema.model.ts +44 -0
  14. package/src/lib/modules/smart-form/smart-form.module.ts +3 -1
  15. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.html +37 -0
  16. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.scss +102 -0
  17. package/src/lib/modules/time-picker/components/time-picker/time-picker.component.ts +178 -0
  18. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.html +78 -0
  19. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.scss +226 -0
  20. package/src/lib/modules/time-picker/components/time-wheel-panel/time-wheel-panel.component.ts +595 -0
  21. package/src/lib/modules/time-picker/models/time-picker.models.ts +49 -0
  22. package/src/lib/modules/time-picker/time-picker.module.ts +23 -0
  23. package/src/lib/modules/time-picker/time-picker.theme.scss +87 -0
  24. package/src/lib/shared-ui.module.ts +5 -0
  25. package/src/lib/styles/global.scss +10 -0
  26. package/src/public-api.ts +6 -0
@@ -0,0 +1,37 @@
1
+ <div class="cc-timepicker-wrapper" [ngStyle]="wrapperStyles">
2
+ <label *ngIf="label" class="cc-timepicker-label" [ngStyle]="labelStyles">
3
+ {{ label }}
4
+ <span *ngIf="required" class="cc-timepicker-required">{{ labels?.requiredMarker || '*' }}</span>
5
+ </label>
6
+
7
+ <!-- Trigger Field -->
8
+ <div class="cc-timepicker-field"
9
+ [class.focused]="isOpen"
10
+ [class.disabled]="disabled"
11
+ [class.error]="errorMessage"
12
+ [ngStyle]="fieldStyles"
13
+ (click)="!disabled && togglePanel()">
14
+ <span class="cc-timepicker-value" [class.is-placeholder]="!displayValue">
15
+ {{ displayValue || placeholder || 'Select time' }}
16
+ </span>
17
+ <span class="cc-timepicker-icon material-icons">schedule</span>
18
+ </div>
19
+
20
+ <!-- Centered Modal Overlay -->
21
+ <div class="cc-timepicker-overlay" *ngIf="isOpen" (click)="onCancelled()">
22
+ <div class="cc-timepicker-modal-content" (click)="$event.stopPropagation()">
23
+ <lib-time-wheel-panel
24
+ [mode]="mode"
25
+ [value]="displayValue"
26
+ [labels]="labels"
27
+ [minuteStep]="minuteStep"
28
+ [minTime]="minTime"
29
+ [maxTime]="maxTime"
30
+ (confirmed)="onConfirmed($event)"
31
+ (cancelled)="onCancelled()">
32
+ </lib-time-wheel-panel>
33
+ </div>
34
+ </div>
35
+
36
+ <div class="cc-error-message" *ngIf="errorMessage">{{ errorMessage }}</div>
37
+ </div>
@@ -0,0 +1,102 @@
1
+ .cc-timepicker-wrapper {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--cc-timepicker-label-gap, 0.5rem);
5
+ font-family: var(--cc-timepicker-font-family, inherit);
6
+ position: relative;
7
+ width: 100%;
8
+ }
9
+
10
+ .cc-timepicker-label {
11
+ font-size: var(--cc-timepicker-label-font-size, 0.875rem);
12
+ font-weight: var(--cc-timepicker-label-font-weight, 500);
13
+ color: var(--cc-timepicker-label-color, #202124);
14
+ display: flex;
15
+ align-items: center;
16
+ }
17
+
18
+ .cc-timepicker-required {
19
+ color: var(--cc-timepicker-required-color, #D93025);
20
+ margin-left: 0.125rem;
21
+ }
22
+
23
+ .cc-timepicker-field {
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: space-between;
27
+ height: var(--cc-timepicker-height, 2.5rem);
28
+ padding: var(--cc-timepicker-padding, 0.375rem 1rem);
29
+ border-radius: var(--cc-timepicker-border-radius, 0.4375rem);
30
+ border: var(--cc-timepicker-border-width, 1px) solid var(--cc-timepicker-border-color, #BDC1C6);
31
+ background-color: var(--cc-timepicker-bg, #FEFEFE);
32
+ cursor: pointer;
33
+ box-sizing: border-box;
34
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
35
+ user-select: none;
36
+ }
37
+
38
+ .cc-timepicker-field:hover:not(.disabled) {
39
+ border-color: var(--cc-timepicker-focus-border-color, #1A73E8);
40
+ }
41
+
42
+ .cc-timepicker-field.focused {
43
+ border-color: var(--cc-timepicker-focus-border-color, #1A73E8);
44
+ box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
45
+ }
46
+
47
+ .cc-timepicker-field.disabled {
48
+ background-color: var(--cc-timepicker-disabled-bg, #F1F3F4);
49
+ border-color: var(--cc-timepicker-border-color, #BDC1C6);
50
+ cursor: not-allowed;
51
+ }
52
+
53
+ .cc-timepicker-value {
54
+ font-size: var(--cc-timepicker-font-size, 0.875rem);
55
+ font-weight: var(--cc-timepicker-font-weight, 400);
56
+ color: var(--cc-timepicker-color, #202124);
57
+ font-variant-numeric: tabular-nums;
58
+ }
59
+
60
+ .cc-timepicker-field.disabled .cc-timepicker-value {
61
+ color: var(--cc-timepicker-disabled-color, #80868B);
62
+ }
63
+
64
+ .cc-timepicker-value.is-placeholder {
65
+ color: var(--cc-timepicker-placeholder-color, #80868B);
66
+ }
67
+
68
+ .cc-timepicker-icon {
69
+ font-size: 20px;
70
+ color: var(--cc-timepicker-icon-color, #5F6368);
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ }
75
+
76
+ .cc-timepicker-field.disabled .cc-timepicker-icon {
77
+ color: var(--cc-timepicker-disabled-color, #80868B);
78
+ }
79
+
80
+ /* ─── Centered Modal Overlay Backdrop ─── */
81
+ .cc-timepicker-overlay {
82
+ position: fixed;
83
+ top: 0;
84
+ left: 0;
85
+ right: 0;
86
+ bottom: 0;
87
+ background-color: rgba(0, 0, 0, 0.4); /* dim backdrop */
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ z-index: 9999;
92
+ }
93
+
94
+ .cc-timepicker-modal-content {
95
+ display: block;
96
+ }
97
+
98
+ .cc-error-message {
99
+ font-size: 0.75rem;
100
+ color: var(--cc-timepicker-error-color, #D93025);
101
+ margin-top: 0.25rem;
102
+ }
@@ -0,0 +1,178 @@
1
+ import { Component, Input, Output, EventEmitter, forwardRef, OnInit, OnChanges, SimpleChanges } from '@angular/core';
2
+ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
3
+ import { TimePickerConfig, TimePickerLabels, TimeMode, TimePickerVariant } from '../../models/time-picker.models';
4
+
5
+ @Component({
6
+ selector: 'lib-time-picker',
7
+ templateUrl: './time-picker.component.html',
8
+ styleUrls: ['./time-picker.component.scss'],
9
+ standalone: false,
10
+ providers: [
11
+ {
12
+ provide: NG_VALUE_ACCESSOR,
13
+ useExisting: forwardRef(() => TimePickerComponent),
14
+ multi: true
15
+ }
16
+ ]
17
+ })
18
+ export class TimePickerComponent implements ControlValueAccessor, OnInit, OnChanges {
19
+ @Input() config?: TimePickerConfig;
20
+ @Input() labels?: TimePickerLabels;
21
+ @Input() label: string = '';
22
+ @Input() placeholder: string = '';
23
+ @Input() variant: TimePickerVariant = 'wheel';
24
+ @Input() mode: TimeMode = '12';
25
+ @Input() minuteStep: number = 0;
26
+ @Input() disabled: boolean = false;
27
+ @Input() required: boolean = false;
28
+ @Input() errorMessage: string = '';
29
+ @Input() minTime?: string;
30
+ @Input() maxTime?: string;
31
+
32
+ // Style inputs (to mirror datepicker pattern)
33
+ @Input() width?: string;
34
+ @Input() height?: string;
35
+ @Input() borderRadius?: string;
36
+ @Input() fontSize?: string;
37
+ @Input() gap?: string;
38
+ @Input() fontFamily?: string;
39
+ @Input() labelColor?: string;
40
+ @Input() labelFontSize?: string;
41
+ @Input() labelFontWeight?: string;
42
+ @Input() backgroundColor?: string;
43
+ @Input() borderColor?: string;
44
+ @Input() borderWidth?: string;
45
+ @Input() padding?: string;
46
+ @Input() fontWeight?: string;
47
+ @Input() color?: string;
48
+ @Input() placeholderColor?: string;
49
+ @Input() focusBorderColor?: string;
50
+ @Input() errorColor?: string;
51
+ @Input() disabledBackgroundColor?: string;
52
+ @Input() disabledColor?: string;
53
+
54
+ @Output() timeChange = new EventEmitter<string>();
55
+
56
+ displayValue: string = '';
57
+ isOpen: boolean = false;
58
+
59
+ onChange: (value: string) => void = () => {};
60
+ onTouched: () => void = () => {};
61
+
62
+ ngOnInit(): void {
63
+ this.updateFromConfig();
64
+ }
65
+
66
+ ngOnChanges(changes: SimpleChanges): void {
67
+ if (changes['config']) {
68
+ this.updateFromConfig();
69
+ }
70
+ }
71
+
72
+ private updateFromConfig(): void {
73
+ if (this.config) {
74
+ this.label = this.config.label ?? this.label;
75
+ this.placeholder = this.config.placeholder ?? this.placeholder;
76
+ this.variant = this.config.variant ?? this.variant;
77
+ this.mode = this.config.mode ?? this.mode;
78
+ this.minuteStep = this.config.minuteStep ?? this.minuteStep;
79
+ this.disabled = this.config.disabled ?? this.disabled;
80
+ this.required = this.config.required ?? this.required;
81
+ this.errorMessage = this.config.errorMessage ?? this.errorMessage;
82
+
83
+ this.width = this.config.width ?? this.width;
84
+ this.height = this.config.height ?? this.height;
85
+ this.borderRadius = this.config.borderRadius ?? this.borderRadius;
86
+ this.fontSize = this.config.fontSize ?? this.fontSize;
87
+ this.gap = this.config.gap ?? this.gap;
88
+ this.fontFamily = this.config.fontFamily ?? this.fontFamily;
89
+ this.labelColor = this.config.labelColor ?? this.labelColor;
90
+ this.labelFontSize = this.config.labelFontSize ?? this.labelFontSize;
91
+ this.labelFontWeight = this.config.labelFontWeight ?? this.labelFontWeight;
92
+ this.backgroundColor = this.config.backgroundColor ?? this.backgroundColor;
93
+ this.borderColor = this.config.borderColor ?? this.borderColor;
94
+ this.borderWidth = this.config.borderWidth ?? this.borderWidth;
95
+ this.padding = this.config.padding ?? this.padding;
96
+ this.fontWeight = this.config.fontWeight ?? this.fontWeight;
97
+ this.color = this.config.color ?? this.color;
98
+ this.placeholderColor = this.config.placeholderColor ?? this.placeholderColor;
99
+ this.focusBorderColor = this.config.focusBorderColor ?? this.focusBorderColor;
100
+ this.errorColor = this.config.errorColor ?? this.errorColor;
101
+ this.disabledBackgroundColor = this.config.disabledBackgroundColor ?? this.disabledBackgroundColor;
102
+ this.disabledColor = this.config.disabledColor ?? this.disabledColor;
103
+ }
104
+ }
105
+
106
+ writeValue(value: string): void {
107
+ this.displayValue = value || '';
108
+ }
109
+
110
+ registerOnChange(fn: any): void {
111
+ this.onChange = fn;
112
+ }
113
+
114
+ registerOnTouched(fn: any): void {
115
+ this.onTouched = fn;
116
+ }
117
+
118
+ setDisabledState(isDisabled: boolean): void {
119
+ this.disabled = isDisabled;
120
+ }
121
+
122
+ togglePanel(): void {
123
+ if (this.disabled) return;
124
+ this.isOpen = !this.isOpen;
125
+ }
126
+
127
+ closePanel(): void {
128
+ this.isOpen = false;
129
+ }
130
+
131
+ onConfirmed(timeString: string): void {
132
+ this.displayValue = timeString;
133
+ this.onChange(timeString);
134
+ this.timeChange.emit(timeString);
135
+ this.isOpen = false;
136
+ this.onTouched();
137
+ }
138
+
139
+ onCancelled(): void {
140
+ this.isOpen = false;
141
+ this.onTouched();
142
+ }
143
+
144
+ get wrapperStyles(): { [key: string]: string | undefined } {
145
+ return {
146
+ 'width': this.width ? `${this.width}` : undefined,
147
+ '--cc-timepicker-label-gap': this.gap ? `${this.gap}` : undefined,
148
+ '--cc-timepicker-font-family': this.fontFamily ? `${this.fontFamily}` : undefined
149
+ };
150
+ }
151
+
152
+ get labelStyles(): { [key: string]: string | undefined } {
153
+ return {
154
+ '--cc-timepicker-label-color': this.labelColor ? `${this.labelColor}` : undefined,
155
+ '--cc-timepicker-label-font-size': this.labelFontSize ? `${this.labelFontSize}` : undefined,
156
+ '--cc-timepicker-label-font-weight': this.labelFontWeight ? `${this.labelFontWeight}` : undefined
157
+ };
158
+ }
159
+
160
+ get fieldStyles(): { [key: string]: string | undefined } {
161
+ return {
162
+ '--cc-timepicker-bg': this.backgroundColor ? `${this.backgroundColor}` : undefined,
163
+ '--cc-timepicker-border-radius': this.borderRadius ? `${this.borderRadius}` : undefined,
164
+ '--cc-timepicker-border-color': this.borderColor ? `${this.borderColor}` : undefined,
165
+ '--cc-timepicker-border-width': this.borderWidth ? `${this.borderWidth}` : undefined,
166
+ '--cc-timepicker-height': this.height ? `${this.height}` : undefined,
167
+ '--cc-timepicker-padding': this.padding ? `${this.padding}` : undefined,
168
+ '--cc-timepicker-font-size': this.fontSize ? `${this.fontSize}` : undefined,
169
+ '--cc-timepicker-font-weight': this.fontWeight ? `${this.fontWeight}` : undefined,
170
+ '--cc-timepicker-color': this.color ? `${this.color}` : undefined,
171
+ '--cc-timepicker-placeholder-color': this.placeholderColor ? `${this.placeholderColor}` : undefined,
172
+ '--cc-timepicker-focus-border-color': this.focusBorderColor ? `${this.focusBorderColor}` : undefined,
173
+ '--cc-timepicker-error-color': this.errorColor ? `${this.errorColor}` : undefined,
174
+ '--cc-timepicker-disabled-bg': this.disabledBackgroundColor ? `${this.disabledBackgroundColor}` : undefined,
175
+ '--cc-timepicker-disabled-color': this.disabledColor ? `${this.disabledColor}` : undefined
176
+ };
177
+ }
178
+ }
@@ -0,0 +1,78 @@
1
+ <div class="cc-twp-dialog">
2
+ <!-- Subtle Configurable Title Header -->
3
+ <div class="cc-twp-header">
4
+ <span class="cc-twp-title">{{ labels?.selectTimeTitle || 'Select Time' }}</span>
5
+ </div>
6
+
7
+ <!-- Digital Preview -->
8
+ <div class="cc-twp-preview">
9
+ <span class="cc-twp-preview-num">{{ selectedHour }}</span>
10
+ <span class="cc-twp-preview-colon">:</span>
11
+ <span class="cc-twp-preview-num">{{ selectedMinute }}</span>
12
+ <span class="cc-twp-preview-period" *ngIf="mode === '12'"> {{ selectedPeriod }}</span>
13
+ </div>
14
+
15
+ <!-- Wheel Area -->
16
+ <div class="cc-twp-wheels" [class.mode-24]="mode === '24'">
17
+ <div class="cc-twp-highlight-bar"></div>
18
+
19
+ <!-- Hour Column -->
20
+ <div class="cc-twp-col">
21
+ <span class="cc-twp-col-label">{{ labels?.hourLabel || 'Hour' }}</span>
22
+ <div class="cc-twp-scroll" #hourWheel>
23
+ <div class="cc-twp-spacer" *ngFor="let s of spacerRows"></div>
24
+ <ng-container *ngFor="let rep of hourRepeats">
25
+ <div *ngFor="let h of hours"
26
+ class="cc-twp-item"
27
+ [class.active]="selectedHour === h"
28
+ [class.disabled]="disabledHours.has(h)"
29
+ [attr.aria-disabled]="disabledHours.has(h)"
30
+ (click)="selectValue('hour', h)">{{ h }}</div>
31
+ </ng-container>
32
+ <div class="cc-twp-spacer" *ngFor="let s of spacerRows"></div>
33
+ </div>
34
+ </div>
35
+
36
+ <!-- Minute Column -->
37
+ <div class="cc-twp-col">
38
+ <span class="cc-twp-col-label">{{ labels?.minuteLabel || 'Minute' }}</span>
39
+ <div class="cc-twp-scroll" #minuteWheel>
40
+ <div class="cc-twp-spacer" *ngFor="let s of spacerRows"></div>
41
+ <ng-container *ngFor="let rep of minuteRepeats">
42
+ <div *ngFor="let m of minutes"
43
+ class="cc-twp-item"
44
+ [class.active]="selectedMinute === m"
45
+ [class.disabled]="disabledMinutes.has(m)"
46
+ [attr.aria-disabled]="disabledMinutes.has(m)"
47
+ (click)="selectValue('minute', m)">{{ m }}</div>
48
+ </ng-container>
49
+ <div class="cc-twp-spacer" *ngFor="let s of spacerRows"></div>
50
+ </div>
51
+ </div>
52
+
53
+ <!-- AM/PM Column — 12hr mode only -->
54
+ <div class="cc-twp-col" *ngIf="mode === '12'">
55
+ <span class="cc-twp-col-label">{{ labels?.periodLabel || 'AM/PM' }}</span>
56
+ <div class="cc-twp-scroll" #periodWheel>
57
+ <div class="cc-twp-spacer" *ngFor="let s of spacerRows"></div>
58
+ <div *ngFor="let p of periods"
59
+ class="cc-twp-item"
60
+ [class.active]="selectedPeriod === p"
61
+ [class.disabled]="disabledPeriods.has(p)"
62
+ [attr.aria-disabled]="disabledPeriods.has(p)"
63
+ (click)="selectValue('period', p)">{{ p }}</div>
64
+ <div class="cc-twp-spacer" *ngFor="let s of spacerRows"></div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Footer -->
70
+ <div class="cc-twp-footer">
71
+ <button class="cc-twp-btn cc-twp-btn-cancel" (click)="onCancel()">
72
+ {{ labels?.cancelLabel || 'Cancel' }}
73
+ </button>
74
+ <button class="cc-twp-btn cc-twp-btn-confirm" (click)="onConfirm()">
75
+ {{ labels?.confirmLabel || 'Confirm' }}
76
+ </button>
77
+ </div>
78
+ </div>
@@ -0,0 +1,226 @@
1
+ .cc-twp-dialog {
2
+ background-color: var(--cc-modal-bg, #ffffff);
3
+ border-radius: var(--cc-modal-radius, 0.75rem);
4
+ box-shadow: var(--cc-modal-shadow, 0 0.25rem 1.5rem rgba(0, 0, 0, 0.15));
5
+ border: var(--cc-modal-border, none);
6
+ width: 280px;
7
+ box-sizing: border-box;
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: 16px;
11
+ padding: 18px;
12
+ font-family: inherit;
13
+ }
14
+
15
+ /* ─── Header Title ─── */
16
+ .cc-twp-header {
17
+ display: flex;
18
+ justify-content: center;
19
+ align-items: center;
20
+ margin-bottom: 4px;
21
+ }
22
+
23
+ .cc-twp-title {
24
+ font-size: 14px;
25
+ font-weight: 600;
26
+ color: var(--cc-timepicker-text-primary, #111827);
27
+ letter-spacing: -0.2px;
28
+ user-select: none;
29
+ }
30
+
31
+ /* ─── Time Preview ─── */
32
+ .cc-twp-preview {
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ background-color: var(--cc-timepicker-accent-light, rgba(0, 0, 0, 0.04));
37
+ border: 1px solid var(--cc-timepicker-accent-border, transparent);
38
+ padding: 10px 18px;
39
+ border-radius: 10px;
40
+ gap: 6px;
41
+ }
42
+
43
+ .cc-twp-preview-num {
44
+ font-size: 26px;
45
+ font-weight: 700;
46
+ color: var(--cc-timepicker-accent, #111827);
47
+ font-variant-numeric: tabular-nums;
48
+ letter-spacing: -0.5px;
49
+ }
50
+
51
+ .cc-twp-preview-colon {
52
+ font-size: 24px;
53
+ font-weight: 600;
54
+ color: var(--cc-timepicker-accent, #111827);
55
+ margin-top: -4px;
56
+ }
57
+
58
+ .cc-twp-preview-period {
59
+ font-size: 15px;
60
+ font-weight: 600;
61
+ color: var(--cc-timepicker-text-muted, #6b7280);
62
+ margin-left: 4px;
63
+ align-self: flex-end;
64
+ margin-bottom: 4px;
65
+ }
66
+
67
+ /* ─── Wheels Wrapper ─── */
68
+ .cc-twp-wheels {
69
+ position: relative;
70
+ display: flex;
71
+ justify-content: space-between;
72
+ border: 1px solid var(--cc-timepicker-wheels-border, transparent);
73
+ border-radius: 10px;
74
+ padding: 6px 12px;
75
+ overflow: hidden;
76
+ height: 140px;
77
+ box-sizing: border-box;
78
+ background-color: var(--cc-modal-bg, #ffffff);
79
+ }
80
+
81
+ .cc-twp-wheels::before,
82
+ .cc-twp-wheels::after {
83
+ content: '';
84
+ position: absolute;
85
+ left: 0;
86
+ right: 0;
87
+ height: 30px;
88
+ pointer-events: none;
89
+ z-index: 4;
90
+ }
91
+
92
+ .cc-twp-wheels::before {
93
+ top: 26px;
94
+ background: linear-gradient(to bottom, var(--cc-modal-bg, #ffffff) 20%, rgba(255, 255, 255, 0) 100%);
95
+ }
96
+
97
+ .cc-twp-wheels::after {
98
+ bottom: 6px;
99
+ background: linear-gradient(to top, var(--cc-modal-bg, #ffffff) 20%, rgba(255, 255, 255, 0) 100%);
100
+ }
101
+
102
+ .cc-twp-highlight-bar {
103
+ position: absolute;
104
+ height: 36px;
105
+ top: 62px; /* centers on the middle visible row */
106
+ left: 6px;
107
+ right: 6px;
108
+ background-color: var(--cc-timepicker-accent-light, rgba(0, 0, 0, 0.05));
109
+ border: 1px solid var(--cc-timepicker-accent-border, transparent);
110
+ border-radius: 8px;
111
+ pointer-events: none;
112
+ z-index: 2;
113
+ }
114
+
115
+ /* ─── Columns ─── */
116
+ .cc-twp-col {
117
+ flex: 1;
118
+ display: flex;
119
+ flex-direction: column;
120
+ align-items: center;
121
+ position: relative;
122
+ z-index: 3;
123
+ }
124
+
125
+ .cc-twp-col-label {
126
+ font-size: 10px;
127
+ font-weight: 400; /* non-bold */
128
+ text-transform: uppercase;
129
+ color: var(--cc-timepicker-text-muted, #9ca3af);
130
+ letter-spacing: 0.5px;
131
+ margin-bottom: 6px;
132
+ height: 14px;
133
+ line-height: 14px;
134
+ user-select: none;
135
+ }
136
+
137
+ .cc-twp-scroll {
138
+ width: 100%;
139
+ height: 108px; /* VISIBLE_ROWS (3) * ITEM_HEIGHT (36) */
140
+ overflow-y: scroll;
141
+ scroll-snap-type: y mandatory;
142
+ scrollbar-width: none;
143
+ -ms-overflow-style: none;
144
+ }
145
+
146
+ .cc-twp-scroll::-webkit-scrollbar {
147
+ display: none;
148
+ }
149
+
150
+ .cc-twp-item,
151
+ .cc-twp-spacer {
152
+ height: 36px;
153
+ line-height: 36px;
154
+ text-align: center;
155
+ box-sizing: border-box;
156
+ }
157
+
158
+ .cc-twp-spacer {
159
+ pointer-events: none;
160
+ }
161
+
162
+ .cc-twp-item {
163
+ font-size: var(--cc-timepicker-item-font-size, 1rem);
164
+ font-weight: 500;
165
+ color: var(--cc-timepicker-text-muted, #9ca3af);
166
+ cursor: pointer;
167
+ scroll-snap-align: center;
168
+ user-select: none;
169
+ font-variant-numeric: tabular-nums;
170
+ backface-visibility: hidden;
171
+ transition: color 0.15s ease-out, font-weight 0.15s ease-out;
172
+ }
173
+
174
+ .cc-twp-item:hover:not(.disabled) {
175
+ color: var(--cc-timepicker-text-primary, #111827);
176
+ }
177
+
178
+ .cc-twp-item.active {
179
+ color: var(--cc-timepicker-accent, #111827);
180
+ font-weight: 700;
181
+ }
182
+
183
+ .cc-twp-item.disabled {
184
+ opacity: 0.25;
185
+ cursor: not-allowed;
186
+ pointer-events: none;
187
+ }
188
+
189
+ /* ─── Footer Buttons ─── */
190
+ .cc-twp-footer {
191
+ display: flex;
192
+ gap: 12px;
193
+ justify-content: space-between;
194
+ padding-top: 4px;
195
+ }
196
+
197
+ .cc-twp-btn {
198
+ flex: 1;
199
+ padding: 9px 16px;
200
+ border-radius: 8px;
201
+ font-size: 14px;
202
+ font-weight: 600;
203
+ cursor: pointer;
204
+ transition: all 0.2s ease;
205
+ border: 1px solid transparent;
206
+ outline: none;
207
+ }
208
+
209
+ .cc-twp-btn-cancel {
210
+ background-color: var(--cc-timepicker-btn-cancel-bg, #f3f4f6);
211
+ border-color: var(--cc-timepicker-btn-cancel-border, #f3f4f6);
212
+ color: var(--cc-timepicker-text-primary, #111827);
213
+ }
214
+
215
+ .cc-twp-btn-cancel:hover {
216
+ background-color: var(--cc-timepicker-btn-cancel-hover, #e5e7eb);
217
+ }
218
+
219
+ .cc-twp-btn-confirm {
220
+ background-color: var(--cc-timepicker-btn-confirm-bg, #111827);
221
+ color: #ffffff;
222
+ }
223
+
224
+ .cc-twp-btn-confirm:hover {
225
+ background-color: var(--cc-timepicker-btn-confirm-hover, #000000);
226
+ }