Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Tesserae.Tests/src/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ private static void Main()
("Defer", () => new DeferSample()),
("Toast", () => new ToastSample()),
("LineAwesomeIcons", () => new LineAwesomeSample()),
("FileSelector", () => new FileSelectorAndDropAreaSample())
("FileSelector", () => new FileSelectorAndDropAreaSample()),
("Observable", () => new ObservableSample()),

};

var sideBar = Sidebar().Stretch();
Expand Down Expand Up @@ -175,6 +177,7 @@ private static IComponent MainNav(Dictionary<string, Nav.NavLink> links, Navbar
links["Toast"],
links["FileSelector"],
links["LineAwesomeIcons"],
links["Observable"],
links["ProgressModal"]),
NavLink("Collections").Expanded()
.SmallPlus()
Expand Down
14 changes: 7 additions & 7 deletions Tesserae.Tests/src/Samples/CheckBoxSample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ public class CheckBoxSample : IComponent
public CheckBoxSample()
{
_content = SectionStack()
.Title(SampleHeader(nameof(CheckBoxSample)))
.Section(Stack().Children(
.Title(SampleHeader(nameof(CheckBoxSample)))
.Section(Stack().Children(
SampleTitle("Overview"),
TextBlock("A CheckBox is a UI element that allows users to switch between two mutually exclusive options (checked or unchecked, on or off) through a single click or tap. It can also be used to indicate a subordinate setting or preference when paired with another control."),
TextBlock("A CheckBox is used to select or deselect action items. It can be used for a single item or for a list of multiple items that a user can choose from. The control has two selection states: unselected and selected."),
TextBlock("Use a single CheckBox for a subordinate setting, such as with a \"Remember me ? \" login scenario or with a terms of service agreement."),
TextBlock("For a binary choice, the main difference between a CheckBox and a toggle switch is that the CheckBox is for status and the toggle switch is for action. You can delay committing a CheckBox interaction (as part of a form submit, for example), while you should immediately commit a toggle switch interaction. Also, only CheckBoxes allow for multi-selection."),
TextBlock("Use multiple CheckBoxes for multi-select scenarios in which a user chooses one or more items from a group of choices that are not mutually exclusive.")
))
.Section(Stack().Children(
))
.Section(Stack().Children(
SampleTitle("Best Practices"),
Stack().Horizontal().Children(
Stack().Width(40.percent()).Children(
Expand All @@ -35,19 +35,19 @@ public CheckBoxSample()
SampleDont("Don’t use a CheckBox when the user can choose only one option from the group, use radio buttons instead."),
SampleDont("Don't put two groups of CheckBoxes next to each other. Separate the two groups with labels.")
))))
.Section(Stack().Children(
.Section(Stack().Children(
SampleTitle("Usage"),
TextBlock("Basic CheckBoxes").Medium(),
CheckBox("Unchecked checkbox"),
CheckBox("Checked checkbox").Checked(),
CheckBox("Disabled checkbox").Disabled(),
CheckBox("Disabled checked checkbox").Checked().Disabled()
));
));
}

public HTMLElement Render()
{
return _content.Render();
}
}
}
}
123 changes: 123 additions & 0 deletions Tesserae.Tests/src/Samples/ObservableSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System.Linq;
using H5.Core;
using Tesserae.Components;
using static Tesserae.UI;
using static Tesserae.Tests.Samples.SamplesHelper;

