1
0
Fork 0
forked from OpenNeo/impress

Add wardrobe-2020 outfit preview to item pages

Eyyy tasty! There were some issues with conflicting styles with the main app, but I think we got it!

Scoping Chakra's CSS reset was a big deal to not accidentally overwrite the app's own styles lol, and we had to solve a specificity problem for that, thanks Aria for the :where tip!! <3
This commit is contained in:
Emi Matchu 2023-08-10 19:58:22 -07:00
parent 2eb8a7cd60
commit 9cc45f0988
7 changed files with 311 additions and 48 deletions

View file

@ -91,6 +91,8 @@ input[type=text], input[type=password], input[type=search], input[type=number],
textarea textarea
font: inherit font: inherit
// TODO: This conflicts with button styles in embedded wardrobe-2020
// components. It'd be nice to not apply it to ALL button elements.
a.button, input[type=submit], button a.button, input[type=submit], button
+awesome-button +awesome-button
&.loud &.loud

View file

@ -53,7 +53,7 @@
&:hover &:hover
color: #fff color: #fff
&:active &:active
top: 1px transform: translateY(1px)
=reset-awesome-button =reset-awesome-button
+border-radius(0) +border-radius(0)

View file

@ -0,0 +1,13 @@
import React from "react";
import ReactDOM from "react-dom";
import { AppProvider, ItemPageOutfitPreview } from "./wardrobe-2020";
const rootNode = document.querySelector("#outfit-preview-root");
const itemId = rootNode.getAttribute("data-item-id");
ReactDOM.render(
<AppProvider>
<ItemPageOutfitPreview itemId={itemId} />
</AppProvider>,
rootNode
);

View file

@ -2,32 +2,14 @@ import React from "react";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import { Integrations } from "@sentry/tracing"; import { Integrations } from "@sentry/tracing";
import { Auth0Provider } from "@auth0/auth0-react"; import { Auth0Provider } from "@auth0/auth0-react";
import { CSSReset, ChakraProvider, extendTheme } from "@chakra-ui/react"; import { ChakraProvider, Box } from "@chakra-ui/react";
import { ApolloProvider } from "@apollo/client"; import { ApolloProvider } from "@apollo/client";
import { useAuth0 } from "@auth0/auth0-react"; import { useAuth0 } from "@auth0/auth0-react";
import { mode } from "@chakra-ui/theme-tools";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { Global } from "@emotion/react";
import buildApolloClient from "./apolloClient"; import buildApolloClient from "./apolloClient";
const theme = extendTheme({
styles: {
global: (props) => ({
html: {
// HACK: Chakra sets body as the relative position element, which is
// fine, except its `min-height: 100%` doesn't actually work
// unless paired with height on the root element too!
height: "100%",
},
body: {
background: mode("gray.50", "gray.800")(props),
color: mode("green.800", "green.50")(props),
transition: "all 0.25s",
},
}),
},
});
export default function AppProvider({ children }) { export default function AppProvider({ children }) {
React.useEffect(() => setupLogging(), []); React.useEffect(() => setupLogging(), []);
@ -45,9 +27,8 @@ export default function AppProvider({ children }) {
scope="" scope=""
> >
<DTIApolloProvider> <DTIApolloProvider>
<ChakraProvider theme={theme}> <ChakraProvider resetCSS={false}>
<CSSReset /> <ScopedCSSReset>{children}</ScopedCSSReset>
{children}
</ChakraProvider> </ChakraProvider>
</DTIApolloProvider> </DTIApolloProvider>
</Auth0Provider> </Auth0Provider>
@ -155,3 +136,288 @@ function setupLogging() {
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
}); });
} }
/**
* ScopedCSSReset applies a copy of Chakra UI's CSS reset, but only to its
* children (or, well, any element with the chakra-css-reset class).
*
* TODO: What about Chakra's portal elements like toast messages, which are
* intentionally mounted elsewhere in the document?
*
* NOTE: We use the `:where` CSS selector, instead of the .chakra-css-reset
* selector directly, to avoid specificity conflicts. e.g. the selector
* `.chakra-css-reset h1` is considered MORE specific than `.my-h1`, whereas
* the selector `:where(.chakra-css-reset) h1` is lower specificity.
*/
function ScopedCSSReset({ children }) {
return (
<>
<Box className="chakra-css-reset">{children}</Box>
<Global
styles={`
:where(.chakra-css-reset) {
*,
*::before,
*::after {
border-width: 0;
border-style: solid;
box-sizing: border-box;
}
main {
display: block;
}
hr {
border-top-width: 1px;
box-sizing: content-box;
height: 0;
overflow: visible;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 1em;
}
a {
background-color: transparent;
color: inherit;
text-decoration: inherit;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
b,
strong {
font-weight: bold;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
img {
border-style: none;
}
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
fieldset {
padding: 0.35em 0.75em 0.625em;
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
progress {
vertical-align: baseline;
}
textarea {
overflow: auto;
}
[type="checkbox"],
[type="radio"] {
box-sizing: border-box;
padding: 0;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none !important;
}
input[type="number"] {
-moz-appearance: textfield;
}
[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none !important;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
details {
display: block;
}
summary {
display: list-item;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
body,
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
button {
background: transparent;
padding: 0;
}
fieldset {
margin: 0;
padding: 0;
}
ol,
ul {
margin: 0;
padding: 0;
}
textarea {
resize: vertical;
}
button,
[role="button"] {
cursor: pointer;
}
button::-moz-focus-inner {
border: 0 !important;
}
table {
border-collapse: collapse;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
button,
input,
optgroup,
select,
textarea {
padding: 0;
line-height: inherit;
color: inherit;
}
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
}
img,
video {
max-width: 100%;
height: auto;
}
[data-js-focus-visible] :focus:not([data-focus-visible-added]) {
outline: none;
box-shadow: none;
}
select::-ms-expand {
display: none;
}
}
`}
/>
</>
);
}

