santo_ui 2.3.0
santo_ui: ^2.3.0 copied to clipboard
Enterprise-grade Flutter UI library with 60+ production-ready widgets and a built-in theming system.
Changelog #
All notable changes are documented here.
Format based on Keep a Changelog; versioning follows Semantic Versioning.
2.3.0 - 2026-09-29 #
Divider title rows #
- Changed:
SantoDividerwithtitlePlacement: start/endnow renders its title row like theSantoSectiontitle — a short edge line segment (~5% width) on the title's near side plus a full-width line on the far side, instead of an empty margin with a single-side line - Removed:
titleMarginparameter (the near-edge distance is now taken by the 5% line segment)
🕒 Timeline #
- Added:
SantoTimeline(an antd Timeline port) — vertical layouts (axis at the start / alternating sides / axis at the end) and horizontal layouts (content below / alternating / above the axis); items support a title beside the content (title/titleSpan), theme semantic colours (color: blue / red / green / gray) or custom colours (dotColor), custom nodes (icon), a loading ghost node (loading, with the rail leading into it dashed), outlined / filled dots (variant), reversed order (reverse) and a per-item side override (placement); see the component docs
2.2.3 - 2026-09-28 #
🖼️ Avatar image sources #
- Added:
SantoAvatar.imageUrlnow accepts app-bundled asset paths (assets/...) in addition tohttp(s)URLs and Data-URI / base64, enabling role-fixed default avatars that ship with the host app
📋 Floating panel header #
- Added:
SantoFloatingPanel.title/desc— whenheaderis not provided, a default header (title + description, styled likeSantoBottomDrawer) renders below the drag bar; passingheaderstill takes precedence for custom layouts such as a right-side close button - Added:
SantoFloatingPanel.headerBottom— a slot below the header (AppBarbottomposition semantics, same usage as PageLayout'sheader): no built-in padding, full width, accepts any widget (TabBar, filter tags, button rows); it joins the drag area together with the drag bar and header
2.2.2 - 2026-09-28 #
👥 Group avatar grid #
- Added:
SantoGroupAvatar(withSantoGroupAvatarItem) — tiles member avatars into a grid for group conversations (WeChat-style): no members falls back to a group icon, a single member fills the whole cell, 2–4 members take 2 columns and 5–9 take 3, capped atmaxCount. Distinct from the overlapping-stackSantoAvatarGroup - Added:
SantoChatConversation.isGroup/memberAvatars;SantoChatListnow renders the grid for group conversations and falls back to a group icon while member data is unavailable (the conversation list endpoint does not return members, so hosts fetch the conversation detail to fill them in)
🖼️ Avatar image sources #
- Fixed:
SantoAvataronly ever usedImage.network, so Data-URI / raw-base64 avatars (how some backends serve them — DevOpsMobile for one) silently fell back to the text avatar. The widget now resolves the source itself:http(s)→NetworkImage, Data URI / raw base64 →MemoryImage, and anything undecodable still falls back to the text / icon avatar
2.2.1 - 2026-09-28 #
💬 Chat input passthrough #
- Fixed:
SantoChatnow forwardscontroller,focusNodeandmentionsto its input bar, so theSantoChat.mentionsdocumented in 2.2.0 actually exists, and hosts that need the input's text controller (custom @ mention, focus restore) can use the composite instead of assemblingSantoChatInputby hand
2.2.0 - 2026-09-28 #
💬 Chat mentions & avatars #
- Added: @ mention composing —
SantoChatInput.mentions/SantoChat.mentionstakeSantoChatMentioncandidates; typing@opens a suggestion panel above the input field, further typing prefix-filters it, and picking a member inserts@displayso the sent text renders as a tappable mention - Changed: chat avatars are rounded squares now (message bubbles and the conversation list) instead of circles
- Changed: the swipe-to-reply hint icon uses
chat-bubble - Changed:
SantoChatListrows get a background — white by default, grey for pinned conversations
2.1.0 - 2026-09-28 #
💬 Chat input bar redesign #
- Changed: the header (e.g. the group notice) gets a 5-px inset on top/left/right
- Changed: message items get a 5-px horizontal inset (the list
paddingoverride still wins) - Changed: the input field box is at least 40 tall with vertically centred text; the toolbar icons (emoji / more) grow to 30 to match
- Changed: the "more" entry uses the
plus-circleicon; toolbar buttons no longer change colour when their panel opens - Added: the send bar is a frosted-glass layer —
BackdropFilterblur 10 over a translucent grey tint (85 % alpha), modelled on the MenuBar floating style; the message list now extends underneath it so the latest messages faintly show through - Added:
SantoChatPanelenum andSantoChatInput.panelNotifier— the panel state (none / emoji / extension) is shared with the host; opening the "more" panel scrolls the chat to the newest message, and tapping the message list while a panel is open collapses it immediately - Added:
SantoChatMessageList.bottomOverlayHeight— bottom clearance for floating overlays, also lifting the scroll-to-bottom button
🏙️ Appraise bottom picker spacing #
- Changed:
SantoAppraiseBottomPickeradds the theme spacing below the submit button (safe area is still handled by the bottom drawer)
2.0.0 - 2026-09-24 #
🎨 Every style token lives in the theme #
- Added (breaking): shadow tokens —
shadowColor,shadowSm/shadowMd/shadowLg(List<BoxShadow>); components default to a preset and can override it - Added (breaking): semantic tint tokens —
brandPrimaryBg/brandSuccessBg/brandWarningBg/brandErrorBg, plusfillBaseInverse(inverse surface, e.g. the image viewer) andappBarDarkBackgroundColor - Added (breaking): chart tokens —
chartPalette(categorical series colours),chartAxisColor,chartAxisTextColor,chartGridColor - Added (breaking):
SantoAppBarConfig.leadingSize/leadingSpacing/doubleLeadingSize - Changed (breaking):
lib/src/components/picker/base/santo_picker_constants.dartis removed from the public API — the symbolspickerBackgroundColor,pickerShowTitleDefault,pickerHeight,pickerTitleHeight,pickerItemHeight,datetimePickerItemTextStyleandpickerItemTextStyleare gone. UseSantoPickerConfig(heights, background) andSantoCommonConfig(colours) instead - Changed (breaking):
SantoAppBarTheme(the privatenavbar/santo_appbar_theme.dart) is removed — its colours and font sizes now come fromSantoCommonConfig, its geometry fromSantoAppBarConfig - Changed: components no longer hard-code colours;
tree,cascader,dropdown_menu,drawer,sidebar,tag,switch,form,picker,chartsand the default configs now readcolorTextBase/colorTextSecondary/fillBase/dividerColorBase/brand*and friends, so a custom theme reaches them - Changed: the chart defaults are theme-derived —
SantoProgressChart.colors/backgroundColor,SantoProgressBarBundle.colors/hintColors,SantoProgressBarChartPainter.unselectedColor/selectedHintTextColor/selectedHintTextBackgroundColorandSantoRadarChart.axisLineColorare nullable and fall back to the theme;SantoRadarChart.defaultRadarChartStylesandSantoFunnelChart.defaultLayerColorsare now derived frombrandPrimary/chartPalette - Changed:
SantoPickerTitleConfig.showTitlekeeps itstruedefault, now expressed as a literal instead of the removed constant - Changed (breaking):
SantoStepperdrops the public constantkSantoStepperRadius(the radius comes fromSantoCommonConfig.radiusMd) - Changed (breaking):
SantoShare.textColor/shareTextColor,SantoStepLine.lineWidthandSantoSearchText.outSideColor/innerPadding/borderRadiusare now nullable so the theme value applies when they are omitted
🧭 SantoAnchorTab renamed SantoAnchor #
- Changed (breaking):
SantoAnchorTabis renamedSantoAnchorandSantoAnchorTabBarStyleis renamedSantoAnchorBarStyle, so no public symbol carries the old name — the builders becomeAnchorWidgetIndexedBuilder(wasAnchorTabWidgetIndexedBuilder) andAnchorIndexedTabBuilder(wasAnchorTabIndexedBuilder), and the component moves tolib/src/components/anchor/santo_anchor.dart - Changed: the example moves to
anchor/anchor_example.dart(AnchorExample); the menu entry, page title and intro title are now allAnchor 锚点
🧩 New layout components #
- Added:
SantoFlex(lib/src/components/flex/santo_flex.dart) — a flex layout component modelled on antd Flex:orientation(horizontal/vertical),wrap,justify(main axis),align(cross axis, defaulting to start horizontally and stretch vertically), a uniformflexvalue that wraps every child inExpanded, and gap control viagapSize(theSantoSpaceSizesmall/middle/large tiers, resolved from theme spacing tokens) or a customgapvalue - Added:
SantoGrid—SantoRow+SantoCol(lib/src/components/grid/santo_grid.dart), a 24-column grid modelled on antd Grid:span/offset/order/push/pull/flexper column,guttercolumn spacing (first/last column content flush with the row edges),verticalGutterrow spacing, automatic wrapping when spans exceed 24, plusjustify/align. antd's responsive breakpoints (xs–xxl) are not ported — they are specific to CSS media queries
📝 SantoInputText renamed SantoInput #
- Changed (breaking):
SantoInputTextis renamedSantoInputand moves tolib/src/components/input/santo_input.dart; the example class becomesSantoInputExample - Added:
typeparameter switches the unified entry between modes, modelled on antd Input —SantoInputMode.text(single-line, default),SantoInputMode.search(built-in leading search icon unless a customprefixis given),SantoInputMode.textarea(multi-line,minLinesdefaults to 4 and the height grows with content). The enum isSantoInputMode(the nameSantoInputTypewas already taken by the form system) - Changed: the single-line content height drops from 44 to 32; the border width is pinned to 1 (no longer follows the
borderWidthMdtheme tier) - Changed:
SantoSearchTextgains an optionalheightfor the inner field; the default is now 32, aligned with the new single-line standard (previously the field auto-sized to the content)
🏙️ City selection refresh #
- Changed: the hot-city chips in
SantoCitySelectionnow render a selectableSantoTaggroup (uniform 15 outer padding, 10 gaps, three equal-width tags per row); tapping a tag returns the city and closes the page - Changed: the search bar keeps
SantoSearchTextbut renders the inner field at the standard 32 height with a uniform 15 padding; the header height estimate is now derived from the real layout so no blank gap is left below the chips
1.5.1 - 2026-09-24 #
💬 Chat custom messages #
- Added:
SantoChatCustomMessage— a message whose content is supplied by the host app through abuilder, rendered in a bare bubble (no bubble background or padding, the content brings its own container), the same treatment as document messages. This closes the gap where the closed message hierarchy could not carry business cards such as an approval card.
💬 More chat message types #
-
Added:
SantoChatApprovalMessage+SantoChatApprovalCard— an approval card (task no, status tag, title, flow / applicant / current node) that shows 通过 / 驳回 when the task is pending andcanApproveis true; the taps callonApprove/onReject, and the card itself callsonApprovalTap.SantoChatApprovalStatuspluskSantoChatApprovalStatusText/santoChatApprovalTagStatecover the status label and tag colour -
Added:
SantoChatNoticeMessage+SantoChatNoticeCard— a notification card (type icon, unread dot, title, body, time).SantoChatNoticeTypedecides the icon and colour (kSantoChatNoticeIcons/santoChatNoticeColor), the tap callsonNoticeTap, and the card also works standalone as a notification-centre row -
Added:
SantoChatEmojiMessage+SantoChatEmojiView— a single emoji message; a[name]token registered inSantoChatEmojiRegistryrenders as an image, otherwise the built-in Unicode character is used -
Added:
recalledonSantoChatMessage— a recalled message renders nothing (the recall notice is expected to arrive as a system message), matching DevOpsMobile -
Changed: group announcements reuse
SantoNoticethrough theheaderslot instead of a hand-rolled container -
Changed: the close button of the input bar's reply / edit banner is now a solid
xmark-circlein the error colour and slightly larger (20,kSantoChatBannerCloseSize) -
Changed: message status is now text instead of icons — outgoing messages show 发送中 / 已发送 / 未读 / 已读 below the bubble, aligned with the avatar side (unread states take the primary colour), and only the failed warning icon stays next to the bubble
-
Added: read receipts —
SantoChatReadReceipt(readCount/unreadCountplus optionalreadMembers/unreadMembers) renders DingTalk-style text: 已读 / 未读 for a 1:1 chat and 「N人未读 / 全部已读」 for a group. Tapping the text callsonReadReceiptTap, andSantoChatReadReceiptSheet.showopens the read / unread member list -
Fixed: messages from the same sender in one run now keep the avatar slot even when the avatar itself is hidden, so bubbles and their receipt line up on the same right edge
🧭 MenuBar selected state #
-
Changed: the floating
SantoMenuBar/SantoAppLayoutselected item now takes a neutral light-grey background (0xFFF0F0F0) with the theme colour on the selected icon and label, instead of a primary-coloured pill with white text. PassitemSelectedBgColor/selectedTextColorto bring back a custom look -
Changed:
selectedTextColornow defaults to the theme colour for both styles, so the selected icon and label followbrandPrimary -
Fixed: with a custom theme colour the outgoing bubble stayed the default blue —
SantoDefaultConfigUtils.defaultChatConfigno longer pins any colour or text style, so every chat colour is derived from the liveSantoCommonConfigat read time
🔔 One SantoNotice for every notice #
- Changed (breaking):
SantoNoticeBarandSantoNoticeBarWithButtonare gone — both are merged intoSantoNotice, the single notice-bar entry point. The left slot is either a status icon or a tag (leftTagText), the right slot is either a status icon or a button (rightButtonText+onRightButtonTap), andminHeightdefaults to 54 when a tag or a button is present, 36 otherwise - Changed: the ten built-in
NoticeStylespresets now resolve their colour from the liveSantoCommonConfig— 进行中 / 通知 take the theme colour (brandPrimary), 完成brandSuccess, 警告brandWarning, 失败brandError, with the background as that colour at 10% opacity, so a custom theme colour reaches the notice bar too. The colours stored onNoticeStylestay as fallbacks for hand-rolled styles, and the 通知 preset is no longer orange - Added:
SantoNoticeStyleTypeandSantoNoticeRightIconKinddescribe a preset;kSantoNoticeStyleIcons/santoNoticeStyleColorexpose the icon and colour used for it
⭐ Rate follows the theme colour #
- Changed: the selected / half star now takes
brandPrimaryinstead ofbrandWarning, the unselected star takesdividerColorBaseinstead of a hard-coded grey, and the star size followsiconSizeMd— so a custom theme colour reaches the rating too. PassstarBuilderwhen a fixed look is needed
1.5.0 - 2026-09-24 #
💬 Chat conversation widgets #
- Added:
SantoChat(header + message list + input bar),SantoChatMessageList,SantoChatBubble,SantoChatInput,SantoChatListandSantoChatSelectionBar - Added: message content widgets
SantoChatText(@mentions, emoji registry, links),SantoChatQuoteView,SantoChatImage,SantoChatVideo,SantoChatVoice,SantoChatFile,SantoChatDocCard,SantoChatSystemNotice,SantoChatTypingIndicator,SantoChatReactionViewandSantoChatMessageMenu - Added: message models (
SantoChatMessagewith text/image/video/voice/file/doc/system variants, plus author, mention, quote and reaction),SantoChatConversation, the globalSantoChatEmojiRegistry,SantoChatMenuItemandSantoChatExtension - Added: message list features — date separators, sender grouping, swipe to reply, a long-press menu (reactions plus per-type and custom actions, wrapping 5 per row), pull-up history loading and a scroll-to-bottom button
- Added: message status indicators (
delivered/readadded toSantoChatMessageStatus, drawn on the avatar side of outgoing bubbles) and theisEditedflag; when a message fails, a solid warning icon (warning-circle-solid) is drawn on the opposite (non-avatar) side of the bubble, vertically centered against it, and tapping it retries viaonRetry - Added: message editing through the input bar, and multi-select with a checkbox per row plus a bottom action bar
- Added: input bar extension menu (photo / camera / file by default, customizable) that swaps with the keyboard below the input row, plus a built-in emoji panel (32 emojis,
[name]tokens) sharing the same fixed 230 panel height — both panels are top-left grids with fixed cells and kept empty slots, paged 2 rows (kSantoChatMenuItemRows) × 5 columns for the extension menu and 4 rows (kSantoChatEmojiRows) × 8 columns for emojis, overflowing pages reached by swiping left/right with a page indicator at the bottom - Added: document messages —
SantoChatDocMessagerenders as aSantoChatDocCard(doc title, optional note, 「点击查看文档」 hint) inside a bare bubble (SantoChatBubble.bare: no bubble background or padding, the card brings its own container); tapping it callsonDocTapso the host app can check permissions and open the doc (mirrors DevOpsMobile) - Added:
SantoChatConfigtheme config, registered inSantoAllThemeConfigandSantoDefaultConfigUtils - Changed: the input bar no longer renders a send button — sending goes through the IME send key, and the keyboard stays up between messages; the
sendButtonparameter was removed - Changed: the input hint no longer wraps — it is single line and ellipsizes
🔄 Pull-to-refresh over nested scrollables #
- Fixed: pull-to-refresh no longer dies when the content contains its own scrollable (e.g. a
SantoTablewithheight, or a nestedListView) —SantoRefreshtakes the gesture over once the nested container reaches its leading edge, shows the refresh header and triggers the refresh; onSantoPageLayoutthe nested content now stays put instead of rubber-banding down and back - Fixed: load-more is only driven by the scrollable
SantoRefreshdirectly hosts, so a nested list reaching its end no longer triggersonLoadMore - Fixed: the refresh header is no longer a transparent overlay floating over the first row — pulling opens the top area and shifts the content down (paint-only translation, viewport height unchanged), so the area always carries the header content instead of overlapping the list
🏷️ One SantoTag for every tag #
- Changed (breaking):
SantoSelectTagandSantoDeleteTagare gone — both are merged intoSantoTag, the single tag entry point.SantoDeleteTagControlleris renamedSantoTagController(same methods) - Added: tag group support on
SantoTag— passtags(or acontroller) and the widget lays out a group of tags, keeping the oldSantoSelectTagparameters:spacing,verticalSpacing,softWrap,fixWidthMode,tagWidth,tagHeight,tagTextStyle,selectedTagTextStyle,tagBackgroundColor,selectedTagBackgroundColor,alignment,themeData - Added:
selectable/deletableswitches. Single tag:initSelected+onSelectedChange, andonDelete. Tag group:isSingleSelect/initTagState/onChanged, andonTagDelete/controller - Added:
heightonSantoTag— the text is centred vertically within it - Changed:
SantoTagis now aStatefulWidget(internal state only, the API is unchanged) andtextis optional, since the same widget also renders tag groups - Changed: defaults now come from the theme —
height32 (tagHeight),fontSize12 (tagTextStyle, was 11),borderRadius12 (tagRadius), horizontal paddinghSpacingSm(10, was a fixed 4);paddingis now nullable so a value can override it - Changed:
SantoTagConfig.tagHeightdefault 34 → 32, andSantoTagsPicker.tagHeightdefault 34 → 32 - Changed:
SantoDeleteTag.backgroundColor→SantoTag.tagBackgroundColor,SantoDeleteTag.horizontalSpacing→SantoTag.spacing; the old white wrapper andpaddingofSantoDeleteTagwere dropped (handle it with the surrounding container) - Fixed: a deletable tag group no longer forces the fixed
tagWidth, which left the label only ~35px wide (ellipsis-only) once the delete icon was in place — such groups size to their content unlesstagWidthis passed explicitly - Fixed: a tag group is now single-line and ellipsised instead of wrapping inside its fixed height
1.4.3 - 2026-09-23 #
1.4.2 - 2026-09-23 #
🗂️ SantoEmpty layout #
- Fixed: vertically center empty-state content, constrain built-in SVG illustrations to their original aspect ratio, and trim their transparent top and bottom canvas space
- Added: a titled
SantoPanelempty-state example with a fixed display height
1.4.1 - 2026-09-23 #
🧭 Selection examples #
- Changed: reorganized complex-filter examples into grouped entry pages, migrated every third-level example page to
SantoPageLayout, and consolidated the flat-filter demos into a shared implementation - Fixed: removed obsolete top-level close-panel placeholders from the date-range and selection-limit examples
1.4.0 - 2026-09-22 #
🗂️ SantoEmpty fixed-height layout #
- Added:
heightfor rendering the empty state at a fixed height and vertically centering its content within that area
📊 SantoTable feature expansion #
- Added: horizontal and vertical cell merging, single/multiple row selection, client-side sorting, expandable rows, pagination, and page-size selection
- Added: stable
rowKeyplus controlled selection and expansion state; existing custom rendering, fixed-height scrolling, fixed columns, and border control continue to work with the new table pipeline - Changed: the default header now uses the theme's light fill color and primary text color instead of a brand-color background with inverse text
- Changed: default cell padding now reads
commonConfig.hSpacingMd
1.3.0 - 2026-09-22 #
🧭 AppBar action icon alignment #
- Added:
icon(icon name) onSantoIconAction— the icon is then built by the component at the theme icon size, so it matches the leading arrow in size and follows the AppBar-resolved content color;childis kept for custom widgets (an unsizedSantoIconstill renders at its own default), andsizenow sets the icon dimension instead of the tap area - Fixed: the right-side icon actions had a 20×20 tap target with Material's circular ink splash while
SantoBackLeadingused a 32×32 target with a 12-radius rounded rect; both sides now shareSantoAppBarTheme.leadingSize(32×32) and the same 12-radiusInkWell - Fixed:
Icon-based children were squeezed into the 20×20 box and drawn overflowing at 24; action icons now render at the resolved 20 - Changed: the gap between actions (
SantoAppBarConfig.itemSpacing, defaulting toSantoAppBarTheme.iconMargin) is now 5 instead of 20, matchingleadingSpacingon the left — both sides keep 15 edge padding, so the whole bar reads symmetrically
🔄 SantoRefresh pull safety zone #
- Added:
triggerDistance— the pull distance required to trigger a refresh (the "safety zone" height), default 50. Below it the header shows nothing and releasing does not refresh; once reached the header shows "release to refresh" and releasing triggers the refresh - Changed:
loadingBarHeightno longer doubles as the trigger threshold — it now only defines the header height (the height the header holds while refreshing); whether the pull is ready is decided bytriggerDistance - Added: an assertion for
triggerDistance <= maxBarHeight, otherwise the pull could never reach the trigger distance
📐 PageLayout top safe area #
- Fixed: with a
headerand noappBar(e.g. a full-page custom brand header), the content area'sMediaQuery.padding.topstill carried the status-bar height. AListViewthat owns its own scrolling and does not pass an explicitpaddingconsumed it as top padding, leaving a status-bar-height blank strip below theheader. The content area'sMediaQuery.padding.topis now always zeroed — the top inset is owned solely by the layout (merged intopaddingwhen there is noheader, or handled by the header's ownSafeAreawhen there is one), consistently across the scrolling and non-scrolling modes - Added: two regression tests (content top inset is 0 with a
header; the gap between the header and the first content block contains onlypadding, not the status-bar height)
➖ Divider custom spacing #
- Added:
spacing— a custom vertical spacing for horizontal dividers that takes precedence oversize; pass0to remove the vertical whitespace entirely (e.g. when the surrounding rows already carry their own padding)
1.2.0 - 2026-09-21 #
🧭 AppBar redesign #
- Changed: the back button and each slot of
SantoDoubleLeadingnow use a fixed 32×32 tap area (SantoAppBarTheme.leadingSize), with 5 spacing between the two leading ops and 15 edge padding on both sides of the app bar; the double-leading width and the reserved leading slot are computed from the same formula, so the previous horizontal overflow is gone - Changed: the title is always centered on the bar (
centerTitlefixed) - Added: light/dark content modes — when the background luminance < 0.5 (dark mode or a custom background color), title / action texts / back arrow / icon theme default to white while custom colors are preserved; on a light background the content defaults to black and a custom white is automatically clamped back to black (invisible on white)
- Fixed:
SantoAppBarConfig.copyWith/ constructor parameter renamed fromsystemUiOverlayStyletosystemOverlayStyle, consistent with Flutter'sAppBar
🧭 MenuBar select styling #
- Added:
selectedTextStyle/unselectedTextStylefor full control of the selected / unselected label font (size, weight, color; falls back to the color params) - Added: docked style now supports
itemSelectedBgColor(sliding animated selection background, same as floating; not rendered by default) - Fixed: docked-style icon colors now follow the selected / unselected colors via
IconTheme(icons with an explicit color still win)
📐 PageLayout extensions #
- Added:
header— a fixed area right below the title (search bar, filters, tabs, steps, calendar…), full-width, no padding, auto height and not scrolling with content - Added:
enableRefresh/onRefresh— the built-in scroll container is hosted bySantoRefresh;AlwaysScrollableScrollPhysicsis applied so short content can still be pulled - Added:
paddingto override the default content inset (top safe-area avoidance is still applied by the layout) - Added:
appBar*passthrough parameters (appBarLeading/appBarActions/appBarBackgroundColor/appBarElevation/appBarShadowColor/appBarShape/appBarIconTheme/appBarActionsIconTheme/appBarSystemOverlayStyle/appBarBackLeadCallback) for the title-only construction path
🧭 ActionBar spacing & divider #
- Changed: the action bar button area now uses 10 horizontal outer padding (first / last button) and 5 vertical padding; the button height is stretched to the remaining bar height instead of a fixed 40
- Added: a 0.5px hairline divider on top of the action bar
Pull-to-refresh rework, TabBar indicator, Empty illustrations, Checkbox/Radio card style, city selection and the area cascader. Breaking changes to SantoEmptyImageType values, SantoTabBar indicator parameters and some legacy SantoAsset constants.
🔄 PageLayout pull-to-refresh jitter #
- Fixed: after releasing a pull past the threshold, the header used to collapse all the way to 0 following the iOS bounce rebound (the
ScrollEndNotificationis delayed until the rebound simulation finishes) and only then pop the loading area back to the loading height. Release is now detected on the first post-drag scroll update: the header smoothly shrinks from the pull distance toloadingBarHeightand holds there while refreshing, then shrinks to 0 after the done state — the same sequence as Vant's PullRefresh - Changed: the default refresh header no longer paints the theme
fillBodyrounded background; it is now transparent and only shows the loading icon and status text - Changed: the done state of the default refresh header now shows the success icon (
check-circle, themebrandSuccesscolor) instead of the pull arrow - Changed: the done state (success icon and "refresh complete" text) now persists through the whole collapse animation — the state only returns to inactive after the header has fully closed, instead of flipping back to the pull arrow as soon as the collapse starts
- Added: regression tests forcing Bouncing physics asserting the header settles at the loading height (never below it) during refresh and returns to 0 after completion
- Fixed: the refresh header used to be a
Columnsibling above the scroll view, so its growing height squeezed the list viewport on every frame — the page jittered while pulling and very little content stayed visible. The header is now an overlay on top of the list and the list content is moved withTransform.translate(paint-only, no relayout), so the viewport stays constant during the whole pull; the translate offset subtracts the negative scroll pixels so Bouncing physics (iOS) does not double-shift the content - Fixed:
SantoPageLayout.enableRefreshchained a bareAlwaysScrollableScrollPhysics(no parent physics) onto the built-inSingleChildScrollView, which removed boundary conditions and the ballistic settle simulation — the scroll position stayed negative after release and every subsequent scroll re-opened the refresh header. The platform physics (ScrollConfiguration.of(context).getScrollPhysics(context)) is now chained as the parent - Added: regression tests asserting the scroll position returns to 0 after a refresh and the scroll viewport height stays constant while pulling
🏙 City selection #
- Fixed: the white
DecoratedBoxbetween the listListTiles and the nearestMaterialhid ink splashes and triggered the framework's "ListTile background color or ink splashes may be invisible" assertion; the body now uses a whiteMaterialdirectly - Changed: the hot-city chips get rounded corners (
radiusXs), keep a fixed 36px height and a transparent background - Changed: the search bar padding is uniform (
hSpacingMd) instead of left/right 20 / top/bottom 10, and the divider under the search bar follows the standard 0.5 hairline spec - Changed: the A-Z index bar shows a persistent selected style — the letter of the current list section renders as a brand-colored circle with a white letter (
IndexBar.currentTag, driven byAzListView); while pressing, the touched letter keeps a grey circle highlight; the unusedIndexBar.touchDownTextStylewas removed
🗺 Area cascader #
- Added:
SantoCascader.showArea()opens a province/city/district picker backed by the built-in area data ported from@vant/area-data(34 provinces / 369 cities / 3478 counties, 6-digit national administrative codes) - Added:
SantoAreaDataexposesprovinceList/cityList/countyListflat code-to-name maps andcascaderItems(a province/city/county tree derived from code prefixes, values are codes and labels are names) for use with the genericSantoCascader.show() - Added: the
showAreaconfirm callback returns aSantoAreaResultwithcodes/names/text(names joined by "/") and per-level gettersprovinceCode/provinceName/cityCode/cityName/districtCode/districtName;initialValuesaccepts administrative codes for echo display
🖼 Empty illustrations #
- Changed: the built-in illustrations of
SantoEmptyare replaced by 12 SVG assets underassets/empty; theSantoEmptyImageTypevalues are redefined as notFound / contentEmpty / importLoading / listEmpty / loadFail / noAccess / notOpenPayType / offline / orderEmpty / searchEmpty / unbindAccount / wait (the old noData / networkError values are removed) - Changed: the
imgparameter is widened fromImage?toWidget?, so any image widget (including SVG) can be passed; custom images still take priority overimageType - Changed: the preset mapping of
SantoAbnormalStateUtilsis updated: getDataFailed → loadFail, networkConnectError → offline, noData → listEmpty - Removed: the legacy illustrations
assets/images/no_data.pngandassets/images/network_error.pngand theSantoAsset.noData/SantoAsset.networkErrorconstants
🧭 TabBar indicator #
- Changed: the selected tab indicator of
SantoTabBaris now the bottom border of the rounded selected area (full item width, clipped to the rounded corners), following the same switch animation as the selected background; the built-inTabBarindicator is disabled - Removed:
SantoTabBar.indicatorWidthandSantoTabBar.indicatorPadding(no longer meaningful);indicatorWeightstill controls the border thickness andindicatorColorthe border color;SantoAnchorTabBarStyle.indicatorPaddingremoved accordingly - Fixed: the "more" button container passed tight constraints down to the icon, so the arrow SVG rendered ~25px regardless of
SantoIcon.size; it now centers a 20px icon (the chevron glyph spans half of its viewBox, ~10px visually)
📜 Drawer long content #
- Fixed: the
SantoBottomDrawercontent area is now scrollable, so long content scrolls withinmaxHeight(default 85% of the screen height) instead of overflowing vertically; the drawer example gained a "long content auto scroll" case
📜 Drawer safe areas & width cap #
- Added: the content area of
SantoDrawernow avoids the top status bar and bottom home indicator according to the slide direction — top drawers keep the top inset, bottom drawers keep the bottom inset, left/right full-height drawers keep both. LikeSantoBottomDrawer/SantoFloatingPanel, the inset is delivered by rewriting the contentMediaQuerypadding (not configurable) so it lives inside the content instead of an extra blank region: scrollable content (e.g. aListViewwithout explicit padding) consumes it as its own scroll padding — the scroll area spans the full drawer and the last item settles above the home indicator when scrolled to the end; fixed headers/footers readMediaQuery.of(context).paddinginside the drawer subtree and fold the inset into their own padding. The drawer example was restructured to demonstrate both patterns (title as the first list item with auto-consumed padding; pinned action row with inset-aware padding) - Changed: the width of left/right drawers is now capped at 95% of the screen width; larger values are clamped to the cap
🔄 Refresh header radius #
- Fixed: the default pull-to-refresh header is now rounded (theme
radiusMd) instead of a square full-bleed band
🗑 Legacy bitmap cleanup #
- Removed: 28 obsolete
SantoAssetconstants (single/multi selected boxes, alert/warning/success, star_size, arrow_up/down, require_red, star_select, the notice family) and their PNG assets; component icons are now fully provided by SantoIcon, trimmingassets/imagesfrom 50 to 40 files andassets/iconsfrom 33 to 12 - Removed: 10 unused example assets —
example/assets/image: arrow_up / icon_clear_grey / icon_navbar_add_hei / icon_navbar_im_bai / icon_navbar_xiala_hei / icon_refresh / icon_theme / network_error / no_data, andexample/assets/icons: navbar_house. The now-emptyassets/icons/entry was dropped from the example pubspec.assets/icons/grey_place_holder.pngis kept: the gallery config and two ActionSheet examples resolve it from the package (SantoTools.getAssetImageaddspackage: santo_ui)
🧭 Example icons de-picturized #
- Changed: the NavBar and Toast examples no longer load PNG icons — the search / plus / close / dropdown / share / group / heart / message glyphs now come from
SantoIcon(SantoIcons.search/plus/xmark/navArrowDown/shareIos/group/heart/chatLines), the Toast pre-icons use the solidcheck-circle/xmark-circle, and the 16 converted PNGs are deleted - Changed:
SantoToast.show'spreIconandToastChild.leadingare widened fromImage?toWidget?, so any widget (includingSantoIcon) can be passed as the pre-icon
☑️ Checkbox & Radio card style #
- Changed: the selected state of a
cardModecard is now a solidcheck-circleicon on the right of the card — same color as the card border, side length 50% of the card height — replacing the brand triangle with a white check that used to sit in the top-left corner; the triangle painter (_CornerCheck) is removed - Fixed: the card description (
subTitle) was indented by one extrainsetSpacingbecause the container padding was counted twice, so it did not line up with the title; the description now left-aligns with the title for every combination ofcardModeandcontentDirection
1.1.1 - 2026-09-21 #
🧩 Segmented badge #
- Added:
SantoSegmentedOption.badgeCountrenders a count badge to the right of the label (viaSantoBadge, capped at 99+) andSantoSegmentedOption.dotrenders a dot badge, aligned with the antd Segmented item badge; comes with a new "badge" example section
🖼 Empty image parameter #
- Changed:
SantoEmptysupports the newimageTypeparameter (SantoEmptyImageType— noData / networkError) to show different built-in illustrations; when neitherimageTypenorimgis set no image is rendered;imgkeeps the highest priority;SantoAbnormalStateUtilsnow presets illustrations viaimageType
1.1.0 - 2026-09-20 #
Buttons, loading, dialogs, Card and Tag converged into single entries, icons unified on SantoIcon, and widget spacings moved to theme tokens. Breaking changes.
💥 Breaking changes #
Buttons converge on a single SantoButton
- Added:
SantoButtonas the unified button with newcolor/variant/size/shape/ghost/iconSizeparameters - Added:
SantoButtonColorsemantic colors neutral / primary / danger / success / warning / info, mapped tocolorTextBase/brandPrimary/brandError/brandSuccess/brandWarning/brandAuxiliary - Added:
SantoButtonSize— large (48/16), middle (32/14, default, min width 84), small (24/12) - Removed:
SantoNormalButton(incl.SantoNormalButton.outline),SantoBigMainButton,SantoBigOutlineButton,SantoBigGhostButton,SantoSmallMainButton,SantoSmallOutlineButton,SantoSmallGhostButton - Removed:
SantoIconButton,SantoVerticalIconButton(icon buttons merged intoSantoButtonviaicon+iconPlacement; theDirectionenum moved to the Guide widget),SantoButtonConstant - Removed:
SantoButtonPanel,SantoButtonPanelConfig,SantoBottomButtonPanel,SantoTextButtonPanel,SantoMultipleBottomButton; button groups are now assembled by callers withRow/Expanded/SantoSpace - Changed:
SantoButtonConfig'sbigButton*/smallButton*renamed tolargeButton*/middleButton*, plus newsmallButton* - Changed: default button font size and height are determined by
size; default size is middle - Changed: removed
themeDataandmaxWidthfrom individual button widgets in favor of the globalSantoButtonConfigand parameters such asfontSize/width
Dialogs converge on a single SantoDialog
- Added:
SantoDialogas the unified dialog entry, with all six parts parameterized — iconicon/iconType, titletitle/titleWidget, messagemessage/messageWidget, inputshowInputplus theinput*group, bottom buttonsokText/cancelText, and close buttonclosable - Added: the input renders
SantoInputTextinternally;messageMaxHeightbounds the message area with inner scrolling - Added: named constructors
SantoDialog.alert(vertical primary/secondary emphasis),SantoDialog.richText(long CSS2 rich text),SantoDialog.singleSelect,SantoDialog.multiSelect,SantoDialog.share - Added: static methods
SantoDialog.confirm/info/success/warning/errorfor one-shot dialogs without manualnewandpop;SantoDialog.show/SantoDialog.dismissclose dialogs precisely by tag - Added:
SantoDialogIconTypepresets info / warning / error / success rendered withSantoIconline icons and theme semantic colors; callback typesSantoDialogSingleSelectSubmit/SantoDialogMultiSelectSubmit/SantoDialogSelectItemClick/SantoDialogShareItemClick - Removed:
SantoDialogManager(its three show methods are covered bySantoDialog.confirmand the constructor +showDialog) - Removed:
SantoEnhanceOperationDialog,SantoDialogConstants - Removed:
SantoContentExportWidget,SantoScrollableTextDialog,SantoScrollableText - Removed:
SantoMiddleInputDialog,SantoSingleSelectDialog,SantoSingleSelectDialogWidget - Removed:
SantoMultiSelectDialog,MultiSelect(optionMultiSelectItemkept) - Removed:
SantoShareDialog(channel form merged intoSantoDialog.share; bottom-sheet sharing keeps usingSantoShare) - Removed:
SantoSafeDialog(covered bySantoDialog.show/SantoDialog.dismiss) andSantoDialogUtils - Changed: bottom buttons changed from
actionsText+indexedActionCallbacktookText/cancelText/onOk/onCancel;actionsWidgetreplaced byfooter— for three or more buttons assemble them withRow/Column - Changed: header icon changed from
showIcon/iconImagetoiconType/icon; close button fromisClose/onCloseClicktoclosable/onClose - Changed: message parameter
messageText→message;dismissOnActionsTap→dismissOnActionTap - Changed: button taps now "close the dialog first, then invoke the callback"; the input value is read from
inputController.text(previouslyonConfirm(value)) - Changed: dialog paddings and block spacings now use standard spacing tokens, dropping legacy Bruno values (title horizontal 40, content horizontal 20, title-body 8, icon-title 12, body-footer 28, warning top 6, no-icon top 25): horizontal and block spacings use
hSpacingMd/vSpacingMd(15), with a dedicated top tier — icon top offsetvSpacingXxl(40), orvSpacingXl(20) without an icon - Changed: dialog width restored to 85% of screen width (same as
SantoDialogbefore the convergence; the convergence briefly misused the unuseddialogWidth300 theme value); passwidthfor a fixed width - Changed: no duplicated gray background/Scaffold inside dialogs; a single white rounded
Material, scrollable as a whole when content overflows
ActionBar adopts the unified icon & button system
- Changed (breaking):
SantoActionBarIcon'siconparameter changed fromWidgetto aStringicon name (seeSantoIcons/SantoSolidIcons), rendered internally bySantoIcon, with color and size controlled by the widget - Changed:
SantoActionBarButtonis now implemented on top ofSantoButtoninstead of custom containers and press feedback; the disabled state changed from 0.4 opacity to the standardSantoButtondisabled style (grayed out), font weight changed to theSantoButtondefault w500, and the global multi-click guard is applied
Card convergence & Descriptions
- Changed (breaking):
SantoShadowCardrenamed toSantoCard, with newtitle/titleWidget/extra/metaparameters and a default background of themefillBase(white) - Added:
SantoCardMeta(avatar + title + description) andSantoDescriptions/SantoDescriptionsItem, aligned with antd Descriptions, supportingcolumn/layout/bordered/size/colon/labelWidth/span - Removed:
SantoInsertInfo(duplicatedSantoBubbleText; useSantoBubbleTextfor bubble texts) - Removed:
SantoFollowPairInfo,SantoAlignPairInfo(covered bySantoPairInfoTable'sisValueAlign; both became private implementations) - Changed (breaking): Tag converged on a single
SantoTag; plain / bordered / state / colorful forms are all parameter-driven.SantoTagCustom(incl.buildBorderTag) andSantoStateTagare removed; theTagStateenum is renamed toSantoTagState
⏳ Loading convergence #
- Added:
SantoLoadingas the unified loading widget, aligned with antd Spin, covering size, tip, controlled visibility, delay, custom indicator, wrapper, fullscreen and progress - Added:
SantoLoadingSize— small (14), medium (20, default), large (32) - Added: static methods
SantoLoading.show/SantoLoading.dismissto show and dismiss the loading overlay - Removed:
SantoPageLoading,SantoLoadingDialog, covered bySantoLoadingandSantoLoading.show/dismiss - Changed: overlay tip parameter renamed from
contenttotip
🎨 Icons unified on SantoIcon #
- Added:
SantoIconunified icon widget, addressed by name;SantoIconsprovides constants for all 1383 regular icons - Added: the open-source Iconoir (MIT) SVG assets bundled under
assets/iconoir/, in regular (1383) and solid (288) styles - Added: the
solidparameter enables the solid style, with theSantoSolidIconsname constants - Changed: new dependency
flutter_svg ^2.3.0 - Changed: line icons such as search, close, right/up/down arrows, triangle, add/remove, question mark and calendar paging replaced PNG assets with
SantoIconacross 43 widget files; multi-color PNG icons (selection indicators, rating stars, filter reset, dropdown arrows, required-star, the ten NoticeBar state icons) were replaced as well, with dialog preset icons and selected/lit states using solid variants fromSantoSolidIcons - Kept: appraisal emojis, share-channel brand icons, step number badges, and illustration images (
SantoEmpty's no_data / network_error, city selection empty state) - Changed: stepper add/remove buttons now differentiate enabled/disabled with theme colors
colorTextSecondary/colorTextDisabled - Removed: 56 unreferenced constants in
SantoAsset; 63 unreferenced asset files underassets/(incl. the wholeassets/icons/radio/directory)
🧩 Table scrolling & fixed columns #
- Added:
SantoTable.heightfor the content area height; data scrolls vertically inside the area with a fixed header (aligned with antdscroll.y) - Added: horizontal scrolling, enabled automatically when column widths exceed the container; all columns render with fixed widths, defaulting to 120 when unset
- Added:
SantoTableColumn.fixed(left / right) pins columns to the sides during horizontal scrolling, combinable with vertical scrolling (aligned withcolumn.fixed) - Removed:
SantoTable.pinnedHeader, replaced byheight - Changed: when all columns are fixed-width and exceed the container, the table scrolls horizontally instead of squeezing proportionally
📏 Spacings unified on theme tokens #
- Changed:
SantoSpacepreset gaps changed from hardcoded 8/16/24 to theme tokens (horizontalhSpacingSm/Md/Lg, verticalvSpacingSm/Md/Lg, default 10/15/20) - Changed:
SantoDividerSizedoc comments corrected to match the implementation (themevSpacingSm/Md/Lg); behavior unchanged
📝 Examples & docs #
- Button example page rewritten in antd Button groups: type / ghost / danger / icon / icon placement / loading / sizes / disabled / block / color & variant / shape / custom disabled background / button groups
- All call sites (dialogs, pickers, tag selection, appraisal, filters, etc.) and examples migrated to
SantoButton - Dialog example page rewritten in antd Modal groups: basic / icon / message / input / warning / close button / custom footer / alert / semantic dialogs / long text / single-select / multi-select / share / close by tag
- The "CardContent" entry renamed to "Descriptions", with its example rewritten as a single page
- Material icons in ActionBar examples and docs fully replaced with SantoIcon icons
- New widget docs:
doc/components/button/santo_button.md,doc/components/icon/santo_icon.md,doc/components/dialog/santo_dialog.md(with a migration table from the 9 legacy dialog classes to the new API),doc/components/card/santo_card.md,doc/components/descriptions/santo_descriptions.md
1.0.1 - 2026-09-18 #
🐛 Bug fixes #
- Fixed a compile error in
SantoProgresswhereinitStatestill referenced_previousValueafter the field was removed - Removed a redundant null check on the result of
Overlay.of(context)inSantoMessage
⚡ Improvements #
- Removed unused variables, redundant non-null assertions and unused imports from
SantoAvatar,SantoDrawer,SantoLink,SantoSkeletonandSantoMenuBar;dart analyzepasses with no warnings
1.0.0 - 2026-09-18 #
Initial release of Santo UI v1.0.0. Ships 80 widgets grouped into General, Layout, Navigation, Data Entry, Data Display, Feedback and Charts following the example app menu.
Highlights #
- Unified theme system:
SantoThemeConfiguratorsupports multiple configIds and provides full customization of brand colors, radii, spacings, etc. - Standard spacing system:
gapXs~gapXxl(5/10/15/20/30/40) plusiGapAll/iGapAllSmall/iGapAllMiddle/iGapAllLargerpresets - Bottom safe area convention: bottom-anchored widgets paint their background to the screen bottom while content avoids the safe area; not configurable
- Unified corner radius: 12px baseline with theme tokens radiusXs/Sm/Md/Lg and per-widget container radii
📦 Widget list (by example menu) #
General (7)
Button, Fab, Link, Panel, Section, SafeArea, CardContent
Layout (7)
Divider, Space, Masonry, Skeleton, FloatingPanel, AppLayout, PageLayout
Navigation (10)
AppBar, Tabs, MenuBar, Sidebar, Steps, AnchorTab, BackTop, Drawer, Guide, ActionBar
Data Entry (16)
Input, Form, Radio, Checkbox, Switch, Rate, Stepper, Slider, SearchText, Picker, Cascader, DropdownMenu, Selection, Tree, Calendar, CitySelection
Data Display (23)
Avatar, Badge, Cell, Card, Swiper, Collapse, Image, Table, Pagination, Segmented, Statistic, Tag, BubbleText, Highlight, TextEllipsis, Popover, SwipeCell, NoticeBar, Progress, TimeCounter, Empty, Footer, Gallery
Feedback (11)
Dialog, ActionSheet, Share, Toast, Message, Tooltip, OverlayWindow, Loading, Refresh, Result, Appraise
Charts (6)
BrokenLine, Radar, Funnel, Doughnut, ProgressChart, BarChart
🎯 Design references #
This library integrates and improves on the following great open-source projects:
- Bruno - Flutter component library by Beike
- antd - Ant Design React component library
- TDesign - Tencent TDesign Flutter component library
- Vant - Vant mobile component library by Youzan
📚 Docs & examples #
- Full widget documentation site (
doc/components/) - Example app (
example/) with demos for every widget - Development conventions (
AGENTS.md) - API version annotation conventions
🔧 Tech stack #
- Flutter >=3.10.0
- Dart >=3.13.3
- Dependencies: xml ^6.1.0, lpinyin ^2.0.3, path_drawing ^1.0.0, intl >=0.18.0 <2.0.0, photo_view ^0.15.0
References #
- Keep a Changelog - changelog format
- Semantic Versioning 2.0.0 - versioning scheme