From a6e36a3994fb6165aeffae20679b5bf038ac609f Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 1 Sep 2026 14:52:31 +0200 Subject: [PATCH 1/7] fix(ui): draw the split button's divider on every surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The divider took borderDisabled while disabled, close enough to the disabled surface to vanish, so a disabled split button read as one wide pill with two icons in it. Checking the other states turned up more of the same: the divider is drawn on the button's own surface, so what it should be is a question about that surface rather than about the button's style. solid, accent fill border/core/on-accent, per the design solid, secondary border/core/default outline the button's own border, so the divider continues it ghost the button's icon colour, the only colour it has disabled border/core/default, whatever the style Only the first two are in the design; there is none for outline and ghost, and the previous grey hairline cut across a blue or red outline rather than continuing it. These are unreleased and unused, so the choice is cheap to revisit. The style defaults now take the resolved button style, since the divider's colour is a property of what it is drawn on. Adds a disabled matrix golden in both brightnesses. The state assertions read what is painted — the surface's outline, the glyph's own text style — rather than restating the tokens the implementation uses. Co-Authored-By: Claude Opus 5 --- .../buttons/stream_split_button.dart | 31 +++++- .../stream_split_button_golden_test.dart | 21 +++- .../buttons/stream_split_button_test.dart | 102 +++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart index cb5d95cd..7461e03f 100644 --- a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart +++ b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart @@ -212,8 +212,6 @@ class DefaultStreamSplitButton extends StatelessWidget { Widget build(BuildContext context) { final spacing = context.streamSpacing; final themeStyle = context.streamSplitButtonTheme.style?.merge(props.themeStyle) ?? props.themeStyle; - final defaults = _StreamSplitButtonDefaults(context); - // Resolved once and shared: the surface below and the halves above are the // same button style, so they cannot render as different colors. final buttonStyle = resolveStreamButtonThemeStyle( @@ -224,6 +222,10 @@ class DefaultStreamSplitButton extends StatelessWidget { themeStyle: themeStyle?.buttonStyle, ); + // Takes the resolved style because the divider's color is a question about + // the surface it is drawn on, which is the button's own. + final defaults = _StreamSplitButtonDefaults(context, props.style, props.type, buttonStyle); + final isEnabled = props.onPressed != null || props.onTrailingPressed != null; final states = {if (!isEnabled) WidgetState.disabled}; @@ -425,17 +427,36 @@ class _RenderHitTarget extends RenderShiftedBox { // These defaults are used when no explicit value is provided via // [StreamSplitButtonStyle] or [StreamSplitButtonThemeData]. class _StreamSplitButtonDefaults extends StreamSplitButtonStyle { - _StreamSplitButtonDefaults(this.context); + _StreamSplitButtonDefaults(this.context, this._style, this._type, this._buttonStyle); final BuildContext context; + final StreamButtonStyle _style; + final StreamButtonType _type; + final StreamButtonThemeStyle _buttonStyle; + late final StreamSpacing _spacing = context.streamSpacing; late final StreamColorScheme _colorScheme = context.streamColorScheme; + // The divider is drawn on the button's own surface, so what it should be is + // a question about that surface rather than about the button's style. @override WidgetStateProperty get separatorColor => WidgetStateProperty.resolveWith((states) { - if (states.contains(WidgetState.disabled)) return _colorScheme.borderDisabled; - return _colorScheme.borderDefault; + // A disabled button drops its fill, its outline and its icon colour for + // the shared disabled treatment, so the divider goes back to the hairline + // that reads on it rather than disappearing into it as borderDisabled did. + if (states.contains(WidgetState.disabled)) return _colorScheme.borderDefault; + + return switch (_type) { + // Drawn against the fill, unless the fill is already a light one. + StreamButtonType.solid => + _style == StreamButtonStyle.secondary ? _colorScheme.borderDefault : _colorScheme.borderOnAccent, + // Continues the outline it sits inside. + StreamButtonType.outline => _buttonStyle.borderColor?.resolve(states) ?? _colorScheme.borderDefault, + // No fill and no outline, so the icons either side of it are the only + // colour the button has to match. + StreamButtonType.ghost => _buttonStyle.foregroundColor?.resolve(states) ?? _colorScheme.borderDefault, + }; }); @override diff --git a/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart b/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart index 54210caf..532f3895 100644 --- a/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart +++ b/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart @@ -43,6 +43,18 @@ void main() { ), ); + goldenTest( + 'renders the disabled matrix', + fileName: 'stream_split_button_disabled_matrix', + builder: () => _buildMatrix(enabled: false), + ); + + goldenTest( + 'renders the disabled matrix in dark theme', + fileName: 'stream_split_button_disabled_matrix_dark', + builder: () => _buildMatrix(brightness: Brightness.dark, enabled: false), + ); + goldenTest( 'renders disabled halves', fileName: 'stream_split_button_disabled', @@ -69,7 +81,7 @@ void main() { }); } -GoldenTestGroup _buildMatrix({Brightness brightness = Brightness.light}) { +GoldenTestGroup _buildMatrix({Brightness brightness = Brightness.light, bool enabled = true}) { return GoldenTestGroup( columns: StreamButtonType.values.length, children: [ @@ -78,7 +90,12 @@ GoldenTestGroup _buildMatrix({Brightness brightness = Brightness.light}) { GoldenTestScenario( name: '${style.name} / ${type.name}', child: _buildInTheme( - _splitButton(style: style, type: type), + _splitButton( + style: style, + type: type, + onPressed: enabled ? () {} : null, + onTrailingPressed: enabled ? () {} : null, + ), brightness: brightness, ), ), diff --git a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart index 2f908c5f..fbb28f5d 100644 --- a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart +++ b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart @@ -262,7 +262,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( streamTheme: streamTheme, - _splitButton(onPressed: () {}, onTrailingPressed: () {}), + _splitButton(style: .secondary, onPressed: () {}, onTrailingPressed: () {}), ), ); @@ -271,6 +271,106 @@ void main() { expect(tester.getSize(divider), const Size(1, 24)); }); + // The divider is drawn on the shared surface, so which colour it takes is + // a question about that surface rather than about the button's style. + testWidgets('draws the divider against an accent fill', (tester) async { + final streamTheme = StreamTheme(); + + for (final style in [StreamButtonStyle.primary, StreamButtonStyle.destructive]) { + await tester.pumpWidget( + _withStreamTheme( + streamTheme: streamTheme, + _splitButton(style: style, onPressed: () {}, onTrailingPressed: () {}), + ), + ); + + final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); + expect( + tester.widget(divider).color, + streamTheme.colorScheme.borderOnAccent, + reason: '$style solid is an accent fill', + ); + } + }); + + // An outlined button has a hairline of its own; the divider continues it + // rather than cutting across in a different colour. + testWidgets('continues the outline through the divider', (tester) async { + final streamTheme = StreamTheme(); + + for (final style in StreamButtonStyle.values) { + await tester.pumpWidget( + _withStreamTheme( + streamTheme: streamTheme, + _splitButton(style: style, type: .outline, onPressed: () {}, onTrailingPressed: () {}), + ), + ); + // Settled, or a subtree can still be reporting the previous + // iteration's colours when it is read. + await tester.pumpAndSettle(); + + final outline = _surfaceOf(tester).shape as OutlinedBorder; + final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); + + expect(tester.widget(divider).color, outline.side.color, reason: '$style outline'); + } + }); + + // A ghost button has neither a fill nor an outline, so the icons either + // side of the divider are the only colour it has to match. + testWidgets('matches the icon colour on a ghost button', (tester) async { + final streamTheme = StreamTheme(); + + for (final style in StreamButtonStyle.values) { + await tester.pumpWidget( + _withStreamTheme( + streamTheme: streamTheme, + _splitButton(style: style, type: .ghost, onPressed: () {}, onTrailingPressed: () {}), + ), + ); + // Settled, or a subtree can still be reporting the previous + // iteration's colours when it is read. + await tester.pumpAndSettle(); + + // The colour the glyph is actually painted with. An Icon carries none + // itself and the button's IconTheme is not the ambient one, so this + // reads the text style the Icon ends up building. + final iconColor = tester + .widget( + find.descendant(of: find.byIcon(StreamIconData.voiceFill), matching: find.byType(RichText)), + ) + .text + .style + ?.color; + final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); + + expect(tester.widget(divider).color, iconColor, reason: '$style ghost'); + } + }); + + // A disabled button drops its accent fill for the shared disabled surface, + // so the divider has to come back to the hairline that reads on it — it + // used to take borderDisabled and vanish. + testWidgets('keeps the divider visible while disabled', (tester) async { + final streamTheme = StreamTheme(); + + for (final style in StreamButtonStyle.values) { + await tester.pumpWidget( + _withStreamTheme( + streamTheme: streamTheme, + _splitButton(style: style), + ), + ); + + final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); + expect( + tester.widget(divider).color, + streamTheme.colorScheme.borderDefault, + reason: 'disabled $style', + ); + } + }); + testWidgets('honours separator overrides', (tester) async { await tester.pumpWidget( _withStreamTheme( From 5096bcb66b964fc0526b1a1aac3d1e330ddef372 Mon Sep 17 00:00:00 2001 From: renefloor <15101411+renefloor@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:57:36 +0000 Subject: [PATCH 2/7] chore: Update Goldens --- .../goldens/ci/stream_split_button_dark.png | Bin 10841 -> 10820 bytes .../ci/stream_split_button_disabled.png | Bin 2430 -> 2451 bytes .../stream_split_button_disabled_matrix.png | Bin 0 -> 8542 bytes ...ream_split_button_disabled_matrix_dark.png | Bin 0 -> 9444 bytes .../goldens/ci/stream_split_button_light.png | Bin 10060 -> 10037 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix.png create mode 100644 packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix_dark.png diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_dark.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_dark.png index c599f31e7270a49e8a80a3b7b795bc789345651d..e32e3b350c7691dd00a6a232fcb1dbac97f88f71 100644 GIT binary patch literal 10820 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 literal 10841 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 diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled.png index f39ee7eb1b9afa2e2fecdeff13c7c3b663380277..70afda8573c3510b737cbd50f32dbfa7ecc8c4e0 100644 GIT binary patch delta 2121 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 N5!}>(qId1ie*u7xt%?8u literal 0 HcmV?d00001 diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix_dark.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..fb0822fb97c1d95a4f1dea2bc7f353dfd4241824 GIT binary patch literal 9444 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 literal 0 HcmV?d00001 diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_light.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_light.png index 2f57f995b13ef0cc5a9d9a4954c6a8df254ba99e..22a533cccab3e322ff9fdc0a6372dce9bc0333a2 100644 GIT binary patch literal 10037 zcmdUVXH-*LxAsP`p{P^^K|J&#MFqh~(?d(>fdJAG=}n|a2MZRIAWfPSNr8X_=}n4C zkq!w(snQKadT-x~zW2RO(hKfXO+>^+jb)|zv!S)OOjywKHFW1#1thaiXnuC9!P zAUbIXqTx7p1bjkM;DQ4;I?r2hgJa+dJa+dv^yWhZAsTgpBhr=yF8;9q-(D657;#lrz3if;BdnP4b$l#U{5q~jfl zl1=swkqa3d++OocYrOvJ8}1=w#YU3YeNH*%Y2yF$Z2!(`Rr6WArW#FX(&*s9V1I*5 z_J?ut2=u%(*Wu85*}nGLt`s8WBv1uhJ?Y(l9t<=7|9J?c*F3Va8W}zFyb}NpTf8{4ADl=R}Osa%P&)j&;}AQEP(UC5^Cx z*ftG)70PLyC*RpRV&1gI`@@{1tbi;I9NxmxK9PvD&3vyFb0pC?lDCy%!~Fzz+CE`* z-4nNQz&d#(w4r~qXmycH3O`J0w!ziNHhE>d#yq|%o$bAibS$_y&vn{X8%z^S(DEBQdDvSBmkXh`xh(FLH`?xksBbb}I$1Pvc;x!vGKY##3b#wpuE2B?TW0 zY}6wvMqzLd+Zs2dyr7||2(|`aI}RqAGb%)4jNd41?=3zK%IUXsn+PXGgm7!G9SJqr z`NBME!r~4K*zS#Di-N-?O!-|0ME7>BDeuQ|L?1E%jJHB+`6#ltBc?3|`okLDxm8@{ zj2wMW;e(ReI=J6VJy7qk>@@*j0GqVmP}y$7Qx3yvAgHsz&fFNBdSX>o8rd4d37`tz;UzSd_kx9pUL`3qhShrN0(zkS_N!p8=G9yi{*CD&< zd~>9Z(=QedS{stYh07^p>3t~|+V9Xn-8yH~+i`e(sJ0Mg?li|9L#(JOu}eN;A!Y6N?L!s z=`Tu4&HJDPyLDV;LM~1El}gnwVj7W@YbkZ7KrmexafMO+oP&^!9LJ2r%hjw~C*39r z1x@S#$v-qr4Ix9~DJ<(hDX&!paC#+v;AIDPR2)Z*eQ8Yg;93@i4sy{Gf(I0m^4ZDo zxq^?D{`YIWs0Bz0QIBh|sl+vIbPH-01_bKJ21AlDna^->mrj}Fv+j|M+yDE$jbC{> zuMIzK(#UcIG++%#Ui3T4=D=Me^j*MIMNh&tB1Q^rhu_P681gyMM(xjc1R(33S2l&GNn#?&(V>`{f~<8mXVKL;r3xmw30DqaHq`9 zER%p+B7Q(m2uf#6Aaq&1;tow@X+fSz(6*N7JKlH(e9@LShU8_>(K!PteZ9;DK7c+b z1HB#*x9J>NsqfcijJnVoG@i!#cK=c5)+A2#LC~Wj`i9fBn*Dc>C04MoDm*>%(w#qI zJWS+ZXxOAlfb$60u8{cZRZ&M8z6{xbnLBqy#HD-1iT`DZ6&Mz{KZ_5@X#OzGOz?&h zuYsA9nXmu9rtkW>aLV2}{fey)f*4kWAd_4e za)gBim7G*JpZ}w>9$1Z!g39bwP!}y*JV`zdedA$_^Cy$gLHL+&lP_*kHWkRiRJnw} zh8v{{^4k=jCbvdkrc_NQYtz&UfWUfqnJhe)FCn^yO(0N15s@t-{bmheR9yi4=zCmV z>^AAQAANo$l^Wv{nWTN^nWEfZDIYuk*a}(H&%OJ@8JoOs%F)8L?>$YrjdgmhYO7fa z@B4_q+<2(dtQ}^tz4e7O8+mWXM#Ej?nj#yN&`{0|oyvB?Kjrf- zhFkh-xPFn!kuYyY>_XWwh$kmb7CQWl+Nxfiurh{#fa%2s=1%IQkrXlWpqulbyFZ&H z|8;-uFe%y63hYjDN|EGS){TjD3g5eZamr+lW~S`VqZ=9_9?=vQsB@6>+4r;16#jM` zNAzmG3?ihde!_aMhcLPc)w-lq-QqZxcB8(*Y-uDU+Twme_XU|0I$%A4){5?XToluV zShkKFt#msNv&hwmmM=>*p(ehg^c2Fodwa*!6?ZYKySAey)avS>RVXfvy=N5J)(3Z2 zEM;4;t0}EA=wE(uZ-^1f!o}y&mx@yia zXIQj>&@&a^Yuwe#!r5&Y#hF#;z@073-ha}q!4 zEo0GGspu5(b1ZD!n3!iQjP#~2i0g}TRW)(4*g{<@_Soa(a*?kX_-q4f{}heQk#8N- zR_u#r4?f-+wAVUq$UJ>Pw5N=9R?MHN5ZQ`v-|ZX+EvaD$*E^Oxcj zYVm>~7d2|Yf$P2U|I!1?(7l#<{8g(XuFR1MXo&NSEaUZu_OPd@X58_;O*KVw(1wBo zj2>;(r)%iStdoH5iajJKASjfGrjar$lEm&T1gNJ@C!_DgwYSF#2nd|_ks}t;R33J7 zdFa{Pe3h1b6Z*z`?9g?z4XnWvaS7Jw^~7FXQyxcHG8c*8vU=7#zEf=$|KeR%O}Wpn zNY*2$)Nk2=!OdPQIh)mjeGv(0njgoe`pd69uj7tDqZtz0X&~u1i}HRj&2!cxv2AD( z%O9WFpcz|Y%8qokUf4S|DOgFL7Za3~c>MvvC#}+p( zi^tH^7EUayu!gI|HK#thsQb>CzN3^pJ>+`{ANVdy=^=~yi_QIozjr!Xnv~79ZY_Oi zRBy0pKCAI8NQ^O(pnFMJP6b9@VAX$T{AOGo%~^j(&o5Q5(IZx~ZqhH6htVkC!61s_ z>0-)5bdW-(-2~Z0Nk7Y(FuL>bepm(q7u}p{+3|M|>I|JoKHo*}bQXT~cSFmtG zt?cYAPFLdle0>nEO=bN|$5nWU(J#vSqmQeY8AUx8ZS_B_Pl%(bUi|t-1E+F$WhEh zvL<_j(3m*enaBZixxGC2KuJ6yO^;w^C~nkE{1g%nS>XHz~i9Pm%RzeyPej`$}Z!J25aH`|urOgmM9_ zB&1rl4OlK6ei!%EBp(q%g3fqH@xmRMqYEJcw#V=^s}A{QfoQ$0h+k(?km_UL}DfTfU=%NlsDJ^YhZn$gbL9#>wSYG+5371qyS4zF(ZM)TtbU_1Ki=WHe3s@j1?OhV~-gk)_F; z5%ni_ifpWmameppEMT2;k~v6TOQH61Q|%+q)U?!mpO(w2LUUK-V8pvT3ik97O=cDY zKBtn<9<|~1CyH&XzImO59Gq^R`T0#nFbPcqbs)W|jwRsPdP|lgru<`I+L| z9x*mtskot~mRQKC){NEfmoDr0y`+?Jk7)%kX-9cWQ<8qF<-93(@8k0exs)BkL-sG` zdi025L1%wCsLg$&Q-O;-;91w_yk}aNa^qUh>RxyHt0Bs%`SSywR?e=FgWcO5njh)e zc22(49b&Ak&1YY>_CmT*b9c3ud^NdPGSqCXEhC?*2?EQ3AfQE|oHQqm9jlQy&$PSI zgkq$xq@uDTZjC$eGkZ*)Q29e1{sLIiHgZTiTTi4ArWlGD_p8P)ANI!gYpUfVfaRl> z-h&hejQu)Xg8>A)UvLmf>UzVe+AfX`ZAS|MSJ+eU3bHV3;%~fp1z^~4esMPY$4j*L zqy#+u>M^Li$-x4%K2-jk-f!;k9N=MOqN$D@rWK?rwZGyevc;OjFZ4KQ)Wz9cb_R*_ zjjjh_RIyKim=Lv~?*#d}rA`Z9cq!7L8IfI7%5QKf|0xw)iDrH5wlXE%e#df1G6wlw zUykiE*B6r|hbM>Ffgh^?U`}Ty)J~5(6PmbLEwSBk`yQv?t|_SGolGJlq#lnMCOSy6 z%|kg;PkuiRxV{4y;4H+J$uSoyWnuC#+aABY`1sygs^3V}G;1&ri~evGhi=<V#NBKBF7&tiee<=M*6|-kvuTY_TGn)p@>B*}iW*L5Kr| z%etQ{o2ewY>atSpk9bhz?CUk?b#nIAD;*KW>0V}FJ43x5!8za96Aez18nm@t;mT&k zg3L(?t%(2zbS=HFAXDoGkm)8M2GLEj0}O^=J+LKw8@{x%{)t}Cz>bkGzmH0@(BS=) zD+pc`!(=0Q$0`s4OvO6ny66{l1LcRZ)YY=J>hHk63x`Kw@}2Lc`2)xQm&*ll!vn4m zL0-Rj?9|RSaP$-{;!_6$)kcr*_Dc*9N*14|X8Pdd%oE&+(>vSkT*(}%1Z2clS>T$$ zE_{`#kOR#hFE`5d;#A|BZ|cW8BDv<_*IP-DZkVxDwqK$)L*xg*BIs_WcP+uW$bB|2 zD!=7$F*Nny8@pW0Ki%sp6tq%74eye>k9I0)S`j)%ne_G1s)7PqYEk&oWb^wjX_RwE zUA~uSyku*Yp_c8WaoN^tNK&xPKcvgR^j5wD_L^gorW_k8+tn>j0h%lN5R!_&?-!wc z7sR-J$vkIwhK>K$gJV=*YpdvfjcrH!l)GsSXFHY=bJ`7(LC_?9{ao$GaXyXo^<4c^ zyuHJvpKVzY-oKH(p;pqdK96p4&!?19UVo)5oS1W$2VXEPI#yxL$Q&VZA<{8eAlKo^$|Ybr^m}U= zJ~a#@0_;raU7q&$)TTfY-LFJ&1K0olfqA%F*`yEk(7EJ=nx-0b>e$=oZ)>k|eG!=? zJqg~$q*1B^xTQeH;DfVJPRR>UMH~n67DS1!d^(lexDiw?&ir?me{@qU zR%vTDmLg=h6r@8uf0>Iml)rg0BLideH{KKAHVpy&W;?YwiF?*3pxg{oG@m8LCO`|c z8t@Hy+P}^6Phs^x1LObUsJAHhT_-CT9VAt_+9@H`Q>;1(nhAQKnLxT$t#_ZN*&A?% z!%$K@=Em>(2{inN^pPcK({xtTzV@V{B{+CG2HCyiW$n}Ambc6#-nIoK@kgrKy2IYH z1xYSiC1wv!O8Cn@ry<WH z*5^fBhXg_bKk6}NPvOboV&)m&qWQrsdxAYPXK&DxrRlDw8f~b3(?~#Gc%%ZJMAi`% zibDm?UH0SUrC;{j8fOmYs?iKPdci<7WY z<%w?fXj81Zd)}C+p-{eEL`LJKtQ7}9^K0$A#iW}9j!5~hv?c9QLI{gr(`Nj#DwZ1? zQN9l2h2p=MoqL76_x)yq2L@QoJKZmFhq6uYa8%hozF)HyAyB;|&!2V>$gg9X17>H+ z(LpY6*$YKCebvgG0$H-xV}zgv%o9pyF{{d81%xx6E1C)XV{)%-x; z4RXjDjWo|0)d@rn#pfDLWs7>Z8p$M}S?*`HME(2}Vwal31LvG7AC3&soaDL@yEC*P zz4Z=g|X|;l&RWbtq}DE+kMtd3sH_Brs8%lgn@qIN^vH)UHf;PIGX+FckMnDS(HlO}`v zh{Al?k76-%2TrHCHw^tApPVP>2kc?=p#m$bITpDSx9zhRnEQ$i-g#uT%j`y=E5`-< zi+_oUC~PNCWWg+w4!t;4T@lCWWO2h^ryukQ=*4{98+DuC9;mSomkLe#9`s!?+j-C5 zJGZjVEB3lIWvHg_hx9?l?$Aa8ntDX`H)yMztUhoO7mB>NQ90tgxM%c-@AwTU3a>S< z#0IaZ;4!VjFROQ4=YwYl4En2w{>^%9fXo~_&4U$_6Lm|BWX4qMInncFVn=J=zY5$x zfp~9@Sxp|f3k&CSoH45DMW3*6hBeS=aF^)>i0`NgAES1Ar&#IwW7`w7|F9sv3+K9j z991+PkkWEmOma3DzG?*;qbK4@T**J%ERj>;50cPV?5}rak>YnB9~t(>QGPcDK4d>o zWZ&N`re6-@t+tqDXI^u7dOq&**GcL7dkiyGzs9BIFN99X+?x#O2^qIXZ~EtUM64yu z_db7RJ}ch!ExsmWm-e*OWN!&{PpEwlx~Ge}DvdBLb(+g5?NQKTK3}zFXy*)NeVyP^ z)F1N`B<$HrT;-~#4@#@l!&rX&4(J>}^SUTp!(wsTYq`92$0TFmew+QXpvYN8bxDk5 ztjAY(d!2OV>wBC1`SI!O#Z15xLC7mTHb6N|ER>h7!%dWa_0?SFq6y{yIBGq)P;Nvp zN^ysUqewu)O;^jS08WAokMtnu4Hn(5Z%dwy!8ftWxEp#f+Z?sB`@V3Xl(_M60N{47 zD%+IsNcSuM9uT$H!QCSWe`&P~aYhg-g9#uR;%$OgzQ#k)5i<3+0LG14IIyuP%b@r1 zo8$nIG8Nz^%GTqmWJbvNV#vrIBGsdUa?TKu8l1GeanSWgXQg_as-M*d>Z^XCT)L2) z49LaxZzS~R%EkfJL+)r;WRPNXs3aF0XaTCRxT&ikdI8WU_E?_&ap5>TxNC?F$cAx9^g4K$$=;9jGL z;!0fsRxz1Oshsff`Inehc-6<3scbD#MhBPsFOL_U3|@1tM_ao6OH9x(@@N+RH?#8F zc5EIwPy^b;9*ol>QcHUO+dz%Z;#q%|l;xP8ev2XX=HhLP8$0Y+O$&^dn6<%K$bSi^ zxI`z;mcybrN>{IQcMfWOKmNJTapGwQP$VVav?hDANf1OQ)s}=Zzz(`aEi88U#6oX# zKxjj3rkbmLjna4SgF0}U`f6xh`ghR(_DyvZPIcqPu()7iR#1|_6d!Z5+0rS%5sk64 z1#3wav++P6UNenxsd^oSravbP)FCcSOt#`RAF2L4wcNr=0YAo|4OKx-r(}}eh$ZcA zNq{~6Tw6Ju+j>Jd5{P922d$ntzY!Ei)ubpWTY!w=z3i@)b7bhT54CHr?dEM~qN)vc@xBsWnZ7Ac<{neLGBH9Q7bH2qUH=zUT1diHeo zMz^>YLu)ZRkGxTpT8jJTd8+o8Jhui=zH=TCA$vY#JwEt+$QF~>cGqdGM%f>P;!Nk! z-18uUL#L+x#p1Go&9}I>jf$%_Rf^%{yU6LPTPupIdDK(WLnqv}AK`=d&PN38ou_8Z z8x{wBCYx({!v1r&sOSu`s(r7)P)=kU_Q?VDriV|r@VPk5u+gPV<-WaQ9r#_y9hv?m0uu}P5e%#Y z@?cVH2KS!s*0XC)i$M!UuUnGQHg%gaHMmU6EfKNZDH#?;iZa=QDu-?xo>6wG3|RCP z^`GTM_l)a2(~GPFPnm4+-~6IcE0d-ZyY0UDD%w6RS4<;5Y}SSDAw``bs^%clf4e8I ztD8%a?4h`q4=_vun%V&S;uP$z4CE?a{WU_E6`M7gdR)c)k}w+`BD;9N0Ij{L{>cEd zwkk#3@Nv-JkYh0Oi(B3p&P+GE#+8ew=GxtZ{bwaroBSO6Bi!U&XN9c8mitVl>%gm& zqFHhqlN)qoZ|;GFyRyIKG+(*z9|0Wv@YXH)Q?{`K!$jg*yohK0joa8$p^PiU{m?1% zJ(>vDG8FH@`eECe16qE=Jj1b~;_&q~k?K2PZ|@@A1Tg9_F~P*x7^nDgRjdOQ8P(sp zXQXwmP%_Cl1p?1?&$&yV-Ej3_@YMP2l5cokKv+9I%&dU}6JAJrmj)|LD{#6Jz}K{oU3yy>%yL7+d!ZX|@7{_cikF+4hu^(|NDV51 zNd%@u+rPC2fJ8mIqSrDT=!}S7J}s9%hKVE;?K!eub2nUC(gGr|Osp(7G_qQjXl^~b z0MnJM(~-Lw_KZ6!Fr?ziF}@L_JCle>?%>VD`uf#AfMG{FI8D4X59F6V#-KoPxt~Ib zu0w%$KzUKn#n!P&M9Iiq$`m62Jo9zn#(Q-E%+Cy!a0yHNx8-r^g}nuRY-wL>C_odn z^Z>!0ij$kwOG?keG_L^X$Sz~?~@#82Av!YHWcKqf7r(OpD?F=MLj02bB~k0 RQ!^g~SJ74`-m-f1{{UoLF6saP literal 10060 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 Date: Tue, 1 Sep 2026 16:48:45 +0200 Subject: [PATCH 3/7] reduce split button to 2 types --- .../lib/components/buttons/split_button.dart | 56 +++--- packages/stream_core_flutter/CHANGELOG.md | 5 +- .../buttons/stream_split_button.dart | 108 ++++++----- .../components/stream_split_button_theme.dart | 59 ++++-- .../stream_split_button_theme.g.theme.dart | 24 ++- .../stream_split_button_golden_test.dart | 40 ++-- .../buttons/stream_split_button_test.dart | 177 +++++++++--------- 7 files changed, 250 insertions(+), 219 deletions(-) diff --git a/apps/design_system_gallery/lib/components/buttons/split_button.dart b/apps/design_system_gallery/lib/components/buttons/split_button.dart index 870cbb9f..338c0f51 100644 --- a/apps/design_system_gallery/lib/components/buttons/split_button.dart +++ b/apps/design_system_gallery/lib/components/buttons/split_button.dart @@ -17,20 +17,12 @@ import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; Widget buildStreamSplitButtonPlayground(BuildContext context) { final icons = context.streamIcons; - final style = context.knobs.object.dropdown( - label: 'Style', - options: StreamButtonStyle.values, - initialOption: StreamButtonStyle.secondary, + final variant = context.knobs.object.dropdown( + label: 'Variant', + options: StreamSplitButtonVariant.values, + initialOption: StreamSplitButtonVariant.regular, labelBuilder: (option) => option.name, - description: 'Split button visual style variant.', - ); - - final type = context.knobs.object.dropdown( - label: 'Type', - options: StreamButtonType.values, - initialOption: StreamButtonType.solid, - labelBuilder: (option) => option.name, - description: 'Split button type variant. Outline draws one border around both halves.', + description: 'Split button color scheme variant.', ); final caretUp = context.knobs.boolean( @@ -61,8 +53,7 @@ Widget buildStreamSplitButtonPlayground(BuildContext context) { child: StreamSplitButton.icon( icon: Icon(icons.voiceFill), trailingIcon: Icon(caretUp ? icons.caretUp : icons.caretDown), - style: style, - type: type, + variant: variant, tooltip: 'Mute', trailingTooltip: 'Audio settings', onPressed: leadingEnabled ? () {} : null, @@ -94,7 +85,7 @@ Widget buildStreamSplitButtonShowcase(BuildContext context) { crossAxisAlignment: CrossAxisAlignment.start, spacing: spacing.xl, children: const [ - _StyleTypeMatrixSection(), + _VariantSection(), _DisabledSection(), _CallControlSection(), ], @@ -103,8 +94,8 @@ Widget buildStreamSplitButtonShowcase(BuildContext context) { ); } -class _StyleTypeMatrixSection extends StatelessWidget { - const _StyleTypeMatrixSection(); +class _VariantSection extends StatelessWidget { + const _VariantSection(); @override Widget build(BuildContext context) { @@ -112,28 +103,26 @@ class _StyleTypeMatrixSection extends StatelessWidget { final spacing = context.streamSpacing; return _ExampleCard( - title: 'Style × type', + title: 'Variants', description: 'The surface resolves from the same button style the halves use, so the two never drift apart.', child: Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: spacing.md, children: [ - for (final style in StreamButtonStyle.values) + for (final variant in StreamSplitButtonVariant.values) Row( spacing: spacing.md, children: [ - SizedBox(width: 88, child: Text(style.name)), - for (final type in StreamButtonType.values) - StreamSplitButton.icon( - icon: Icon(icons.voiceFill), - trailingIcon: Icon(icons.caretDown), - style: style, - type: type, - tooltip: 'Mute', - trailingTooltip: 'Audio settings', - onPressed: () {}, - onTrailingPressed: () {}, - ), + SizedBox(width: 88, child: Text(variant.name)), + StreamSplitButton.icon( + icon: Icon(icons.voiceFill), + trailingIcon: Icon(icons.caretDown), + variant: variant, + tooltip: 'Mute', + trailingTooltip: 'Audio settings', + onPressed: () {}, + onTrailingPressed: () {}, + ), ], ), ], @@ -167,7 +156,6 @@ class _DisabledSection extends StatelessWidget { StreamSplitButton.icon( icon: Icon(icons.voiceFill), trailingIcon: Icon(icons.caretDown), - style: StreamButtonStyle.secondary, onPressed: leading ? () {} : null, onTrailingPressed: trailing ? () {} : null, ), @@ -206,7 +194,7 @@ class _CallControlSectionState extends State<_CallControlSection> { child: StreamSplitButton.icon( icon: Icon(_isMuted ? icons.voiceOffFill : icons.voiceFill), trailingIcon: Icon(_isSettingsOpen ? icons.caretUp : icons.caretDown), - style: _isMuted ? StreamButtonStyle.destructive : StreamButtonStyle.secondary, + variant: _isMuted ? StreamSplitButtonVariant.destructive : StreamSplitButtonVariant.regular, tooltip: _isMuted ? 'Unmute' : 'Mute', trailingTooltip: 'Audio settings', onPressed: () => setState(() => _isMuted = !_isMuted), diff --git a/packages/stream_core_flutter/CHANGELOG.md b/packages/stream_core_flutter/CHANGELOG.md index 249dcf30..6126fde9 100644 --- a/packages/stream_core_flutter/CHANGELOG.md +++ b/packages/stream_core_flutter/CHANGELOG.md @@ -4,7 +4,10 @@ - Added `StreamColorScheme.backgroundOverlayDarkStrong`, the design system's `background/core/overlay-dark-strong` token. A heavier version of `backgroundOverlayDark`, for content that has to stay legible on top of arbitrary imagery or video. - Added `StreamReactions.onReactionLongPressed`, reporting the long-pressed `StreamReactionsItem` — or `null` for the cluster/overflow chip. When null, the chips register no long-press gesture, leaving it to an ancestor. -- Added `StreamSplitButton`. +- Added `StreamSplitButton`, in the two variants the design covers — + `StreamSplitButtonVariant.regular` and `.destructive`. Each variant carries + its own `StreamSplitButtonStyle` on `StreamSplitButtonThemeData`, so the two + can be themed independently. - Refreshed the icon set from the design tokens and added 44 icons, including a filled variant for many existing icons: `blurFill`, `boltFill`, `cameraFlipFill`, `captionFill`, `caretDown`, `caretUp`, `copyFill`, diff --git a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart index 7461e03f..17ff80c7 100644 --- a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart +++ b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart @@ -26,10 +26,10 @@ import 'stream_button.dart'; /// wraps the two icons rather than their tap targets, which stay full height /// and overhang it. The control has no size of its own. /// -/// The surface is resolved from the same [StreamButtonTheme] entry the halves -/// use, which is what keeps the two from drifting apart. For -/// [StreamButtonType.outline] the border is drawn once around the whole -/// control rather than around each half. +/// The design gives the control the two variants of +/// [StreamSplitButtonVariant], both filled. The surface is resolved from the +/// same [StreamButtonTheme] entry the halves use, which is what keeps the two +/// from drifting apart. /// /// Each half keeps its own tap target, hover and press feedback, and /// accessibility node; the divider is decorative. @@ -40,7 +40,6 @@ import 'stream_button.dart'; /// /// ```dart /// StreamSplitButton.icon( -/// style: StreamButtonStyle.secondary, /// icon: Icon(context.streamIcons.voiceFill), /// trailingIcon: Icon(context.streamIcons.caretDown), /// tooltip: 'Mute', @@ -53,14 +52,15 @@ import 'stream_button.dart'; /// /// {@tool snippet} /// -/// Flip the caret while the menu it opens is showing: +/// Go destructive once the microphone is off, and flip the caret while the +/// menu it opens is showing: /// /// ```dart /// StreamSplitButton.icon( -/// type: StreamButtonType.outline, -/// icon: const Icon(Icons.share), +/// variant: isMuted ? StreamSplitButtonVariant.destructive : StreamSplitButtonVariant.regular, +/// icon: Icon(isMuted ? icons.voiceOffFill : icons.voiceFill), /// trailingIcon: Icon(isMenuOpen ? icons.caretUp : icons.caretDown), -/// onPressed: () => share(), +/// onPressed: () => toggleMute(), /// onTrailingPressed: () => toggleMenu(), /// ) /// ``` @@ -69,6 +69,7 @@ import 'stream_button.dart'; /// See also: /// /// * [StreamButton], the button each half is built from. +/// * [StreamSplitButtonVariant], for the available variants. /// * [StreamSplitButtonTheme], for customizing split button appearance. @experimental class StreamSplitButton extends StatelessWidget { @@ -88,8 +89,7 @@ class StreamSplitButton extends StatelessWidget { required Widget trailingIcon, VoidCallback? onPressed, VoidCallback? onTrailingPressed, - StreamButtonStyle style = .primary, - StreamButtonType type = .solid, + StreamSplitButtonVariant variant = .regular, String? tooltip, String? trailingTooltip, StreamSplitButtonStyle? themeStyle, @@ -98,8 +98,7 @@ class StreamSplitButton extends StatelessWidget { trailingIcon: trailingIcon, onPressed: onPressed, onTrailingPressed: onTrailingPressed, - style: style, - type: type, + variant: variant, tooltip: tooltip, trailingTooltip: trailingTooltip, themeStyle: themeStyle, @@ -116,6 +115,27 @@ class StreamSplitButton extends StatelessWidget { } } +/// The color scheme variant for a [StreamSplitButton]. +/// +/// A split button has no visual weight axis — both variants are filled — so +/// this stands apart from the [StreamButtonStyle] / [StreamButtonType] pair a +/// [StreamButton] takes. +@experimental +enum StreamSplitButtonVariant { + /// The neutral surface fill, for an action in its ordinary state. + regular, + + /// The error/danger fill, for an action that is off or destructive — a + /// muted microphone, a stopped camera. + destructive; + + // The button variant the shared surface and both halves are painted as. + StreamButtonStyle get _buttonStyle => switch (this) { + StreamSplitButtonVariant.regular => StreamButtonStyle.secondary, + StreamSplitButtonVariant.destructive => StreamButtonStyle.destructive, + }; +} + /// Properties for configuring a [StreamSplitButton]. /// /// This class holds all the configuration options for a split button, @@ -132,8 +152,7 @@ class StreamSplitButtonProps { required this.trailingIcon, this.onPressed, this.onTrailingPressed, - this.style = .primary, - this.type = .solid, + this.variant = .regular, this.tooltip, this.trailingTooltip, this.themeStyle, @@ -157,16 +176,10 @@ class StreamSplitButtonProps { /// If null, that half is disabled. final VoidCallback? onTrailingPressed; - /// The visual style variant of the split button. - /// - /// Determines the color scheme used (primary, secondary, destructive). - final StreamButtonStyle style; - - /// The type variant of the split button. + /// The visual variant of the split button. /// - /// Controls the visual weight (solid, outline, ghost). An outline split - /// button draws a single border around both halves. - final StreamButtonType type; + /// Determines the color scheme used (regular, destructive). + final StreamSplitButtonVariant variant; /// Text shown in a [Tooltip] on hover / long-press of the primary half, and /// used as its accessibility label. @@ -211,25 +224,27 @@ class DefaultStreamSplitButton extends StatelessWidget { @override Widget build(BuildContext context) { final spacing = context.streamSpacing; - final themeStyle = context.streamSplitButtonTheme.style?.merge(props.themeStyle) ?? props.themeStyle; + final inheritedStyle = context.streamSplitButtonTheme.styleOf(props.variant); + final themeStyle = inheritedStyle?.merge(props.themeStyle) ?? props.themeStyle; + final buttonVariant = props.variant._buttonStyle; // Resolved once and shared: the surface below and the halves above are the // same button style, so they cannot render as different colors. final buttonStyle = resolveStreamButtonThemeStyle( context, - style: props.style, - type: props.type, + style: buttonVariant, + type: StreamButtonType.solid, isFloating: false, themeStyle: themeStyle?.buttonStyle, ); - // Takes the resolved style because the divider's color is a question about - // the surface it is drawn on, which is the button's own. - final defaults = _StreamSplitButtonDefaults(context, props.style, props.type, buttonStyle); + final defaults = _StreamSplitButtonDefaults(context, props.variant); final isEnabled = props.onPressed != null || props.onTrailingPressed != null; final states = {if (!isEnabled) WidgetState.disabled}; final shape = buttonStyle.shape?.resolve(states) ?? const StadiumBorder(); + // Neither variant is outlined, but a theme may still ask for a border; when + // it does it is drawn once around the whole control rather than each half. final borderColor = buttonStyle.borderColor?.resolve(states); final effectiveSeparatorColor = (themeStyle?.separatorColor ?? defaults.separatorColor).resolve(states); @@ -261,8 +276,7 @@ class DefaultStreamSplitButton extends StatelessWidget { child: StreamButton.icon( icon: icon, onPressed: onPressed, - style: props.style, - type: props.type, + style: buttonVariant, size: _halfButtonSize, tooltip: tooltip, themeStyle: halfStyle, @@ -427,35 +441,29 @@ class _RenderHitTarget extends RenderShiftedBox { // These defaults are used when no explicit value is provided via // [StreamSplitButtonStyle] or [StreamSplitButtonThemeData]. class _StreamSplitButtonDefaults extends StreamSplitButtonStyle { - _StreamSplitButtonDefaults(this.context, this._style, this._type, this._buttonStyle); + _StreamSplitButtonDefaults(this.context, this._variant); final BuildContext context; - final StreamButtonStyle _style; - final StreamButtonType _type; - final StreamButtonThemeStyle _buttonStyle; + final StreamSplitButtonVariant _variant; late final StreamSpacing _spacing = context.streamSpacing; late final StreamColorScheme _colorScheme = context.streamColorScheme; - // The divider is drawn on the button's own surface, so what it should be is - // a question about that surface rather than about the button's style. + // The divider is drawn on the button's own fill, so what it should be is a + // question about that fill rather than about the button's variant. @override WidgetStateProperty get separatorColor => WidgetStateProperty.resolveWith((states) { - // A disabled button drops its fill, its outline and its icon colour for - // the shared disabled treatment, so the divider goes back to the hairline - // that reads on it rather than disappearing into it as borderDisabled did. + // A disabled button drops its fill and its icon colour for the shared + // disabled treatment, so the divider goes back to the hairline that reads + // on it rather than disappearing into it as borderDisabled did. if (states.contains(WidgetState.disabled)) return _colorScheme.borderDefault; - return switch (_type) { - // Drawn against the fill, unless the fill is already a light one. - StreamButtonType.solid => - _style == StreamButtonStyle.secondary ? _colorScheme.borderDefault : _colorScheme.borderOnAccent, - // Continues the outline it sits inside. - StreamButtonType.outline => _buttonStyle.borderColor?.resolve(states) ?? _colorScheme.borderDefault, - // No fill and no outline, so the icons either side of it are the only - // colour the button has to match. - StreamButtonType.ghost => _buttonStyle.foregroundColor?.resolve(states) ?? _colorScheme.borderDefault, + return switch (_variant) { + // A light surface fill, which the ordinary hairline reads on. + StreamSplitButtonVariant.regular => _colorScheme.borderDefault, + // Drawn against the accent fill. + StreamSplitButtonVariant.destructive => _colorScheme.borderOnAccent, }; }); diff --git a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart index 24d58dba..ddbe5b24 100644 --- a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart +++ b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart @@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart'; +import '../../components/buttons/stream_split_button.dart'; import '../stream_theme.dart'; import 'stream_button_theme.dart'; @@ -15,12 +16,12 @@ part 'stream_split_button_theme.g.theme.dart'; /// /// {@tool snippet} /// -/// Override the separator for a specific section: +/// Override the separator of regular split buttons for a specific section: /// /// ```dart /// StreamSplitButtonTheme( /// data: StreamSplitButtonThemeData( -/// style: StreamSplitButtonStyle( +/// regular: StreamSplitButtonStyle( /// separatorColor: WidgetStatePropertyAll(Colors.white24), /// ), /// ), @@ -71,6 +72,9 @@ class StreamSplitButtonTheme extends InheritedTheme { /// Theme data for customizing [StreamSplitButton] widgets. /// +/// Organizes split button styles by [StreamSplitButtonVariant], so the +/// destructive variant can be styled without touching the regular one. +/// /// {@tool snippet} /// /// Customize split button appearance globally via [StreamTheme]: @@ -78,7 +82,10 @@ class StreamSplitButtonTheme extends InheritedTheme { /// ```dart /// StreamTheme( /// splitButtonTheme: StreamSplitButtonThemeData( -/// style: StreamSplitButtonStyle(separatorThickness: 2), +/// regular: StreamSplitButtonStyle(separatorThickness: 2), +/// destructive: StreamSplitButtonStyle( +/// separatorColor: WidgetStatePropertyAll(Colors.white), +/// ), /// ), /// ) /// ``` @@ -92,11 +99,31 @@ class StreamSplitButtonTheme extends InheritedTheme { @immutable @experimental class StreamSplitButtonThemeData with _$StreamSplitButtonThemeData { - /// Creates split button theme data with optional style overrides. - const StreamSplitButtonThemeData({this.style}); + /// Creates split button theme data with optional style overrides per + /// variant. + const StreamSplitButtonThemeData({this.regular, this.destructive}); - /// The visual styling for split buttons. - final StreamSplitButtonStyle? style; + /// Creates split button theme data that applies [style] to every variant. + /// + /// Both [regular] and [destructive] are set to [style]. Useful when scoping + /// a [StreamSplitButtonTheme] to a slot that should override every split + /// button regardless of its configured [StreamSplitButtonVariant]. + const StreamSplitButtonThemeData.all( + StreamSplitButtonStyle style, + ) : regular = style, + destructive = style; + + /// Styling for regular (neutral surface) split buttons. + final StreamSplitButtonStyle? regular; + + /// Styling for destructive (error/danger) split buttons. + final StreamSplitButtonStyle? destructive; + + /// The styling for split buttons of the given [variant]. + StreamSplitButtonStyle? styleOf(StreamSplitButtonVariant variant) => switch (variant) { + StreamSplitButtonVariant.regular => regular, + StreamSplitButtonVariant.destructive => destructive, + }; /// Linearly interpolate between two [StreamSplitButtonThemeData] objects. static StreamSplitButtonThemeData? lerp( @@ -106,7 +133,7 @@ class StreamSplitButtonThemeData with _$StreamSplitButtonThemeData { ) => _$StreamSplitButtonThemeData.lerp(a, b, t); } -/// Visual styling properties for [StreamSplitButton]. +/// Visual styling properties for a single [StreamSplitButtonVariant]. /// /// A split button paints one shared surface behind two [StreamButton] halves. /// That surface is derived from the same [StreamButtonTheme] entry the halves @@ -132,8 +159,8 @@ class StreamSplitButtonStyle with _$StreamSplitButtonStyle { /// Per-instance style overrides for the split button. /// - /// These take precedence over the inherited [StreamButtonTheme] entry for - /// the split button's `style`/`type` combination, and apply to both the + /// These take precedence over the inherited [StreamButtonTheme] entry the + /// split button's [StreamSplitButtonVariant] maps to, and apply to both the /// shared surface and the two halves, without affecting other /// [StreamButton] instances in the tree. /// @@ -159,8 +186,10 @@ class StreamSplitButtonStyle with _$StreamSplitButtonStyle { /// The color of the divider between the two halves. /// - /// Defaults to [StreamColorScheme.borderDefault], or - /// [StreamColorScheme.borderDisabled] while the whole control is disabled. + /// Defaults to [StreamColorScheme.borderDefault] on the regular variant and + /// [StreamColorScheme.borderOnAccent] on the destructive one, falling back + /// to [StreamColorScheme.borderDefault] while the whole control is disabled + /// and painting the shared disabled surface. final WidgetStateProperty? separatorColor; /// The width of the divider between the two halves, in logical pixels. @@ -171,9 +200,9 @@ class StreamSplitButtonStyle with _$StreamSplitButtonStyle { /// The height of the divider between the two halves, in logical pixels. /// /// The divider is shorter than the halves it separates, which are in turn - /// shorter than the surface. Defaults to the button size inset by - /// [StreamSpacing.xxs] twice over on both ends — 24 for a - /// [StreamButtonSize.medium] split button. + /// shorter than the surface. Defaults to the half's own size inset by + /// [StreamSpacing.xxs] at both ends — 24, since the halves are + /// [StreamButtonSize.small]. final double? separatorHeight; /// Linearly interpolate between two [StreamSplitButtonStyle] objects. diff --git a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.g.theme.dart b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.g.theme.dart index 106f7d67..798a3dbc 100644 --- a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.g.theme.dart +++ b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.g.theme.dart @@ -30,14 +30,21 @@ mixin _$StreamSplitButtonThemeData { } return StreamSplitButtonThemeData( - style: StreamSplitButtonStyle.lerp(a.style, b.style, t), + regular: StreamSplitButtonStyle.lerp(a.regular, b.regular, t), + destructive: StreamSplitButtonStyle.lerp(a.destructive, b.destructive, t), ); } - StreamSplitButtonThemeData copyWith({StreamSplitButtonStyle? style}) { + StreamSplitButtonThemeData copyWith({ + StreamSplitButtonStyle? regular, + StreamSplitButtonStyle? destructive, + }) { final _this = (this as StreamSplitButtonThemeData); - return StreamSplitButtonThemeData(style: style ?? _this.style); + return StreamSplitButtonThemeData( + regular: regular ?? _this.regular, + destructive: destructive ?? _this.destructive, + ); } StreamSplitButtonThemeData merge(StreamSplitButtonThemeData? other) { @@ -51,7 +58,11 @@ mixin _$StreamSplitButtonThemeData { return other; } - return copyWith(style: _this.style?.merge(other.style) ?? other.style); + return copyWith( + regular: _this.regular?.merge(other.regular) ?? other.regular, + destructive: + _this.destructive?.merge(other.destructive) ?? other.destructive, + ); } @override @@ -67,14 +78,15 @@ mixin _$StreamSplitButtonThemeData { final _this = (this as StreamSplitButtonThemeData); final _other = (other as StreamSplitButtonThemeData); - return _other.style == _this.style; + return _other.regular == _this.regular && + _other.destructive == _this.destructive; } @override int get hashCode { final _this = (this as StreamSplitButtonThemeData); - return Object.hash(runtimeType, _this.style); + return Object.hash(runtimeType, _this.regular, _this.destructive); } } diff --git a/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart b/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart index 532f3895..ecb62b4f 100644 --- a/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart +++ b/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart @@ -37,7 +37,7 @@ void main() { children: [ GoldenTestScenario( name: 'pressed', - child: _buildInTheme(_splitButton(style: .secondary)), + child: _buildInTheme(_splitButton()), ), ], ), @@ -63,17 +63,15 @@ void main() { children: [ GoldenTestScenario( name: 'leading disabled', - child: _buildInTheme(_splitButton(style: .secondary, onPressed: null)), + child: _buildInTheme(_splitButton(onPressed: null)), ), GoldenTestScenario( name: 'trailing disabled', - child: _buildInTheme(_splitButton(style: .secondary, onTrailingPressed: null)), + child: _buildInTheme(_splitButton(onTrailingPressed: null)), ), GoldenTestScenario( name: 'both disabled', - child: _buildInTheme( - _splitButton(style: .secondary, onPressed: null, onTrailingPressed: null), - ), + child: _buildInTheme(_splitButton(onPressed: null, onTrailingPressed: null)), ), ], ), @@ -83,37 +81,33 @@ void main() { GoldenTestGroup _buildMatrix({Brightness brightness = Brightness.light, bool enabled = true}) { return GoldenTestGroup( - columns: StreamButtonType.values.length, + columns: StreamSplitButtonVariant.values.length, children: [ - for (final style in StreamButtonStyle.values) - for (final type in StreamButtonType.values) - GoldenTestScenario( - name: '${style.name} / ${type.name}', - child: _buildInTheme( - _splitButton( - style: style, - type: type, - onPressed: enabled ? () {} : null, - onTrailingPressed: enabled ? () {} : null, - ), - brightness: brightness, + for (final variant in StreamSplitButtonVariant.values) + GoldenTestScenario( + name: variant.name, + child: _buildInTheme( + _splitButton( + variant: variant, + onPressed: enabled ? () {} : null, + onTrailingPressed: enabled ? () {} : null, ), + brightness: brightness, ), + ), ], ); } StreamSplitButton _splitButton({ - StreamButtonStyle style = StreamButtonStyle.primary, - StreamButtonType type = StreamButtonType.solid, + StreamSplitButtonVariant variant = StreamSplitButtonVariant.regular, VoidCallback? onPressed = _noop, VoidCallback? onTrailingPressed = _noop, }) { return StreamSplitButton.icon( icon: const Icon(StreamIconData.voiceFill), trailingIcon: const Icon(StreamIconData.caretDown), - style: style, - type: type, + variant: variant, onPressed: onPressed, onTrailingPressed: onTrailingPressed, ); diff --git a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart index fbb28f5d..413622ea 100644 --- a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart +++ b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart @@ -10,8 +10,7 @@ Widget _withStreamTheme(Widget child, {StreamTheme? streamTheme}) { } StreamSplitButton _splitButton({ - StreamButtonStyle style = StreamButtonStyle.primary, - StreamButtonType type = StreamButtonType.solid, + StreamSplitButtonVariant variant = StreamSplitButtonVariant.regular, IconData trailingIcon = StreamIconData.caretDown, VoidCallback? onPressed, VoidCallback? onTrailingPressed, @@ -22,8 +21,7 @@ StreamSplitButton _splitButton({ return StreamSplitButton.icon( icon: const Icon(StreamIconData.voiceFill), trailingIcon: Icon(trailingIcon), - style: style, - type: type, + variant: variant, onPressed: onPressed, onTrailingPressed: onTrailingPressed, tooltip: tooltip, @@ -55,13 +53,18 @@ void main() { testWidgets('paints the background a StreamButton of the same variant would', (tester) async { // The whole point of the component: the surface and the halves resolve // from one button style, so they cannot drift into different colours. - for (final style in StreamButtonStyle.values) { + const buttonStyles = { + StreamSplitButtonVariant.regular: StreamButtonStyle.secondary, + StreamSplitButtonVariant.destructive: StreamButtonStyle.destructive, + }; + + for (final MapEntry(key: variant, value: buttonStyle) in buttonStyles.entries) { await tester.pumpWidget( _withStreamTheme( Column( children: [ - _splitButton(style: style, onPressed: () {}, onTrailingPressed: () {}), - StreamButton.icon(icon: const Icon(Icons.mic), style: style, onPressed: () {}), + _splitButton(variant: variant, onPressed: () {}, onTrailingPressed: () {}), + StreamButton.icon(icon: const Icon(Icons.mic), style: buttonStyle, onPressed: () {}), ], ), ), @@ -74,7 +77,7 @@ void main() { expect( _surfaceOf(tester).color, reference.style!.backgroundColor!.resolve({}), - reason: 'surface should match a $style StreamButton', + reason: 'surface should match a $buttonStyle StreamButton', ); } }); @@ -84,7 +87,7 @@ void main() { _withStreamTheme( streamTheme: StreamTheme( buttonTheme: const StreamButtonThemeData( - primary: StreamButtonTypeStyle( + secondary: StreamButtonTypeStyle( solid: StreamButtonThemeStyle(backgroundColor: WidgetStatePropertyAll(Color(0xFF00FF00))), ), ), @@ -98,9 +101,7 @@ void main() { testWidgets('halves paint neither background nor border', (tester) async { await tester.pumpWidget( - _withStreamTheme( - _splitButton(type: StreamButtonType.outline, onPressed: () {}, onTrailingPressed: () {}), - ), + _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), ); for (var index = 0; index < 2; index++) { @@ -111,29 +112,42 @@ void main() { } }); - testWidgets('outline draws a single border around the whole control', (tester) async { + testWidgets('draws no border of its own', (tester) async { + // Neither variant is outlined. await tester.pumpWidget( - _withStreamTheme( - _splitButton(type: StreamButtonType.outline, onPressed: () {}, onTrailingPressed: () {}), - ), + _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), ); final shape = _surfaceOf(tester).shape as OutlinedBorder; - expect(shape.side.style, BorderStyle.solid); + expect(shape.side.style, BorderStyle.none); }); - testWidgets('solid draws no border', (tester) async { + testWidgets('draws a themed border once around the whole control', (tester) async { + // A theme may still ask for one, and then it wraps the pair rather than + // outlining each half. await tester.pumpWidget( - _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), + _withStreamTheme( + _splitButton( + onPressed: () {}, + onTrailingPressed: () {}, + themeStyle: const StreamSplitButtonStyle( + buttonStyle: StreamButtonThemeStyle(borderColor: WidgetStatePropertyAll(Color(0xFF00FF00))), + ), + ), + ), ); final shape = _surfaceOf(tester).shape as OutlinedBorder; - expect(shape.side.style, BorderStyle.none); + expect(shape.side.color, const Color(0xFF00FF00)); + + for (var index = 0; index < 2; index++) { + expect(_halfStyleOf(tester, index).side?.resolve({}), isNull); + } }); testWidgets('only takes the disabled surface once both halves are disabled', (tester) async { final streamTheme = StreamTheme(); - final enabledColor = streamTheme.colorScheme.accentPrimary; + final enabledColor = streamTheme.colorScheme.backgroundSurface; final disabledColor = streamTheme.colorScheme.backgroundDisabled; await tester.pumpWidget( @@ -154,7 +168,7 @@ void main() { group('StreamSplitButton layout', () { testWidgets('matches the design: 89x48 control over an 81x40 surface', (tester) async { await tester.pumpWidget( - _withStreamTheme(_splitButton(style: .secondary, onPressed: () {}, onTrailingPressed: () {})), + _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), ); // The control is wider than the surface it paints: the same 4pt inset @@ -180,7 +194,7 @@ void main() { _withStreamTheme( Column( children: [ - _splitButton(style: .secondary, onPressed: () {}, onTrailingPressed: () {}), + _splitButton(onPressed: () {}, onTrailingPressed: () {}), StreamButton.icon(icon: const Icon(Icons.mic), style: .secondary, onPressed: () {}), ], ), @@ -262,7 +276,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( streamTheme: streamTheme, - _splitButton(style: .secondary, onPressed: () {}, onTrailingPressed: () {}), + _splitButton(onPressed: () {}, onTrailingPressed: () {}), ), ); @@ -276,99 +290,82 @@ void main() { testWidgets('draws the divider against an accent fill', (tester) async { final streamTheme = StreamTheme(); - for (final style in [StreamButtonStyle.primary, StreamButtonStyle.destructive]) { - await tester.pumpWidget( - _withStreamTheme( - streamTheme: streamTheme, - _splitButton(style: style, onPressed: () {}, onTrailingPressed: () {}), - ), - ); + await tester.pumpWidget( + _withStreamTheme( + streamTheme: streamTheme, + _splitButton(variant: .destructive, onPressed: () {}, onTrailingPressed: () {}), + ), + ); - final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); - expect( - tester.widget(divider).color, - streamTheme.colorScheme.borderOnAccent, - reason: '$style solid is an accent fill', - ); - } + final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); + expect(tester.widget(divider).color, streamTheme.colorScheme.borderOnAccent); }); - // An outlined button has a hairline of its own; the divider continues it - // rather than cutting across in a different colour. - testWidgets('continues the outline through the divider', (tester) async { + // A disabled button drops its accent fill for the shared disabled surface, + // so the divider has to come back to the hairline that reads on it — it + // used to take borderDisabled and vanish. + testWidgets('keeps the divider visible while disabled', (tester) async { final streamTheme = StreamTheme(); - for (final style in StreamButtonStyle.values) { + for (final variant in StreamSplitButtonVariant.values) { await tester.pumpWidget( - _withStreamTheme( - streamTheme: streamTheme, - _splitButton(style: style, type: .outline, onPressed: () {}, onTrailingPressed: () {}), - ), + _withStreamTheme(streamTheme: streamTheme, _splitButton(variant: variant)), ); - // Settled, or a subtree can still be reporting the previous - // iteration's colours when it is read. - await tester.pumpAndSettle(); - final outline = _surfaceOf(tester).shape as OutlinedBorder; final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); - - expect(tester.widget(divider).color, outline.side.color, reason: '$style outline'); + expect( + tester.widget(divider).color, + streamTheme.colorScheme.borderDefault, + reason: 'disabled $variant', + ); } }); - // A ghost button has neither a fill nor an outline, so the icons either - // side of the divider are the only colour it has to match. - testWidgets('matches the icon colour on a ghost button', (tester) async { - final streamTheme = StreamTheme(); + testWidgets('takes the separator style of its own variant', (tester) async { + // Each variant carries its own entry, so styling the destructive one + // must leave the regular one alone. + const themeData = StreamSplitButtonThemeData( + regular: StreamSplitButtonStyle(separatorColor: WidgetStatePropertyAll(Color(0xFF00FF00))), + destructive: StreamSplitButtonStyle(separatorColor: WidgetStatePropertyAll(Color(0xFF0000FF))), + ); - for (final style in StreamButtonStyle.values) { + const expected = { + StreamSplitButtonVariant.regular: Color(0xFF00FF00), + StreamSplitButtonVariant.destructive: Color(0xFF0000FF), + }; + + for (final MapEntry(key: variant, value: color) in expected.entries) { await tester.pumpWidget( _withStreamTheme( - streamTheme: streamTheme, - _splitButton(style: style, type: .ghost, onPressed: () {}, onTrailingPressed: () {}), + streamTheme: StreamTheme(splitButtonTheme: themeData), + _splitButton(variant: variant, onPressed: () {}, onTrailingPressed: () {}), ), ); // Settled, or a subtree can still be reporting the previous // iteration's colours when it is read. await tester.pumpAndSettle(); - // The colour the glyph is actually painted with. An Icon carries none - // itself and the button's IconTheme is not the ambient one, so this - // reads the text style the Icon ends up building. - final iconColor = tester - .widget( - find.descendant(of: find.byIcon(StreamIconData.voiceFill), matching: find.byType(RichText)), - ) - .text - .style - ?.color; final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); - - expect(tester.widget(divider).color, iconColor, reason: '$style ghost'); + expect(tester.widget(divider).color, color, reason: '$variant'); } }); - // A disabled button drops its accent fill for the shared disabled surface, - // so the divider has to come back to the hairline that reads on it — it - // used to take borderDisabled and vanish. - testWidgets('keeps the divider visible while disabled', (tester) async { - final streamTheme = StreamTheme(); + testWidgets('leaves a variant with no entry on its defaults', (tester) async { + final streamTheme = StreamTheme( + splitButtonTheme: const StreamSplitButtonThemeData( + destructive: StreamSplitButtonStyle(separatorThickness: 4), + ), + ); - for (final style in StreamButtonStyle.values) { - await tester.pumpWidget( - _withStreamTheme( - streamTheme: streamTheme, - _splitButton(style: style), - ), - ); + await tester.pumpWidget( + _withStreamTheme( + streamTheme: streamTheme, + _splitButton(onPressed: () {}, onTrailingPressed: () {}), + ), + ); - final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); - expect( - tester.widget(divider).color, - streamTheme.colorScheme.borderDefault, - reason: 'disabled $style', - ); - } + final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); + expect(tester.getSize(divider), const Size(1, 24)); }); testWidgets('honours separator overrides', (tester) async { From e32bb18bccf38efb3c1338ca701c51d973bc6b1d Mon Sep 17 00:00:00 2001 From: renefloor <15101411+renefloor@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:51:10 +0000 Subject: [PATCH 4/7] chore: Update Goldens --- .../goldens/ci/stream_split_button_dark.png | Bin 10820 -> 2382 bytes .../stream_split_button_disabled_matrix.png | Bin 8542 -> 1882 bytes ...ream_split_button_disabled_matrix_dark.png | Bin 9444 -> 2042 bytes .../goldens/ci/stream_split_button_light.png | Bin 10037 -> 2350 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_dark.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_dark.png index e32e3b350c7691dd00a6a232fcb1dbac97f88f71..7f653a056b4ababc601c3835f77f9451711e1688 100644 GIT binary patch literal 2382 zcmbW3i#HQ~AIC=?ExDAGB}>iZHcL_|e#7YPlheZQbtNi3IG7e zAg`L+0{~)3(cA+lDeB4a`OBg~0&j|R1d3WL5SuPqi{b4pO#$Q~WexxU{Eaj>aU?!j z{^lC&>MypJ*Acp^gBzI}&2mUnvnlk`D$~Rr)y}+^xwrg*lMj6Z;FC@7-eV*qGYWy9 zo)fdVgV3!uJtQw5kWPV&LL|$UrF0hIjw>3=i(c&c-_R)KsZ}+^{9Owi<}z=Uayfy$ zonY@lT6~glnYJ5CWAmF+8z1N!XX%5$Ch*4ehzh8ol%2EHFsk({)$G8*sIvvogE@d} zr1YzaYDjbFI}#Kk?}d~ zw;?DzUs)TVKM}>r_@8m6ZQA(tTiwZl-}Sv` z-=u}<5B;svhSCT>)_v$NY=C!7d0Rpe3mi@ojm2@4pf9dpdOfD4l`kd2szTn$*_qPb z4mT{;>+Osyx)wWvo2B^Uk_-m~=E=EL7M-N6fthIBqYbE_H5 zx&r<*PFmZH{n8SpEf5F}NJ~yNS2gg7MnK8r-Sq^Ge5*e8uRg!Jmt#RA{HUt_c$wjl zT@8fFTg9n6-i&bT`vS9cff#`7(CR;EeG2 z;7BS=2U(fNdipiw0wf10(jhOQ(de)r&zc+tR#6mV;Aw6=i20>ikw*6Dyx%YJI`yRC z5llCg6xr;S-sAI0pG}_GHo>%f3n`}u>MO}GaxVF49O%s^BuL81NDpK|Y+wgE<&OVW z2ue*Vg&v}^9$LU2twg`|$jr_*Y&j8H5vbHlqs^=nxNSv_xk9C#OY13R`}uqR2)U5; z^I|&dwrnRWVkCC5-35*;9K1`RoJr|gjMrt#&V+%c8bi|t=R@;|wThL9IG~wW`%Ytw zU!-Dt<5Diw_u)TW;Qcc*-ba48uWfAj5Q%6)X2ZUiHiPJCyMRB$7OGgfS7f9*b)O`%*52`RSve#D+P>))k+Q6TGrQx7< z(ZC>JtFFNb-}+4ynd)2rffX_1dT0wvvmFxYsk8p-&oVhxq=hW`c)qf+(V37^V6knI znR;O`>sfiSYZ}(_#Gzt%zwwDg@-kdnpHO7WmotDnTO1p04(a|G4eg%WX@)Ps108RgxIefH#s*G{B-^Y?sia#lRM* z48l4uUAde?`oWi|wu|1@nkA8Nb4JbhA{iXTs`AMf9U(oiNZO2qSq+11x)0ovNy zRMq-mvRn7f<9fxNn5JUu{2#Nkkeb3^k~gRi%ME`S#a*1Ds%og`c=mFvtq9V3?;Vj& zRX4D8m?iob^!dj(1pHE0q0hY1cemm~u$kttdjsLsfkkEd1^Lr3m@!?SXjCF6j5ZuE zr?^z=jW0#PV6aH6=hKL_0gJXB-c0LG8~D};y4vCB@B;pch1Q3)pp5kxFeh?yQvZCu zRfTQM#7$c5XlY#?@yw%#;IgIa(bvR{2YZej})@p*ne#~FJ7W}Y-j;*fDe(q-Dn=Rwi8E;3^ON`HxyyCr_(0B zzt7TK$CK>~2M^hZ)()L_~wV=oLzJ zJZVmK%y5n}FrgK!Xdk%`JG}Qc4v{f2znsue%B|e03^zBIr~JPk?5-qK_K~S6blO@} P5CM=DD08x@*PVX@jT(bJ literal 10820 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 diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix.png index 2ed68174a05077b640c48332358809b8e57498a3..6646eebdf612c92e89c2434dfe4df847ba5fc446 100644 GIT binary patch literal 1882 zcmb_d{WlZ(AK$tXE>AIz2s2bhQBtFA7V$DI z!xpZ}!|0N^ZI`8TT3Fr9O^7kOyMMs_p7TBD`~Knm{=8qG&->x?Ij_%o7oH08)6p`~ z0ssIyXp|2Y005#@>vJ0FD%}E|_fi?PGz2epB0caUFlK=p_)X+ZO z!I{<4+1_ICG2@}H!j`4avYNV`Kt0PEGc_w~+w1vMgKmAbQ11XOLjX{W%D-)-x##lk z=!xoDnY=ylwJZ17SaC)Z=SrhguB@N*ubX_uWq*c}DlTjFvunc4 z3w{G2RI*>W#pPoRfdCP@f8u`OM`u65MY|h7F z`a;w>j==|ah3q0lRiofvYw-KG*PdX6xuEs6ljJnodoD9=W3`oR|9(T(1zp}u>FeK; zMlFAf+PWZd!57iMfXv-=zz1*Cj@(^VngEOZ{|5NU&*qG{+hu!=oTnJY3`W=uMWl>- zhH92T8o-kgEIb_?!3WbkFussZV#2^-!MpGMdkIXVt8%wonIW?*o@v{o9Oe7)VS70_ z=1>#5z#Y!6S;;<>*H*rTGyGItGGMgF##&Le7T`}1VN>YUllOll|?1EoB%tQ^CEE34Ojh%ZC@OSDp1brV#N(|og(gy1Ev65{rJy41p;0!p9J_VdV55Q>(b0K=51Y9|U;DYqU! zl0JvIgC&dK^52mO+kedUG^hUw(S5y0jUByQON}zNTTxDjHEK;PQpHny7utZW3vfH{ zXW)&>!S)vy0*4B~HP;x_WEa6r)4zn);_axWNh`??3(d$p6$t$>)9O3oTG(mQzcYSk z>$ZyXbtmW_HOKW+H=pJb`u9bo{U;$_mBWVDk^h#gX}CU(kSRMW57Wg6xZk4Ga2d63 z@|rwnoIUs${fFTOACcS-Lo=XO@vNv!WK>%gBl0o5OruwkIV&VaIHGX~7g*g17c>ie z{oXBx-U-o7i)G9|7nI!j4Tsdny}V3|XI8iby|niTC_%J4rLi%^^`)qgp_KS+C*{3` zQg9_z`}cZk0E^LXKi_&FI_|VjhoWx8l^1r#G!8eIMjW0A=<9qF2MrW?ZamM!RHI$G z78*}OY|~@wS~(8lZ-;7+hgNUr*kJULG+@=1fj7ifbX%9rsZWf&b0^kd!&3E;?snf$ z>IDhWokZiKFT%UJtS4d*Uuk|lJSl~nXudTpoqycP zm_4waz`*;HL{}s(EkU&n*s_%OmcnR8?36!mtm+^k`1`1JNx7S7QDF6e-cb1L*|c+A zcNfz!YoN%+q(fK9PVbQyz(t&hPl#TDe-f!GUKr(d3+sP;Ib_svQ;2R~+Htsz(aBj|mYs-S=$f`Qpn;e8$)<_0S*T zuo_mRowJ4XVJQbRTzzD^D>lK}O%$OXBffm8&hr+Ya8MO@|6+d3OQ?J?kf6AtbzB=c z(^5WQ)HYstRguoqnt%8^WOo_%(Hlu1)R*xUSq?&#h*xLM3PF5QuMuHz8uz{+)m~85 zXL$Pa%Gc-CCwrV*UUo=NqNegsg||&vAlWZ0J>F{lPm^SOm+93Qx$p~aKs6izXx|{8 J7R31*e*?raYzF`U literal 8542 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 N5!}>(qId1ie*u7xt%?8u diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix_dark.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_disabled_matrix_dark.png index fb0822fb97c1d95a4f1dea2bc7f353dfd4241824..24611862e66bde7d730471527803b8218a21da85 100644 GIT binary patch literal 2042 zcmb_dc~FyC5|2}eiWn~hL{v^Wg%K$ZqePJFix-n+U;=>*S0Y!qhbw@DVGx0!Ujzoh z01{wO3`$HO!3zaKAcBC%KnN%VGzo{qWk8Ow-^|ul?biOaRoj2Oe(zV^{kq@l`gN|y zCAVEFS}HIYY!~3}jE2Fs0MOolyE3#gEM}09Qi^i|Jhwwa+8%rjI&XFN3lmb7mvc>Z9jw7M5j@Mg4>>2p~T7s=~ME^YsFIvhGK1NwYIG8 z%ADJ3U1pqNdgYKYQRh9ZO&LE(&!|0fpfj2@yWrMcTlAhEvmQ-~m>p9vU)AeS?;&yq zB&1s({BVhCIya>Y6Eo%_U6>Bv7=U?H8f-b62`a(1qV~bm{vUw6Fnz6VUiFQ^#A=~k zY6f-advEC0QpI{!sR8Ua4-i&)7Wn4sH-;|Atf8uHa&aS}dG{CPR!6Xj;8;D5_lem=FD5)RYn7_Z?(-dv4uJP4tC#$JRuOo1AGd(B$xuuBc z?em~m$+7Y{4sB1-XUI@B=)FRA0xwsev0-5i%K(FY}6b(brQ1!}(Tz$p7T z0)d$1GWMR#{!N>=mlqS9n(BMHkBVmknDiqhVJy=_hYoS8%{@8T;#iLPP}uic1i#ZqRnng*JF-WiP@Kc0(2NEpo{8q|25e9;9oy8} zYQ`uN5$q;PYCM@Oujra|HXdr<#Dh>%vDxg& z%hW)ItQK$cb!BnN`7pk$(&vGca`l>CQ83Hr;s!?8f+=3=87bKj%h5)Vlqgft`*ur4>qyPXDg>aw2Vw&<_F4W-~$ZgT$Vu{1zYDz8sG3$$t8 zCd`0>mluOI_~ON#FX;uWrhUn(hKyF2q|T6o@;d#f*hlqwS~I#oQo;H|BAr_ zFEfR;$-6KXIu73=BIYfuaf39cH`bQ9`Z{;JaGjCXT9e%Q%TY-WuMxoZNX$XrRE!jl z^Y-?Z%cpxP6iDIb&WYtx7cF!7;6C2eqqnl9q(Qpp125`gQUk)ZA>}D8iB^Yjoh;x5 z)enVngMNg^ok?6BoF1&SHmg6Km+|tsr;Q3u$ z8Pu|0pS%Ok*wVvjdDFm{hlh5D{$?WN$DB&I7*$7#kTGHGG#glT0fwFQ*d^%L$~365FWp z&yipsQwA|kOUO}Yjq^aacZN30=Qxvj-Mr9ok5X((is|t1Ft_^3{$HxfX*4U0d+F~B zyoF@i$}>x$P}sy`8FH`|^Udz%lK9o=eP%s*e}}kY{8`$cRKoHpaw|L{p*&U#&_vxI zLJhGVt_14tK7J6MgT6l`mP%3ghVDf~M&^ce*o0op=LhRSPRtRp`VTmWFnfD@+hepx zM52pxUuv^-Zs3_Fw2zCs9;^9&qTV6v+(>z9v6SM6ssSU2L{z;eSX5M$b98@qoKB(a~3I+v&EzWg$}a+>TcsToL23F!GWY zzI{M}ii(OdDN=3aaDa|o4mZy5D>}Dab^Z6@^Iy~GKY-83xj6cLJWd}44R09Wa><$D H^dtEn-4oRR literal 9444 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 diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_light.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_light.png index 22a533cccab3e322ff9fdc0a6372dce9bc0333a2..31873426e038034aff7c29de747512f490a764f9 100644 GIT binary patch literal 2350 zcmbVOXH*l|5)RTr31y`OB#+)9f`CspN(>;7NDHE{(uol1C>n}De8JGfAT1Q>2n1X~ z0w_oml+Fea6e7}<8i~NtBiVTN|NG(nF>~jhId{JE&7GN>WN&K;5|kAL001DQmAL}| zz=h=O-GO`@Sp!{ya}-aQDbf+hS<%27i5#0N%)!zWP(zbj1pt7PNOOc^ME#>f5tw9yL6__SYY zgFIDOE!mjG`=p0Q>9k-xgJ41t;1#@A;<9@N7DgLt-oHOWeB(sRHQ4?ue2INIf?hDh z+QZQB?FO^WHwUw8QZ7RL(6cCO>y*wNEozxqV|m!}?D!qJVq)Sanak;kR`*qr<|=Q} zRg`}7ME)i&@Ioaik;ggR-28+NQb~!6tRkKU_;t4&w*b5rj2r3g&Jq$F+(_`zeGDN| zF8l~q$8tg_{ofEdmF{#!7=iR$V4Ex`6}z*K4V_qnO10;xfffa#f=IHTiV3J=RpV>sox~#BSf*<(BZ8bxi42X@) z&R8YdIGSgsKZDW{$+u&GMZ?3T($@P)yM-w;?q9C8iZ)AXd&bQD=&N;k$oh15M9u|g z=>NY8@(r*BCCX@g{q8G=mybux!Jb9YYJ#;GM zO^2Qi%+|Ye5=^MmZ|Ws^Y;Sk<*p)Th)qdUaw?ad!vaY<5(-qViVP!`*(ctHNylTlw z2M7QhXuos*+)H9>P`wV@gS2?B&@^&Rfy&~e$e9oZ=9MFsbFm}a)6*+6b#Xqqh^kn1 znRszuEhyB+rfkipMFIOzsd(*UsGq;A!BuImO4j_uCSOA6{6o=&p~|zR(Vhxpyv9%9 zXOinI+f&D$!3jwo@KnmR3`mkg=9)EiAu6HIjC_0MWp4S_undmk7kr9$KROL zGONYZl!b+|RD?m;%JG^Rbkc+fHKj>f0B|-b>uC?*~t1_xmc&>#rjBQnpWj1>VuNwG3|=J&yKh%1|;eFc{DhlDTPuDaRerLz* zR2w0S4&(Xk{?kFU>>jI5$#!bHAW=_Ufbf9vTgzPu(V=S&)u#`bHIY>&^4D`lPs#mE zAR2#iS~|%Bcx?8C>m>p0=)-pO+3X^E;lT zFn|(QSm{H3UOH3?OYtqJ_15u9&d)k9z^q=o z!LP-`m)WK#3*IrlT00oky>cG24_6N1uTCwh_s?PgQT5D){VKIxseoJI&lIau^_qyzv< z3zuFnTg`C7s<)(SD$G?^&q=|>Fz*c&n9fwp(^BjNlrEl$!S9=C1h+f=n-AI7BE2ui z>@vJaLE16svQYplmKNmn!3@J4l|Z+N+6}@{ETHz<&rwtc9Q}a* z%lfg1O^a({UIk*pFsFhGmL1oBHAZoHqowZeSEsoF1Kj-RG4&+;12oo7iU0rr literal 10037 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 RQ!^g~SJ74`-m-f1{{UoLF6saP From 183ca8b81dc8af662ae9d69177521d270f0f3c98 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 2 Sep 2026 14:35:04 +0200 Subject: [PATCH 5/7] update default split color --- .../src/components/buttons/stream_split_button.dart | 12 ++++++++++-- .../theme/components/stream_split_button_theme.dart | 6 +++--- .../components/buttons/stream_split_button_test.dart | 9 +++++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart index 17ff80c7..a090bfff 100644 --- a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart +++ b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart @@ -205,6 +205,11 @@ class StreamSplitButtonProps { // scales the control. const _halfButtonSize = StreamButtonSize.small; +// The opacity the design draws the divider at on the destructive fill. At full +// strength the white hairline cuts the accent surface in two; knocked back it +// reads as a seam in one control. +const _accentSeparatorOpacity = 0.35; + /// Default implementation of [StreamSplitButton]. /// /// Renders a [Row] of two [StreamButton.icon] halves over a shared surface, @@ -462,8 +467,11 @@ class _StreamSplitButtonDefaults extends StreamSplitButtonStyle { return switch (_variant) { // A light surface fill, which the ordinary hairline reads on. StreamSplitButtonVariant.regular => _colorScheme.borderDefault, - // Drawn against the accent fill. - StreamSplitButtonVariant.destructive => _colorScheme.borderOnAccent, + // Drawn against the accent fill, knocked back to the opacity the design + // draws it at. + StreamSplitButtonVariant.destructive => _colorScheme.borderOnAccent.withValues( + alpha: _accentSeparatorOpacity, + ), }; }); diff --git a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart index ddbe5b24..a2432aed 100644 --- a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart +++ b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart @@ -187,9 +187,9 @@ class StreamSplitButtonStyle with _$StreamSplitButtonStyle { /// The color of the divider between the two halves. /// /// Defaults to [StreamColorScheme.borderDefault] on the regular variant and - /// [StreamColorScheme.borderOnAccent] on the destructive one, falling back - /// to [StreamColorScheme.borderDefault] while the whole control is disabled - /// and painting the shared disabled surface. + /// [StreamColorScheme.borderOnAccent] at 35% opacity on the destructive one, + /// falling back to a solid [StreamColorScheme.borderDefault] while the whole + /// control is disabled and painting the shared disabled surface. final WidgetStateProperty? separatorColor; /// The width of the divider between the two halves, in logical pixels. diff --git a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart index 413622ea..6534f83d 100644 --- a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart +++ b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart @@ -287,7 +287,9 @@ void main() { // The divider is drawn on the shared surface, so which colour it takes is // a question about that surface rather than about the button's style. - testWidgets('draws the divider against an accent fill', (tester) async { + // At full strength the white hairline cuts the accent surface in two, so + // the design knocks it back to 35%. + testWidgets('draws the divider against an accent fill at 35% opacity', (tester) async { final streamTheme = StreamTheme(); await tester.pumpWidget( @@ -298,7 +300,10 @@ void main() { ); final divider = find.descendant(of: find.byType(StreamSplitButton), matching: find.byType(ColoredBox)); - expect(tester.widget(divider).color, streamTheme.colorScheme.borderOnAccent); + expect( + tester.widget(divider).color, + streamTheme.colorScheme.borderOnAccent.withValues(alpha: 0.35), + ); }); // A disabled button drops its accent fill for the shared disabled surface, From 32a1d98cccce26e64282ce2e4789af99408c3748 Mon Sep 17 00:00:00 2001 From: renefloor <15101411+renefloor@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:37:00 +0000 Subject: [PATCH 6/7] chore: Update Goldens --- .../goldens/ci/stream_split_button_dark.png | Bin 2382 -> 2379 bytes .../goldens/ci/stream_split_button_light.png | Bin 2350 -> 2350 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_dark.png b/packages/stream_core_flutter/test/components/buttons/goldens/ci/stream_split_button_dark.png index 7f653a056b4ababc601c3835f77f9451711e1688..a609ec6614877969570fa0f3df4960ba33147985 100644 GIT binary patch delta 2131 zcmV-Z2(0(c63Y^hL4Wm0L_t(|obBCPY+Gjm2k^gd@hxf6<n9kB|NNJLmtC@{&~N{QEin=Q}=^&mT3sb)*4uq*!zW`TP>f zm6GfzL|rK}H{){44naiJ&5`{?lNtdXAi%rnf$z)CK$3WKuOgKA3VM1p*-?nP(@Rfs zCP_pj!jbPBD}Xoo(vz|?(EqUWu@~F2-Fj^uslWaZh1KhA**>){yk!C-2cDH3!_hdC zk?=HsydB$-*NeWsK7>Lc`2BwP{eJZH^uXisbSi>r^?DuEY891A1(iw#rBVs2tE<@9 z*pMAf8DrsoOhvY0SHFhQ@Ft9eHsS729|8)z{@F?iGsRV;iUnLLuHcNmfHT+TQLfiz zN23mCYf1WiK5W^t1^xZ~2nK`g+*Mn7JRSspgTep$Ka@%(6bc38ayis$HEH>})#QMN zr?-6yUyk05t--MDIHpDYAw>Nl+<#Z}mhb;%X%;WfU&Sx}^$)Dq`3KXk+ES7}pAXS! z6q=^N?Y8d`eJB({C=@~@5`nJk$Y!%F%m0+);fZ}XI5Y~E>@aOK6-i(!lEAYQdvRoc z`T~y4yw9?MRj@V$HXiT}-S zk6OCV`lH{pbbIJv#>oK<2Zu)S`}vfm`=ib<$@ z^ANHbhbQ)NHmN96B^iswY=73)YHqh1@pxQzjFx#^E_`=luk0AgnHiWQVCdew+ohjwPv{rc2V`!$gV5L7FZ}E>F-JmB%kzF3FCdyhVWx+L@Ye0!2OgzDqR$9YEsiBSuU4RF7uGf3_Fb7@r4;U zXAMYe9bZUs>=b0`DL{on!TKZ)u(GluJ3`Cm%SBw|A)_grg{#R_NiHod@p{Q78HRyu z)*gpiN2cGF9YHyN$-**DX4A4`kO`(rvRbVom&;k7#>R5FoYI&7-0au0m+|_w1=%r_ z^2|kRmkOcIF?}QP;^HDSO@qhd`PiSgV@(>Xbt+@MUdQ6%Vn_DXDbHOxk5?Z&bjP2! zV@(>Xbt>a2eV(&Og_&M0v0kqulgU_{#-`HgH0t%b>=>4R`de-e&!>{IV|2ZhS`|;f z{hI6;)Pk1Q-CkZ^?z+#}u3WhyJHjop*(|PKzb-pQTYT%?(>Sfq%Z~Apl~pv}o0gs8 z2BXoy*UtY5)2nJ<_T_Xfl_UVn&COwLZOxiAHd81RFgG_RJ4V~o3U0MKYOkWQzOPN&gmIP<;&Cy&8=xrm2ZUPxWScP94W zOOavOkvig9wTz=5T*S{lcn{0fitIRKv9>I@QZAQ~PN%W3uz*-BhQYx>xZRdbTDyi} z7|?Zp9ocMF=|`Yh?UX)`Q)lL}XLAJ4-Ln@D$3|pFYKz(RHT?MUJ2*D;K8hQB{q3r) zt5Vf!HDodwWHK3q!(nXRycywe7#@%3#+M4^X&46T_4Q9QbJ3_JP;(C_nObB`ZdPe67!#Q)3MMh*E|1^HS9xk?!)vuXTh_A=5X zUe)0Q9b3AuSS+$s9PE{a6kb`d|Ji=BpYET=5fMqmk?$}T{2M(`m_kCEIo|*P002ov JPDHLkV1lIRK~Vqz delta 2155 zcmV-x2$c8B63!BkL4Wv3L_t(|obBCPY+Gjm2k^gd@hxrA<Mm67DDCQC z=jQk%bsXFAB|hebMQvF>@bR%ff9L#vQeKkkoPR&Z|9r>i@*uf}w~aJFjx-n@K`yt5 za-}3Y3Q<=|&rZACvO^FNbu*I?0Wgyp0VgSdB}qgiz>*{)67X(*=sU7AkR;yh>j=fa zfSw*rb`+xS^vctmNfHr>lK}x8D&S4L^0e#>^grx+;^nq%w_aOA@=xDKe)VQswok1K zZ=Jx%!RKYia5T=7umLNRZUG8^Z^w4z^4C@N=~M*M>h(IR z)ha5L3M!QfN~IE3S68vVzAihOGRDGvn2Kz}?mi8p;msHcZN|N!O$aFP`e!O7Ocz&? zEaq{&xPo)~JkBl6pmOLF^ADz7wWTC|J|CjdC^Suj+il+?`cNo@P$+~*Bm!O6k;!COmj5ZoBNO{^XlN8J z*4D( z#>dCu^>*yMm{gPf8vZ)y>=+wdyY7SgyI+?b<2EPuPGPd&p0B@Cm@Y}5&xd$CZfzQyipS&d`FyftSnBZ5 zD89IDOm>W}7xZ}X(#NM9b++GbOrKeZ#bUzCe9oUgZ+6pvAL_t?1D0+N9n9! zF*7q`>HcK#xLo-5{rkoL=2yop-DmyruUfi2bTH#&zlK9Yqxj8S($f7=XPBBv`u%!CmwcFEhp{^pbJi_~R(dQp!m(3O(`Hl!zRp>5}B~1g%kd z{4(W|>5_EfvIJIAdHiO}MbvmC(Gr%VdN`BpFqOxzW*Atmm1W0J8fW9` zX}TnT*VfkBlf)rZAHR2Vy6gx_duN5?r<$fqGMCG>Cy7I-K7M@MmwEH$5-u)hWXGT; zHC>YBav9|^54p^+!`PjipN4bRfV9@h`6S0qL8hJpl+Wj_PvQV8D=V@iv}~?i#AO~b zn!;JQnoO1C;^HE&mu!+@7|3Moaj13d>f5q^BPb`4U&iT7N_Gq~!Bk0Bt5syPS?kl- zST>tg`tqM!{bJ@C-dviO9YZP4UAA_q5b7M$Hxe%_EI`vVcs!mD{dqgqq_JA3GS=&L zEG#T^WM7?f^vXrN{_x?u{=6M)(pari8PDi*oJA_k^lFLqdL8L>+S)WWl}e>huh(UN z$FS6&v$J?HnUEc$>#fwPc=qi#WXGTuw6yN_^73-mea?3M`gPe6Zj;GmaP#I(*)iJU zYww=LS$$4+j17&%H5f-2N<#a8TBmm6L&Z1B#Sd+$P^7%Yw zXJ=){Xq%d0;He9zu~cVN>(|w0inG}v)eM@!>vVbKexxT)RL?QuQ=k;>{pzAskiG=MwhrFA!`W(hi z{S1Fuve(HeGpdGxXWn`PkG=U@*-^+MmXss_G#U-0QYoZTDKr|+ysyB?V=z~LF5;21 zuj1$Ff69&`%~G|D&z|`;PE22s9fd4p`C0(=yN!8ZVr35L~!)}eRy#_3E7znr;-REbx0I6=pj?kg6HEENZPZ9a+D=IwvBpX{gmr*T9?60wu8 h0V^9=l0-xT{{|!unLlh^U>hll%xA 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 k0U(nK2@aDW0R From 2f499b2c5ee81f3ea9c9743c81dab25accd0046d Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 2 Sep 2026 16:22:54 +0200 Subject: [PATCH 7/7] update leading names --- .../lib/components/buttons/split_button.dart | 22 +++---- .../buttons/stream_split_button.dart | 46 ++++++------- .../components/stream_split_button_theme.dart | 4 +- .../stream_split_button_golden_test.dart | 12 ++-- .../buttons/stream_split_button_test.dart | 64 +++++++++---------- 5 files changed, 74 insertions(+), 74 deletions(-) diff --git a/apps/design_system_gallery/lib/components/buttons/split_button.dart b/apps/design_system_gallery/lib/components/buttons/split_button.dart index 338c0f51..0c613c4b 100644 --- a/apps/design_system_gallery/lib/components/buttons/split_button.dart +++ b/apps/design_system_gallery/lib/components/buttons/split_button.dart @@ -51,12 +51,12 @@ Widget buildStreamSplitButtonPlayground(BuildContext context) { child: _MaybeBadged( showErrorBadge: showErrorBadge, child: StreamSplitButton.icon( - icon: Icon(icons.voiceFill), + leadingIcon: Icon(icons.voiceFill), trailingIcon: Icon(caretUp ? icons.caretUp : icons.caretDown), variant: variant, - tooltip: 'Mute', + leadingTooltip: 'Mute', trailingTooltip: 'Audio settings', - onPressed: leadingEnabled ? () {} : null, + onLeadingPressed: leadingEnabled ? () {} : null, onTrailingPressed: trailingEnabled ? () {} : null, ), ), @@ -115,12 +115,12 @@ class _VariantSection extends StatelessWidget { children: [ SizedBox(width: 88, child: Text(variant.name)), StreamSplitButton.icon( - icon: Icon(icons.voiceFill), + leadingIcon: Icon(icons.voiceFill), trailingIcon: Icon(icons.caretDown), variant: variant, - tooltip: 'Mute', + leadingTooltip: 'Mute', trailingTooltip: 'Audio settings', - onPressed: () {}, + onLeadingPressed: () {}, onTrailingPressed: () {}, ), ], @@ -154,9 +154,9 @@ class _DisabledSection extends StatelessWidget { spacing: spacing.xs, children: [ StreamSplitButton.icon( - icon: Icon(icons.voiceFill), + leadingIcon: Icon(icons.voiceFill), trailingIcon: Icon(icons.caretDown), - onPressed: leading ? () {} : null, + onLeadingPressed: leading ? () {} : null, onTrailingPressed: trailing ? () {} : null, ), Text(label), @@ -192,12 +192,12 @@ class _CallControlSectionState extends State<_CallControlSection> { child: _MaybeBadged( showErrorBadge: true, child: StreamSplitButton.icon( - icon: Icon(_isMuted ? icons.voiceOffFill : icons.voiceFill), + leadingIcon: Icon(_isMuted ? icons.voiceOffFill : icons.voiceFill), trailingIcon: Icon(_isSettingsOpen ? icons.caretUp : icons.caretDown), variant: _isMuted ? StreamSplitButtonVariant.destructive : StreamSplitButtonVariant.regular, - tooltip: _isMuted ? 'Unmute' : 'Mute', + leadingTooltip: _isMuted ? 'Unmute' : 'Mute', trailingTooltip: 'Audio settings', - onPressed: () => setState(() => _isMuted = !_isMuted), + onLeadingPressed: () => setState(() => _isMuted = !_isMuted), onTrailingPressed: () => setState(() => _isSettingsOpen = !_isSettingsOpen), ), ), diff --git a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart index a090bfff..bfa47f93 100644 --- a/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart +++ b/packages/stream_core_flutter/lib/src/components/buttons/stream_split_button.dart @@ -40,11 +40,11 @@ import 'stream_button.dart'; /// /// ```dart /// StreamSplitButton.icon( -/// icon: Icon(context.streamIcons.voiceFill), +/// leadingIcon: Icon(context.streamIcons.voiceFill), /// trailingIcon: Icon(context.streamIcons.caretDown), -/// tooltip: 'Mute', +/// leadingTooltip: 'Mute', /// trailingTooltip: 'Audio settings', -/// onPressed: () => toggleMute(), +/// onLeadingPressed: () => toggleMute(), /// onTrailingPressed: () => showAudioSettings(), /// ) /// ``` @@ -58,9 +58,9 @@ import 'stream_button.dart'; /// ```dart /// StreamSplitButton.icon( /// variant: isMuted ? StreamSplitButtonVariant.destructive : StreamSplitButtonVariant.regular, -/// icon: Icon(isMuted ? icons.voiceOffFill : icons.voiceFill), +/// leadingIcon: Icon(isMuted ? icons.voiceOffFill : icons.voiceFill), /// trailingIcon: Icon(isMenuOpen ? icons.caretUp : icons.caretDown), -/// onPressed: () => toggleMute(), +/// onLeadingPressed: () => toggleMute(), /// onTrailingPressed: () => toggleMenu(), /// ) /// ``` @@ -75,7 +75,7 @@ import 'stream_button.dart'; class StreamSplitButton extends StatelessWidget { /// Creates a split button with an icon in each half. /// - /// [icon] labels the primary half and [trailingIcon] the secondary one. + /// [leadingIcon] labels the leading half and [trailingIcon] the trailing one. /// Both are configurable so the trailing half can point the caret at /// whatever it opens — [StreamIcons.caretDown] for a menu below, /// [StreamIcons.caretUp] for one above. @@ -85,21 +85,21 @@ class StreamSplitButton extends StatelessWidget { @experimental StreamSplitButton.icon({ super.key, - required Widget icon, + required Widget leadingIcon, required Widget trailingIcon, - VoidCallback? onPressed, + VoidCallback? onLeadingPressed, VoidCallback? onTrailingPressed, StreamSplitButtonVariant variant = .regular, - String? tooltip, + String? leadingTooltip, String? trailingTooltip, StreamSplitButtonStyle? themeStyle, }) : props = .new( - icon: icon, + leadingIcon: leadingIcon, trailingIcon: trailingIcon, - onPressed: onPressed, + onLeadingPressed: onLeadingPressed, onTrailingPressed: onTrailingPressed, variant: variant, - tooltip: tooltip, + leadingTooltip: leadingTooltip, trailingTooltip: trailingTooltip, themeStyle: themeStyle, ); @@ -148,28 +148,28 @@ enum StreamSplitButtonVariant { class StreamSplitButtonProps { /// Creates properties for a split button. const StreamSplitButtonProps({ - required this.icon, + required this.leadingIcon, required this.trailingIcon, - this.onPressed, + this.onLeadingPressed, this.onTrailingPressed, this.variant = .regular, - this.tooltip, + this.leadingTooltip, this.trailingTooltip, this.themeStyle, }); - /// The icon rendered in the primary (leading) half. - final Widget icon; + /// The icon rendered in the leading half. + final Widget leadingIcon; /// The icon rendered in the secondary (trailing) half. /// /// Typically a caret pointing at whatever the half opens. final Widget trailingIcon; - /// Called when the primary half is pressed. + /// Called when the leading half is pressed. /// /// If null, that half is disabled. - final VoidCallback? onPressed; + final VoidCallback? onLeadingPressed; /// Called when the trailing half is pressed. /// @@ -181,11 +181,11 @@ class StreamSplitButtonProps { /// Determines the color scheme used (regular, destructive). final StreamSplitButtonVariant variant; - /// Text shown in a [Tooltip] on hover / long-press of the primary half, and + /// Text shown in a [Tooltip] on hover / long-press of the leading half, and /// used as its accessibility label. /// /// When null, that half has no tooltip. - final String? tooltip; + final String? leadingTooltip; /// Text shown in a [Tooltip] on hover / long-press of the trailing half, and /// used as its accessibility label. @@ -244,7 +244,7 @@ class DefaultStreamSplitButton extends StatelessWidget { final defaults = _StreamSplitButtonDefaults(context, props.variant); - final isEnabled = props.onPressed != null || props.onTrailingPressed != null; + final isEnabled = props.onLeadingPressed != null || props.onTrailingPressed != null; final states = {if (!isEnabled) WidgetState.disabled}; final shape = buttonStyle.shape?.resolve(states) ?? const StadiumBorder(); @@ -321,7 +321,7 @@ class DefaultStreamSplitButton extends StatelessWidget { mainAxisSize: MainAxisSize.min, spacing: inset, children: [ - half(icon: props.icon, onPressed: props.onPressed, tooltip: props.tooltip), + half(icon: props.leadingIcon, onPressed: props.onLeadingPressed, tooltip: props.leadingTooltip), SizedBox( width: effectiveSeparatorThickness, height: effectiveSeparatorHeight, diff --git a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart index a2432aed..b2bcc6e0 100644 --- a/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart +++ b/packages/stream_core_flutter/lib/src/theme/components/stream_split_button_theme.dart @@ -26,9 +26,9 @@ part 'stream_split_button_theme.g.theme.dart'; /// ), /// ), /// child: StreamSplitButton.icon( -/// icon: Icon(icons.voiceFill), +/// leadingIcon: Icon(icons.voiceFill), /// trailingIcon: Icon(icons.caretDown), -/// onPressed: () {}, +/// onLeadingPressed: () {}, /// onTrailingPressed: () {}, /// ), /// ) diff --git a/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart b/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart index ecb62b4f..5c9ce480 100644 --- a/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart +++ b/packages/stream_core_flutter/test/components/buttons/stream_split_button_golden_test.dart @@ -63,7 +63,7 @@ void main() { children: [ GoldenTestScenario( name: 'leading disabled', - child: _buildInTheme(_splitButton(onPressed: null)), + child: _buildInTheme(_splitButton(onLeadingPressed: null)), ), GoldenTestScenario( name: 'trailing disabled', @@ -71,7 +71,7 @@ void main() { ), GoldenTestScenario( name: 'both disabled', - child: _buildInTheme(_splitButton(onPressed: null, onTrailingPressed: null)), + child: _buildInTheme(_splitButton(onLeadingPressed: null, onTrailingPressed: null)), ), ], ), @@ -89,7 +89,7 @@ GoldenTestGroup _buildMatrix({Brightness brightness = Brightness.light, bool ena child: _buildInTheme( _splitButton( variant: variant, - onPressed: enabled ? () {} : null, + onLeadingPressed: enabled ? () {} : null, onTrailingPressed: enabled ? () {} : null, ), brightness: brightness, @@ -101,14 +101,14 @@ GoldenTestGroup _buildMatrix({Brightness brightness = Brightness.light, bool ena StreamSplitButton _splitButton({ StreamSplitButtonVariant variant = StreamSplitButtonVariant.regular, - VoidCallback? onPressed = _noop, + VoidCallback? onLeadingPressed = _noop, VoidCallback? onTrailingPressed = _noop, }) { return StreamSplitButton.icon( - icon: const Icon(StreamIconData.voiceFill), + leadingIcon: const Icon(StreamIconData.voiceFill), trailingIcon: const Icon(StreamIconData.caretDown), variant: variant, - onPressed: onPressed, + onLeadingPressed: onLeadingPressed, onTrailingPressed: onTrailingPressed, ); } diff --git a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart index 6534f83d..b9d569b4 100644 --- a/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart +++ b/packages/stream_core_flutter/test/components/buttons/stream_split_button_test.dart @@ -12,19 +12,19 @@ Widget _withStreamTheme(Widget child, {StreamTheme? streamTheme}) { StreamSplitButton _splitButton({ StreamSplitButtonVariant variant = StreamSplitButtonVariant.regular, IconData trailingIcon = StreamIconData.caretDown, - VoidCallback? onPressed, + VoidCallback? onLeadingPressed, VoidCallback? onTrailingPressed, - String? tooltip, + String? leadingTooltip, String? trailingTooltip, StreamSplitButtonStyle? themeStyle, }) { return StreamSplitButton.icon( - icon: const Icon(StreamIconData.voiceFill), + leadingIcon: const Icon(StreamIconData.voiceFill), trailingIcon: Icon(trailingIcon), variant: variant, - onPressed: onPressed, + onLeadingPressed: onLeadingPressed, onTrailingPressed: onTrailingPressed, - tooltip: tooltip, + leadingTooltip: leadingTooltip, trailingTooltip: trailingTooltip, themeStyle: themeStyle, ); @@ -63,7 +63,7 @@ void main() { _withStreamTheme( Column( children: [ - _splitButton(variant: variant, onPressed: () {}, onTrailingPressed: () {}), + _splitButton(variant: variant, onLeadingPressed: () {}, onTrailingPressed: () {}), StreamButton.icon(icon: const Icon(Icons.mic), style: buttonStyle, onPressed: () {}), ], ), @@ -92,7 +92,7 @@ void main() { ), ), ), - _splitButton(onPressed: () {}, onTrailingPressed: () {}), + _splitButton(onLeadingPressed: () {}, onTrailingPressed: () {}), ), ); @@ -101,7 +101,7 @@ void main() { testWidgets('halves paint neither background nor border', (tester) async { await tester.pumpWidget( - _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), + _withStreamTheme(_splitButton(onLeadingPressed: () {}, onTrailingPressed: () {})), ); for (var index = 0; index < 2; index++) { @@ -115,7 +115,7 @@ void main() { testWidgets('draws no border of its own', (tester) async { // Neither variant is outlined. await tester.pumpWidget( - _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), + _withStreamTheme(_splitButton(onLeadingPressed: () {}, onTrailingPressed: () {})), ); final shape = _surfaceOf(tester).shape as OutlinedBorder; @@ -128,7 +128,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( _splitButton( - onPressed: () {}, + onLeadingPressed: () {}, onTrailingPressed: () {}, themeStyle: const StreamSplitButtonStyle( buttonStyle: StreamButtonThemeStyle(borderColor: WidgetStatePropertyAll(Color(0xFF00FF00))), @@ -151,7 +151,7 @@ void main() { final disabledColor = streamTheme.colorScheme.backgroundDisabled; await tester.pumpWidget( - _withStreamTheme(streamTheme: streamTheme, _splitButton(onPressed: () {})), + _withStreamTheme(streamTheme: streamTheme, _splitButton(onLeadingPressed: () {})), ); expect(_surfaceOf(tester).color, enabledColor); @@ -168,7 +168,7 @@ void main() { group('StreamSplitButton layout', () { testWidgets('matches the design: 89x48 control over an 81x40 surface', (tester) async { await tester.pumpWidget( - _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), + _withStreamTheme(_splitButton(onLeadingPressed: () {}, onTrailingPressed: () {})), ); // The control is wider than the surface it paints: the same 4pt inset @@ -194,7 +194,7 @@ void main() { _withStreamTheme( Column( children: [ - _splitButton(onPressed: () {}, onTrailingPressed: () {}), + _splitButton(onLeadingPressed: () {}, onTrailingPressed: () {}), StreamButton.icon(icon: const Icon(Icons.mic), style: .secondary, onPressed: () {}), ], ), @@ -222,9 +222,9 @@ void main() { ), ), _splitButton( - onPressed: () {}, + onLeadingPressed: () {}, onTrailingPressed: () {}, - tooltip: 'Mute', + leadingTooltip: 'Mute', trailingTooltip: 'Audio settings', ), ), @@ -249,9 +249,9 @@ void main() { await tester.pumpWidget( _withStreamTheme( _splitButton( - onPressed: () => pressed++, + onLeadingPressed: () => pressed++, onTrailingPressed: () => trailingPressed++, - tooltip: 'Mute', + leadingTooltip: 'Mute', trailingTooltip: 'Audio settings', ), ), @@ -276,7 +276,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( streamTheme: streamTheme, - _splitButton(onPressed: () {}, onTrailingPressed: () {}), + _splitButton(onLeadingPressed: () {}, onTrailingPressed: () {}), ), ); @@ -295,7 +295,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( streamTheme: streamTheme, - _splitButton(variant: .destructive, onPressed: () {}, onTrailingPressed: () {}), + _splitButton(variant: .destructive, onLeadingPressed: () {}, onTrailingPressed: () {}), ), ); @@ -343,7 +343,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( streamTheme: StreamTheme(splitButtonTheme: themeData), - _splitButton(variant: variant, onPressed: () {}, onTrailingPressed: () {}), + _splitButton(variant: variant, onLeadingPressed: () {}, onTrailingPressed: () {}), ), ); // Settled, or a subtree can still be reporting the previous @@ -365,7 +365,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( streamTheme: streamTheme, - _splitButton(onPressed: () {}, onTrailingPressed: () {}), + _splitButton(onLeadingPressed: () {}, onTrailingPressed: () {}), ), ); @@ -377,7 +377,7 @@ void main() { await tester.pumpWidget( _withStreamTheme( _splitButton( - onPressed: () {}, + onLeadingPressed: () {}, onTrailingPressed: () {}, themeStyle: const StreamSplitButtonStyle( separatorColor: WidgetStatePropertyAll(Color(0xFFFF0000)), @@ -397,7 +397,7 @@ void main() { group('StreamSplitButton icons', () { testWidgets('renders the leading icon before the trailing one', (tester) async { await tester.pumpWidget( - _withStreamTheme(_splitButton(onPressed: () {}, onTrailingPressed: () {})), + _withStreamTheme(_splitButton(onLeadingPressed: () {}, onTrailingPressed: () {})), ); final leading = tester.getCenter(find.byIcon(StreamIconData.voiceFill)); @@ -410,7 +410,7 @@ void main() { // to pick rather than something the component hard-codes. await tester.pumpWidget( _withStreamTheme( - _splitButton(trailingIcon: StreamIconData.caretUp, onPressed: () {}, onTrailingPressed: () {}), + _splitButton(trailingIcon: StreamIconData.caretUp, onLeadingPressed: () {}, onTrailingPressed: () {}), ), ); @@ -423,7 +423,7 @@ void main() { _withStreamTheme( Directionality( textDirection: TextDirection.rtl, - child: _splitButton(onPressed: () {}, onTrailingPressed: () {}), + child: _splitButton(onLeadingPressed: () {}, onTrailingPressed: () {}), ), ), ); @@ -442,9 +442,9 @@ void main() { await tester.pumpWidget( _withStreamTheme( _splitButton( - onPressed: () => pressed++, + onLeadingPressed: () => pressed++, onTrailingPressed: () => trailingPressed++, - tooltip: 'Mute', + leadingTooltip: 'Mute', trailingTooltip: 'Audio settings', ), ), @@ -466,7 +466,7 @@ void main() { _withStreamTheme( _splitButton( onTrailingPressed: () => trailingPressed++, - tooltip: 'Mute', + leadingTooltip: 'Mute', trailingTooltip: 'Audio settings', ), ), @@ -485,9 +485,9 @@ void main() { await tester.pumpWidget( _withStreamTheme( _splitButton( - onPressed: () {}, + onLeadingPressed: () {}, onTrailingPressed: () {}, - tooltip: 'Mute', + leadingTooltip: 'Mute', trailingTooltip: 'Audio settings', ), ), @@ -516,9 +516,9 @@ void main() { _withStreamTheme( StreamComponentFactory( builders: StreamComponentBuilders( - splitButton: (context, props) => Text('custom ${props.tooltip}'), + splitButton: (context, props) => Text('custom ${props.leadingTooltip}'), ), - child: _splitButton(onPressed: () {}, onTrailingPressed: () {}, tooltip: 'Mute'), + child: _splitButton(onLeadingPressed: () {}, onTrailingPressed: () {}, leadingTooltip: 'Mute'), ), ), );