Tweak widget and overall app styling Aiming for a dark, dense, professional feel.
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss index 7364327..15b77d7 100644 --- a/ui/src/assets/perfetto.scss +++ b/ui/src/assets/perfetto.scss
@@ -44,6 +44,7 @@ @import "widgets/checkbox"; @import "widgets/chip"; @import "widgets/code_snippet"; +@import "widgets/control_group"; @import "widgets/details_shell"; @import "widgets/drawer_panel"; @import "widgets/editor";
diff --git a/ui/src/assets/theme.scss b/ui/src/assets/theme.scss index 5cd9414..d6430cd 100644 --- a/ui/src/assets/theme.scss +++ b/ui/src/assets/theme.scss
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -$border-radius: 2px; +$border-radius: 3px; $border-radius-large: 4px; $anim-timing: 150ms cubic-bezier(0.4, 0, 0.2, 1); @@ -74,6 +74,7 @@ @mixin focus { outline: 2px solid var(--pf-color-accent); + outline-offset: -2px; } @mixin transition($time: 0.1s) {
diff --git a/ui/src/assets/theme_provider.scss b/ui/src/assets/theme_provider.scss index 45d740a..ec43ab1 100644 --- a/ui/src/assets/theme_provider.scss +++ b/ui/src/assets/theme_provider.scss
@@ -25,6 +25,7 @@ scrollbar-color: var(--pf-color-border) var(--pf-color-background); /* thumb color track color */ &--light { + --pf-color-void: white; --pf-color-background: white; --pf-color-background-secondary: #edf0f1; --pf-color-background-tertiary: #e3e9eb; @@ -34,6 +35,12 @@ --pf-color-text-disabled: gray; --pf-color-border: #ccc; --pf-color-border-secondary: #e0e0e0; + --pf-color-border-controls: #cfcfcf; + + --pf-color-background-controls: #f0f0f0; + --pf-color-control-highlight: rgba(255, 255, 255, 0.2); + --pf-color-control-shadow: rgba(0, 0, 0, 0.06); + --pf-color-text-hint: #808080; --pf-color-box-shadow: rgba(0, 0, 0, 0.2); --pf-color-neutral: gray; @@ -41,7 +48,7 @@ --pf-color-text-on-accent: white; --pf-color-highlight: #ffe263; - --pf-color-primary: #3d5688; + --pf-color-primary: #2759bc; --pf-color-text-on-primary: white; --pf-color-danger: rgb(202, 38, 38); --pf-color-text-on-danger: white; @@ -75,30 +82,38 @@ } &--dark { - --pf-color-background: #232426; - --pf-color-background-secondary: #383a3e; + --pf-color-void: #101010; + --pf-color-background: #202020; + --pf-color-background-secondary: #303030; --pf-color-background-tertiary: #4a4c52; --pf-color-interactive-base: white; - --pf-color-text: #dce0e2; - --pf-color-text-muted: #a0a2a5; + --pf-color-text: #d0d0d0; + --pf-color-text-muted: #a0a0a0; + --pf-color-text-hint: #707070; --pf-color-text-disabled: #8d8d8e; - --pf-color-border: #626568; - --pf-color-border-secondary: #404042; - --pf-color-text-hint: #9aa0a6; + + --pf-color-background-controls: #303030; + --pf-color-control-highlight: rgba(255, 255, 255, 0.06); + --pf-color-control-shadow: rgba(0, 0, 0, 0.2); + + --pf-color-border: #505050; + --pf-color-border-secondary: #303030; + --pf-color-border-controls: #101010; + --pf-color-box-shadow: rgba(0, 0, 0, 0.4); --pf-color-neutral: gray; - --pf-color-accent: #2667e7; + --pf-color-accent: #366bd5; --pf-color-text-on-accent: white; --pf-color-highlight: #5f4d06; - --pf-color-primary: #7197e3; - --pf-color-text-on-primary: #333; + --pf-color-primary: #366bd5; + --pf-color-text-on-primary: #eeeeee; --pf-color-danger: rgb(230, 90, 90); - --pf-color-text-on-danger: #333; + --pf-color-text-on-danger: #222; --pf-color-success: rgb(99, 192, 99); - --pf-color-text-on-success: #333; + --pf-color-text-on-success: #222; --pf-color-warning: rgb(244, 188, 67); - --pf-color-text-on-warning: #333; + --pf-color-text-on-warning: #222; // Chart color palette for data visualization (brighter for dark mode) --pf-chart-color-1: #5e97f6; // Lighter Blue
diff --git a/ui/src/assets/widgets/button.scss b/ui/src/assets/widgets/button.scss index 56c49ce..de7624c 100644 --- a/ui/src/assets/widgets/button.scss +++ b/ui/src/assets/widgets/button.scss
@@ -24,7 +24,8 @@ &:active, &.pf-active { - background: color_active($intent-color); + background: color-mix(in srgb, $intent-color, currentColor -8%); + box-shadow: none; } &[disabled] { @@ -50,14 +51,17 @@ .pf-button { font-family: var(--pf-font-compact); - line-height: 1; + // line-height: 1; user-select: none; - border-radius: $border-radius; - padding: 4px 8px; + border-radius: 3px; + padding: 2px 8px; white-space: nowrap; min-width: max-content; position: relative; + display: inline-flex; + align-items: baseline; + &--loading { // The button is unclickable when loading pointer-events: none; @@ -90,16 +94,14 @@ overflow: hidden; } - &--rounded { - border-radius: 100px; - } - & > .pf-left-icon { margin-right: 6px; // Make some room between the icon and label + align-self: center; } & > .pf-right-icon { margin-left: 6px; // Make some room between the icon and label + align-self: center; } &__tooltip { @@ -128,7 +130,7 @@ margin: 0; } - padding: 4px; + padding: 3px; &.pf-compact { padding: 0; @@ -148,47 +150,17 @@ // From here on in we just describe the colors &--filled { - background: color-mix( - in srgb, - transparent, - var(--pf-color-interactive-base) 10% + background: var(--pf-color-background-controls); + box-shadow: + var(--pf-color-control-highlight) 0px 1px 0px inset, + var(--pf-color-control-shadow) 0px -1px 0px inset; + border: solid 1px var(--pf-color-border-controls); + + @include filled-color-scheme( + var(--pf-color-background-controls), + var(--pf-color-text) ); - &:hover { - background: color-mix( - in srgb, - transparent, - var(--pf-color-interactive-base) 15% - ); - } - - &.pf-active, - &:active { - background: color-mix( - in srgb, - transparent, - var(--pf-color-interactive-base) 20% - ); - } - - &[disabled] { - &:hover { - background: color-mix( - in srgb, - transparent, - var(--pf-color-interactive-base) 10% - ); // Reset to the default background - } - - &.pf-active { - background: color-mix( - in srgb, - transparent, - var(--pf-color-interactive-base) 40% - ); - } - } - &.pf-intent-primary { @include filled-color-scheme( var(--pf-color-primary), @@ -218,12 +190,7 @@ } } - &--outlined { - border: solid 1px var(--pf-color-border); - } - - &--minimal, - &--outlined { + &--minimal { &:hover { background: color_hover(transparent); }
diff --git a/ui/src/assets/widgets/chip.scss b/ui/src/assets/widgets/chip.scss index 95bf64c..c467b62 100644 --- a/ui/src/assets/widgets/chip.scss +++ b/ui/src/assets/widgets/chip.scss
@@ -39,10 +39,6 @@ overflow: hidden; } - &--rounded { - border-radius: 100px; - } - &__icon { align-self: center; margin-right: 6px;
diff --git a/ui/src/assets/widgets/control_group.scss b/ui/src/assets/widgets/control_group.scss new file mode 100644 index 0000000..70f8483 --- /dev/null +++ b/ui/src/assets/widgets/control_group.scss
@@ -0,0 +1,49 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@import "../theme"; + +.pf-control-group { + display: flex; + align-items: stretch; + + $items: ".pf-text-input, .pf-select, .pf-button"; + + // Normalize sizing so all items match height and text aligns. + > :is(#{$items}) { + border-radius: 0; + // line-height: 1; + padding: 2px 6px; + + &:not(:first-child) { + margin-left: -1px; + } + + &:focus, + &:focus-within { + z-index: 1; + } + } + + // Restore rounded corners on the outer edges only. + > :is(#{$items}):first-child { + border-top-left-radius: $border-radius; + border-bottom-left-radius: $border-radius; + } + + > :is(#{$items}):last-child { + border-top-right-radius: $border-radius; + border-bottom-right-radius: $border-radius; + } +}
diff --git a/ui/src/assets/widgets/empty_state.scss b/ui/src/assets/widgets/empty_state.scss index d356cac..b901183 100644 --- a/ui/src/assets/widgets/empty_state.scss +++ b/ui/src/assets/widgets/empty_state.scss
@@ -22,10 +22,10 @@ user-select: none; margin: auto; justify-content: center; - color: var(--pf-color-text-muted); + color: var(--pf-color-text-hint); &__main-icon { - font-size: 5em; // Size of the icon is relative to the font size. + font-size: 3em; // Size of the icon is relative to the font size. margin-bottom: 12px; } @@ -42,7 +42,7 @@ &__title { text-align: center; - font-weight: bolder; + font-weight: 500; margin-bottom: 8px; }
diff --git a/ui/src/assets/widgets/multiselect.scss b/ui/src/assets/widgets/multiselect.scss index aaa91d6..263b2d6 100644 --- a/ui/src/assets/widgets/multiselect.scss +++ b/ui/src/assets/widgets/multiselect.scss
@@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Generic list with y-overflow, move me to a common file if we want to reuse. -.pf-list { - overflow-y: auto; +.pf-popup { + .pf-popup-content:has(.pf-multiselect-panel) { + padding: 0; + } } .pf-multi-select-fixed-size { @@ -22,22 +23,41 @@ } .pf-multiselect-panel { + .pf-search-bar { + padding: 4px; + padding-bottom: 0; + display: flex; + gap: 2px; + + .pf-search-box { + flex-grow: 1; + } + } + + .pf-bulk-actions { + padding: 4px; + border-bottom: solid 1px var(--pf-color-border); + font-size: var(--pf-font-size-s); + + .pf-button { + text-transform: uppercase; + } + } + display: flex; flex-direction: column; align-items: stretch; width: 280px; - & > .pf-search-bar { - margin-bottom: 8px; - display: flex; - & > .pf-search-box { - flex-grow: 1; - } - } + .pf-multiselect-item { display: block; // Put each item on a new line margin-top: 5px; } + .pf-multiselect-header { + .pf-button { + text-transform: uppercase; + } align-items: baseline; display: flex; position: sticky; @@ -53,8 +73,10 @@ margin-right: auto; } } + .pf-multiselect-container { - position: relative; - margin-bottom: 16px; + // position: relative; + overflow-y: auto; + padding-inline: 8px; } }
diff --git a/ui/src/assets/widgets/popup.scss b/ui/src/assets/widgets/popup.scss index 824b48f..c9a47bc 100644 --- a/ui/src/assets/widgets/popup.scss +++ b/ui/src/assets/widgets/popup.scss
@@ -33,7 +33,7 @@ .pf-popup { background: var(--pf-color-background); border: solid 1px var(--pf-color-border); - border-radius: $border-radius; + border-radius: 5px; box-shadow: 2px 2px 16px var(--pf-color-box-shadow); max-width: 350px; // Sensible default width for most popups @@ -41,7 +41,7 @@ // Ensures all content is rendered above the arrow position: relative; // Default padding set to some sensible value that works for most content - padding: 4px; + padding: 4px 6px; overflow: hidden; }
diff --git a/ui/src/assets/widgets/select.scss b/ui/src/assets/widgets/select.scss index 65e823c..806315a 100644 --- a/ui/src/assets/widgets/select.scss +++ b/ui/src/assets/widgets/select.scss
@@ -14,45 +14,57 @@ @import "../theme"; -// Select field styled to look similar to a text input with a thin underline. -// Inspired by matherial design. .pf-select { + display: inline-flex; + align-items: center; + position: relative; font-family: var(--pf-font-compact); font-size: inherit; - outline: none; // Disable the default outline - border: none; // Disable the default border - border-bottom: solid 1px var(--pf-color-border); // Thin underline - background: none; + background: var(--pf-color-void); color: inherit; - transition: - border $anim-timing, - box-shadow $anim-timing, - background $anim-timing; - // Round only the top corners to avoid rounding the edges of the underline - border-radius: $border-radius $border-radius 0 0; cursor: pointer; - - // Very opinionated min width for a select input - // ... any smaller and it stops looking like a select input! + border: solid 1px var(--pf-color-border-secondary); + border-radius: $border-radius; + padding: 2px 4px; + gap: 2px; min-width: 80px; &:hover { - background: color_hover(transparent); + border: solid 1px var(--pf-color-border); } - &:focus { - background: color_hover(transparent); - border-bottom: solid 1px var(--pf-color-primary); - - // The box-shadow thickens the bottom border, without adding to the height. - // This is the same technique used by materializecss: - // See https://materializecss.com/text-inputs.html - box-shadow: 0 1px 0 var(--pf-color-primary); + &:has(.pf-select__input:focus) { + // border: solid 1px var(--pf-color-primary); + @include focus; } - &[disabled] { + &:has(.pf-select__input[disabled]) { opacity: 0.5; pointer-events: none; cursor: not-allowed; } + + &__input { + appearance: none; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: inherit; + background: none; + border: none; + outline: none; + padding: 0; + margin: 0; + cursor: inherit; + flex: 1; + min-width: 0; + } + + &__chevron { + width: 12px; + height: 12px; + flex-shrink: 0; + pointer-events: none; + opacity: 0.6; + } }
diff --git a/ui/src/assets/widgets/tabs.scss b/ui/src/assets/widgets/tabs.scss index 8d63d91..4288f21 100644 --- a/ui/src/assets/widgets/tabs.scss +++ b/ui/src/assets/widgets/tabs.scss
@@ -22,7 +22,7 @@ &__tabs { display: flex; background-color: var(--pf-color-background-secondary); - border-bottom: 1px solid var(--pf-color-border); + border-bottom: 1px solid var(--pf-color-border-secondary); padding-inline: 3px; align-items: baseline; }
diff --git a/ui/src/assets/widgets/tag_input.scss b/ui/src/assets/widgets/tag_input.scss index cca5952..43039e1 100644 --- a/ui/src/assets/widgets/tag_input.scss +++ b/ui/src/assets/widgets/tag_input.scss
@@ -21,15 +21,11 @@ font-size: inherit; outline: none; // Disable the default outline border: none; // Disable the default border + background: var(--pf-color-void); border-bottom: solid 1px var(--pf-color-border); // Thin underline - background: none; - transition: - border $anim-timing, - box-shadow $anim-timing, - background $anim-timing; - - // Round only the top corners to avoid rounding the edges of the underline - border-radius: $border-radius $border-radius 0 0; + border: solid 1px var(--pf-color-border-secondary); + border-radius: $border-radius; + padding: 2px 4px; input { outline: none; @@ -48,16 +44,10 @@ // The gentle hover effect indicates this component is interactive &:hover { - background: color_hover(transparent); + border: solid 1px var(--pf-color-border); } &:focus-within { - background: color_hover(transparent); - border-bottom: solid 1px var(--pf-color-primary); - - // The box-shadow thickens the bottom border, without adding to the height. - // This is the same technique used by materializecss: - // See https://materializecss.com/text-inputs.html - box-shadow: 0 1px 0 var(--pf-color-primary); + border: solid 1px var(--pf-color-primary); } }
diff --git a/ui/src/assets/widgets/text_input.scss b/ui/src/assets/widgets/text_input.scss index ed11cb9..0767123 100644 --- a/ui/src/assets/widgets/text_input.scss +++ b/ui/src/assets/widgets/text_input.scss
@@ -17,19 +17,18 @@ .pf-text-input { display: inline-flex; align-items: baseline; - line-height: 1; + // line-height: 1; font-family: var(--pf-font-compact); font-size: inherit; border-bottom: solid 1px var(--pf-color-border); // Thin underline - background: none; + background: var(--pf-color-void); padding: 2px 4px; - transition: - border $anim-timing, - box-shadow $anim-timing, - background $anim-timing; gap: 4px; + border: solid 1px var(--pf-color-border-secondary); + border-radius: $border-radius; &__input { + appearance: none; font-size: inherit; line-height: inherit; font-family: inherit; @@ -48,25 +47,17 @@ &__left-icon { align-self: center; line-height: inherit; - margin-inline: 2px; + color: var(--pf-color-text-muted); } - // Round only the top corners to avoid rounding the edges of the underline - border-radius: $border-radius $border-radius 0 0; - // The gentle hover effect indicates this component is interactive &:hover { - background: color_hover(transparent); + border: solid 1px var(--pf-color-border); } &:has(.pf-text-input__input:focus) { - background: color_hover(transparent); - border-bottom: solid 1px var(--pf-color-primary); - - // The box-shadow thickens the bottom border, without adding to the height. - // This is the same technique used by materializecss: - // See https://materializecss.com/text-inputs.html - box-shadow: 0 1px 0 var(--pf-color-primary); + // border: solid 1px var(--pf-color-primary); + @include focus; } &:has(.pf-text-input__input[disabled]) {
diff --git a/ui/src/components/widgets/datagrid/datagrid.ts b/ui/src/components/widgets/datagrid/datagrid.ts index 152a0db..6fba141 100644 --- a/ui/src/components/widgets/datagrid/datagrid.ts +++ b/ui/src/components/widgets/datagrid/datagrid.ts
@@ -2106,7 +2106,6 @@ className: 'pf-visible-on-row-hover pf-datagrid__drilldown-button', icon: Icons.GoTo, - rounded: true, title: 'Drill down into this group', fillWidth: true, onclick: () => {
diff --git a/ui/src/core_plugins/dev.perfetto.FlagsPage/flags_page.ts b/ui/src/core_plugins/dev.perfetto.FlagsPage/flags_page.ts index 2e4ffad..e3a2724 100644 --- a/ui/src/core_plugins/dev.perfetto.FlagsPage/flags_page.ts +++ b/ui/src/core_plugins/dev.perfetto.FlagsPage/flags_page.ts
@@ -174,6 +174,7 @@ { trigger: m(Button, { icon: 'restore', + variant: ButtonVariant.Filled, label: 'Restore Defaults', }), },
diff --git a/ui/src/core_plugins/dev.perfetto.FlagsPage/plugins_page.ts b/ui/src/core_plugins/dev.perfetto.FlagsPage/plugins_page.ts index d430828..ecacd8e 100644 --- a/ui/src/core_plugins/dev.perfetto.FlagsPage/plugins_page.ts +++ b/ui/src/core_plugins/dev.perfetto.FlagsPage/plugins_page.ts
@@ -133,6 +133,7 @@ icon: 'restore', disabled: !anyNonDefaults, label: 'Restore Defaults', + variant: ButtonVariant.Filled, title: anyNonDefaults ? 'Restore all plugins to their default enabled/disabled state' : 'All plugins are in their default state',
diff --git a/ui/src/core_plugins/dev.perfetto.SettingsPage/settings_page.ts b/ui/src/core_plugins/dev.perfetto.SettingsPage/settings_page.ts index 99a5e8f..eaac138 100644 --- a/ui/src/core_plugins/dev.perfetto.SettingsPage/settings_page.ts +++ b/ui/src/core_plugins/dev.perfetto.SettingsPage/settings_page.ts
@@ -77,6 +77,7 @@ trigger: m(Button, { icon: 'restore', label: 'Restore Defaults', + variant: ButtonVariant.Filled, }), }, m(
diff --git a/ui/src/frontend/omnibox.ts b/ui/src/frontend/omnibox.ts index 2a70d88..5626860 100644 --- a/ui/src/frontend/omnibox.ts +++ b/ui/src/frontend/omnibox.ts
@@ -382,12 +382,11 @@ m(Chip, { className: 'pf-omnibox__source', label: source, - rounded: true, compact: true, intent: Intent.Primary, }), m('span.pf-title', this.renderTitle(displayName)), - label && m(Chip, {className: 'pf-omnibox__tag', label, rounded: true}), + label && m(Chip, {className: 'pf-omnibox__tag', label}), rightContent, ); }
diff --git a/ui/src/frontend/timeline_page/timeline_toolbar.ts b/ui/src/frontend/timeline_page/timeline_toolbar.ts index c52dbf6..97592b6 100644 --- a/ui/src/frontend/timeline_page/timeline_toolbar.ts +++ b/ui/src/frontend/timeline_page/timeline_toolbar.ts
@@ -96,7 +96,6 @@ rightIcon: 'arrow_drop_down', icon: 'check', compact: true, - rounded: true, label: `${selection.tracks.length} ${selection.tracks.length === 1 ? 'track' : 'tracks'}`, variant: ButtonVariant.Filled, intent: Intent.Primary,
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/function_list.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/function_list.ts index 67a5f51..04faa3d 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/function_list.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/function_list.ts
@@ -317,7 +317,6 @@ const isDisabled = disabledTags.has(tag); return m(Chip, { label: tag, - rounded: true, intent: isSelected ? Intent.Primary : undefined, className: classNames( 'pf-tag-chip',
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/node_panel.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/node_panel.ts index 4dc5887..9f70004 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/node_panel.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/node_panel.ts
@@ -186,7 +186,7 @@ for (const btn of buttons) { const attrs = { onclick: btn.onclick, - variant: btn.variant ?? ButtonVariant.Outlined, // Default to Outlined + variant: btn.variant ?? ButtonVariant.Minimal, // Default to Outlined label: btn.label, icon: btn.icon, compact: btn.compact,
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/add_columns_node.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/add_columns_node.ts index e12f9c7..89fed33 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/add_columns_node.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/add_columns_node.ts
@@ -519,31 +519,31 @@ onclick: () => this.showJoinModal(), variant: hasConnectedNode ? ButtonVariant.Filled - : ButtonVariant.Outlined, + : ButtonVariant.Minimal, }), m(Button, { label: 'Expression', icon: 'functions', onclick: () => this.showExpressionModal(), - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, }), m(Button, { label: 'Switch', icon: 'alt_route', onclick: () => this.showSwitchModal(), - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, }), m(Button, { label: 'If', icon: 'rule', onclick: () => this.showIfModal(), - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, }), m(Button, { label: 'From args', icon: 'list', onclick: () => this.showArgsModal(), - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, disabled: !hasArgSetId, title: hasArgSetId ? 'Add a column from args' @@ -553,7 +553,7 @@ label: 'Apply function', icon: 'function', onclick: () => this.showFunctionModal(), - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, title: 'Apply a stdlib function to create a new column', }), ), @@ -1202,7 +1202,7 @@ m(Button, { label: 'Edit', icon: 'edit', - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, compact: true, onclick: () => this.showJoinModal(), }), @@ -1269,7 +1269,7 @@ m(Button, { label: 'Edit', icon: 'edit', - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, compact: true, onclick: () => { if (col.type === 'switch') {
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/filter_node.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/filter_node.ts index ad442fc..f578fe4 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/filter_node.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/filter_node.ts
@@ -197,7 +197,7 @@ content: m(Button, { label: 'Edit WHERE clause', icon: 'edit', - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, onclick: () => this.showSqlExpressionModal(), }), });
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/metrics_node.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/metrics_node.ts index 281104f..217390e 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/metrics_node.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/metrics_node.ts
@@ -270,7 +270,7 @@ icon: 'download', onclick: () => this.showExportModal(), disabled: !canExport, - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, className: 'pf-metrics-v2-export-button', }), ),
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/sources/timerange_source.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/sources/timerange_source.ts index 621d6e8..b247488 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/sources/timerange_source.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/sources/timerange_source.ts
@@ -301,7 +301,7 @@ m(Button, { label: 'Update from Timeline', onclick: () => this.updateFromSelection(), - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, }), ), });
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/visualisation_node.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/visualisation_node.ts index bdded93..8dc45e6 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/visualisation_node.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/nodes/visualisation_node.ts
@@ -459,7 +459,6 @@ key: `filter-${i}`, label: formatFilterValue(filter), compact: true, - rounded: true, removable: true, onRemove: () => { // Find the index in the full filters array
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/operations/filter.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/operations/filter.ts index 2e9fdde..4b41bec 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/operations/filter.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/operations/filter.ts
@@ -732,7 +732,6 @@ }, m(Chip, { label, - rounded: true, removable: effectiveOnRemove !== undefined, intent: isEnabled ? Intent.Primary : Intent.None, onpointerdown: (e: PointerEvent) => {
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/table_list.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/table_list.ts index 960392b..8212100 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/table_list.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/table_list.ts
@@ -506,7 +506,6 @@ const isDisabled = disabledTags.has(tag); return m(Chip, { label: tag, - rounded: true, intent: isSelected ? Intent.Primary : undefined, className: classNames( 'pf-tag-chip',
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts index d678cf8..7ac0da7 100644 --- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts +++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts
@@ -58,7 +58,6 @@ onclick: attrs.onclick, disabled: attrs.disabled, variant: ButtonVariant.Filled, - rounded: true, iconFilled: true, intent: Intent.Primary, className: classNames('pf-qb-round-action-button', attrs.className), @@ -175,7 +174,7 @@ buttons.map((btn) => m(Button, { label: btn.label, - variant: btn.variant ?? ButtonVariant.Outlined, + variant: btn.variant ?? ButtonVariant.Minimal, onclick: btn.onclick, }), ), @@ -268,14 +267,14 @@ label: action.label, icon: action.icon, title: action.title, - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, compact: true, onclick: action.onclick, } : { icon: action.icon ?? 'help', title: action.title, - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, compact: true, onclick: action.onclick, }; @@ -317,7 +316,7 @@ m(Button, { label: btn.active ? `${btn.label} ✓` : btn.label, icon: btn.icon, - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, onclick: btn.onclick, }), ), @@ -581,7 +580,7 @@ label: btn.label, icon: btn.icon, onclick: btn.onclick, - variant: btn.variant ?? ButtonVariant.Outlined, + variant: btn.variant ?? ButtonVariant.Minimal, disabled: btn.disabled, }), ), @@ -737,7 +736,6 @@ options: MultiSelectOption[]; onChange: (diffs: MultiSelectDiff[]) => void; showNumSelected?: boolean; - repeatCheckedItemsAtTop?: boolean; compact?: boolean; } @@ -958,13 +956,13 @@ m(Button, { label: 'Select All', onclick: onSelectAll, - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, compact: true, }), m(Button, { label: 'Deselect All', onclick: onDeselectAll, - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Minimal, compact: true, }), );
diff --git a/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts b/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts index 89e04ba..1ef3e3d 100644 --- a/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts +++ b/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts
@@ -20,9 +20,8 @@ import {Spinner} from '../../widgets/spinner'; import {assertExists, assertUnreachable} from '../../base/assert'; import {Trace} from '../../public/trace'; -import {SegmentedButtons} from '../../widgets/segmented_buttons'; import {Editor} from '../../widgets/editor'; -import {Button, ButtonVariant} from '../../widgets/button'; +import {Button, ButtonGroup, ButtonVariant} from '../../widgets/button'; import {Intent} from '../../widgets/common'; import {CodeSnippet} from '../../widgets/code_snippet'; import {Callout} from '../../widgets/callout'; @@ -480,13 +479,21 @@ '.pf-metricsv2-result', m( '.pf-metricsv2-result__header', - m(SegmentedButtons, { - options: [{label: 'Table'}, {label: 'JSON'}], - selectedOption: viewMode === 'table' ? 0 : 1, - onOptionSelected: (num) => { - onViewModeChange(num === 0 ? 'table' : 'json'); - }, - }), + m( + ButtonGroup, + m(Button, { + label: 'Table', + onclick: () => onViewModeChange('table'), + active: viewMode === 'table', + variant: ButtonVariant.Filled, + }), + m(Button, { + label: 'JSON', + onclick: () => onViewModeChange('json'), + active: viewMode === 'json', + variant: ButtonVariant.Filled, + }), + ), ), viewMode === 'json' ? m(CodeSnippet, {language: 'json', text}) @@ -614,7 +621,8 @@ ), m(Button, { label: 'Run', - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Filled, + intent: Intent.Primary, icon: 'play_arrow', onclick: () => this.runQuery(), }), @@ -674,30 +682,48 @@ '.pf-metrics-page', m( '', - m(SegmentedButtons, { - options: [{label: 'Metric v1'}, {label: 'Metric v2'}], - selectedOption: this.mode === 'V1' ? 0 : 1, - onOptionSelected: (num) => { - if (num === 0) { - this.mode = 'V1'; - } else { - this.mode = 'V2'; - } - }, - }), + m( + ButtonGroup, + m(Button, { + label: 'Metric v1', + active: this.mode === 'V1', + onclick: () => (this.mode = 'V1'), + variant: ButtonVariant.Filled, + }), + m(Button, { + label: 'Metric v2', + active: this.mode === 'V2', + onclick: () => (this.mode = 'V2'), + variant: ButtonVariant.Filled, + }), + ), ), this.mode === 'V2' && m( '', - m(SegmentedButtons, { - options: [{label: 'Metric Spec'}, {label: 'Full Summary'}], - selectedOption: this.v2Mode === 'metric-spec' ? 0 : 1, - onOptionSelected: (num) => { - this.v2Mode = num === 0 ? 'metric-spec' : 'full-trace-summary'; - this.showV2MetricExample = false; - this.v2Result = undefined; - }, - }), + m( + ButtonGroup, + m(Button, { + label: 'Metric Spec', + active: this.v2Mode === 'metric-spec', + onclick: () => { + this.v2Mode = 'metric-spec'; + this.showV2MetricExample = false; + this.v2Result = undefined; + }, + variant: ButtonVariant.Filled, + }), + m(Button, { + label: 'Full Summary', + active: this.v2Mode === 'full-trace-summary', + onclick: () => { + this.v2Mode = 'full-trace-summary'; + this.showV2MetricExample = false; + this.v2Result = undefined; + }, + variant: ButtonVariant.Filled, + }), + ), ), this.mode === 'V1' && m(MetricV1Fetcher, { @@ -706,7 +732,7 @@ this.mode === 'V2' && [ m(Button, { label: 'Load example', - variant: ButtonVariant.Outlined, + variant: ButtonVariant.Filled, onclick: () => { this.showV2MetricExample = true; this.fetcherGeneration++;
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/chrome.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/chrome.ts index 1d8a1fa..07be702 100644 --- a/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/chrome.ts +++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/chrome.ts
@@ -480,7 +480,6 @@ }, m(MultiSelect, { options: categoriesOptions, - repeatCheckedItemsAtTop: false, fixedSize: false, onChange: (diffs: MultiSelectDiff[]) => { diffs.forEach(({id, checked}) => @@ -500,7 +499,6 @@ }, m(MultiSelect, { options: slowCategoriesOptions, - repeatCheckedItemsAtTop: false, fixedSize: false, onChange: (diffs: MultiSelectDiff[]) => { diffs.forEach(({id, checked}) =>
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/control_group_demo.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/control_group_demo.ts new file mode 100644 index 0000000..27f7f1c --- /dev/null +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/control_group_demo.ts
@@ -0,0 +1,49 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import m from 'mithril'; +import {Button, ButtonVariant} from '../../../widgets/button'; +import {ControlGroup} from '../../../widgets/control_group'; +import {Select} from '../../../widgets/select'; +import {TextInput} from '../../../widgets/text_input'; + +export function renderControlGroup(): m.Children { + return [ + m( + '.pf-widget-intro', + m('h1', 'ControlGroup'), + m( + 'p', + 'A horizontal flex layout for grouping related controls ' + + '(selects, inputs, buttons) on a single line with baseline alignment.', + ), + ), + m( + ControlGroup, + m( + Select, + {}, + m('option', {value: 'equals', label: 'equals'}), + m('option', {value: 'contains', label: 'contains'}), + m('option', {value: 'regex', label: 'regex'}), + ), + m(TextInput, {placeholder: 'Filter value...', leftIcon: 'search'}), + m(Button, { + label: 'Apply', + variant: ButtonVariant.Filled, + icon: 'add', + }), + ), + ]; +}
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/multiselect_demo.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/multiselect_demo.ts index 13f6ed3..cc989f2 100644 --- a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/multiselect_demo.ts +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/multiselect_demo.ts
@@ -38,6 +38,7 @@ 'xyzzy', 'thud', 'a really really long option to test overflow and wrapping handling', + 'anotherreallyreallylongstringwithnowhitespacetotestwrapperingissues', ]; let selectedOptions: string[] = ['foo', 'qux', 'grault']; @@ -73,7 +74,6 @@ ...rest, }), initialOpts: { - repeatCheckedItemsAtTop: false, fixedSize: false, }, }), @@ -92,7 +92,7 @@ checked: selectedOptions.includes(value), }; }), - position: PopupPosition.Top, + position: PopupPosition.Bottom, label: 'Multi Select', icon: icon && Icons.LibraryAddCheck, onChange: (diffs: MultiSelectDiff[]) => { @@ -108,7 +108,6 @@ initialOpts: { icon: true, showNumSelected: true, - repeatCheckedItemsAtTop: false, }, }),
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tooltip_demo.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tooltip_demo.ts index f51ce38..2860f21 100644 --- a/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tooltip_demo.ts +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/demos/tooltip_demo.ts
@@ -18,17 +18,6 @@ import {Tooltip} from '../../../widgets/tooltip'; import {EnumOption, renderWidgetShowcase} from '../widgets_page_utils'; -function lorem() { - const text = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim - veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea - commodo consequat.Duis aute irure dolor in reprehenderit in voluptate - velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum.`; - return m('', {style: {width: '200px'}}, text); -} - export function renderTooltip(): m.Children { return [ m( @@ -47,7 +36,7 @@ trigger: m(Icon, {icon: 'Warning'}), ...rest, }, - lorem(), + 'Tooltip content', ), initialOpts: { position: new EnumOption(
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts index ce9cb39..fac99ec 100644 --- a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page.ts
@@ -25,6 +25,7 @@ import {renderCheckbox} from './demos/checkbox_demo'; import {renderChip} from './demos/chip_demo'; import {renderCodeSnippet} from './demos/code_snippet_demo'; +import {renderControlGroup} from './demos/control_group_demo'; import {renderCopyableLink} from './demos/copyable_link_demo'; import {cursorTooltip} from './demos/cursor_tooltip_demo'; import {renderDataGrid} from './demos/datagrid_demo'; @@ -79,6 +80,7 @@ {id: 'checkbox', label: 'Checkbox', view: renderCheckbox}, {id: 'chip', label: 'Chip', view: renderChip}, {id: 'codesnippet', label: 'CodeSnippet', view: renderCodeSnippet}, + {id: 'control-group', label: 'ControlGroup', view: renderControlGroup}, {id: 'copyablelink', label: 'CopyableLink', view: renderCopyableLink}, {id: 'cursor-tooltip', label: 'CursorTooltip', view: cursorTooltip}, {id: 'datagrid', label: 'DataGrid', view: renderDataGrid},
diff --git a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page_utils.ts b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page_utils.ts index 1498fcf..3f91fec 100644 --- a/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page_utils.ts +++ b/ui/src/plugins/dev.perfetto.WidgetsPage/widgets_page_utils.ts
@@ -17,7 +17,7 @@ import {Select} from '../../widgets/select'; import {TextInput} from '../../widgets/text_input'; import {Form, FormLabel} from '../../widgets/form'; -import {Switch} from '../../widgets/switch'; +import {Checkbox} from '../../widgets/checkbox'; export type Options = { [key: string]: EnumOption | boolean | string | number; @@ -139,7 +139,7 @@ } private renderBooleanOption(key: string, value: boolean) { - return m(Switch, { + return m(Checkbox, { checked: value, label: key, onchange: () => {
diff --git a/ui/src/widgets/button.ts b/ui/src/widgets/button.ts index 1aa2854..3ab9b15 100644 --- a/ui/src/widgets/button.ts +++ b/ui/src/widgets/button.ts
@@ -23,7 +23,6 @@ export enum ButtonVariant { Filled = 'Filled', - Outlined = 'Outlined', Minimal = 'Minimal', } @@ -60,8 +59,6 @@ // on toolbars. // Defaults to Filled. readonly variant?: ButtonVariant; - // Turns the button into a pill shape. - readonly rounded?: boolean; // Makes the button shrink to fit inside it's container, rather than its width // being defined by its content. Useful for when you have buttons with dynamic // content that may change size, and you don't want the button to change size @@ -101,7 +98,6 @@ iconFilled, intent = Intent.None, variant = ButtonVariant.Minimal, - rounded, shrink, loading, tooltip, @@ -118,7 +114,6 @@ classForIntent(intent), iconOnly && 'pf-icon-only', dismissPopup && Popup.DISMISS_POPUP_GROUP_CLASS, - rounded && 'pf-button--rounded', shrink && 'pf-button--shrink', loading && 'pf-button--loading', className, @@ -171,8 +166,6 @@ switch (variant) { case ButtonVariant.Filled: return 'pf-button--filled'; - case ButtonVariant.Outlined: - return 'pf-button--outlined'; case ButtonVariant.Minimal: return 'pf-button--minimal'; default:
diff --git a/ui/src/widgets/chip.ts b/ui/src/widgets/chip.ts index ac6d474..2b4d194 100644 --- a/ui/src/widgets/chip.ts +++ b/ui/src/widgets/chip.ts
@@ -37,8 +37,6 @@ // Indicate chip colouring by intent. // Defaults to undefined aka "None" readonly intent?: Intent; - // Turns the chip into a pill shape. - readonly rounded?: boolean; // If true, shows a little cross on the right hand side. readonly removable?: boolean; // Called when the little cross is pressed (only applicable when removable is @@ -56,7 +54,6 @@ className, iconFilled, intent = Intent.None, - rounded, removable, onRemove, label, @@ -68,7 +65,6 @@ compact && 'pf-compact', classForIntent(intent), className, - rounded && 'pf-chip--rounded', ); return m( @@ -87,7 +83,6 @@ removable && m(Button, { compact: true, - rounded, icon: 'close', title: removeButtonTitle ?? 'Remove', onclick: () => onRemove?.(),
diff --git a/ui/src/widgets/control_group.ts b/ui/src/widgets/control_group.ts new file mode 100644 index 0000000..8461556 --- /dev/null +++ b/ui/src/widgets/control_group.ts
@@ -0,0 +1,21 @@ +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import m from 'mithril'; + +export class ControlGroup implements m.ClassComponent { + view({attrs, children}: m.CVnode) { + return m('.pf-control-group', attrs, children); + } +}
diff --git a/ui/src/widgets/flamegraph.ts b/ui/src/widgets/flamegraph.ts index 2fdea3a..c8caac7 100644 --- a/ui/src/widgets/flamegraph.ts +++ b/ui/src/widgets/flamegraph.ts
@@ -818,7 +818,6 @@ { trigger: m(Button, { icon: Icons.Add, - compact: true, active: this.showFilterBuilder, onclick: () => { this.showFilterBuilder = !this.showFilterBuilder; @@ -837,12 +836,10 @@ ), m(CopyToClipboardButton(), { textToCopy: () => tags.join(' '), - compact: true, disabled: !hasFilters, }), m(Button, { icon: 'delete', - compact: true, disabled: !hasFilters, onclick: () => { attrs.onStateChange({ @@ -1065,7 +1062,6 @@ { trigger: m(Button, { icon: 'menu', - compact: true, }), position: PopupPosition.Bottom, },
diff --git a/ui/src/widgets/grid.ts b/ui/src/widgets/grid.ts index 8053ae8..79b120a 100644 --- a/ui/src/widgets/grid.ts +++ b/ui/src/widgets/grid.ts
@@ -99,7 +99,6 @@ !sort && 'pf-visible-on-hover', ), ariaLabel: 'Sort column', - rounded: true, icon: sortIconDirection === 'DESC' ? Icons.SortDesc : Icons.SortAsc, onclick: (e: MouseEvent) => { onSort(nextDirection); @@ -116,7 +115,6 @@ trigger: m(Button, { className: 'pf-visible-on-hover pf-grid-header-cell__menu-button', icon: Icons.ContextMenuAlt, - rounded: true, ariaLabel: 'Column menu', }), }, @@ -186,7 +184,6 @@ chevron === 'leaf' && 'pf-grid-cell__chevron--leaf', ), icon, - rounded: true, ariaLabel, onclick: (e: MouseEvent) => { if (onChevronClick) { @@ -230,7 +227,6 @@ trigger: m(Button, { className: 'pf-visible-on-hover pf-grid-cell__menu-button', icon: Icons.ContextMenuAlt, - rounded: true, ariaLabel: 'Cell menu', }), position: PopupPosition.Bottom,
diff --git a/ui/src/widgets/multiselect.ts b/ui/src/widgets/multiselect.ts index 28ba8c7..8b3da94 100644 --- a/ui/src/widgets/multiselect.ts +++ b/ui/src/widgets/multiselect.ts
@@ -38,7 +38,6 @@ export interface MultiSelectAttrs { options: MultiSelectOption[]; onChange?: (diffs: MultiSelectDiff[]) => void; - repeatCheckedItemsAtTop?: boolean; showNumSelected?: boolean; fixedSize?: boolean; readonly showSelectAllButton?: boolean; @@ -93,11 +92,7 @@ attrs: MultiSelectAttrs, options: MultiSelectOption[], ) { - const { - repeatCheckedItemsAtTop, - onChange = () => {}, - showSelectAllButton = true, - } = attrs; + const {onChange = () => {}, showSelectAllButton = true} = attrs; const allChecked = options.every(({checked}) => checked); const anyChecked = options.some(({checked}) => checked); @@ -108,73 +103,32 @@ } else { return [ m( - '.pf-list', - repeatCheckedItemsAtTop && - anyChecked && - m( - '.pf-multiselect-container', - m( - '.pf-multiselect-header', - m( - 'span', - this.searchText === '' ? 'Selected' : `Selected (Filtered)`, - ), - m(Button, { - label: - this.searchText === '' ? 'Clear All' : 'Clear Filtered', - icon: Icons.Deselect, - onclick: () => { - const diffs = options - .filter(({checked}) => checked) - .map(({id}) => ({id, checked: false})); - onChange(diffs); - }, - disabled: !anyChecked, - }), - ), - this.renderOptions( - attrs, - options.filter(({checked}) => checked), - ), - ), - m( - '.pf-multiselect-container', - m( - '.pf-multiselect-header', - m( - 'span', - this.searchText === '' ? 'Options' : `Options (Filtered)`, - ), - showSelectAllButton && - m(Button, { - label: - this.searchText === '' ? 'Select All' : 'Select Filtered', - icon: Icons.SelectAll, - compact: true, - onclick: () => { - const diffs = options - .filter(({checked}) => !checked) - .map(({id}) => ({id, checked: true})); - onChange(diffs); - }, - disabled: allChecked, - }), - m(Button, { - label: this.searchText === '' ? 'Clear All' : 'Clear Filtered', - icon: Icons.Deselect, - compact: true, - onclick: () => { - const diffs = options - .filter(({checked}) => checked) - .map(({id}) => ({id, checked: false})); - onChange(diffs); - }, - disabled: !anyChecked, - }), - ), - this.renderOptions(attrs, options), - ), + '.pf-bulk-actions', + showSelectAllButton && + m(Button, { + label: this.searchText === '' ? 'All' : 'Select Filtered', + compact: true, + onclick: () => { + const diffs = options + .filter(({checked}) => !checked) + .map(({id}) => ({id, checked: true})); + onChange(diffs); + }, + disabled: allChecked, + }), + m(Button, { + label: this.searchText === '' ? 'None' : 'Clear Filtered', + compact: true, + onclick: () => { + const diffs = options + .filter(({checked}) => checked) + .map(({id}) => ({id, checked: false})); + onChange(diffs); + }, + disabled: !anyChecked, + }), ), + m('.pf-multiselect-container', this.renderOptions(attrs, options)), ]; } } @@ -234,7 +188,7 @@ implements m.ClassComponent<PopupMultiSelectAttrs> { view({attrs}: m.CVnode<PopupMultiSelectAttrs>) { - const {icon, position = PopupPosition.Auto, intent, compact} = attrs; + const {icon, position = PopupPosition.Bottom, intent, compact} = attrs; return m( Popup,
diff --git a/ui/src/widgets/nodegraph.ts b/ui/src/widgets/nodegraph.ts index f4b1e4a..c7ca160 100644 --- a/ui/src/widgets/nodegraph.ts +++ b/ui/src/widgets/nodegraph.ts
@@ -1805,7 +1805,6 @@ PopupMenu, { trigger: m(Button, { - rounded: true, icon: Icons.ContextMenuAlt, className: contextMenuOnHover ? 'pf-show-on-hover' : '', }), @@ -1824,7 +1823,6 @@ PopupMenu, { trigger: m(Button, { - rounded: true, icon: Icons.ContextMenuAlt, }), },
diff --git a/ui/src/widgets/select.ts b/ui/src/widgets/select.ts index c2cd3b7..af7bb4c 100644 --- a/ui/src/widgets/select.ts +++ b/ui/src/widgets/select.ts
@@ -17,6 +17,24 @@ export class Select implements m.ClassComponent<HTMLInputAttrs> { view({attrs, children}: m.CVnode<HTMLInputAttrs>) { - return m('select.pf-select', attrs, children); + return m( + 'label.pf-select', + m('select.pf-select__input', attrs, children), + m( + 'svg.pf-select__chevron', + { + viewBox: '0 0 12 12', + fill: 'none', + xmlns: 'http://www.w3.org/2000/svg', + }, + m('path', { + 'd': 'M3 5L6 8L9 5', + 'stroke': 'currentColor', + 'stroke-width': '1.5', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + }), + ), + ); } }
diff --git a/ui/src/widgets/track_shell.ts b/ui/src/widgets/track_shell.ts index afaacdc..021f100 100644 --- a/ui/src/widgets/track_shell.ts +++ b/ui/src/widgets/track_shell.ts
@@ -333,9 +333,7 @@ spacing: 'small', orientation: 'horizontal', }, - chips.map((chip) => - m(Chip, {label: chip, compact: true, rounded: true}), - ), + chips.map((chip) => m(Chip, {label: chip, compact: true})), ), m( ButtonBar,