namespace Tesserae.Tests.Samples
{
public class ObservableSample : IComponent
{
private readonly IComponent _content;

public ObservableSample()
{
var boolObservable = new SettableObservable<bool>(true);
var stringObservable = new SettableObservable<string>("Text that has been set in the Observable");
var colorObservable = new SettableObservable<string>("");
var dateObservable = new SettableObservable<string>("");
var dateTimeObservable = new SettableObservable<string>("");
var monthObservable = new SettableObservable<string>("");
var weekObservable = new SettableObservable<string>("");
var timeObservable = new SettableObservable<string>("");
var sliderObservable = new SettableObservable<int>(0);

_content = SectionStack()
.Title(SampleHeader(nameof(ObservableSample)))
.Section(Stack().Children(
SampleTitle("Overview"),
TextBlock("Components that implement the \"Bindable\" interface can be bound to a \"Observable\" value."),
TextBlock("The internal state of the Component are then kept in sync with the Observable.")))
.Section(Stack().Children(
SampleTitle("Best Practices"),
Stack().Horizontal().Children(
Stack().Width(40.percent()).Children(
SampleSubTitle("Do"),
SampleDo("TODO")),
Stack().Width(40.percent()).Children(
SampleSubTitle("Don't"),
SampleDont("TODO")))))
.Section(Stack().Children(
SampleTitle("Usage"),
SectionStack()
.Section(Stack().Children(
SampleTitle("Bindable to bool"),
Label("Value: ").Inline().SetContent(Defer(boolObservable, value => TextBlock(value.ToString()).AsTask())),
CheckBox("CheckBox global").Bind(boolObservable),
Toggle("Toggle global").Bind(boolObservable)))
.Section(Stack().Children(
SampleTitle("Bindable to string"),
Label("Value: ").Inline().SetContent(Defer(stringObservable, value => TextBlock(value.ToString()).AsTask())),
TextBox("Initial value TextBox").Bind(stringObservable),
TextArea("Initial value TextArea").Bind(stringObservable),
EditableLabel("Initial value EditableLabel").Bind(stringObservable),
EditableArea("Initial value EditableArea").Bind(stringObservable)))
.Section(Stack().Children(
SampleTitle("Slider"),
Slider(100, 0, 100, 1).Bind(sliderObservable),
Slider(100, 0, 100, 1).Bind(sliderObservable),
Label("Slider: ").Inline().SetContent(Defer(sliderObservable, value => TextBlock(value.ToString()).AsTask()))))
.Section(Stack().Children(
SampleTitle("ColorPicker"),
ColorPicker().Bind(colorObservable).Width(200.px()),
ColorPicker().Bind(colorObservable).Width(200.px()),
Label("Color: ").Inline().SetContent(Defer(colorObservable, value => TextBlock(value.ToString()).AsTask()))))
.Section(Stack().Children(
SampleTitle("DatePicker"),
new DatePicker().Bind(dateObservable).Width(200.px()),
new DatePicker().Bind(dateObservable).Width(200.px()),
Label("Date: ").Inline().SetContent(Defer(dateObservable, value => TextBlock(value.ToString()).AsTask()))))
.Section(Stack().Children(
SampleTitle("DateTimePicker"),
DateTimePicker().Bind(dateTimeObservable).Width(200.px()),
DateTimePicker().Bind(dateTimeObservable).Width(200.px()),
Label("DateTime: ").Inline().SetContent(Defer(monthObservable, value => TextBlock(value.ToString()).AsTask()))))
.Section(Stack().Children(
SampleTitle("MonthPicker"),
new MonthPicker((1, 1)).Bind(monthObservable).Width(200.px()),
new MonthPicker((1, 1)).Bind(monthObservable).Width(200.px()),
Label("Month: ").Inline().SetContent(Defer(colorObservable, value => TextBlock(value.ToString()).AsTask()))))
.Section(Stack().Children(
SampleTitle("TimePicker"),
new TimePicker().Bind(timeObservable).Width(200.px()),
new TimePicker().Bind(timeObservable).Width(200.px()),
Label("Time: ").Inline().SetContent(Defer(timeObservable, value => TextBlock(value.ToString()).AsTask()))))
.Section(Stack().Children(
SampleTitle("WeekPicker"),
new WeekPicker((1, 1)).Bind(weekObservable).Width(200.px()),
new WeekPicker((1, 1)).Bind(weekObservable).Width(200.px()),
Label("Week: ").Inline().SetContent(Defer(weekObservable, value => TextBlock(value.ToString()).AsTask()))))
.Section(Stack().Children(
SampleTitle("ChoiceGroup"),
ChoiceGroup("Observable ChoiceGroup").Required()
.Choices(
Choice("Option A"),
Choice("Option B"),
Choice("Option C"),
Choice("Option D")).Var(out var choiceGroup),
Label("Choice: ").Inline().SetContent(Defer(choiceGroup.AsObservable(), value => TextBlock(value?.Text ?? "").AsTask()))))
.Section(Stack().Children(
SampleTitle("Dropdown"),
Dropdown("Observable Multi Dropdown").Required().Width(200.px()).Multi()
.Items(
DropdownItem("Option A"),
DropdownItem("Option B"),
DropdownItem("Option C"),
DropdownItem("Option D")).Var(out var dropdownMulti),
Label("Multi Dropdown Selected: ").Inline().SetContent(Defer(dropdownMulti.AsObservableList(), value => TextBlock(string.Join(", ", value.Select(e => e.Text))).AsTask())),
Dropdown("Observable Dropdown").Required().Width(200.px())
.Items(
DropdownItem("Option A"),
DropdownItem("Option B"),
DropdownItem("Option C"),
DropdownItem("Option D")).Var(out var dropdown),
Label("Dropdown Selected: ").Inline().SetContent(Defer(dropdown.AsObservableList(), value => TextBlock(string.Join(", ", value.Select(e => e.Text))).AsTask()))))
));
}

public dom.HTMLElement Render()
{
return _content.Render();
}
}
}
59 changes: 46 additions & 13 deletions Tesserae/src/Components/CheckBox.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
using static H5.Core.dom;
using System;
using static H5.Core.dom;
using static Tesserae.UI;

