Detailed changes
@@ -1,6 +1,118 @@
-export const ButtonVariant = {
- Default: 'default',
- Ghost: 'ghost'
-} as const
+import { font_sizes, useTheme } from "../common"
+import { Layer, Theme } from "../theme"
+import { TextStyle, background } from "../style_tree/components"
-export type Variant = typeof ButtonVariant[keyof typeof ButtonVariant]
+// eslint-disable-next-line @typescript-eslint/no-namespace
+export namespace Button {
+ export type Options = {
+ layer: Layer,
+ background: keyof Theme["lowest"]
+ color: keyof Theme["lowest"]
+ variant: Button.Variant
+ size: Button.Size
+ shape: Button.Shape
+ margin: {
+ top?: number
+ bottom?: number
+ left?: number
+ right?: number
+ },
+ states: {
+ enabled?: boolean,
+ hovered?: boolean,
+ pressed?: boolean,
+ focused?: boolean,
+ disabled?: boolean,
+ }
+ }
+
+ export type ToggleableOptions = Options & {
+ active_background: keyof Theme["lowest"]
+ active_color: keyof Theme["lowest"]
+ }
+
+ /** Padding added to each side of a Shape.Rectangle button */
+ export const RECTANGLE_PADDING = 2
+ export const FONT_SIZE = font_sizes.sm
+ export const ICON_SIZE = 14
+ export const CORNER_RADIUS = 6
+
+ export const variant = {
+ Default: 'filled',
+ Outline: 'outline',
+ Ghost: 'ghost'
+ } as const
+
+ export type Variant = typeof variant[keyof typeof variant]
+
+ export const shape = {
+ Rectangle: 'rectangle',
+ Square: 'square'
+ } as const
+
+ export type Shape = typeof shape[keyof typeof shape]
+
+ export const size = {
+ Small: "sm",
+ Medium: "md"
+ } as const
+
+ export type Size = typeof size[keyof typeof size]
+
+ export type BaseStyle = {
+ corder_radius: number
+ background: string | null
+ padding: {
+ top: number
+ bottom: number
+ left: number
+ right: number
+ },
+ margin: Button.Options['margin']
+ button_height: number
+ }
+
+ export type LabelButtonStyle = BaseStyle & TextStyle
+ // export type IconButtonStyle = ButtonStyle
+
+ export const button_base = (
+ options: Partial<Button.Options> = {
+ variant: Button.variant.Default,
+ shape: Button.shape.Rectangle,
+ states: {
+ hovered: true,
+ pressed: true
+ }
+ }
+ ): BaseStyle => {
+ const theme = useTheme()
+
+ const layer = options.layer ?? theme.middle
+ const color = options.color ?? "base"
+ const background_color = options.variant === Button.variant.Ghost ? null : background(layer, options.background ?? color)
+
+ const m = {
+ top: options.margin?.top ?? 0,
+ bottom: options.margin?.bottom ?? 0,
+ left: options.margin?.left ?? 0,
+ right: options.margin?.right ?? 0,
+ }
+ const size = options.size || Button.size.Medium
+ const padding = 2
+
+ const base: BaseStyle = {
+ background: background_color,
+ corder_radius: Button.CORNER_RADIUS,
+ padding: {
+ top: padding,
+ bottom: padding,
+ left: options.shape === Button.shape.Rectangle ? padding + Button.RECTANGLE_PADDING : padding,
+ right: options.shape === Button.shape.Rectangle ? padding + Button.RECTANGLE_PADDING : padding
+ },
+ margin: m,
+ button_height: 16,
+ }
+
+ return base
+ }
+}
@@ -1,7 +1,7 @@
import { interactive, toggleable } from "../element"
import { background, foreground } from "../style_tree/components"
-import { useTheme, Theme } from "../theme"
-import { ButtonVariant, Variant } from "./button"
+import { useTheme, Theme, Layer } from "../theme"
+import { Button } from "./button"
export type Margin = {
top: number
@@ -17,19 +17,24 @@ interface IconButtonOptions {
| Theme["highest"]
color?: keyof Theme["lowest"]
margin?: Partial<Margin>
- variant?: Variant
+ variant?: Button.Variant
+ size?: Button.Size
}
type ToggleableIconButtonOptions = IconButtonOptions & {
active_color?: keyof Theme["lowest"]
+ active_layer?: Layer
}
-export function icon_button({ color, margin, layer, variant = ButtonVariant.Default }: IconButtonOptions) {
+export function icon_button({ color, margin, layer, variant, size }: IconButtonOptions = {
+ variant: Button.variant.Default,
+ size: Button.size.Medium,
+}) {
const theme = useTheme()
if (!color) color = "base"
- const background_color = variant === ButtonVariant.Ghost ? null : background(layer ?? theme.lowest, color)
+ const background_color = variant === Button.variant.Ghost ? null : background(layer ?? theme.lowest, color)
const m = {
top: margin?.top ?? 0,
@@ -38,15 +43,17 @@ export function icon_button({ color, margin, layer, variant = ButtonVariant.Defa
right: margin?.right ?? 0,
}
+ const padding = {
+ top: size === Button.size.Small ? 0 : 2,
+ bottom: size === Button.size.Small ? 0 : 2,
+ left: size === Button.size.Small ? 0 : 4,
+ right: size === Button.size.Small ? 0 : 4,
+ }
+
return interactive({
base: {
corner_radius: 6,
- padding: {
- top: 2,
- bottom: 2,
- left: 4,
- right: 4,
- },
+ padding: padding,
margin: m,
icon_width: 14,
icon_height: 14,
@@ -72,17 +79,18 @@ export function icon_button({ color, margin, layer, variant = ButtonVariant.Defa
export function toggleable_icon_button(
theme: Theme,
- { color, active_color, margin, variant }: ToggleableIconButtonOptions
+ { color, active_color, margin, variant, size, active_layer }: ToggleableIconButtonOptions
) {
if (!color) color = "base"
return toggleable({
state: {
- inactive: icon_button({ color, margin, variant }),
+ inactive: icon_button({ color, margin, variant, size }),
active: icon_button({
color: active_color ? active_color : color,
margin,
- layer: theme.middle,
+ layer: active_layer,
+ size
}),
},
})
@@ -0,0 +1,78 @@
+import { Interactive, interactive, toggleable, Toggleable } from "../element"
+import { TextStyle, background, text } from "../style_tree/components"
+import { useTheme } from "../theme"
+import { Button } from "./button"
+
+type LabelButtonStyle = {
+ corder_radius: number
+ background: string | null
+ padding: {
+ top: number
+ bottom: number
+ left: number
+ right: number
+ },
+ margin: Button.Options['margin']
+ button_height: number
+} & TextStyle
+
+/** Styles an Interactive<ContainedText> */
+export function label_button_style(
+ options: Partial<Button.Options> = {
+ variant: Button.variant.Default,
+ shape: Button.shape.Rectangle,
+ states: {
+ hovered: true,
+ pressed: true
+ }
+ }
+): Interactive<LabelButtonStyle> {
+ const theme = useTheme()
+
+ const base = Button.button_base(options)
+ const layer = options.layer ?? theme.middle
+ const color = options.color ?? "base"
+
+ const default_state = {
+ ...base,
+ ...text(layer ?? theme.lowest, "sans", color),
+ font_size: Button.FONT_SIZE,
+ }
+
+ return interactive({
+ base: default_state,
+ state: {
+ hovered: {
+ background: background(layer, options.background ?? color, "hovered")
+ },
+ clicked: {
+ background: background(layer, options.background ?? color, "pressed")
+ }
+ }
+ })
+}
+
+/** Styles an Toggleable<Interactive<ContainedText>> */
+export function toggle_label_button_style(
+ options: Partial<Button.ToggleableOptions> = {
+ variant: Button.variant.Default,
+ shape: Button.shape.Rectangle,
+ states: {
+ hovered: true,
+ pressed: true
+ }
+ }
+): Toggleable<Interactive<LabelButtonStyle>> {
+ const activeOptions = {
+ ...options,
+ color: options.active_color || options.color,
+ background: options.active_background || options.background
+ }
+
+ return toggleable({
+ state: {
+ inactive: label_button_style(options),
+ active: label_button_style(activeOptions),
+ },
+ })
+}
@@ -6,7 +6,7 @@ import {
text,
} from "../style_tree/components"
import { useTheme, Theme } from "../theme"
-import { ButtonVariant, Variant } from "./button"
+import { Button } from "./button"
import { Margin } from "./icon_button"
interface TextButtonOptions {
@@ -14,7 +14,7 @@ interface TextButtonOptions {
| Theme["lowest"]
| Theme["middle"]
| Theme["highest"]
- variant?: Variant
+ variant?: Button.Variant
color?: keyof Theme["lowest"]
margin?: Partial<Margin>
text_properties?: TextProperties
@@ -25,7 +25,7 @@ type ToggleableTextButtonOptions = TextButtonOptions & {
}
export function text_button({
- variant = ButtonVariant.Default,
+ variant = Button.variant.Default,
color,
layer,
margin,
@@ -34,7 +34,7 @@ export function text_button({
const theme = useTheme()
if (!color) color = "base"
- const background_color = variant === ButtonVariant.Ghost ? null : background(layer ?? theme.lowest, color)
+ const background_color = variant === Button.variant.Ghost ? null : background(layer ?? theme.lowest, color)
const text_options: TextProperties = {
size: "xs",
@@ -1,4 +1,4 @@
import { interactive, Interactive } from "./interactive"
-import { toggleable } from "./toggle"
+import { toggleable, Toggleable } from "./toggle"
-export { interactive, Interactive, toggleable }
+export { interactive, Interactive, toggleable, Toggleable }
@@ -3,7 +3,7 @@ import { DeepPartial } from "utility-types"
type ToggleState = "inactive" | "active"
-type Toggleable<T> = Record<ToggleState, T>
+export type Toggleable<T> = Record<ToggleState, T>
export const NO_INACTIVE_OR_BASE_ERROR =
"A toggleable object must have an inactive state, or a base property."
@@ -9,7 +9,7 @@ import { interactive, toggleable } from "../element"
import { useTheme } from "../theme"
import collab_modals from "./collab_modals"
import { text_button } from "../component/text_button"
-import { toggleable_icon_button } from "../component/icon_button"
+import { icon_button, toggleable_icon_button } from "../component/icon_button"
import { indicator } from "../component/indicator"
export default function contacts_panel(): any {
@@ -27,7 +27,7 @@ export default function contacts_panel(): any {
color: foreground(layer, "on"),
icon_width: 14,
button_width: 16,
- corner_radius: 8,
+ corner_radius: 8
}
const project_row = {
@@ -62,8 +62,9 @@ export default function contacts_panel(): any {
}
const header_icon_button = toggleable_icon_button(theme, {
- layer: theme.middle,
variant: "ghost",
+ size: "sm",
+ active_layer: theme.lowest,
})
const subheader_row = toggleable({
@@ -87,8 +88,8 @@ export default function contacts_panel(): any {
state: {
active: {
default: {
- ...text(layer, "ui_sans", "active", { size: "sm" }),
- background: background(layer, "active"),
+ ...text(theme.lowest, "ui_sans", { size: "sm" }),
+ background: background(theme.lowest),
},
clicked: {
background: background(layer, "pressed"),
@@ -140,8 +141,8 @@ export default function contacts_panel(): any {
},
active: {
default: {
- ...text(layer, "ui_sans", "active", { size: "sm" }),
- background: background(layer, "active"),
+ ...text(theme.lowest, "ui_sans", { size: "sm" }),
+ background: background(theme.lowest),
},
clicked: {
background: background(layer, "pressed"),
@@ -221,8 +222,8 @@ export default function contacts_panel(): any {
},
active: {
default: {
- ...text(layer, "ui_sans", "active", { size: "sm" }),
- background: background(layer, "active"),
+ ...text(theme.lowest, "ui_sans", { size: "sm" }),
+ background: background(theme.lowest),
},
clicked: {
background: background(layer, "pressed"),
@@ -271,8 +272,8 @@ export default function contacts_panel(): any {
},
active: {
default: {
- ...text(layer, "ui_sans", "active", { size: "sm" }),
- background: background(layer, "active"),
+ ...text(theme.lowest, "ui_sans", { size: "sm" }),
+ background: background(theme.lowest),
},
clicked: {
background: background(layer, "pressed"),
@@ -306,13 +307,10 @@ export default function contacts_panel(): any {
},
},
contact_button_spacing: NAME_MARGIN,
- contact_button: interactive({
- base: { ...contact_button },
- state: {
- hovered: {
- background: background(layer, "hovered"),
- },
- },
+ contact_button: icon_button({
+ variant: "ghost",
+ color: "variant",
+ size: "sm",
}),
disabled_button: {
...contact_button,
@@ -364,7 +362,7 @@ export default function contacts_panel(): any {
}),
state: {
active: {
- default: { background: background(layer, "active") },
+ default: { background: background(theme.lowest) },
},
},
}),