Redesign deck options inputs (#2082)

* Create _input-mixins.scss

* Use button-mixins on more elements

* Replace <select> tag with custom Select component

* Fix RevertButton causing cursor: pointer when hidden

* Increase SaveButton chevron width

* Hide floating component box-shadow when inactive

* Rework SpinBox and move it into components

* Run eslint and prettier

* Remove leftover options prop

* Pass disabled array to EnumSelector again

* Update MapperRow.svelte

* Darken QHeaderView border color

Slipping this in without an extra PR.

* Adjust disabled color, border and cursor

* Remove redundant icon definition from stylesheets

* Fix deck options initial config

* Fix z-index issues in change notetype screen

It might be best to handle z-index locally in each user component instead of hard-coded component values.

* Give web SpinBox a horizontal design

* Give QRadioButton the same treatment as QCheckBox in #2079

* Fix unused CSS selector warning with base button-mixin

* Remove redundant import

* Fix deck options save button

* Delete input-mixins and remove unused down-arrow

* Run eslint on change-notetype

* Run eslint on components
This commit is contained in:
Matthias Metelka 2022-09-27 04:16:45 +02:00 committed by GitHub
parent 6a7f2d8a79
commit 23e6b2123e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 596 additions and 505 deletions

View file

@ -353,6 +353,7 @@ class AnkiApp(QApplication):
( (
QPushButton, QPushButton,
QCheckBox, QCheckBox,
QRadioButton,
# classes with PyQt5 compatibility proxy # classes with PyQt5 compatibility proxy
without_qt5_compat_wrapper(QToolButton), without_qt5_compat_wrapper(QToolButton),
without_qt5_compat_wrapper(QTabBar), without_qt5_compat_wrapper(QTabBar),

View file

@ -16,42 +16,42 @@
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="3" column="0"> <item row="3" column="0" alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="radioAhead"> <widget class="QRadioButton" name="radioAhead">
<property name="text"> <property name="text">
<string>custom_study_review_ahead</string> <string>custom_study_review_ahead</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0" alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="radioForgot"> <widget class="QRadioButton" name="radioForgot">
<property name="text"> <property name="text">
<string>custom_study_review_forgotten_cards</string> <string>custom_study_review_forgotten_cards</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0" alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="radioNew"> <widget class="QRadioButton" name="radioNew">
<property name="text"> <property name="text">
<string>custom_study_increase_todays_new_card_limit</string> <string>custom_study_increase_todays_new_card_limit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0" alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="radioRev"> <widget class="QRadioButton" name="radioRev">
<property name="text"> <property name="text">
<string>custom_study_increase_todays_review_card_limit</string> <string>custom_study_increase_todays_review_card_limit</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="5" column="0" alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="radioCram"> <widget class="QRadioButton" name="radioCram">
<property name="text"> <property name="text">
<string>custom_study_study_by_card_state_or_tag</string> <string>custom_study_study_by_card_state_or_tag</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="4" column="0" alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="radioPreview"> <widget class="QRadioButton" name="radioPreview">
<property name="text"> <property name="text">
<string>custom_study_preview_new_cards</string> <string>custom_study_preview_new_cards</string>

View file

@ -155,7 +155,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1" alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="sortField"> <widget class="QRadioButton" name="sortField">
<property name="text"> <property name="text">
<string>fields_sort_by_this_field_in_the</string> <string>fields_sort_by_this_field_in_the</string>

View file

@ -34,7 +34,7 @@
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="preview_front"> <widget class="QRadioButton" name="preview_front">
<property name="text"> <property name="text">
<string notr="true">FRONT</string> <string notr="true">FRONT</string>
@ -44,7 +44,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="preview_back"> <widget class="QRadioButton" name="preview_back">
<property name="text"> <property name="text">
<string notr="true">BACK</string> <string notr="true">BACK</string>

View file

@ -74,7 +74,7 @@
<string/> <string/>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="groups"> <widget class="QRadioButton" name="groups">
<property name="text"> <property name="text">
<string notr="true">deck</string> <string notr="true">deck</string>
@ -84,7 +84,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="all"> <widget class="QRadioButton" name="all">
<property name="text"> <property name="text">
<string notr="true">collection</string> <string notr="true">collection</string>
@ -100,7 +100,7 @@
<string/> <string/>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="month"> <widget class="QRadioButton" name="month">
<property name="text"> <property name="text">
<string notr="true">1 month</string> <string notr="true">1 month</string>
@ -110,14 +110,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="year"> <widget class="QRadioButton" name="year">
<property name="text"> <property name="text">
<string notr="true">1 year</string> <string notr="true">1 year</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="life"> <widget class="QRadioButton" name="life">
<property name="text"> <property name="text">
<string notr="true">deck life</string> <string notr="true">deck life</string>

View file

@ -52,7 +52,7 @@
</property> </property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="front_button"> <widget class="QRadioButton" name="front_button">
<property name="toolTip"> <property name="toolTip">
<string notr="true"/> <string notr="true"/>
@ -65,7 +65,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="back_button"> <widget class="QRadioButton" name="back_button">
<property name="toolTip"> <property name="toolTip">
<string notr="true"/> <string notr="true"/>
@ -75,7 +75,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item alignment="Qt::AlignLeft">
<widget class="QRadioButton" name="style_button"> <widget class="QRadioButton" name="style_button">
<property name="toolTip"> <property name="toolTip">
<string notr="true"/> <string notr="true"/>

View file

@ -233,7 +233,7 @@ QHeaderView {{
background: {tm.var(colors.CANVAS)}; background: {tm.var(colors.CANVAS)};
}} }}
QHeaderView::section {{ QHeaderView::section {{
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER)};
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
@ -261,19 +261,19 @@ QHeaderView::section:hover {{
}; };
}} }}
QHeaderView::section:first {{ QHeaderView::section:first {{
border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-left: 1px solid {tm.var(colors.BORDER)};
border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; border-top-left-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QHeaderView::section:!first {{ QHeaderView::section:!first {{
border-left: none; border-left: none;
}} }}
QHeaderView::section:last {{ QHeaderView::section:last {{
border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-right: 1px solid {tm.var(colors.BORDER)};
border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; border-top-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QHeaderView::section:only-one {{ QHeaderView::section:only-one {{
border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-left: 1px solid {tm.var(colors.BORDER)};
border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-right: 1px solid {tm.var(colors.BORDER)};
border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; border-top-left-radius: {tm.var(props.BORDER_RADIUS)};
border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; border-top-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
@ -368,24 +368,34 @@ QSpinBox::down-arrow:off {{
def checkbox_styles(tm: ThemeManager) -> str: def checkbox_styles(tm: ThemeManager) -> str:
return f""" return f"""
QCheckBox {{ QCheckBox,
QRadioButton {{
spacing: 8px; spacing: 8px;
margin: 2px 0; margin: 2px 0;
}} }}
QCheckBox::indicator {{ QCheckBox::indicator,
QRadioButton::indicator {{
border: 1px solid {tm.var(colors.BUTTON_BORDER)}; border: 1px solid {tm.var(colors.BUTTON_BORDER)};
border-radius: {tm.var(props.BORDER_RADIUS)};
background: {tm.var(colors.CANVAS_INSET)}; background: {tm.var(colors.CANVAS_INSET)};
width: 16px; width: 16px;
height: 16px; height: 16px;
}} }}
QCheckBox::indicator {{
border-radius: {tm.var(props.BORDER_RADIUS)};
}}
QRadioButton::indicator {{
border-radius: 8px;
}}
QCheckBox::indicator:hover, QCheckBox::indicator:hover,
QCheckBox::indicator:checked:hover {{ QCheckBox::indicator:checked:hover,
QRadioButton::indicator:hover,
QRadioButton::indicator:checked:hover {{
border: 2px solid {tm.var(colors.BORDER_STRONG)}; border: 2px solid {tm.var(colors.BORDER_STRONG)};
width: 14px; width: 14px;
height: 14px; height: 14px;
}} }}
QCheckBox::indicator:checked {{ QCheckBox::indicator:checked,
QRadioButton::indicator:checked {{
image: url({tm.themed_icon("mdi:check")}); image: url({tm.themed_icon("mdi:check")});
}} }}
QCheckBox::indicator:indeterminate {{ QCheckBox::indicator:indeterminate {{

View file

@ -11,7 +11,7 @@
rgba(black, $intensity); rgba(black, $intensity);
} }
@mixin btn-border-radius { @mixin border-radius {
border-top-left-radius: var(--border-left-radius); border-top-left-radius: var(--border-left-radius);
border-bottom-left-radius: var(--border-left-radius); border-bottom-left-radius: var(--border-left-radius);
@ -19,75 +19,100 @@
border-bottom-right-radius: var(--border-right-radius); border-bottom-right-radius: var(--border-right-radius);
} }
@mixin base( @mixin background($primary: false) {
$selector: button, @if $primary {
$with-primary: false, background: linear-gradient(
$with-hover: true, 180deg,
$with-active: true, var(--button-primary-gradient-start) 0%,
$with-disabled: true var(--button-primary-gradient-end) 100%
) { );
#{$selector} { &:hover {
-webkit-appearance: none; background: linear-gradient(
border: 1px solid var(--button-border); 180deg,
var(--button-primary-hover-gradient-start) 0%,
color: var(--fg); var(--button-primary-hover-gradient-end) 100%
);
border-color: var(--button-hover-border);
}
} @else {
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
var(--button-gradient-start) 0%, var(--button-gradient-start) 0%,
var(--button-gradient-end) 100% var(--button-gradient-end) 100%
); );
&:hover {
@if ($with-hover) { background: linear-gradient(
&:hover { 180deg,
background: linear-gradient( var(--button-hover-gradient-start) 0%,
180deg, var(--button-hover-gradient-end) 100%
var(--button-hover-gradient-start) 0%, );
var(--button-hover-gradient-end) 100% border-color: var(--button-hover-border);
);
border-color: var(--button-hover-border);
}
}
@if ($with-primary) {
&.btn-primary {
color: white;
background: linear-gradient(
180deg,
var(--button-primary-gradient-start) 0%,
var(--button-primary-gradient-end) 100%
);
&:hover {
background: linear-gradient(
180deg,
var(--button-primary-hover-gradient-start) 0%,
var(--button-primary-hover-gradient-end) 100%
);
}
}
}
@if ($with-active) {
&:active {
@include impressed-shadow(0.35);
box-shadow: none;
border-color: var(--button-border);
}
}
@if ($with-disabled) {
&[disabled] {
color: var(--fg-disabled);
background-color: var(--button-gradient-end) !important;
box-shadow: none !important;
border-color: var(--button-gradient-end) !important;
}
} }
} }
} }
$focus-color: vars.color(shadow-focus); @mixin base(
$primary: false,
$border: true,
$with-hover: true,
$with-active: true,
$active-class: "",
$with-disabled: true
) {
-webkit-appearance: none;
cursor: pointer;
@if $border {
border: 1px solid var(--button-border);
} @else {
border: none;
}
@include background($primary);
@function down-arrow($color) { @if ($primary) {
@return url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='transparent' stroke='#{$color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); color: white;
} @else {
color: var(--fg);
}
@if ($with-active) {
&:active {
@include impressed-shadow(0.35);
border-color: var(--button-border);
}
@if ($active-class != "") {
&.#{$active-class} {
@include impressed-shadow(0.35);
border-color: var(--button-border);
}
}
}
@if ($with-disabled) {
&[disabled],
&[disabled]:hover {
cursor: not-allowed;
color: var(--fg-disabled);
box-shadow: none !important;
background-color: var(--button-gradient-end);
}
}
}
$focus-color: var(--shadow-focus);
@mixin select($with-disabled: true) {
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: all;
cursor: pointer;
@include base($with-disabled: $with-disabled);
border-radius: var(--border-radius);
&.rtl {
direction: rtl;
}
} }

View file

@ -34,8 +34,8 @@ $vars: (
dark: palette(lightgray, 3), dark: palette(lightgray, 3),
), ),
disabled: ( disabled: (
light: palette(darkgray, 2), light: palette(lightgray, 9),
dark: palette(lightgray, 8), dark: palette(darkgray, 0),
), ),
faint: ( faint: (
light: palette(lightgray, 7), light: palette(lightgray, 7),

View file

@ -1,5 +1,6 @@
@use "vars"; @use "vars";
@use "scrollbar"; @use "scrollbar";
@use "button-mixins" as button;
$body-color: var(--fg); $body-color: var(--fg);
$body-bg: var(--canvas); $body-bg: var(--canvas);
@ -79,6 +80,11 @@ samp {
background-position: left 0.75rem center; background-position: left 0.75rem center;
} }
} }
.form-select:focus {
outline: none;
border: 1px solid var(--border-focus);
box-shadow: none !important;
}
.night-mode .form-select:disabled { .night-mode .form-select:disabled {
background-color: var(--fg-disabled); background-color: var(--fg-disabled);
@ -88,3 +94,20 @@ samp {
transition: none !important; transition: none !important;
animation: none !important; animation: none !important;
} }
select {
@include button.background;
@include button.border-radius;
cursor: pointer;
outline: none;
&:focus,
&.focus {
border: 1px solid var(--border-focus);
}
option {
background: var(--canvas-elevated);
color: var(--fg);
}
}

View file

@ -1,4 +1,5 @@
@use "vars"; @use "vars";
@use "button-mixins" as button;
:root { :root {
--focus-color: #{vars.palette-of(shadow-focus)}; --focus-color: #{vars.palette-of(shadow-focus)};
@ -21,30 +22,7 @@
} }
button { button {
-webkit-appearance: none; @include button.base;
color: vars.color(fg); border-radius: var(--border-radius);
cursor: pointer;
background: linear-gradient(
180deg,
vars.color(button-gradient-start) 0%,
vars.color(button-gradient-end) 100%
);
border: 1px solid vars.color(button-border);
border-radius: vars.prop(border-radius);
padding: 3px 10px 3px; padding: 3px 10px 3px;
&:focus {
outline: none;
box-shadow: 0 0 0 1px var(--focus-color);
}
}
button:hover {
background: linear-gradient(
180deg,
vars.color(button-hover-gradient-start) 0%,
vars.color(button-hover-gradient-end) 100%
);
} }

View file

@ -8,6 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import Col from "../components/Col.svelte"; import Col from "../components/Col.svelte";
import Container from "../components/Container.svelte"; import Container from "../components/Container.svelte";
import Row from "../components/Row.svelte"; import Row from "../components/Row.svelte";
import StickyContainer from "../components/StickyContainer.svelte";
import * as tr from "../lib/ftl"; import * as tr from "../lib/ftl";
import { ChangeNotetypeState, MapContext } from "./lib"; import { ChangeNotetypeState, MapContext } from "./lib";
import Mapper from "./Mapper.svelte"; import Mapper from "./Mapper.svelte";
@ -20,20 +21,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</script> </script>
<div bind:offsetHeight={offset}> <div bind:offsetHeight={offset}>
<NotetypeSelector {state} /> <StickyContainer
--gutter-block="0.1rem"
--gutter-inline="0.25rem"
--sticky-borders="0 0 1px"
--z-index="4"
>
<NotetypeSelector {state} />
</StickyContainer>
</div> </div>
<div id="scrollArea" style="--offset: {offset}px; --gutter-inline: 0.25rem;"> <div id="scrollArea" style="--offset: {offset}px; --gutter-inline: 0.25rem;">
<Row class="gx-0" --cols={2}> <Row class="gx-0" --cols={2}>
<Col --col-size={1} breakpoint="md"> <Col --col-size={1} breakpoint="md">
<Container> <Container>
<StickyHeader {state} ctx={MapContext.Field} /> <StickyHeader {state} ctx={MapContext.Field} --z-index="2" />
<Mapper {state} ctx={MapContext.Field} /> <Mapper {state} ctx={MapContext.Field} />
</Container> </Container>
</Col> </Col>
<Col --col-size={1} breakpoint="md"> <Col --col-size={1} breakpoint="md">
<Container> <Container>
<StickyHeader {state} ctx={MapContext.Template} /> <StickyHeader {state} ctx={MapContext.Template} --z-index="2" />
{#if $info.templates} {#if $info.templates}
<Mapper {state} ctx={MapContext.Template} /> <Mapper {state} ctx={MapContext.Template} />
{:else} {:else}

View file

@ -5,6 +5,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts"> <script lang="ts">
import Col from "../components/Col.svelte"; import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte"; import Row from "../components/Row.svelte";
import Select from "../components/Select.svelte";
import SelectOption from "../components/SelectOption.svelte";
import type { ChangeNotetypeState, MapContext } from "./lib"; import type { ChangeNotetypeState, MapContext } from "./lib";
export let state: ChangeNotetypeState; export let state: ChangeNotetypeState;
@ -13,24 +15,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const info = state.info; const info = state.info;
function onChange(evt: Event) { let oldIndex = $info.getOldIndex(ctx, newIndex);
const oldIdx = parseInt((evt.target as HTMLSelectElement).value, 10);
state.setOldIndex(ctx, newIndex, oldIdx); const options = $info.getOldNamesIncludingNothing(ctx);
} $: state.setOldIndex(ctx, newIndex, oldIndex);
</script> </script>
<Row --cols={2}> <Row --cols={2}>
<Col --col-size={1}> <Col --col-size={1}>
<!-- svelte-ignore a11y-no-onchange --> <!-- svelte-ignore a11y-no-onchange -->
<select <Select current={options[oldIndex]}>
value={$info.getOldIndex(ctx, newIndex)} {#each options as name, idx}
class="form-select" <SelectOption on:select={() => (oldIndex = idx)}>{name}</SelectOption>
on:change={onChange}
>
{#each $info.getOldNamesIncludingNothing(ctx) as name, idx}
<option value={idx}>{name}</option>
{/each} {/each}
</select> </Select>
</Col> </Col>
<Col --col-size={1}> <Col --col-size={1}>
{$info.getNewName(ctx, newIndex)} {$info.getNewName(ctx, newIndex)}

View file

@ -7,9 +7,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import ButtonGroup from "../components/ButtonGroup.svelte"; import ButtonGroup from "../components/ButtonGroup.svelte";
import ButtonToolbar from "../components/ButtonToolbar.svelte"; import ButtonToolbar from "../components/ButtonToolbar.svelte";
import LabelButton from "../components/LabelButton.svelte"; import LabelButton from "../components/LabelButton.svelte";
import SelectButton from "../components/SelectButton.svelte"; import Select from "../components/Select.svelte";
import SelectOption from "../components/SelectOption.svelte"; import SelectOption from "../components/SelectOption.svelte";
import StickyContainer from "../components/StickyContainer.svelte";
import { arrowLeftIcon, arrowRightIcon } from "./icons"; import { arrowLeftIcon, arrowRightIcon } from "./icons";
import type { ChangeNotetypeState } from "./lib"; import type { ChangeNotetypeState } from "./lib";
import SaveButton from "./SaveButton.svelte"; import SaveButton from "./SaveButton.svelte";
@ -17,40 +16,30 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let state: ChangeNotetypeState; export let state: ChangeNotetypeState;
const notetypes = state.notetypes; const notetypes = state.notetypes;
const info = state.info; const info = state.info;
let value: number = 0;
async function blur(event: Event): Promise<void> { $: state.setTargetNotetypeIndex(value);
await state.setTargetNotetypeIndex( $: options = Array.from($notetypes, (notetype) => notetype.name);
parseInt((event.target! as HTMLSelectElement).value),
);
}
</script> </script>
<StickyContainer <ButtonToolbar class="justify-content-between" size={2.3} wrap={false}>
--gutter-block="0.1rem" <LabelButton disabled={true}>
--gutter-inline="0.25rem" {$info.oldNotetypeName}
--sticky-borders="0 0 1px" </LabelButton>
> <Badge iconSize={70}>
<ButtonToolbar class="justify-content-between" size={2.3} wrap={false}> {#if window.getComputedStyle(document.body).direction == "rtl"}
<LabelButton disabled={true}> {@html arrowLeftIcon}
{$info.oldNotetypeName} {:else}
</LabelButton> {@html arrowRightIcon}
<Badge iconSize={70}> {/if}
{#if window.getComputedStyle(document.body).direction == "rtl"} </Badge>
{@html arrowLeftIcon} <ButtonGroup class="flex-grow-1">
{:else} <Select class="flex-grow-1" current={options[value]}>
{@html arrowRightIcon} {#each options as option, idx}
{/if} <SelectOption on:select={() => (value = idx)}>{option}</SelectOption>
</Badge> {/each}
<ButtonGroup class="flex-grow-1"> </Select>
<SelectButton class="flex-grow-1" on:change={blur}> </ButtonGroup>
{#each $notetypes as entry}
<SelectOption value={String(entry.idx)} selected={entry.current}>
{entry.name}
</SelectOption>
{/each}
</SelectButton>
</ButtonGroup>
<SaveButton {state} /> <SaveButton {state} />
</ButtonToolbar> </ButtonToolbar>
</StickyContainer>

View file

@ -24,7 +24,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<ButtonGroup> <ButtonGroup>
<LabelButton <LabelButton
theme="primary" primary
tooltip={getPlatformString(keyCombination)} tooltip={getPlatformString(keyCombination)}
on:click={save} on:click={save}
--border-left-radius="5px" --border-left-radius="5px"

View file

@ -27,8 +27,3 @@ html {
padding: 0.5em 0.5em 1em 0.5em; padding: 0.5em 0.5em 1em 0.5em;
height: 100vh; height: 100vh;
} }
// override the default down arrow colour in <select> elements
.night-mode select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
}

View file

@ -11,6 +11,7 @@ _ts_deps = [
"@npm//@floating-ui/dom", "@npm//@floating-ui/dom",
"@npm//bootstrap", "@npm//bootstrap",
"@npm//svelte", "@npm//svelte",
"@npm//@mdi",
] ]
compile_svelte( compile_svelte(

View file

@ -3,17 +3,16 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import { pageTheme } from "../sveltelib/theme";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className = ""; let className = "";
export { className as class }; export { className as class };
let buttonRef: HTMLButtonElement; export let buttonRef: HTMLButtonElement | undefined = undefined;
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let active = false; export let active = false;
export let disabled = false;
$: if (buttonRef && active) { $: if (buttonRef && active) {
/* buttonRef.scrollIntoView({ behavior: "smooth", block: "start" }); */ /* buttonRef.scrollIntoView({ behavior: "smooth", block: "start" }); */
@ -33,8 +32,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
tabindex={tabbable ? 0 : -1} tabindex={tabbable ? 0 : -1}
class="dropdown-item {className}" class="dropdown-item {className}"
class:active class:active
class:btn-day={!$pageTheme.isDark} class:disabled
class:btn-night={$pageTheme.isDark}
title={tooltip} title={tooltip}
on:mouseenter on:mouseenter
on:focus on:focus
@ -46,11 +44,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</button> </button>
<style lang="scss"> <style lang="scss">
@use "sass/button-mixins" as button;
button { button {
display: flex; display: flex;
justify-content: start; justify-content: start;
width: 100%;
font-size: var(--dropdown-font-size, calc(0.8 * var(--base-font-size))); font-size: var(--dropdown-font-size, calc(0.8 * var(--base-font-size)));
@ -58,19 +55,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
box-shadow: none !important; box-shadow: none !important;
border: none; border: none;
border-radius: 0; border-radius: 0;
color: var(--fg);
&:active, &:hover {
&.active { background: var(--highlight-bg);
background-color: button.$focus-color; color: var(--highlight-fg);
color: white;
} }
} }
.btn-day {
color: black;
}
.btn-night {
color: white;
}
</style> </style>

View file

@ -3,7 +3,6 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import { pageTheme } from "../sveltelib/theme";
import IconConstrain from "./IconConstrain.svelte"; import IconConstrain from "./IconConstrain.svelte";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
@ -11,6 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export { className as class }; export { className as class };
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let primary = false;
export let active = false; export let active = false;
export let disabled = false; export let disabled = false;
export let tabbable = false; export let tabbable = false;
@ -22,10 +22,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<button <button
{id} {id}
class="icon-button btn {className}" class="icon-button {className}"
class:active class:active
class:btn-day={!$pageTheme.isDark} class:primary
class:btn-night={$pageTheme.isDark}
title={tooltip} title={tooltip}
{disabled} {disabled}
tabindex={tabbable ? 0 : -1} tabindex={tabbable ? 0 : -1}
@ -41,12 +40,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
@use "sass/button-mixins" as button; @use "sass/button-mixins" as button;
.icon-button { .icon-button {
@include button.base($active-class: active);
&.primary {
@include button.base($primary: true);
}
@include button.border-radius;
padding: 0; padding: 0;
font-size: var(--base-font-size); font-size: var(--base-font-size);
height: var(--buttons-size); height: var(--buttons-size);
min-width: calc(var(--buttons-size) * 0.75);
@include button.btn-border-radius;
} }
@include button.base(".icon-button");
</style> </style>

View file

@ -22,7 +22,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
vertical-align: var(--icon-align, middle); vertical-align: var(--icon-align, middle);
/* constrain icon */ /* constrain icon */
width: calc((var(--buttons-size, 22px) - 2px) * var(--width-multiplier)); min-width: calc((var(--buttons-size, 22px) - 2px) * var(--width-multiplier));
height: calc(var(--buttons-size, 22px) - 2px); height: calc(var(--buttons-size, 22px) - 2px);
& > :global(svg), & > :global(svg),

View file

@ -5,16 +5,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, onMount } from "svelte"; import { createEventDispatcher, onMount } from "svelte";
import { pageTheme } from "../sveltelib/theme";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className: string = ""; let className: string = "";
export { className as class }; export { className as class };
export let theme = "anki"; export let primary = false;
function extendClassName(className: string, theme: string): string {
return `btn ${theme !== "anki" ? `btn-${theme}` : ""}${className}`;
}
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let active = false; export let active = false;
@ -30,10 +24,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<button <button
bind:this={buttonRef} bind:this={buttonRef}
{id} {id}
class="label-button {extendClassName(className, theme)}" class="label-button {className}"
class:active class:active
class:btn-day={theme === "anki" && !$pageTheme.isDark} class:primary
class:btn-night={theme === "anki" && $pageTheme.isDark}
title={tooltip} title={tooltip}
{disabled} {disabled}
tabindex={tabbable ? 0 : -1} tabindex={tabbable ? 0 : -1}
@ -47,6 +40,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
@use "sass/button-mixins" as button; @use "sass/button-mixins" as button;
button { button {
@include button.base($active-class: active);
&.primary {
@include button.base($primary: true);
}
@include button.border-radius;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -54,9 +53,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
font-size: var(--base-font-size); font-size: var(--base-font-size);
width: auto; width: auto;
height: var(--buttons-size); height: var(--buttons-size);
@include button.btn-border-radius;
} }
@include button.base($with-primary: true);
</style> </style>

View file

@ -2,19 +2,29 @@
Copyright: Ankitects Pty Ltd and contributors Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script> <script lang="ts">
import { slide } from "svelte/transition";
import { pageTheme } from "../sveltelib/theme"; import { pageTheme } from "../sveltelib/theme";
export let scrollable = false;
</script> </script>
<div class="popover" class:dark={$pageTheme.isDark} on:mousedown|preventDefault> <div
class="popover"
class:scrollable
class:dark={$pageTheme.isDark}
transition:slide={{ duration: 200 }}
>
<slot /> <slot />
</div> </div>
<style lang="scss"> <style lang="scss">
@use "sass/vars";
.popover { .popover {
border-radius: 5px; border-radius: 5px;
background-color: var(--canvas-elevated); background-color: var(--canvas-elevated);
min-width: 1rem; min-width: var(--popover-width, 1rem);
max-width: 95vw; max-width: 95vw;
/* Needs this much space for FloatingArrow to be positioned */ /* Needs this much space for FloatingArrow to be positioned */
@ -24,17 +34,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
color: var(--fg); color: var(--fg);
/* outer border */ /* outer border */
border: 1px solid #b6b6b6; border: 1px solid vars.palette(lightgray, 6);
&.dark { &.dark {
border-color: #060606; border-color: vars.palette(darkgray, 9);
} }
/* inner border */ /* inner border */
box-shadow: inset 0 0 0 1px #eeeeee; box-shadow: inset 0 0 0 1px vars.palette(lightgray, 3);
&.dark { &.dark {
box-shadow: inset 0 0 0 1px #565656; box-shadow: inset 0 0 0 1px vars.palette(darkgray, 2);
}
&.scrollable {
max-height: 80vh;
overflow: hidden auto;
} }
} }
</style> </style>

View file

@ -0,0 +1,86 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import IconConstrain from "./IconConstrain.svelte";
import { chevronDown } from "./icons";
import Popover from "./Popover.svelte";
import WithFloating from "./WithFloating.svelte";
export let id: string | undefined = undefined;
let className = "";
export { className as class };
export let disabled = false;
export let current: string = "";
export let tooltip: string | undefined = undefined;
const rtl: boolean = window.getComputedStyle(document.body).direction == "rtl";
export let element: HTMLElement | undefined = undefined;
let hover = false;
let showFloating = false;
let clientWidth: number;
</script>
<WithFloating
show={showFloating}
placement="bottom"
offset={0}
hideArrow
inline
closeOnInsideClick
on:close={() => (showFloating = false)}
let:asReference
>
<div
{id}
class="{className} select-container"
class:rtl
class:hover
{disabled}
title={tooltip}
tabindex="-1"
on:mouseenter={() => (hover = true)}
on:mouseleave={() => (hover = false)}
on:click={() => (showFloating = !showFloating)}
bind:this={element}
use:asReference
bind:clientWidth
>
{current}
<div class="chevron">
<IconConstrain iconSize={80}>
{@html chevronDown}
</IconConstrain>
</div>
</div>
<Popover slot="floating" scrollable --popover-width="{clientWidth}px">
<slot />
</Popover>
</WithFloating>
<style lang="scss">
@use "sass/button-mixins" as button;
.select-container {
@include button.select($with-disabled: false);
padding: 0.2rem 2rem 0.2rem 0.75rem;
line-height: 1.5;
height: var(--buttons-size, 100%);
position: relative;
}
.chevron {
position: absolute;
inset: 0 0 0 auto;
border-left: 1px solid var(--button-border);
}
:global([dir="rtl"]) {
.chevron {
inset: 0 auto 0 0;
border-left: none;
border-right: 1px solid var(--button-border);
}
}
</style>

View file

@ -1,82 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import { pageTheme } from "../sveltelib/theme";
const rtl: boolean = window.getComputedStyle(document.body).direction == "rtl";
export let id: string | undefined = undefined;
let className = "";
export { className as class };
export let tooltip: string | undefined = undefined;
export let disabled = false;
let buttonRef: HTMLSelectElement;
const dispatch = createEventDispatcher();
onMount(() => dispatch("mount", { button: buttonRef }));
</script>
<!-- svelte-ignore a11y-no-onchange -->
<select
tabindex="-1"
bind:this={buttonRef}
{id}
{disabled}
class="{className} form-select"
class:btn-day={!$pageTheme.isDark}
class:btn-night={$pageTheme.isDark}
class:rtl
title={tooltip}
on:change
>
<slot />
</select>
<div class="arrow" class:dark={$pageTheme.isDark} class:rtl />
<style lang="scss">
@use "sass/button-mixins" as button;
@include button.base($selector: "select", $with-hover: false);
select {
height: var(--buttons-size);
/* Long option name can create overflow */
text-overflow: ellipsis;
/* Prevents text getting cropped on Windows */
padding: {
top: 0;
bottom: 0;
}
&.rtl {
direction: rtl;
/* Reversed Bootstrap values */
padding-left: 2.25rem;
padding-right: 0.75rem;
}
&.btn-day {
/* Hide default arrow for consistency */
background: var(--canvas-elevated);
}
}
.arrow {
top: 0;
right: 10px;
&.rtl {
left: 10px;
}
width: 15px;
height: 100%;
position: absolute;
pointer-events: none;
background: button.down-arrow(black) no-repeat right;
&.dark {
background-image: button.down-arrow(white);
}
}
</style>

View file

@ -3,16 +3,22 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
export let value: string | undefined = undefined; import { createEventDispatcher } from "svelte";
export let selected = false;
import DropdownItem from "./DropdownItem.svelte";
export let disabled = false;
const dispatch = createEventDispatcher();
let element: HTMLButtonElement;
function onSelect(): void {
if (!disabled) {
dispatch("select");
}
}
</script> </script>
<option class="select-option" {value} {selected}> <DropdownItem {disabled} on:click={onSelect} bind:buttonRef={element}>
<slot /> <slot />
</option> </DropdownItem>
<style lang="scss">
.select-option {
background-color: var(--canvas-elevated);
}
</style>

View file

@ -0,0 +1,135 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import IconConstrain from "./IconConstrain.svelte";
import { chevronLeft, chevronRight } from "./icons";
export let value = 1;
export let step = 1;
export let min = 1;
export let max = 9999;
const rtl: boolean = window.getComputedStyle(document.body).direction == "rtl";
let input: HTMLInputElement;
let focused = false;
function decimalPlaces(value: number) {
if (Math.floor(value) === value) return 0;
return value.toString().split(".")[1].length || 0;
}
let stringValue: string;
$: stringValue = value.toFixed(decimalPlaces(step));
function update(this: HTMLInputElement): void {
value = Math.min(max, Math.max(min, parseFloat(this.value)));
if (value > max) {
value = max;
} else if (value < min) {
value = min;
}
}
function handleWheel(event: WheelEvent) {
if (focused) {
value += event.deltaY < 0 ? step : -step;
event.preventDefault();
}
}
</script>
<div class="spin-box" on:wheel={handleWheel}>
<button
class="left"
disabled={rtl ? value == max : value == min}
on:click={() => {
input.focus();
if (rtl && value < max) {
value += step;
} else if (value > min) {
value -= step;
}
}}
>
<IconConstrain>
{@html chevronLeft}
</IconConstrain>
</button>
<input
type="number"
pattern="[0-9]*"
inputmode="numeric"
{min}
{max}
{step}
value={stringValue}
bind:this={input}
on:blur={update}
on:focusin={() => (focused = true)}
on:focusout={() => (focused = false)}
/>
<button
class="right"
disabled={rtl ? value == min : value == max}
on:click={() => {
input.focus();
if (rtl && value > min) {
value -= step;
} else if (value < max) {
value += step;
}
}}
>
<IconConstrain>
{@html chevronRight}
</IconConstrain>
</button>
</div>
<style lang="scss">
@use "sass/button-mixins" as button;
.spin-box {
width: 100%;
border: 1px solid var(--border);
border-radius: var(--border-radius);
overflow: hidden;
position: relative;
input {
width: 100%;
padding: 0.2rem 1.5rem 0.2rem 0.75rem;
border: none;
outline: none;
text-align: center;
&::-webkit-inner-spin-button {
display: none;
}
}
&:hover,
&:focus-within {
button {
opacity: 1;
}
}
}
button {
opacity: 0;
position: absolute;
@include button.base($border: false);
&.left {
inset: 0 auto 0 0;
border-right: 1px solid var(--border);
}
&.right {
position: absolute;
inset: 0 0 0 auto;
border-left: 1px solid var(--border);
}
}
</style>

View file

@ -27,7 +27,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
z-index: 50; z-index: var(--z-index, 50);
background: var(--sticky-bg, var(--canvas)); background: var(--sticky-bg, var(--canvas));
border-style: solid; border-style: solid;

View file

@ -61,6 +61,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let closeOnInsideClick = false; export let closeOnInsideClick = false;
export let keepOnKeyup = false; export let keepOnKeyup = false;
export let hideArrow = false;
export let reference: ReferenceElement | undefined = undefined; export let reference: ReferenceElement | undefined = undefined;
let floating: FloatingElement; let floating: FloatingElement;
@ -170,13 +171,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/if} {/if}
{/if} {/if}
<div bind:this={floating} class="floating" use:portal={portalTarget}> <div bind:this={floating} class="floating" class:show use:portal={portalTarget}>
{#if show} {#if show}
<slot name="floating" /> <slot name="floating" />
{/if} {/if}
<div bind:this={arrow} class="floating-arrow" hidden={!show}> <div bind:this={arrow} class="floating-arrow" hidden={!show}>
<FloatingArrow /> {#if !hideArrow}
<FloatingArrow />
{/if}
</div> </div>
</div> </div>
@ -188,7 +191,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
border-radius: 5px; border-radius: 5px;
z-index: 890; z-index: 890;
@include elevation.elevation(8); &.show {
@include elevation.elevation(8);
}
&-arrow { &-arrow {
position: absolute; position: absolute;

View file

@ -140,7 +140,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/if} {/if}
{/if} {/if}
<div bind:this={floating} class="overlay"> <div bind:this={floating} class="overlay" class:show>
{#if show} {#if show}
<slot name="overlay" /> <slot name="overlay" />
{/if} {/if}
@ -154,6 +154,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
border-radius: 5px; border-radius: 5px;
z-index: 40; z-index: 40;
@include elevation.elevation(5); &.show {
@include elevation.elevation(5);
}
} }
</style> </style>

8
ts/components/icons.ts Normal file
View file

@ -0,0 +1,8 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/// <reference types="../lib/image-import" />
export { default as chevronDown } from "@mdi/svg/svg/chevron-down.svg";
export { default as chevronLeft } from "@mdi/svg/svg/chevron-left.svg";
export { default as chevronRight } from "@mdi/svg/svg/chevron-right.svg";

View file

@ -9,7 +9,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import ButtonGroup from "../components/ButtonGroup.svelte"; import ButtonGroup from "../components/ButtonGroup.svelte";
import ButtonToolbar from "../components/ButtonToolbar.svelte"; import ButtonToolbar from "../components/ButtonToolbar.svelte";
import { modalsKey } from "../components/context-keys"; import { modalsKey } from "../components/context-keys";
import SelectButton from "../components/SelectButton.svelte"; import Select from "../components/Select.svelte";
import SelectOption from "../components/SelectOption.svelte"; import SelectOption from "../components/SelectOption.svelte";
import StickyContainer from "../components/StickyContainer.svelte"; import StickyContainer from "../components/StickyContainer.svelte";
import * as tr from "../lib/ftl"; import * as tr from "../lib/ftl";
@ -23,16 +23,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const dispatchPresetChange = () => dispatch("presetchange"); const dispatchPresetChange = () => dispatch("presetchange");
$: {
state.setCurrentIndex(value);
dispatchPresetChange();
}
$: options = Array.from($configList, (entry) => configLabel(entry));
$: value = $configList.find((entry) => entry.current)?.idx || 0;
function configLabel(entry: ConfigListEntry): string { function configLabel(entry: ConfigListEntry): string {
const count = tr.deckConfigUsedByDecks({ decks: entry.useCount }); const count = tr.deckConfigUsedByDecks({ decks: entry.useCount });
return `${entry.name} (${count})`; return `${entry.name} (${count})`;
} }
function blur(event: Event): void {
state.setCurrentIndex(parseInt((event.target! as HTMLSelectElement).value));
dispatchPresetChange();
}
function onAddConfig(text: string): void { function onAddConfig(text: string): void {
const trimmed = text.trim(); const trimmed = text.trim();
if (trimmed.length) { if (trimmed.length) {
@ -93,18 +96,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<StickyContainer --gutter-block="0.5rem" --sticky-borders="0 0 1px" breakpoint="sm"> <StickyContainer --gutter-block="0.5rem" --sticky-borders="0 0 1px" breakpoint="sm">
<ButtonToolbar class="justify-content-between" size={2.3} wrap={false}> <ButtonToolbar class="justify-content-between" size={2.3} wrap={false}>
<ButtonGroup class="flex-grow-1"> <ButtonGroup class="flex-grow-1">
<SelectButton <Select class="flex-grow-1" current={options[value]}>
class="flex-grow-1" {#each options as option, idx}
on:change={blur} <SelectOption on:select={() => (value = idx)}
--border-left-radius="5px" >{option}
--border-right-radius="5px"
>
{#each $configList as entry}
<SelectOption value={String(entry.idx)} selected={entry.current}>
{configLabel(entry)}
</SelectOption> </SelectOption>
{/each} {/each}
</SelectButton> </Select>
</ButtonGroup> </ButtonGroup>
<SaveButton <SaveButton

View file

@ -3,39 +3,18 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import { pageTheme } from "../sveltelib/theme"; import Select from "../components/Select.svelte";
import SelectOption from "../components/SelectOption.svelte";
export let choices: string[]; export let options: string[] = [];
export let value: number = 0;
export let disabled: number[] = []; export let disabled: number[] = [];
export let value = 0;
</script> </script>
<select <Select current={options[value]}>
bind:value {#each options as option, idx}
class:nightMode={$pageTheme.isDark} <SelectOption disabled={disabled.includes(idx)} on:select={() => (value = idx)}
class:visible-down-arrow={$pageTheme.isDark} >{option}</SelectOption
class="enum-selector form-select" >
>
{#each choices as choice, idx}
<option value={idx} disabled={disabled.includes(idx)}>{choice}</option>
{/each} {/each}
</select> </Select>
<style lang="scss">
@use "sass/night-mode" as nightmode;
@use "sass/button-mixins" as button;
.nightMode {
@include nightmode.input;
}
.enum-selector {
/* overwrite Bootstrap */
padding: 0.2rem 0.75rem;
}
.visible-down-arrow {
/* override the default down arrow */
background-image: button.down-arrow(white);
}
</style>

View file

@ -23,7 +23,7 @@
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel> <TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
</Col> </Col>
<Col --col-size={5} {breakpoint}> <Col --col-size={5} {breakpoint}>
<EnumSelector bind:value {choices} {disabled} /> <EnumSelector bind:value options={choices} {disabled} />
<RevertButton bind:value {defaultValue} /> <RevertButton bind:value {defaultValue} />
</Col> </Col>
</Row> </Row>

View file

@ -95,5 +95,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
.hide :global(.badge) { .hide :global(.badge) {
opacity: 0; opacity: 0;
cursor: initial;
} }
</style> </style>

View file

@ -5,11 +5,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, tick } from "svelte"; import { createEventDispatcher, tick } from "svelte";
import ButtonGroup from "../components/ButtonGroup.svelte";
import DropdownDivider from "../components/DropdownDivider.svelte"; import DropdownDivider from "../components/DropdownDivider.svelte";
import DropdownItem from "../components/DropdownItem.svelte"; import DropdownItem from "../components/DropdownItem.svelte";
import LabelButton from "../components/IconButton.svelte";
import IconButton from "../components/IconButton.svelte"; import IconButton from "../components/IconButton.svelte";
import LabelButton from "../components/LabelButton.svelte";
import Popover from "../components/Popover.svelte"; import Popover from "../components/Popover.svelte";
import Shortcut from "../components/Shortcut.svelte"; import Shortcut from "../components/Shortcut.svelte";
import WithFloating from "../components/WithFloating.svelte"; import WithFloating from "../components/WithFloating.svelte";
@ -64,48 +63,52 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let showFloating = false; let showFloating = false;
</script> </script>
<ButtonGroup> <LabelButton
<LabelButton primary
theme="primary" on:click={() => save(false)}
on:click={() => save(false)} tooltip={getPlatformString(saveKeyCombination)}
tooltip={getPlatformString(saveKeyCombination)} --border-left-radius="var(--border-radius)"
--border-left-radius="5px">{tr.deckConfigSaveButton()}</LabelButton >
> <div class="save">{tr.deckConfigSaveButton()}</div>
<Shortcut keyCombination={saveKeyCombination} on:action={() => save(false)} /> </LabelButton>
<Shortcut keyCombination={saveKeyCombination} on:action={() => save(false)} />
<WithFloating <WithFloating
show={showFloating} show={showFloating}
closeOnInsideClick closeOnInsideClick
inline inline
on:close={() => (showFloating = false)} on:close={() => (showFloating = false)}
>
<IconButton
class="chevron"
slot="reference"
on:click={() => (showFloating = !showFloating)}
--border-right-radius="var(--border-radius)"
iconSize={80}
> >
<IconButton {@html chevronDown}
slot="reference" </IconButton>
widthMultiplier={0.5} <Popover slot="floating">
iconSize={120} <DropdownItem on:click={() => dispatch("add")}
--border-right-radius="5px" >{tr.deckConfigAddGroup()}</DropdownItem
on:click={() => (showFloating = !showFloating)}
> >
{@html chevronDown} <DropdownItem on:click={() => dispatch("clone")}
</IconButton> >{tr.deckConfigCloneGroup()}</DropdownItem
>
<DropdownItem on:click={() => dispatch("rename")}>
{tr.deckConfigRenameGroup()}
</DropdownItem>
<DropdownItem on:click={removeConfig}>{tr.deckConfigRemoveGroup()}</DropdownItem
>
<DropdownDivider />
<DropdownItem on:click={() => save(true)}>
{tr.deckConfigSaveToAllSubdecks()}
</DropdownItem>
</Popover>
</WithFloating>
<Popover slot="floating"> <style lang="scss">
<DropdownItem on:click={() => dispatch("add")} .save {
>{tr.deckConfigAddGroup()}</DropdownItem margin: 0.2rem 0.75rem;
> }
<DropdownItem on:click={() => dispatch("clone")} </style>
>{tr.deckConfigCloneGroup()}</DropdownItem
>
<DropdownItem on:click={() => dispatch("rename")}>
{tr.deckConfigRenameGroup()}
</DropdownItem>
<DropdownItem on:click={removeConfig}
>{tr.deckConfigRemoveGroup()}</DropdownItem
>
<DropdownDivider />
<DropdownItem on:click={() => save(true)}>
{tr.deckConfigSaveToAllSubdecks()}
</DropdownItem>
</Popover>
</WithFloating>
</ButtonGroup>

View file

@ -1,44 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { pageTheme } from "../sveltelib/theme";
export let value: number;
export let min = 1;
export let max = 9999;
function checkMinMax() {
if (value > max) {
value = max;
} else if (value < min) {
value = min;
}
}
</script>
<input
type="number"
pattern="[0-9]*"
inputmode="numeric"
{min}
{max}
bind:value
class="spin-box form-control"
class:nightMode={$pageTheme.isDark}
on:blur={checkMinMax}
/>
<style lang="scss">
@use "sass/night-mode" as nightmode;
.spin-box {
/* overwrite Bootstrap */
padding: 0.2rem 0.75rem;
}
.nightMode {
@include nightmode.input;
}
</style>

View file

@ -1,37 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { pageTheme } from "../sveltelib/theme";
export let value: number;
export let min = 1;
export let max = 9999;
let stringValue: string;
$: stringValue = value.toFixed(2);
function update(this: HTMLInputElement): void {
value = Math.min(max, Math.max(min, parseFloat(this.value)));
}
</script>
<input
type="number"
class="form-control"
class:nightMode={$pageTheme.isDark}
{min}
{max}
step="0.01"
value={stringValue}
on:blur={update}
/>
<style lang="scss">
@use "sass/night-mode" as nightmode;
.nightMode {
@include nightmode.input;
}
</style>

View file

@ -5,8 +5,8 @@
<script lang="ts"> <script lang="ts">
import Col from "../components/Col.svelte"; import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte"; import Row from "../components/Row.svelte";
import SpinBox from "../components/SpinBox.svelte";
import RevertButton from "./RevertButton.svelte"; import RevertButton from "./RevertButton.svelte";
import SpinBoxFloat from "./SpinBoxFloat.svelte";
import TooltipLabel from "./TooltipLabel.svelte"; import TooltipLabel from "./TooltipLabel.svelte";
export let value: any; export let value: any;
@ -21,7 +21,7 @@
<TooltipLabel {markdownTooltip}><slot /></TooltipLabel> <TooltipLabel {markdownTooltip}><slot /></TooltipLabel>
</Col> </Col>
<Col --col-size={5} breakpoint="sm"> <Col --col-size={5} breakpoint="sm">
<SpinBoxFloat bind:value {min} {max} /> <SpinBox bind:value {min} {max} step={0.01} />
<RevertButton bind:value {defaultValue} /> <RevertButton bind:value {defaultValue} />
</Col> </Col>
</Row> </Row>

View file

@ -5,8 +5,8 @@
<script lang="ts"> <script lang="ts">
import Col from "../components/Col.svelte"; import Col from "../components/Col.svelte";
import Row from "../components/Row.svelte"; import Row from "../components/Row.svelte";
import SpinBox from "../components/SpinBox.svelte";
import RevertButton from "./RevertButton.svelte"; import RevertButton from "./RevertButton.svelte";
import SpinBox from "./SpinBox.svelte";
import TooltipLabel from "./TooltipLabel.svelte"; import TooltipLabel from "./TooltipLabel.svelte";
export let value: any; export let value: any;

View file

@ -8,14 +8,15 @@ $size: var(--buttons-size);
$padding: 2px; $padding: 2px;
.linkb { .linkb {
@include button.base;
@include button.border-radius;
width: $size; width: $size;
height: $size; height: $size;
padding: $padding; padding: $padding;
font-size: calc($size * 0.6); font-size: calc($size * 0.6);
} }
@include button.base($selector: ".linkb");
img.topbut { img.topbut {
max-width: calc($size - 2 * $padding); max-width: calc($size - 2 * $padding);
max-height: calc($size - 2 * $padding); max-height: calc($size - 2 * $padding);

View file

@ -7,4 +7,6 @@ input[type="checkbox"] {
cursor: pointer; cursor: pointer;
} }
@include button.base; button {
@include button.base;
}

View file

@ -27,7 +27,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
><Col --col-size={4}>{basename(path)}</Col><Col --col-justify="end"> ><Col --col-size={4}>{basename(path)}</Col><Col --col-justify="end">
<ButtonGroup size={2}> <ButtonGroup size={2}>
<LabelButton <LabelButton
theme="primary" primary
tooltip={getPlatformString(keyCombination)} tooltip={getPlatformString(keyCombination)}
on:click={onImport} on:click={onImport}
--border-left-radius="5px" --border-left-radius="5px"

View file

@ -22,7 +22,6 @@ body {
padding-bottom: 5em; padding-bottom: 5em;
} }
// override the default down arrow colour in <select> elements html {
.night-mode select { overflow-x: hidden;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
} }

View file

@ -48,6 +48,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
flex-grow: 1; flex-grow: 1;
border-radius: 0; border-radius: 0;
} }
button {
@include button.base($with-disabled: false); @include button.base($with-disabled: false, $active-class: active);
}
</style> </style>

View file

@ -58,6 +58,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
.tag { .tag {
@include button.base($with-active: false, $with-disabled: false);
font-size: var(--base-font-size); font-size: var(--base-font-size);
padding: 0; padding: 0;
@ -81,6 +83,4 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--border-color: var(--border-focus); --border-color: var(--border-focus);
} }
} }
@include button.base($with-active: false, $with-disabled: false);
</style> </style>