Skip to content

Commit d966d71

Browse files
committed
compose_box: Cast inset shadow for scrollable contents
The shadow is always present, but the overlay is only visible when there is text under it. This only happens when the TextField is long enough to be scrollable. See also: - https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3954-13395 - https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3862-14350 Fixes: zulip#915 Signed-off-by: Zixuan James Li <[email protected]>
1 parent 1a0a4a7 commit d966d71

File tree

1 file changed

+48
-19
lines changed

1 file changed

+48
-19
lines changed

lib/widgets/compose_box.dart

+48-19
Original file line numberDiff line numberDiff line change
@@ -307,25 +307,54 @@ class _ContentInput extends StatelessWidget {
307307
narrow: narrow,
308308
controller: controller,
309309
focusNode: focusNode,
310-
fieldViewBuilder: (context) => TextField(
311-
controller: controller,
312-
focusNode: focusNode,
313-
// `contentPadding` causes the text to be clipped by a rect
314-
// shorter than the compose box, because it shrinks the body of
315-
// [TextField]. Overriding this gives us fine control
316-
// over the clipping behavior with [ClipRect].
317-
clipBehavior: Clip.none,
318-
style: TextStyle(
319-
fontSize: 17,
320-
height: (contentLineHeight / 17),
321-
color: designVariables.textInput),
322-
maxLines: null,
323-
textCapitalization: TextCapitalization.sentences,
324-
decoration: InputDecoration(
325-
contentPadding: const EdgeInsets.symmetric(vertical: verticalPadding),
326-
hintText: hintText,
327-
hintStyle: TextStyle(
328-
color: designVariables.textInput.withValues(alpha: 0.5)))))));
310+
fieldViewBuilder: (context) => _InsetShadowBox(
311+
color: designVariables.composeBoxBg,
312+
child: TextField(
313+
controller: controller,
314+
focusNode: focusNode,
315+
// `contentPadding` causes the text to be clipped by a rect
316+
// shorter than the compose box, because it shrinks the body of
317+
// [TextField]. Overriding this gives us fine control
318+
// over the clipping behavior with [ClipRect].
319+
clipBehavior: Clip.none,
320+
style: TextStyle(
321+
fontSize: 17,
322+
height: (contentLineHeight / 17),
323+
color: designVariables.textInput),
324+
maxLines: null,
325+
textCapitalization: TextCapitalization.sentences,
326+
decoration: InputDecoration(
327+
contentPadding: const EdgeInsets.symmetric(vertical: verticalPadding),
328+
hintText: hintText,
329+
hintStyle: TextStyle(
330+
color: designVariables.textInput.withValues(alpha: 0.5))))))));
331+
}
332+
}
333+
334+
/// A widget that overlays inset shadows on a child.
335+
class _InsetShadowBox extends StatelessWidget {
336+
const _InsetShadowBox({required this.color, required this.child});
337+
338+
/// The shadow color to fade into transparency from the top and bottom borders.
339+
final Color color;
340+
final Widget child;
341+
342+
BoxDecoration _shadowFrom(AlignmentGeometry begin) {
343+
return BoxDecoration(gradient: LinearGradient(
344+
begin: begin, end: -begin,
345+
colors: [color, color.withValues(alpha: 0)]));
346+
}
347+
348+
@override
349+
Widget build(BuildContext context) {
350+
return Stack(
351+
children: [
352+
child,
353+
Positioned(top: 0, left: 0, right: 0,
354+
child: Container(height: 8, decoration: _shadowFrom(Alignment.topCenter))),
355+
Positioned(bottom: 0, left: 0, right: 0,
356+
child: Container(height: 8, decoration: _shadowFrom(Alignment.bottomCenter))),
357+
]);
329358
}
330359
}
331360

0 commit comments

Comments
 (0)