Skip to content

Commit d89f2a5

Browse files
authored
Merge pull request #276 from qualcomm/olaf/fix/tag-icon-size-selected
feat(tag): support controlled selected state
2 parents e64f381 + 6466664 commit d89f2a5

14 files changed

Lines changed: 498 additions & 71 deletions

File tree

packages/common/qds-core/src/tag/qds-tag.css

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.qui-tag__root {
2-
> span {
2+
> span:not(.qui-tag__icon) {
33
text-box: cap alphabetic;
44
}
55

@@ -20,6 +20,7 @@
2020
user-select: none;
2121

2222
--tag-size: var(--sizing-90);
23+
--tag-icon-size: var(--sizing-60);
2324
--tag-radius: var(--border-radius-md);
2425
--tag-bg-base: transparent;
2526
--tag-border-base: var(--color-interactive-border-neutral-idle);
@@ -35,7 +36,15 @@
3536
}
3637
&[data-size="lg"] {
3738
--tag-size: var(--sizing-100);
39+
--tag-icon-size: var(--sizing-70);
3840
padding-inline: var(--spacing-80);
41+
42+
> .qui-tag__icon[data-tag-part="start-icon"] {
43+
margin-inline-end: var(--spacing-70);
44+
}
45+
> .qui-tag__icon[data-tag-part="end-icon"] {
46+
margin-inline-start: var(--spacing-70);
47+
}
3948
}
4049

4150
&[data-shape="rounded"] {
@@ -232,23 +241,36 @@
232241

233242
.qui-tag__icon {
234243
color: var(--icon-color);
244+
245+
/* For when the icon is declared as a node, i.e. `<Plus />` */
246+
.lucide {
247+
display: block;
248+
height: var(--icon-size);
249+
stroke: currentColor;
250+
stroke-linecap: round;
251+
stroke-linejoin: round;
252+
width: var(--icon-size);
253+
}
254+
235255
&[data-tag-part="start-icon"] {
236256
margin-inline-end: var(--spacing-50);
237257
}
238-
&[data-size="lg"][data-tag-part="start-icon"] {
239-
margin-inline-end: var(--spacing-70);
240-
}
241258
&[data-tag-part="end-icon"] {
242259
margin-inline-start: var(--spacing-50);
243260
}
244-
&[data-size="lg"][data-tag-part="end-icon"] {
245-
margin-inline-start: var(--spacing-70);
246-
}
247261
.qui-tag__dismiss-button > &[data-tag-part="end-icon"] {
248262
margin-inline-start: 0;
249263
}
250264
}
251265

266+
/*
267+
* Overrides .qui-icon__root's own [data-size] mapping when the
268+
* icon class lands directly on the SVG.
269+
*/
270+
.qui-tag__root .qui-tag__icon[data-tag-part] {
271+
--icon-size: var(--tag-icon-size);
272+
}
273+
252274
.qui-tag__dismiss-button {
253275
align-items: center;
254276
background: transparent;
@@ -279,15 +301,4 @@
279301
pointer-events: none;
280302
--icon-color: var(--color-utility-disabled-icon);
281303
}
282-
283-
&[data-size] {
284-
.qui-tag__icon[data-tag-part$="-icon"] {
285-
--icon-size: var(--sizing-60);
286-
}
287-
}
288-
&[data-size="lg"] {
289-
.qui-tag__icon[data-tag-part$="-icon"] {
290-
--icon-size: var(--sizing-70);
291-
}
292-
}
293304
}

packages/common/qds-core/src/tag/tag.api.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
22
// SPDX-License-Identifier: BSD-3-Clause-Clear
33

4-
import {booleanDataAttr} from "@qualcomm-ui/utils/attributes"
4+
import {booleanAriaAttr, booleanDataAttr} from "@qualcomm-ui/utils/attributes"
55
import type {PropNormalizer} from "@qualcomm-ui/utils/machine"
66

77
import {tagAnatomy} from "./tag.anatomy"
@@ -22,6 +22,7 @@ export function createQdsTagApi(
2222
normalize: PropNormalizer,
2323
): QdsTagApi {
2424
const size = props.size || "md"
25+
const selected = props.variant === "selectable" && props.selected
2526

2627
function isInteractiveVariant(): boolean {
2728
return props.variant === "link" || props.variant === "selectable"
@@ -32,7 +33,7 @@ export function createQdsTagApi(
3233
className: tagClasses.root,
3334
"data-disabled": booleanDataAttr(props.disabled),
3435
"data-emphasis": props.emphasis || "outline-brand",
35-
"data-selected": booleanDataAttr(props.selected),
36+
"data-selected": booleanDataAttr(selected),
3637
"data-shape": props.shape || props.radius || "square",
3738
"data-size": size,
3839
"data-variant": props.variant,
@@ -54,19 +55,25 @@ export function createQdsTagApi(
5455
return normalize.element({
5556
...parts.endIcon,
5657
className: tagClasses.icon,
57-
"data-size": size,
5858
})
5959
},
6060
getRootBindings(): QdsTagRootBindings {
61-
return isInteractiveVariant()
62-
? normalize.button({...commonBindings, disabled: props.disabled})
63-
: normalize.element(commonBindings)
61+
if (!isInteractiveVariant()) {
62+
return normalize.element(commonBindings)
63+
}
64+
return normalize.button({
65+
...commonBindings,
66+
"aria-pressed":
67+
props.variant === "selectable"
68+
? booleanAriaAttr(selected)
69+
: undefined,
70+
disabled: props.disabled,
71+
})
6472
},
6573
getStartIconBindings(): QdsTagStartIconBindings {
6674
return normalize.element({
6775
...parts.startIcon,
6876
className: tagClasses.icon,
69-
"data-size": size,
7077
})
7178
},
7279
isInteractiveVariant,

packages/common/qds-core/src/tag/tag.types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
// SPDX-License-Identifier: BSD-3-Clause-Clear
33

44
import type {AnatomyPart, AnatomyPartName} from "@qualcomm-ui/utils/anatomy"
5-
import type {BooleanDataAttr} from "@qualcomm-ui/utils/attributes"
5+
import type {
6+
BooleanAriaAttr,
7+
BooleanDataAttr,
8+
} from "@qualcomm-ui/utils/attributes"
69

710
import type {tagAnatomy} from "./tag.anatomy"
811
import type {tagClasses} from "./tag.classes"
@@ -98,6 +101,7 @@ export interface QdsTagSpanRootBindings extends Part<"root"> {
98101
}
99102

100103
export interface QdsTagButtonRootBindings extends Part<"root"> {
104+
"aria-pressed"?: BooleanAriaAttr
101105
className: TagClasses["root"]
102106
"data-disabled": BooleanDataAttr
103107
"data-emphasis": QdsTagEmphasis
@@ -113,12 +117,10 @@ export type QdsTagRootBindings =
113117

114118
export interface QdsTagStartIconBindings extends Part<"startIcon"> {
115119
className: TagClasses["icon"]
116-
"data-size": QdsTagSize
117120
}
118121

119122
export interface QdsTagEndIconBindings extends Part<"endIcon"> {
120123
className: TagClasses["icon"]
121-
"data-size": QdsTagSize
122124
}
123125

124126
export interface QdsTagDismissButtonBindings extends Part<"dismissButton"> {

packages/debug-apps/angular-csr/src/app/components/tag.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Component} from "@angular/core"
22

3+
import {TagControlledDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-controlled-demo"
34
import {TagEmphasisDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-emphasis-demo"
45
import {TagIconsDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-icons-demo"
56
import {TagShapeDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-shape-demo"
@@ -9,6 +10,7 @@ import {TagVariantsDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/
910

1011
@Component({
1112
imports: [
13+
TagControlledDemo,
1214
TagEmphasisDemo,
1315
TagIconsDemo,
1416
TagShapeDemo,
@@ -19,6 +21,12 @@ import {TagVariantsDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/
1921
selector: "app-tag",
2022
template: `
2123
<div class="container">
24+
<div class="section">
25+
<h2 class="section-title">Controlled</h2>
26+
<div class="demo-container">
27+
<tag-controlled-demo />
28+
</div>
29+
</div>
2230
<div class="section">
2331
<h2 class="section-title">Emphasis</h2>
2432
<div class="demo-container">

packages/debug-apps/angular-ssr/src/app/components/tag.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Component} from "@angular/core"
22

3+
import {TagControlledDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-controlled-demo"
34
import {TagEmphasisDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-emphasis-demo"
45
import {TagIconsDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-icons-demo"
56
import {TagShapeDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/tag-shape-demo"
@@ -9,6 +10,7 @@ import {TagVariantsDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/
910

1011
@Component({
1112
imports: [
13+
TagControlledDemo,
1214
TagEmphasisDemo,
1315
TagIconsDemo,
1416
TagShapeDemo,
@@ -19,6 +21,12 @@ import {TagVariantsDemo} from "@qualcomm-ui/angular-docs/components+/tag+/demos/
1921
selector: "app-tag",
2022
template: `
2123
<div class="container">
24+
<div class="section">
25+
<h2 class="section-title">Controlled</h2>
26+
<div class="demo-container">
27+
<tag-controlled-demo />
28+
</div>
29+
</div>
2230
<div class="section">
2331
<h2 class="section-title">Emphasis</h2>
2432
<div class="demo-container">

packages/docs/angular-docs/src/routes/components+/tag+/_tag.mdx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ import {TagDirective} from "@qualcomm-ui/angular/tag"
2424
### Icons
2525

2626
::: terms
27-
leading icon
28-
trailing icon
29-
prefix
30-
suffix
27+
leading icon, trailing icon, prefix, suffix
3128
:::
3229

3330
Icons are supplied using the [startIcon](./#startIcon) and [endIcon](./#endIcon) properties.
@@ -37,9 +34,7 @@ Icons are supplied using the [startIcon](./#startIcon) and [endIcon](./#endIcon)
3734
### Variants
3835

3936
::: terms
40-
clickable
41-
removable
42-
closable
37+
clickable, removable, closable
4338
:::
4439

4540
Choose from three [variants](./#variant): `link`, `selectable` and `dismissable`.
@@ -49,6 +44,16 @@ The `dismissable` variant and tags without a variant are non-interactive and sho
4944

5045
<QdsDemo name="TagVariantsDemo" />
5146

47+
### Controlled State
48+
49+
::: terms
50+
signal, state management, two-way binding
51+
:::
52+
53+
Set the initial value using the [defaultSelected](./#defaultSelected) input, or use the [selected](./#selected) input together with the [selectedChange](./#selectedChange) output to control the value manually. The pair supports `[(selected)]` two-way binding. Only applicable when [variant](./#variant) is `selectable`.
54+
55+
<QdsDemo name="TagControlledDemo" />
56+
5257
### Colors
5358

5459
::: terms
@@ -62,9 +67,7 @@ Control the visual emphasis with different colors via the [emphasis](./#emphasis
6267
### Sizes
6368

6469
::: terms
65-
small
66-
medium
67-
large
70+
small, medium, large
6871
:::
6972

7073
Choose from three [sizes](./#size): `sm`, `md` and `lg`. The default size is `md`.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {Component, signal} from "@angular/core"
2+
import {Check, Plus} from "lucide-angular"
3+
4+
import {TagDirective} from "@qualcomm-ui/angular/tag"
5+
import {provideIcons} from "@qualcomm-ui/angular-core/lucide"
6+
7+
@Component({
8+
imports: [TagDirective],
9+
providers: [provideIcons({Check, Plus})],
10+
selector: "tag-controlled-demo",
11+
template: `
12+
<div class="flex flex-col items-start gap-2">
13+
<!-- preview -->
14+
<button
15+
q-tag
16+
variant="selectable"
17+
[startIcon]="selected() ? 'Check' : 'Plus'"
18+
[(selected)]="selected"
19+
>
20+
{{ selected() ? "Subscribed" : "Subscribe" }}
21+
</button>
22+
<!-- preview -->
23+
</div>
24+
`,
25+
})
26+
export class TagControlledDemo {
27+
readonly selected = signal<boolean>(false)
28+
}

packages/docs/react-docs/src/routes/components+/tag+/_tag.mdx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ import {Tag} from "@qualcomm-ui/react/tag"
2222
### Icons
2323

2424
::: terms
25-
leading icon
26-
trailing icon
27-
prefix
28-
suffix
25+
leading icon, trailing icon, prefix, suffix
2926
:::
3027

3128
Icons are supplied using the [startIcon](./#startIcon) and [endIcon](./#endIcon) props.
@@ -35,15 +32,27 @@ Icons are supplied using the [startIcon](./#startIcon) and [endIcon](./#endIcon)
3532
### Variants
3633

3734
::: terms
38-
clickable
39-
removable
40-
closable
35+
clickable, removable, closable
4136
:::
4237

4338
Choose from three [variants](./#variant): `link`, `selectable` and `dismissable`.
4439

4540
<Demo name="TagVariantsDemo" component={demos.TagVariantsDemo} />
4641

42+
### Controlled State
43+
44+
::: terms
45+
useState, state management, two-way binding
46+
:::
47+
48+
Set the initial value using the [defaultSelected](./#defaultSelected) prop, or use [selected](./#selected) and [onSelectedChange](./#onSelectedChange) to control the value manually. These props follow our [controlled state](/patterns/controlled-state) pattern. Only applicable when [variant](./#variant) is `selectable`.
49+
50+
<Demo
51+
expanded
52+
name="TagControlledDemo"
53+
component={demos.TagControlledDemo}
54+
/>
55+
4756
### Colors
4857

4958
::: terms
@@ -57,9 +66,7 @@ Control the visual emphasis with different colors via the [emphasis](./#emphasis
5766
### Sizes
5867

5968
::: terms
60-
small
61-
medium
62-
large
69+
small, medium, large
6370
:::
6471

6572
Choose from three [sizes](./#size): `sm`, `md` and `lg`. The default size is `md`.

packages/docs/react-docs/src/routes/components+/tag+/demos/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./tag-controlled-demo"
12
export * from "./tag-emphasis-demo"
23
export * from "./tag-icons-demo"
34
export * from "./tag-shape-demo"

0 commit comments

Comments
 (0)