namespace Tesserae.Components
{
public class CheckBox : ComponentBase<CheckBox, HTMLInputElement>, IObservableComponent<bool>
public class CheckBox : ComponentBase<CheckBox, HTMLInputElement>, IBindableComponent<bool>
{
private readonly HTMLSpanElement _checkSpan;
private readonly HTMLSpanElement _checkSpan;
private readonly HTMLLabelElement _label;
private readonly SettableObservable<bool> _observable = new SettableObservable<bool>();

private SettableObservable<bool> _observable;
private ObservableEvent.ValueChanged<bool> valueGetter;
private bool _observableReferenceUsed = false;

public SettableObservable<bool> Observable
{
get
{
_observableReferenceUsed = true;
return _observable;
}
set
{
if (_observableReferenceUsed)
{
throw new ArgumentException("Can't set the observable after a reference of it has been used! (.AsObservable() might have been called before .Bind())");
}

if (_observable is object)
_observable.StopObserving(valueGetter);
_observable = value;
_observable.Observe(valueGetter);
}
}

public CheckBox(string text = string.Empty)
{


InnerElement = CheckBox(_("tss-checkbox"));
_checkSpan = Span(_("tss-checkbox-mark"));
_label = Label(_("tss-checkbox-container", text: text), InnerElement, _checkSpan);
_checkSpan = Span(_("tss-checkbox-mark"));
_label = Label(_("tss-checkbox-container", text: text), InnerElement, _checkSpan);

valueGetter = v => IsChecked = v;
Observable = new SettableObservable<bool>();

InnerElement.onchange += (e) =>
{
StopEvent(e);
IsChecked = IsChecked;
RaiseOnChange(ev: null);
};

AttachClick();
AttachChange();
AttachFocus();
Expand Down Expand Up @@ -57,7 +95,7 @@ public bool IsChecked
set
{
InnerElement.@checked = value;
_observable.Value = value;
_observable.Value = value;
}
}

Expand All @@ -83,10 +121,5 @@ public CheckBox SetText(string text)
Text = text;
return this;
}

public IObservable<bool> AsObservable()
{
return _observable;
}
}
}
}
31 changes: 21 additions & 10 deletions Tesserae/src/Components/ChoiceGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,26 @@ namespace Tesserae.Components
public sealed class ChoiceGroup : ComponentBase<ChoiceGroup, HTMLDivElement>, IContainer<ChoiceGroup, ChoiceGroup.Choice>, IObservableComponent<ChoiceGroup.Choice>
{
private readonly TextBlock _header;
private readonly SettableObservable<Choice> _selectedOption = new SettableObservable<Choice>();

private SettableObservable<Choice> _selectedOption;

public IObservable<Choice> Observable => _selectedOption;

public ChoiceGroup(string label = "Pick one")
{
_header = (new TextBlock(label)).SemiBold();
var h = _header.Render();
h.style.alignSelf = "baseline";
InnerElement = Div(_("tss-choice-group", styles: s => { s.flexDirection = "column"; }), h);
InnerElement = Div(_("tss-choice-group", styles: s => { s.flexDirection = "column"; }), h);

_selectedOption = new SettableObservable<Choice>();
}

public Choice SelectedOption { get => _selectedOption.Value; private set => _selectedOption.Value = value; }
public Choice SelectedOption
{
get => _selectedOption.Value;
private set => _selectedOption.Value = value;
}

public string Label
{
Expand All @@ -30,7 +40,7 @@ public ChoiceGroupOrientation Orientation
set
{
if (value == ChoiceGroupOrientation.Horizontal) InnerElement.style.flexDirection = "row";
else InnerElement.style.flexDirection = "column";
else InnerElement.style.flexDirection = "column";
}
}

Expand Down Expand Up @@ -79,6 +89,7 @@ public ChoiceGroup Horizontal()
Orientation = ChoiceGroup.ChoiceGroupOrientation.Horizontal;
return this;
}

