Skip to content

Commit 2d5c2b0

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 76a747a commit 2d5c2b0

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-
// Not clipping content input with [TextField] gives us fine
314-
// control over the clipping behavior. Otherwise, the non-zero
315-
// vertical `contentPadding` would cause the text to be clipped
316-
// by a rectangle shorter than the compose box.
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+
// Not clipping content input with [TextField] gives us fine
316+
// control over the clipping behavior. Otherwise, the non-zero
317+
// vertical `contentPadding` would cause the text to be clipped
318+
// by a rectangle shorter than the compose box.
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)