/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/lib/src/components/core/Block/styled-components.ts
313 строк
9 KB
Thiago Teixeira
Add gaps and sizes for xxsmall, xsmall, xlarge, xxlarge in st.container/columns/space (#13345)
03 янв 2026, 23:42
Не верифицирован
03 янв 2026, 23:42
cffc27d
Код
Авторство
О чём код?
/** * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { CSSProperties } from "react" import styled from "@emotion/styled" import { Block as BlockProto, streamlit } from "@streamlit/protobuf" import { StyledCheckbox } from "~lib/components/widgets/Checkbox/styled-components" import { EmotionTheme, STALE_STYLES } from "~lib/theme" import { assertNever } from "~lib/util/assertNever" function translateGapWidth( gap: streamlit.GapSize | undefined, theme: EmotionTheme ): string { switch (gap) { case streamlit.GapSize.XXSMALL: return theme.spacing.twoXS case streamlit.GapSize.XSMALL: return theme.spacing.sm case streamlit.GapSize.SMALL: return theme.spacing.lg case streamlit.GapSize.MEDIUM: return theme.spacing.threeXL case streamlit.GapSize.LARGE: return theme.spacing.fourXL case streamlit.GapSize.XLARGE: return theme.spacing.fiveXL case streamlit.GapSize.XXLARGE: return theme.spacing.sixXL case streamlit.GapSize.NONE: return theme.spacing.none default: return theme.spacing.lg } } export interface StyledElementContainerProps { isStale: boolean width: React.CSSProperties["width"] height: React.CSSProperties["height"] elementType: string overflow: React.CSSProperties["overflow"] flex?: React.CSSProperties["flex"] minWidth?: React.CSSProperties["minWidth"] textAlign?: React.CSSProperties["textAlign"] } export const StyledSpace = styled.div({ // Styling is handled in StyledElementContainerLayoutWrapper. // Space component should fill the container. width: "100%", height: "100%", }) const GLOBAL_ELEMENTS = ["balloons", "snow"] export const StyledElementContainer = styled.div<StyledElementContainerProps>( ({ theme, isStale, width, height, elementType, overflow, flex, minWidth, textAlign, }) => ({ width, height, textAlign, maxWidth: "100%", // Important so that individual elements don't take up too much space // in horizontal layouts. Particularly when an element uses the full screen wrapper. // Some components support zero width (e.g. iframe). minWidth: width === "0px" ? 0 : (minWidth ?? "1rem"), // Allows to have absolutely-positioned nodes inside app elements, like // floating buttons. position: "relative", overflow, flex, "@media print": { overflow: "visible", }, ":has(> .stCacheSpinner)": { height: theme.spacing.none, overflow: "visible", visibility: "visible", marginBottom: `-${theme.spacing.lg}`, zIndex: theme.zIndices.cacheSpinner, }, ":has(> .stPageLink)": { marginTop: `-${theme.spacing.xs}`, marginBottom: `-${theme.spacing.xs}`, }, ...(isStale && elementType !== "skeleton" && STALE_STYLES), ...(elementType === "empty" ? { // Use display: none for empty elements to avoid the flexbox gap. display: "none", } : {}), ...(elementType === "space" ? { // Space elements should have minimal cross-axis dimensions. // The FlexContext logic in StyledElementContainerLayoutWrapper handles // the primary dimension (width for horizontal, height for vertical). minWidth: 0, minHeight: 0, } : {}), ...(GLOBAL_ELEMENTS.includes(elementType) ? { // Global elements are rendered in their delta position, but they // are not part of the flexbox layout. We apply a negative margin // to remove the flexbox gap. display: none does not work for these, // since they needs to be visible. marginBottom: `-${theme.spacing.lg}`, } : {}), }) ) interface StyledColumnProps { weight: number gap: streamlit.GapSize | undefined showBorder: boolean verticalAlignment?: BlockProto.Column.VerticalAlignment } export const StyledColumn = styled.div<StyledColumnProps>( ({ theme, weight, gap, showBorder, verticalAlignment }) => { const { VerticalAlignment } = BlockProto.Column const percentage = weight * 100 const gapWidth = translateGapWidth(gap, theme) const width = gapWidth === theme.spacing.none ? `${percentage}%` : `calc(${percentage}% - ${gapWidth})` return { // Calculate width based on percentage, but fill all available space, // e.g. if it overflows to next row. width, flex: `1 1 ${width}`, [`@media (max-width: ${theme.breakpoints.columns})`]: { minWidth: `calc(100% - ${theme.spacing.twoXL})`, }, ...(verticalAlignment === VerticalAlignment.BOTTOM && { marginTop: "auto", // Add margin to the first checkbox/toggle within the column to align it // better with other input widgets. [`& ${StyledElementContainer}:last-of-type > ${StyledCheckbox}`]: { marginBottom: theme.spacing.sm, }, }), ...(verticalAlignment === VerticalAlignment.TOP && { // Add margin to the first checkbox/toggle within the column to align it // better with other input widgets. [`& ${StyledElementContainer}:first-of-type > ${StyledCheckbox}`]: { marginTop: theme.spacing.sm, }, }), ...(verticalAlignment === VerticalAlignment.CENTER && { marginTop: "auto", marginBottom: "auto", }), ...(showBorder && { border: `${theme.sizes.borderWidth} solid ${theme.colors.borderColor}`, borderRadius: theme.radii.default, padding: `calc(${theme.spacing.lg} - ${theme.sizes.borderWidth})`, }), } } ) const getAlignItems = ( align: BlockProto.FlexContainer.Align | undefined | null ): CSSProperties["alignItems"] => { switch (align) { case BlockProto.FlexContainer.Align.ALIGN_START: return "start" case BlockProto.FlexContainer.Align.ALIGN_CENTER: return "center" case BlockProto.FlexContainer.Align.ALIGN_END: return "end" case BlockProto.FlexContainer.Align.STRETCH: return "stretch" case BlockProto.FlexContainer.Align.ALIGN_UNDEFINED: case undefined: case null: return "stretch" default: assertNever(align) } } const getJustifyContent = ( justify: BlockProto.FlexContainer.Justify | undefined | null ): CSSProperties["justifyContent"] => { switch (justify) { case BlockProto.FlexContainer.Justify.JUSTIFY_START: return "start" case BlockProto.FlexContainer.Justify.JUSTIFY_CENTER: return "center" case BlockProto.FlexContainer.Justify.JUSTIFY_END: return "end" case BlockProto.FlexContainer.Justify.SPACE_BETWEEN: return "space-between" case BlockProto.FlexContainer.Justify.JUSTIFY_UNDEFINED: case undefined: case null: return "start" default: assertNever(justify) } } export interface StyledFlexContainerBlockProps { direction: React.CSSProperties["flexDirection"] gap?: streamlit.GapSize | undefined flex?: React.CSSProperties["flex"] // This marks the prop as a transient property so it is // not passed to the DOM. It overlaps with a valid attribute // so passing it to the DOM will cause an error in the console. $wrap?: boolean height?: React.CSSProperties["height"] border: boolean align?: BlockProto.FlexContainer.Align | null justify?: BlockProto.FlexContainer.Justify | null overflow?: React.CSSProperties["overflow"] } export const StyledFlexContainerBlock = styled.div<StyledFlexContainerBlockProps>( ({ theme, direction, gap, flex, $wrap, height, border, align, justify, overflow, }) => { let gapWidth if (gap !== undefined) { gapWidth = translateGapWidth(gap, theme) } return { display: "flex", gap: gapWidth, width: "100%", maxWidth: "100%", height: height ?? "auto", minWidth: "1rem", flexDirection: direction, flex, alignItems: getAlignItems(align), justifyContent: getJustifyContent(justify), flexWrap: $wrap ? "wrap" : "nowrap", ...(border && { border: `${theme.sizes.borderWidth} solid ${theme.colors.borderColor}`, borderRadius: theme.radii.default, padding: `calc(${theme.spacing.lg} - ${theme.sizes.borderWidth})`, }), overflow, } } ) export interface StyledLayoutWrapperProps { width?: React.CSSProperties["width"] height?: React.CSSProperties["height"] flex?: React.CSSProperties["flex"] } export const StyledLayoutWrapper = styled.div<StyledLayoutWrapperProps>( ({ width, height, flex }) => ({ display: "flex", // This shouldn't matter since this is a wrapper and should only have one child. // However, adding it here to be explicit. flexDirection: "column", width, maxWidth: "100%", minWidth: "1rem", height, flex, }) )