public ChoiceGroup Vertical()
{
Orientation = ChoiceGroup.ChoiceGroupOrientation.Vertical;
Expand All @@ -95,7 +106,7 @@ private void OnChoiceSelected(Choice sender)
{
if (SelectedOption == sender)
return;

if (SelectedOption is object)
SelectedOption.IsSelected = false;

Expand All @@ -104,8 +115,6 @@ private void OnChoiceSelected(Choice sender)
RaiseOnChange(ev: null);
}

public IObservable<Choice> AsObservable() => _selectedOption;

public enum ChoiceGroupOrientation
{
Vertical,
Expand All @@ -116,13 +125,14 @@ public sealed class Choice : ComponentBase<Choice, HTMLInputElement>
{
private event ComponentEventHandler<Choice> SelectedItem;

private readonly HTMLSpanElement _radioSpan;
private readonly HTMLSpanElement _radioSpan;
private readonly HTMLLabelElement _label;

public Choice(string text)
{
InnerElement = RadioButton(_("tss-option"));
_radioSpan = Span(_("tss-option-mark"));
_label = Label(_("tss-option-container", text: text), InnerElement, _radioSpan);
_radioSpan = Span(_("tss-option-mark"));
_label = Label(_("tss-option-container", text: text), InnerElement, _radioSpan);
AttachClick();
AttachChange();
AttachFocus();
Expand Down Expand Up @@ -193,6 +203,7 @@ public Choice SelectedIf(bool shouldSelect)
{
IsSelected = true;
}

return this;
}

Expand Down
8 changes: 3 additions & 5 deletions Tesserae/src/Components/ColorPicker.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
namespace Tesserae.Components
{
public class ColorPicker : Input<ColorPicker>
public class ColorPicker : Input<ColorPicker>, IBindableComponent<string>
{
public ColorPicker(Color color) : base("color", color?.ToHex() ?? "#000000")
{
}
public ColorPicker(Color color) : base("color", color?.ToHex() ?? "#000000") { }

public Color Color => Color.FromString(Text);

public ColorPicker SetColor(Color color) => SetText(color.ToHex());
}
}
}
Loading