View file

@ -759,7 +759,7 @@ function IconCheckbox({ icon, isChecked, ...props }) {
); );
} }
function ItemPageOutfitPreview({ itemId }) { export function ItemPageOutfitPreview({ itemId }) {
const idealPose = React.useMemo( const idealPose = React.useMemo(
() => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"), () => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"),
[] []
@ -1018,6 +1018,7 @@ function ItemPageOutfitPreview({ itemId }) {
rowGap="4" rowGap="4"
columnGap="6" columnGap="6"
justifyContent="center" justifyContent="center"
width="100%"
> >
<AspectRatio <AspectRatio
gridArea="preview" gridArea="preview"

View file

@ -1,4 +1,5 @@
import AppProvider from "./AppProvider"; import AppProvider from "./AppProvider";
import { ItemPageOutfitPreview } from "./ItemPage";
import WardrobePage from "./WardrobePage"; import WardrobePage from "./WardrobePage";
export { AppProvider, WardrobePage }; export { AppProvider, ItemPageOutfitPreview, WardrobePage };

View file

@ -69,16 +69,7 @@
%span.more= t '.trading_closet_hangers.show_more' %span.more= t '.trading_closet_hangers.show_more'
%span.less= t '.trading_closet_hangers.show_less' %span.less= t '.trading_closet_hangers.show_less'
#item-preview-header #outfit-preview-root{'data-item-id': @item.id}
%h3= t '.preview.header'
= link_to t('.preview.customize_more'), root_path, :id => 'customize-more',
:class => 'button'
#item-preview
%ul#item-preview-species{'data-supported-species-ids' => @supported_species_ids.join(',')}
= standard_species_images_for(@basic_colored_pet_types_by_species_id)
#item-preview-error
#item-preview-swf= t '.preview.requirements_not_met'
- unless @contributors_with_counts.empty? - unless @contributors_with_counts.empty?
#item-contributors #item-contributors
@ -88,17 +79,6 @@
%li= link_to(contributor.name, user_contributions_path(contributor)) + format_contribution_count(count) %li= link_to(contributor.name, user_contributions_path(contributor)) + format_contribution_count(count)
%footer= t '.contributors.footer' %footer= t '.contributors.footer'
:javascript
var CURRENT_ITEM_ZONES_RESTRICT = #{@item.zones_restrict.inspect},
IMPRESS_HOST = #{RemoteImpressHost.inspect};
%script#swf-assets-not-found-template{:type => 'text/x-jquery-tmpl'}
= twl '.preview.not_found', :item_name => h(@item.name),
:species_name => '${species_name}',
:color_name => '${color_name}',
:modeling_link_url => root_path
- content_for :javascripts do - content_for :javascripts do
= include_javascript_libraries :jquery, :swfobject, :jquery_tmpl = javascript_include_tag 'item-page', defer: true
= javascript_include_tag 'items/show'