Merged PR 2048: fix(shared-barcode, crm-customer-card): improve barcode rendering with transp...

fix(shared-barcode, crm-customer-card): improve barcode rendering with transparent SVG background

Enhance barcode component flexibility by separating container and SVG
background colors. The SVG barcode now defaults to transparent background
while maintaining container background control, enabling better integration
with various card designs.

Changes:
- Add separate svgBackground input for SVG element (default: transparent)
- Keep background input for container styling (default: #ffffff)
- Add containerWidth and containerHeight inputs for flexible sizing
- Update customer card to remove explicit white background on barcode
- Add opacity styling for inactive customer cards
- Enhance test coverage for new background and sizing inputs

The separation of concerns allows the barcode to adapt to parent container
backgrounds while maintaining consistent rendering across different contexts.

Ref: #5498
This commit is contained in:
Nino Righi
2025-11-24 14:58:05 +00:00
committed by Lorenz Hilpert
parent 949101a1ed
commit 8b852cbd7a
5 changed files with 151 additions and 34 deletions

View File

@@ -1,6 +1,7 @@
<!-- Card container: 337×213px, rounded-2xl, shadow -->
<div
class="relative flex h-[14.8125rem] w-[21.0625rem] flex-col bg-isa-black"
class="relative flex h-[14.8125rem] w-[21.0625rem] flex-col"
[class.opacity-40]="!card().isActive"
[attr.data-what]="'customer-card'"
[attr.data-which]="card().code"
>
@@ -11,13 +12,11 @@
<div>
<!-- Card type label (grey) -->
<div class="isa-text-body-2-bold text-center text-isa-neutral-500">
{{ card().isPrimary ? 'Kundenkarte Nr.:' : 'Mitarbeitendenkarte Nr.:' }}
Kundenkarte Nr.:
</div>
<!-- Card number (white, large) -->
<div
class="isa-text-subtitle-1-bold w-[150px] text-center text-isa-white"
>
<div class="isa-text-subtitle-1-bold text-center text-isa-white">
{{ card().code }}
</div>
</div>
@@ -30,7 +29,6 @@
[width]="barcodeWidth"
[margin]="barcodeMargin"
[format]="'CODE128'"
[background]="'#ffffff'"
[attr.data-what]="'card-barcode'"
[attr.data-which]="card().code"
class="rounded-[0.25rem] overflow-hidden"

View File

@@ -7,7 +7,10 @@ vi.mock('jsbarcode', () => ({
default: vi.fn((element, value, options) => {
// Simulate JsBarcode by adding a rect element to the SVG
if (element && element.tagName === 'svg') {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
const rect = document.createElementNS(
'http://www.w3.org/2000/svg',
'rect',
);
rect.setAttribute('data-value', value);
rect.setAttribute('data-format', options?.format || 'CODE128');
element.appendChild(rect);
@@ -108,7 +111,7 @@ describe('BarcodeComponent', () => {
expect(component.lineColor()).toBe('#FF0000');
});
it('should pass background input to directive', () => {
it('should pass container background input', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('background', '#F0F0F0');
fixture.detectChanges();
@@ -116,6 +119,30 @@ describe('BarcodeComponent', () => {
expect(component.background()).toBe('#F0F0F0');
});
it('should pass svgBackground input to directive', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('svgBackground', '#FFFFFF');
fixture.detectChanges();
expect(component.svgBackground()).toBe('#FFFFFF');
});
it('should pass containerWidth input', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('containerWidth', '20rem');
fixture.detectChanges();
expect(component.containerWidth()).toBe('20rem');
});
it('should pass containerHeight input', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('containerHeight', '6rem');
fixture.detectChanges();
expect(component.containerHeight()).toBe('6rem');
});
it('should pass fontSize input to directive', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('fontSize', 24);
@@ -169,13 +196,34 @@ describe('BarcodeComponent', () => {
expect(component.lineColor()).toBe('#000000');
});
it('should use default background #ffffff', () => {
it('should use default container background #ffffff', () => {
fixture.componentRef.setInput('value', '123');
fixture.detectChanges();
expect(component.background()).toBe('#ffffff');
});
it('should use default svgBackground transparent', () => {
fixture.componentRef.setInput('value', '123');
fixture.detectChanges();
expect(component.svgBackground()).toBe('transparent');
});
it('should use default containerWidth 12.5rem', () => {
fixture.componentRef.setInput('value', '123');
fixture.detectChanges();
expect(component.containerWidth()).toBe('12.5rem');
});
it('should use default containerHeight 5.5rem', () => {
fixture.componentRef.setInput('value', '123');
fixture.detectChanges();
expect(component.containerHeight()).toBe('5.5rem');
});
it('should use default fontSize 20', () => {
fixture.componentRef.setInput('value', '123');
fixture.detectChanges();
@@ -191,6 +239,41 @@ describe('BarcodeComponent', () => {
});
});
describe('Host Styling', () => {
it('should apply container width style', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('containerWidth', '25rem');
fixture.detectChanges();
expect(fixture.nativeElement.style.width).toBe('25rem');
});
it('should apply container height style', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('containerHeight', '8rem');
fixture.detectChanges();
expect(fixture.nativeElement.style.height).toBe('8rem');
});
it('should apply background color style', () => {
fixture.componentRef.setInput('value', '123');
fixture.componentRef.setInput('background', '#EEEEEE');
fixture.detectChanges();
expect(fixture.nativeElement.style.backgroundColor).toBe(
'rgb(238, 238, 238)',
);
});
it('should apply flex display style', () => {
fixture.componentRef.setInput('value', '123');
fixture.detectChanges();
expect(fixture.nativeElement.style.display).toBe('flex');
});
});
describe('Barcode Rendering', () => {
it('should render barcode with custom value', () => {
fixture.componentRef.setInput('value', '987654321');

View File

@@ -3,11 +3,18 @@ import { BarcodeDirective } from './barcode.directive';
/**
* Component wrapper for the barcode directive that provides an easier-to-use API.
* Renders a Code 128 barcode as an SVG element.
* Renders a Code 128 barcode as an SVG element within a container.
*
* @example
* ```html
* <shared-barcode [value]="'123456789'" [width]="2" [height]="100" />
* <shared-barcode
* [value]="'123456789'"
* [width]="2"
* [height]="100"
* [containerWidth]="'300px'"
* [containerHeight]="'88px'"
* [background]="'#ffffff'"
* />
* ```
*/
@Component({
@@ -23,12 +30,20 @@ import { BarcodeDirective } from './barcode.directive';
[height]="height()"
[displayValue]="displayValue()"
[lineColor]="lineColor()"
[background]="background()"
[background]="svgBackground()"
[fontSize]="fontSize()"
[margin]="margin()"
></svg>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[style.width]': 'containerWidth()',
'[style.height]': 'containerHeight()',
'[style.background-color]': 'background()',
'[style.display]': '"flex"',
'[style.align-items]': '"center"',
'[style.justify-content]': '"center"',
},
})
export class BarcodeComponent {
/**
@@ -62,10 +77,15 @@ export class BarcodeComponent {
lineColor = input<string>('#000000');
/**
* Background color (default: #ffffff)
* Background color of the container (default: #ffffff)
*/
background = input<string>('#ffffff');
/**
* Background color of the SVG barcode itself (default: transparent)
*/
svgBackground = input<string>('transparent');
/**
* Font size for the human-readable text (default: 20)
*/
@@ -75,4 +95,14 @@ export class BarcodeComponent {
* Margin around the barcode in pixels (default: 10)
*/
margin = input<number>(10);
/**
* Width of the container (default: 12.5rem)
*/
containerWidth = input<string>('12.5rem');
/**
* Height of the container (default: 5.5rem)
*/
containerHeight = input<string>('5.5rem');
}

View File

@@ -8,7 +8,10 @@ vi.mock('jsbarcode', () => ({
default: vi.fn((element, value, options) => {
// Simulate JsBarcode by adding a rect element to the SVG
if (element && element.tagName === 'svg') {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
const rect = document.createElementNS(
'http://www.w3.org/2000/svg',
'rect',
);
rect.setAttribute('data-value', value);
rect.setAttribute('data-format', options?.format || 'CODE128');
element.appendChild(rect);
@@ -168,11 +171,7 @@ describe('BarcodeDirective', () => {
standalone: true,
imports: [BarcodeDirective],
template: `
<svg
sharedBarcode
[value]="'123456'"
[lineColor]="'#FF0000'"
></svg>
<svg sharedBarcode [value]="'123456'" [lineColor]="'#FF0000'"></svg>
`,
})
class ColorTestComponent {}
@@ -190,11 +189,7 @@ describe('BarcodeDirective', () => {
standalone: true,
imports: [BarcodeDirective],
template: `
<svg
sharedBarcode
[value]="'123456'"
[background]="'#F0F0F0'"
></svg>
<svg sharedBarcode [value]="'123456'" [background]="'#F0F0F0'"></svg>
`,
})
class BackgroundTestComponent {}
@@ -206,6 +201,21 @@ describe('BarcodeDirective', () => {
expect(svg).toBeTruthy();
});
it('should render with transparent background by default', () => {
@Component({
standalone: true,
imports: [BarcodeDirective],
template: ` <svg sharedBarcode [value]="'123456'"></svg> `,
})
class DefaultBackgroundTestComponent {}
const bgFixture = TestBed.createComponent(DefaultBackgroundTestComponent);
bgFixture.detectChanges();
const svg = bgFixture.nativeElement.querySelector('svg');
expect(svg).toBeTruthy();
});
it('should render with custom font size', () => {
@Component({
standalone: true,

View File

@@ -56,9 +56,9 @@ export class BarcodeDirective implements OnDestroy {
lineColor = input<string>('#000000');
/**
* Background color (default: #ffffff)
* Background color of the SVG barcode itself (default: transparent)
*/
background = input<string>('#ffffff');
background = input<string>('transparent');
/**
* Font size for the human-readable text (default: 20)
@@ -112,14 +112,10 @@ export class BarcodeDirective implements OnDestroy {
format: this.format(),
}));
} catch (error) {
this.#logger.error(
'Failed to render barcode',
error as Error,
() => ({
value,
format: this.format(),
})
);
this.#logger.error('Failed to render barcode', error as Error, () => ({
value,
format: this.format(),
}));
}
}
}