From 6e86c47eeb5d37e4e4ef34eec0244fab56eec061 Mon Sep 17 00:00:00 2001 From: Lasse Boisen Andersen Date: Fri, 5 Sep 2025 09:52:00 +0200 Subject: [PATCH 01/13] improve searching --- packages/browser-sdk/src/toolbar/Flags.tsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/browser-sdk/src/toolbar/Flags.tsx b/packages/browser-sdk/src/toolbar/Flags.tsx index da25650d..64a6cb0d 100644 --- a/packages/browser-sdk/src/toolbar/Flags.tsx +++ b/packages/browser-sdk/src/toolbar/Flags.tsx @@ -4,6 +4,12 @@ import { useEffect, useState } from "preact/hooks"; import { Switch } from "./Switch"; import { FlagItem } from "./Toolbar"; +const isFound = (flagKey: string, searchQuery: string | null) => { + return flagKey + .toLocaleLowerCase() + .includes(searchQuery?.toLocaleLowerCase() ?? ""); +}; + export function FlagsTable({ flags, searchQuery, @@ -19,9 +25,7 @@ export function FlagsTable({ }) { const hasFlags = flags.length > 0; const hasShownFlags = flags.some((flag) => - flag.flagKey - .toLocaleLowerCase() - .includes(searchQuery?.toLocaleLowerCase() ?? ""), + isFound(flag.flagKey, searchQuery), ); // List flags that match the search query first then alphabetically @@ -29,8 +33,8 @@ export function FlagsTable({ searchQuery === null ? flags : [...flags].sort((a, b) => { - const aMatches = a.flagKey.includes(searchQuery); - const bMatches = b.flagKey.includes(searchQuery); + const aMatches = isFound(a.flagKey, searchQuery); + const bMatches = isFound(b.flagKey, searchQuery); // If both match or both don't match, sort alphabetically if (aMatches === bMatches) { @@ -47,7 +51,7 @@ export function FlagsTable({ {(!hasFlags || !hasShownFlags) && ( - No flags {!hasShownFlags ? `matching "${searchQuery} "` : ""} + No flags {hasFlags ? `matching "${searchQuery} "` : ""} found @@ -59,10 +63,7 @@ export function FlagsTable({ flag={flag} index={index} isNotVisible={ - searchQuery !== null && - !flag.flagKey - .toLocaleLowerCase() - .includes(searchQuery.toLocaleLowerCase()) + searchQuery !== null && !isFound(flag.flagKey, searchQuery) } isOpen={isOpen} setEnabledOverride={(override) => From 57498254ae5794b2dd2897e9feef769aeb6f1c44 Mon Sep 17 00:00:00 2001 From: Lasse Boisen Andersen Date: Fri, 5 Sep 2025 11:00:15 +0200 Subject: [PATCH 02/13] clean up styling and get rid of opacity --- packages/browser-sdk/src/toolbar/Flags.css | 14 ------- packages/browser-sdk/src/toolbar/Flags.tsx | 11 +---- packages/browser-sdk/src/toolbar/Toolbar.css | 44 +++++++++----------- packages/browser-sdk/src/ui/Dialog.css | 40 +++++++++++------- 4 files changed, 47 insertions(+), 62 deletions(-) diff --git a/packages/browser-sdk/src/toolbar/Flags.css b/packages/browser-sdk/src/toolbar/Flags.css index a1a7bcf5..c4d9f664 100644 --- a/packages/browser-sdk/src/toolbar/Flags.css +++ b/packages/browser-sdk/src/toolbar/Flags.css @@ -44,20 +44,6 @@ } .flag-row { - opacity: 0; - transform: translateY(-7px); - transition-property: opacity, transform; - transition-duration: 0.075s; - transition-timing-function: cubic-bezier(0.75, -0.015, 0.565, 1.055); - - &.show-on-open { - opacity: 1; - transform: translateY(0); - /* stagger effect where first item (i=0) has no delay, - and delay is based on item count (n) so total animation time always is 509ms */ - transition-delay: calc(0.05s * var(--i) / max(var(--n) - 1, 1)); - } - &.not-visible { visibility: hidden; } diff --git a/packages/browser-sdk/src/toolbar/Flags.tsx b/packages/browser-sdk/src/toolbar/Flags.tsx index 64a6cb0d..5d512085 100644 --- a/packages/browser-sdk/src/toolbar/Flags.tsx +++ b/packages/browser-sdk/src/toolbar/Flags.tsx @@ -91,19 +91,10 @@ function FlagRow({ index: number; isNotVisible: boolean; }) { - const [showOnOpen, setShowOnOpen] = useState(isOpen); - useEffect(() => { - setShowOnOpen(isOpen); - }, [isOpen]); return ( Date: Fri, 5 Sep 2025 11:00:52 +0200 Subject: [PATCH 03/13] make flags that starts writh search query appear in top --- packages/browser-sdk/src/toolbar/Flags.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/browser-sdk/src/toolbar/Flags.tsx b/packages/browser-sdk/src/toolbar/Flags.tsx index 5d512085..395620f4 100644 --- a/packages/browser-sdk/src/toolbar/Flags.tsx +++ b/packages/browser-sdk/src/toolbar/Flags.tsx @@ -38,6 +38,9 @@ export function FlagsTable({ // If both match or both don't match, sort alphabetically if (aMatches === bMatches) { + if (a.flagKey.startsWith(searchQuery)) { + return -1; + } return a.flagKey.localeCompare(b.flagKey); } From 27f770fecfa0154631fd523d91dfebfad9d1cc21 Mon Sep 17 00:00:00 2001 From: Lasse Boisen Andersen Date: Fri, 5 Sep 2025 12:10:30 +0200 Subject: [PATCH 04/13] add toolbar hide and open app buttons --- .../browser-sdk/example/typescript/app.ts | 2 +- packages/browser-sdk/src/toolbar/Flags.css | 1 + packages/browser-sdk/src/toolbar/Switch.css | 4 +- packages/browser-sdk/src/toolbar/Toolbar.css | 71 +++++++++++++++++++ packages/browser-sdk/src/toolbar/Toolbar.tsx | 46 ++++++++++++ packages/browser-sdk/src/ui/Dialog.css | 4 ++ 6 files changed, 125 insertions(+), 3 deletions(-) diff --git a/packages/browser-sdk/example/typescript/app.ts b/packages/browser-sdk/example/typescript/app.ts index e0bb6d9a..718d9782 100644 --- a/packages/browser-sdk/example/typescript/app.ts +++ b/packages/browser-sdk/example/typescript/app.ts @@ -1,6 +1,6 @@ import { ReflagClient, CheckEvent, RawFlags } from "../../src"; -const urlParams = new URLSearchParams(window.location.search); +const urlParams = new URLSearchParams(window?.location?.search); const publishableKey = urlParams.get("publishableKey"); const flagKey = urlParams.get("flagKey") ?? "huddles"; diff --git a/packages/browser-sdk/src/toolbar/Flags.css b/packages/browser-sdk/src/toolbar/Flags.css index c4d9f664..3c42747e 100644 --- a/packages/browser-sdk/src/toolbar/Flags.css +++ b/packages/browser-sdk/src/toolbar/Flags.css @@ -4,6 +4,7 @@ color: white; width: 100%; font-size: var(--text-size); + height: 28px; &::placeholder { color: var(--gray500); diff --git a/packages/browser-sdk/src/toolbar/Switch.css b/packages/browser-sdk/src/toolbar/Switch.css index a86de5c9..10a57ca5 100644 --- a/packages/browser-sdk/src/toolbar/Switch.css +++ b/packages/browser-sdk/src/toolbar/Switch.css @@ -24,8 +24,8 @@ } .switch-input:focus-visible + .switch-track { - outline: none; - box-shadow: 0 0 0 1px #fff; + outline: 1px solid #fff; + outline-offset: 1px; } .switch[data-enabled="true"] .switch-track { diff --git a/packages/browser-sdk/src/toolbar/Toolbar.css b/packages/browser-sdk/src/toolbar/Toolbar.css index 98e1bc86..7bdb5d8b 100644 --- a/packages/browser-sdk/src/toolbar/Toolbar.css +++ b/packages/browser-sdk/src/toolbar/Toolbar.css @@ -70,6 +70,7 @@ --logo-color: white; --text-color: white; --text-size: 13px; + --text-small-size: 12px; font-family: system-ui, -apple-system, @@ -195,3 +196,73 @@ } } } + +.toolbar-header-button { + background: none; + border: none; + color: var(--dimmed-color); + cursor: pointer; + border-radius: 4px; + transition: background-color 0.1s ease; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + height: 28px; + width: 28px; + + &:hover { + background-color: var(--bg-light-color); + color: var(--text-color); + } + + &:focus-visible { + outline: 1px solid #fff; + outline-offset: 0px; + } + + & + .button-tooltip { + pointer-events: none; + opacity: 0; + + background: var(--bg-color); + color: var(--text-color); + padding: 6px 8px; + border-radius: 4px; + font-size: 13px; + } + + &:hover + .button-tooltip { + opacity: 1; + } +} + +[data-tooltip] { + position: relative; +} + +[data-tooltip]:after { + content: attr(data-tooltip); + position: absolute; + right: 100%; + top: 0%; + margin-right: 3px; + user-select: none; + pointer-events: none; + background-color: var(--bg-color); + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 0px 6px; + height: 28px; + line-height: 26px; + color: var(--text-color); + font-size: var(--text-small-size); + font-weight: normal; + width: max-content; + display: none; + box-sizing: border-box; +} + +[data-tooltip]:hover:after { + display: block; +} diff --git a/packages/browser-sdk/src/toolbar/Toolbar.tsx b/packages/browser-sdk/src/toolbar/Toolbar.tsx index 96f3e76d..6c440f01 100644 --- a/packages/browser-sdk/src/toolbar/Toolbar.tsx +++ b/packages/browser-sdk/src/toolbar/Toolbar.tsx @@ -17,6 +17,8 @@ import { parseUnanchoredPosition } from "../ui/utils"; import { FlagSearch, FlagsTable } from "./Flags"; import styles from "./index.css?inline"; +const TOOLBAR_HIDE_KEY = "reflag-toolbar-hidden"; + export type FlagItem = { flagKey: string; localOverride: boolean | null; @@ -40,6 +42,10 @@ export default function Toolbar({ const dialogContentRef = useRef(null); const [flags, setFlags] = useState([]); + const wasHidden = + window?.sessionStorage?.getItem(TOOLBAR_HIDE_KEY) === "true"; + const [isHidden, setIsHidden] = useState(wasHidden); + const updateFlags = useCallback(() => { const rawFlags = reflagClient.getFlags(); setFlags( @@ -79,6 +85,16 @@ export default function Toolbar({ const { isOpen, close, toggle } = useDialog(); + const hideToolbar = useCallback(() => { + window?.sessionStorage?.setItem(TOOLBAR_HIDE_KEY, "true"); + setIsHidden(true); + close(); + }, [close]); + + if (isHidden) { + return null; + } + return (