ui: add segmented buttons widget
Change-Id: Id8e80b90f2d24ebcf5ca706cf88dc6899f2ebd08
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index ca985ea..fdf03d2 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -49,6 +49,7 @@
@import "widgets/multiselect";
@import "widgets/popup";
@import "widgets/section";
+@import "widgets/segmented_buttons";
@import "widgets/select";
@import "widgets/spinner";
@import "widgets/switch";
diff --git a/ui/src/assets/widgets/segmented_buttons.scss b/ui/src/assets/widgets/segmented_buttons.scss
new file mode 100644
index 0000000..5f11e7e
--- /dev/null
+++ b/ui/src/assets/widgets/segmented_buttons.scss
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 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-segmented-buttons {
+ display: flex;
+ flex-direction: row;
+
+ .pf-button {
+ border-radius: 0;
+
+ &:first-child {
+ border-radius: $pf-border-radius 0 0 $pf-border-radius;
+ }
+
+ &:last-child {
+ border-radius: 0 $pf-border-radius $pf-border-radius 0;
+ }
+ }
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 970b644..9b258b1 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -56,6 +56,7 @@
VirtualTableRow,
} from '../widgets/virtual_table';
import {TagInput} from '../widgets/tag_input';
+import {SegmentedButtons} from '../widgets/segmented_buttons';
const DATA_ENGLISH_LETTER_FREQUENCY = {
table: [
@@ -628,6 +629,23 @@
};
}
+function SegmentedButtonsDemo({attrs}: {attrs: {}}) {
+ let selectedIdx = 0;
+ return {
+ view: () => {
+ return m(SegmentedButtons, {
+ ...attrs,
+ options: [{label: 'Yes'}, {label: 'Maybe'}, {label: 'No'}],
+ selectedOption: selectedIdx,
+ onOptionSelected: (num) => {
+ selectedIdx = num;
+ raf.scheduleFullRedraw();
+ },
+ });
+ },
+ };
+}
+
export const WidgetsPage = createPage({
view() {
return m(
@@ -654,6 +672,17 @@
},
}),
m(WidgetShowcase, {
+ label: 'Segmented Buttons',
+ description: `
+ Segmented buttons are a group of buttons where one of them is
+ 'selected'; they act similar to a set of radio buttons.
+ `,
+ renderWidget: (opts) => m(SegmentedButtonsDemo, opts),
+ initialOpts: {
+ disabled: false,
+ },
+ }),
+ m(WidgetShowcase, {
label: 'Checkbox',
renderWidget: (opts) => m(Checkbox, {label: 'Checkbox', ...opts}),
initialOpts: {
diff --git a/ui/src/widgets/segmented_buttons.ts b/ui/src/widgets/segmented_buttons.ts
new file mode 100644
index 0000000..9e8c533
--- /dev/null
+++ b/ui/src/widgets/segmented_buttons.ts
@@ -0,0 +1,68 @@
+// Copyright (C) 2024 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} from './button';
+import {HTMLAttrs} from './common';
+
+interface IconOption {
+ // Icon buttons require an icon.
+ readonly icon: string;
+}
+
+interface LabelOption {
+ // Label buttons require a label.
+ readonly label: string;
+ // Label buttons can have an optional icon.
+ readonly icon?: string;
+}
+
+type Option = LabelOption | IconOption;
+
+export interface SegmentedButtonsAttrs extends HTMLAttrs {
+ // Options for segmented buttons.
+ readonly options: ReadonlyArray<Option>;
+
+ // The index of the selected button.
+ readonly selectedOption: number;
+
+ // Callback function which is called every time a
+ readonly onOptionSelected: (num: number) => void;
+
+ // Whether the segmented buttons is disabled.
+ // false by default.
+ readonly disabled?: boolean;
+}
+
+export class SegmentedButtons
+ implements m.ClassComponent<SegmentedButtonsAttrs>
+{
+ view({attrs}: m.CVnode<SegmentedButtonsAttrs>) {
+ const {options, selectedOption, disabled, onOptionSelected, ...htmlAttrs} =
+ attrs;
+ return m(
+ '.pf-segmented-buttons',
+ htmlAttrs,
+ options.map((o, i) =>
+ m(Button, {
+ ...o,
+ disabled: disabled,
+ active: i === selectedOption,
+ onclick: () => onOptionSelected(i),
+ }),
+ ),
+ );
+ }
+}