diff --git a/.env.example b/.env.example index 2f79012..9a50945 100644 --- a/.env.example +++ b/.env.example @@ -3,13 +3,13 @@ SECRET_KEY=django-insecure-change-me DEBUG=True ALLOWED_HOSTS=localhost,127.0.0.1 -# MySQL (opcional, si MYSQL_ENABLED=False se usa SQLite) -MYSQL_ENABLED=False -MYSQL_DATABASE=tienda -MYSQL_USER=tienda_user -MYSQL_PASSWORD= -MYSQL_HOST=127.0.0.1 -MYSQL_PORT=3306 +# PostgreSQL (por defecto habilitado; si POSTGRES_ENABLED=False se usa SQLite) +POSTGRES_ENABLED=True +POSTGRES_DB=tienda +POSTGRES_USER=tienda_user +POSTGRES_PASSWORD= +POSTGRES_HOST=127.0.0.1 +POSTGRES_PORT=5432 # Redis REDIS_URL=redis://127.0.0.1:6379/1 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2d61140..b3470d7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -27,10 +27,7 @@ Templates use Django's inheritance pattern: - **Collection directory**: `STATIC_ROOT = BASE_DIR / 'staticfiles'` for production - **Development dirs**: `STATICFILES_DIRS` includes `tienda/static/` - **URL prefix**: `STATIC_URL = 'static/'` -- **SCSS compilation**: Styles are authored in [tienda/static/scss/custom.scss](tienda/static/scss/custom.scss) and compiled to [tienda/static/css/custom.css](tienda/static/css/custom.css) - - **IMPORTANT**: Always edit `custom.scss`, never `custom.css` - CSS file is auto-generated - - Compile with Sass compiler (npm sass or similar) -- Bootstrap CSS is vendored in `tienda/static/css/styles.css` - don't add CDN links +- **Custom CSS**: Edit [tienda/static/css/custom.css](tienda/static/css/custom.css) directly - no compilation needed ## Media Files (User Uploads) Configuration - **Storage location**: `MEDIA_ROOT = BASE_DIR / 'tienda' / 'static' / 'media'` - all uploads go here diff --git a/Dockerfile b/Dockerfile index 90766f0..acf9d2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,28 @@ -FROM python:3.14-slim +FROM python:3.14-alpine ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 -ENV DEBIANFRONTEND=noninteractive WORKDIR /app -RUN apt update && apt install -y build-essential figlet libpq-dev default-libmysqlclient-dev pkg-config && rm -rf /var/lib/apt/lists/* +RUN adduser -D -S app COPY requirements.txt /app/ -RUN pip install --no-cache-dir -r requirements.txt +RUN apk --no-cache update && apk --no-cache upgrade +RUN apk add --no-cache --virtual .build-deps build-base mariadb-dev libffi-dev \ + && apk add --no-cache mariadb-connector-c \ + && pip install --no-cache-dir -r requirements.txt \ + && apk del .build-deps COPY . /app/ RUN chmod +x /app/entrypoint.sh + EXPOSE 8000 RUN mkdir -pv /fonts COPY tienda/static/fonts/ /fonts/ -CMD ["/app/entrypoint.sh"] \ No newline at end of file + +RUN chown -R app: /app /fonts +RUN chmod 770 /app/entrypoint.sh +USER app + +ENTRYPOINT ["/bin/sh", "/app/entrypoint.sh"] \ No newline at end of file diff --git a/__pycache__/test_paypal.cpython-314.pyc b/__pycache__/test_paypal.cpython-314.pyc new file mode 100644 index 0000000..bc3a279 Binary files /dev/null and b/__pycache__/test_paypal.cpython-314.pyc differ diff --git a/__pycache__/test_product_cache.cpython-314.pyc b/__pycache__/test_product_cache.cpython-314.pyc new file mode 100644 index 0000000..54b98ca Binary files /dev/null and b/__pycache__/test_product_cache.cpython-314.pyc differ diff --git a/entrypoint.sh b/entrypoint.sh index e39956a..7fdb705 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,6 +1,5 @@ -#!/bin/bash +#!/bin/sh -figlet Proyecto MVC echo "Sleeping due to mysql..." sleep 10 echo "Running DB migrations..." diff --git a/manage.py b/manage.py index b3419aa..d26a70a 100755 --- a/manage.py +++ b/manage.py @@ -8,6 +8,13 @@ import sys def main(): """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "proyecto.settings") + + # Si se ejecuta `manage.py test` sin labels, limitar a pruebas de tienda/tests.py. + if len(sys.argv) >= 2 and sys.argv[1] == "test": + has_test_labels = any(not arg.startswith("-") for arg in sys.argv[2:]) + if not has_test_labels: + sys.argv.append("tienda.tests") + try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/proyecto/settings.py b/proyecto/settings.py index 1c9c39f..9eac4f9 100644 --- a/proyecto/settings.py +++ b/proyecto/settings.py @@ -15,6 +15,9 @@ import os, sys from pathlib import Path +RUNNING_TESTS = any(arg in {'test', 'pytest'} for arg in sys.argv) or 'PYTEST_CURRENT_TEST' in os.environ + + def load_dotenv(dotenv_path: Path) -> None: if not dotenv_path.exists(): return @@ -126,21 +129,24 @@ WSGI_APPLICATION = 'proyecto.wsgi.application' # Database # https://docs.djangoproject.com/en/6.0/ref/settings/#databases -# Activa MySQL con MYSQL_ENABLED=True en el .env; si no, se usa SQLite. +# Usa PostgreSQL por defecto (POSTGRES_ENABLED=True); si no, SQLite. -if env_bool('MYSQL_ENABLED', False): +if RUNNING_TESTS: DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': os.getenv('MYSQL_DATABASE', 'tienda'), - 'USER': os.getenv('MYSQL_USER', 'root'), - 'PASSWORD': os.getenv('MYSQL_PASSWORD', ''), - 'HOST': os.getenv('MYSQL_HOST', '127.0.0.1'), - 'PORT': env_int('MYSQL_PORT', 3306), - 'OPTIONS': { - 'charset': 'utf8mb4', - 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", - }, + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } +elif env_bool('POSTGRES_ENABLED', True): + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('POSTGRES_DB', 'tienda'), + 'USER': os.getenv('POSTGRES_USER', 'postgres'), + 'PASSWORD': os.getenv('POSTGRES_PASSWORD', ''), + 'HOST': os.getenv('POSTGRES_HOST', '127.0.0.1'), + 'PORT': env_int('POSTGRES_PORT', 5432), } } else: @@ -207,16 +213,7 @@ STATICFILES_FINDERS = [ 'compressor.finders.CompressorFinder' ] -COMPRESS_PRECOMPILERS = ( - ('text/x-scss', 'sass {infile} {outfile} --load-path=tienda/static/scss'), -) - -import shutil -SASS_BINARY = shutil.which('sass') -if SASS_BINARY: - COMPRESS_PRECOMPILERS = ( - ('text/x-scss', f'{SASS_BINARY} {{infile}} {{outfile}} --load-path=tienda/static/scss'), - ) +COMPRESS_PRECOMPILERS = () # Media files (User uploads) MEDIA_URL = 'media/' @@ -267,7 +264,7 @@ SECURITY = os.getenv('SECURITY', 'tls') SMTP_USERNAME = os.getenv('SMTP_USERNAME', None) SMTP_PASSWORD = os.getenv('SMTP_PASSWORD', None) SMTP_EMAIL = os.getenv("SMTP_EMAIL", None) -if SMTP_USERNAME is None or SMTP_PASSWORD is None or SMTP_EMAIL is None: +if not RUNNING_TESTS and (SMTP_USERNAME is None or SMTP_PASSWORD is None or SMTP_EMAIL is None): print("Se requieren credenciales SMTP") sys.exit(1) @@ -354,7 +351,11 @@ logging.captureWarnings(True) -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +if RUNNING_TESTS: + EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' +else: + EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_HOST = SMTP_ENDPOINT EMAIL_PORT = SMTP_PORT EMAIL_USE_TLS = (SECURITY == 'tls') # True si SECURITY es 'tls' diff --git a/requirements.txt b/requirements.txt index 0f5f3e8..4b6da18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,28 +9,26 @@ click==8.3.1 click-didyoumean==0.3.1 click-plugins==1.1.1.2 click-repl==0.3.0 -cryptography==46.0.4 -Django==6.0.1 +cryptography==46.0.7 +Django==6.0.4 django-appconf==1.2.0 -django-libsass==0.9 django-redis==5.4.0 django_compressor==4.6.0 gunicorn==25.1.0 idna==3.11 Jinja2==3.1.6 kombu==5.6.2 -libsass==0.23.0 MarkupSafe==3.0.3 packaging==26.0 paypalrestsdk==1.13.3 -pillow==12.1.0 +pillow==12.2.0 prompt_toolkit==3.0.52 pycparser==3.0 -pyOpenSSL==25.3.0 +pyOpenSSL==26.0.0 python-dateutil==2.9.0.post0 rcssmin==1.2.2 redis==5.2.1 -requests==2.32.5 +requests==2.33.0 rjsmin==1.2.5 six==1.17.0 sqlparse==0.5.5 @@ -42,5 +40,5 @@ urllib3==2.6.3 vine==5.1.0 wcwidth==0.6.0 whitenoise==6.12.0 -mysqlclient -fpdf2==2.8.7 \ No newline at end of file +fpdf2==2.8.7 +psycopg2-binary==2.9.11 \ No newline at end of file diff --git a/test_paypal.py b/test_paypal.py index c3e39da..fe056d0 100644 --- a/test_paypal.py +++ b/test_paypal.py @@ -7,98 +7,103 @@ import os import sys import django -# Configurar Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proyecto.settings') -sys.path.insert(0, os.path.dirname(__file__)) -django.setup() +def main() -> None: + # Configurar Django solo cuando se ejecuta script manualmente. + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proyecto.settings') + sys.path.insert(0, os.path.dirname(__file__)) + django.setup() -from django.conf import settings + from django.conf import settings -print("=" * 60) -print("TEST DE CONFIGURACIÓN DE PAYPAL") -print("=" * 60) + print("=" * 60) + print("TEST DE CONFIGURACIÓN DE PAYPAL") + print("=" * 60) -# Verificar configuración -print("\n1. Verificando configuración en settings.py:") -print(f" PAYPAL_MODE: {settings.PAYPAL_MODE}") -print(f" PAYPAL_CLIENT_ID: {settings.PAYPAL_CLIENT_ID[:20]}..." if settings.PAYPAL_CLIENT_ID else " ❌ NO CONFIGURADO") -print(f" PAYPAL_CLIENT_SECRET: {settings.PAYPAL_CLIENT_SECRET[:20]}..." if settings.PAYPAL_CLIENT_SECRET else " ❌ NO CONFIGURADO") + # Verificar configuración + print("\n1. Verificando configuración en settings.py:") + print(f" PAYPAL_MODE: {settings.PAYPAL_MODE}") + print(f" PAYPAL_CLIENT_ID: {settings.PAYPAL_CLIENT_ID[:20]}..." if settings.PAYPAL_CLIENT_ID else " ❌ NO CONFIGURADO") + print(f" PAYPAL_CLIENT_SECRET: {settings.PAYPAL_CLIENT_SECRET[:20]}..." if settings.PAYPAL_CLIENT_SECRET else " ❌ NO CONFIGURADO") -# Intentar importar paypalrestsdk -print("\n2. Verificando SDK de PayPal:") -try: - import paypalrestsdk - print(" ✓ paypalrestsdk importado correctamente") - print(f" Versión: {paypalrestsdk.__version__ if hasattr(paypalrestsdk, '__version__') else 'Desconocida'}") -except ImportError as e: - print(f" ❌ Error: {e}") - print(" SOLUCIÓN: pip install paypalrestsdk") - sys.exit(1) + # Intentar importar paypalrestsdk + print("\n2. Verificando SDK de PayPal:") + try: + import paypalrestsdk + print(" ✓ paypalrestsdk importado correctamente") + print(f" Versión: {paypalrestsdk.__version__ if hasattr(paypalrestsdk, '__version__') else 'Desconocida'}") + except ImportError as e: + print(f" ❌ Error: {e}") + print(" SOLUCIÓN: pip install paypalrestsdk") + sys.exit(1) -# Intentar conectar a PayPal -print("\n3. Probando conexión a PayPal:") -try: - paypalrestsdk.configure({ - "mode": settings.PAYPAL_MODE, - "client_id": settings.PAYPAL_CLIENT_ID, - "client_secret": settings.PAYPAL_CLIENT_SECRET - }) - print(" ✓ Configuración de PayPal aplicada") - - # Intentar crear un pago de prueba - print("\n4. Creando pago de prueba:") - test_payment = paypalrestsdk.Payment({ - "intent": "sale", - "payer": { - "payment_method": "paypal" - }, - "redirect_urls": { - "return_url": "http://localhost:8000/test-return", - "cancel_url": "http://localhost:8000/test-cancel" - }, - "transactions": [ - { - "amount": { - "total": "10.00", - "currency": "EUR", - "details": { - "subtotal": "10.00", - "tax": "0", - "shipping": "0" - } - }, - "description": "Pago de prueba", - "item_list": { - "items": [ - { - "name": "Test Item", - "sku": "test_1", - "price": "10.00", - "currency": "EUR", - "quantity": 1 + # Intentar conectar a PayPal + print("\n3. Probando conexión a PayPal:") + try: + paypalrestsdk.configure({ + "mode": settings.PAYPAL_MODE, + "client_id": settings.PAYPAL_CLIENT_ID, + "client_secret": settings.PAYPAL_CLIENT_SECRET + }) + print(" ✓ Configuración de PayPal aplicada") + + # Intentar crear un pago de prueba + print("\n4. Creando pago de prueba:") + test_payment = paypalrestsdk.Payment({ + "intent": "sale", + "payer": { + "payment_method": "paypal" + }, + "redirect_urls": { + "return_url": "http://localhost:8000/test-return", + "cancel_url": "http://localhost:8000/test-cancel" + }, + "transactions": [ + { + "amount": { + "total": "10.00", + "currency": "EUR", + "details": { + "subtotal": "10.00", + "tax": "0", + "shipping": "0" } - ] + }, + "description": "Pago de prueba", + "item_list": { + "items": [ + { + "name": "Test Item", + "sku": "test_1", + "price": "10.00", + "currency": "EUR", + "quantity": 1 + } + ] + } } - } - ] - }) - - if test_payment.create(): - print(" ✓ Pago creado exitosamente") - print(f" Payment ID: {test_payment.id}") - for link in test_payment.links: - if link.rel == "approval_url": - print(f" URL de aprobación: {link.href}") - else: - print(" ❌ Error al crear el pago:") - if hasattr(test_payment, 'error') and test_payment.error: - print(f" {test_payment.error}") - -except Exception as e: - print(f" ❌ Error de conexión: {e}") - import traceback - traceback.print_exc() + ] + }) -print("\n" + "=" * 60) -print("TEST COMPLETADO") -print("=" * 60) + if test_payment.create(): + print(" ✓ Pago creado exitosamente") + print(f" Payment ID: {test_payment.id}") + for link in test_payment.links: + if link.rel == "approval_url": + print(f" URL de aprobación: {link.href}") + else: + print(" ❌ Error al crear el pago:") + if hasattr(test_payment, 'error') and test_payment.error: + print(f" {test_payment.error}") + + except Exception as e: + print(f" ❌ Error de conexión: {e}") + import traceback + traceback.print_exc() + + print("\n" + "=" * 60) + print("TEST COMPLETADO") + print("=" * 60) + + +if __name__ == '__main__': + main() diff --git a/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc b/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc new file mode 100644 index 0000000..407f3a4 Binary files /dev/null and b/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc b/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc new file mode 100644 index 0000000..cdafd5e Binary files /dev/null and b/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc differ diff --git a/tienda/migrations/__pycache__/0003_order_transaction_code.cpython-314.pyc b/tienda/migrations/__pycache__/0003_order_transaction_code.cpython-314.pyc new file mode 100644 index 0000000..aeecff1 Binary files /dev/null and b/tienda/migrations/__pycache__/0003_order_transaction_code.cpython-314.pyc differ diff --git a/tienda/migrations/__pycache__/0004_product_stock_stockreservation_stockreservationitem.cpython-314.pyc b/tienda/migrations/__pycache__/0004_product_stock_stockreservation_stockreservationitem.cpython-314.pyc new file mode 100644 index 0000000..ac75c7e Binary files /dev/null and b/tienda/migrations/__pycache__/0004_product_stock_stockreservation_stockreservationitem.cpython-314.pyc differ diff --git a/tienda/migrations/__pycache__/__init__.cpython-314.pyc b/tienda/migrations/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..a48244f Binary files /dev/null and b/tienda/migrations/__pycache__/__init__.cpython-314.pyc differ diff --git a/tienda/models.py b/tienda/models.py index ab57703..6b74a54 100644 --- a/tienda/models.py +++ b/tienda/models.py @@ -36,10 +36,20 @@ class VerificationCode(models.Model): choices = VerificationModes.choices, default = VerificationModes.VERIFY_ACCOUNT ) + + def generate(user: User, code_mode: str) -> VerificationCode: + while True: + code = "".join(random.choices(string.ascii_letters+string.digits+string.punctuation)) + if not VerificationCode.objects.filter(code=code).exists(): + return VerificationCode.objects.create( + code = code, + user = user, + code_mode = code_mode + ) # Create your models here. class Category(models.Model): - name = models.CharField(max_length=200) + name = models.CharField(max_length=200, unique=True) def __str__(self): return self.name diff --git a/tienda/static/css/custom.css.map b/tienda/static/css/custom.css.map deleted file mode 100644 index e4356e9..0000000 --- a/tienda/static/css/custom.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sourceRoot":"","sources":["../scss/custom.scss"],"names":[],"mappings":"AAuBA;EACI;IANA;IACA;;;AAUJ;EACI;IAZA;IACA;;;AAgBJ;EACI;IAlBA;IACA;;;AAsBJ;EACI,OAzCS;EA0CT;;;AAIA;EACI;EACA;EACA;;;AAKJ;EACI;EACA;EACA;;AAJR;EAMI;;;AAIA;EACI;EACA;EACA;EACA;;AACA;EACI;EACA;;;AAMZ;EACI;EACA;;AAEA;EACI;;AAGJ;EACI;;AAGJ;EACI;;AAEA;EACI;;AAIR;EACI;;AAGJ;EACI;EACA;;AAEA;EACI;EACA;;AAIR;EACI;EACA;;AAEA;EACI;EACA;;AAIR;EACI;EACA;EACA;;AAEA;EACI;EACA;;;AAMZ;EACI;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;EAIR;IACI;IACA;;EAEA;IACI;IACA;;EAIR;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;IACA;;EAIR;IACI;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;;EAEA;IACI;;;AAKZ;EACI;IACI;;EAGJ;IACI;;EAGJ;IACI;;;AAMJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAGJ;EACI;EACA;EACA;;AAGJ;EACI;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;;AAIR;EACI;;;AAKZ;EACI;EACA;;;AAKA;EACI;EACA;;AAGJ;EACI;;AAEA;EACI;EACA;EACA","file":"custom.css"} \ No newline at end of file diff --git a/tienda/static/scss/bootstrap/_accordion.scss b/tienda/static/scss/bootstrap/_accordion.scss deleted file mode 100644 index fc62ceb..0000000 --- a/tienda/static/scss/bootstrap/_accordion.scss +++ /dev/null @@ -1,118 +0,0 @@ -// -// Base styles -// - -.accordion-button { - position: relative; - display: flex; - align-items: center; - width: 100%; - padding: $accordion-button-padding-y $accordion-button-padding-x; - @include font-size($font-size-base); - color: $accordion-button-color; - text-align: left; // Reset button style - background-color: $accordion-button-bg; - border: 0; - @include border-radius(0); - overflow-anchor: none; - @include transition($accordion-transition); - - &:not(.collapsed) { - color: $accordion-button-active-color; - background-color: $accordion-button-active-bg; - box-shadow: inset 0 ($accordion-border-width * -1) 0 $accordion-border-color; - - &::after { - background-image: escape-svg($accordion-button-active-icon); - transform: $accordion-icon-transform; - } - } - - // Accordion icon - &::after { - flex-shrink: 0; - width: $accordion-icon-width; - height: $accordion-icon-width; - margin-left: auto; - content: ""; - background-image: escape-svg($accordion-button-icon); - background-repeat: no-repeat; - background-size: $accordion-icon-width; - @include transition($accordion-icon-transition); - } - - &:hover { - z-index: 2; - } - - &:focus { - z-index: 3; - border-color: $accordion-button-focus-border-color; - outline: 0; - box-shadow: $accordion-button-focus-box-shadow; - } -} - -.accordion-header { - margin-bottom: 0; -} - -.accordion-item { - background-color: $accordion-bg; - border: $accordion-border-width solid $accordion-border-color; - - &:first-of-type { - @include border-top-radius($accordion-border-radius); - - .accordion-button { - @include border-top-radius($accordion-inner-border-radius); - } - } - - &:not(:first-of-type) { - border-top: 0; - } - - // Only set a border-radius on the last item if the accordion is collapsed - &:last-of-type { - @include border-bottom-radius($accordion-border-radius); - - .accordion-button { - &.collapsed { - @include border-bottom-radius($accordion-inner-border-radius); - } - } - - .accordion-collapse { - @include border-bottom-radius($accordion-border-radius); - } - } -} - -.accordion-body { - padding: $accordion-body-padding-y $accordion-body-padding-x; -} - - -// Flush accordion items -// -// Remove borders and border-radius to keep accordion items edge-to-edge. - -.accordion-flush { - .accordion-collapse { - border-width: 0; - } - - .accordion-item { - border-right: 0; - border-left: 0; - @include border-radius(0); - - &:first-child { border-top: 0; } - &:last-child { border-bottom: 0; } - - .accordion-button { - @include border-radius(0); - } - } -} diff --git a/tienda/static/scss/bootstrap/_alert.scss b/tienda/static/scss/bootstrap/_alert.scss deleted file mode 100644 index 34f1e84..0000000 --- a/tienda/static/scss/bootstrap/_alert.scss +++ /dev/null @@ -1,57 +0,0 @@ -// -// Base styles -// - -.alert { - position: relative; - padding: $alert-padding-y $alert-padding-x; - margin-bottom: $alert-margin-bottom; - border: $alert-border-width solid transparent; - @include border-radius($alert-border-radius); -} - -// Headings for larger alerts -.alert-heading { - // Specified to prevent conflicts of changing $headings-color - color: inherit; -} - -// Provide class for links that match alerts -.alert-link { - font-weight: $alert-link-font-weight; -} - - -// Dismissible alerts -// -// Expand the right padding and account for the close button's positioning. - -.alert-dismissible { - padding-right: $alert-dismissible-padding-r; - - // Adjust close link position - .btn-close { - position: absolute; - top: 0; - right: 0; - z-index: $stretched-link-z-index + 1; - padding: $alert-padding-y * 1.25 $alert-padding-x; - } -} - - -// scss-docs-start alert-modifiers -// Generate contextual modifier classes for colorizing the alert. - -@each $state, $value in $theme-colors { - $alert-background: shift-color($value, $alert-bg-scale); - $alert-border: shift-color($value, $alert-border-scale); - $alert-color: shift-color($value, $alert-color-scale); - @if (contrast-ratio($alert-background, $alert-color) < $min-contrast-ratio) { - $alert-color: mix($value, color-contrast($alert-background), abs($alert-color-scale)); - } - .alert-#{$state} { - @include alert-variant($alert-background, $alert-border, $alert-color); - } -} -// scss-docs-end alert-modifiers diff --git a/tienda/static/scss/bootstrap/_badge.scss b/tienda/static/scss/bootstrap/_badge.scss deleted file mode 100644 index 08df1b8..0000000 --- a/tienda/static/scss/bootstrap/_badge.scss +++ /dev/null @@ -1,29 +0,0 @@ -// Base class -// -// Requires one of the contextual, color modifier classes for `color` and -// `background-color`. - -.badge { - display: inline-block; - padding: $badge-padding-y $badge-padding-x; - @include font-size($badge-font-size); - font-weight: $badge-font-weight; - line-height: 1; - color: $badge-color; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - @include border-radius($badge-border-radius); - @include gradient-bg(); - - // Empty badges collapse automatically - &:empty { - display: none; - } -} - -// Quick fix for badges in buttons -.btn .badge { - position: relative; - top: -1px; -} diff --git a/tienda/static/scss/bootstrap/_breadcrumb.scss b/tienda/static/scss/bootstrap/_breadcrumb.scss deleted file mode 100644 index f7fafe7..0000000 --- a/tienda/static/scss/bootstrap/_breadcrumb.scss +++ /dev/null @@ -1,28 +0,0 @@ -.breadcrumb { - display: flex; - flex-wrap: wrap; - padding: $breadcrumb-padding-y $breadcrumb-padding-x; - margin-bottom: $breadcrumb-margin-bottom; - @include font-size($breadcrumb-font-size); - list-style: none; - background-color: $breadcrumb-bg; - @include border-radius($breadcrumb-border-radius); -} - -.breadcrumb-item { - // The separator between breadcrumbs (by default, a forward-slash: "/") - + .breadcrumb-item { - padding-left: $breadcrumb-item-padding-x; - - &::before { - float: left; // Suppress inline spacings and underlining of the separator - padding-right: $breadcrumb-item-padding-x; - color: $breadcrumb-divider-color; - content: var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; - } - } - - &.active { - color: $breadcrumb-active-color; - } -} diff --git a/tienda/static/scss/bootstrap/_button-group.scss b/tienda/static/scss/bootstrap/_button-group.scss deleted file mode 100644 index 13aa056..0000000 --- a/tienda/static/scss/bootstrap/_button-group.scss +++ /dev/null @@ -1,139 +0,0 @@ -// Make the div behave like a button -.btn-group, -.btn-group-vertical { - position: relative; - display: inline-flex; - vertical-align: middle; // match .btn alignment given font-size hack above - - > .btn { - position: relative; - flex: 1 1 auto; - } - - // Bring the hover, focused, and "active" buttons to the front to overlay - // the borders properly - > .btn-check:checked + .btn, - > .btn-check:focus + .btn, - > .btn:hover, - > .btn:focus, - > .btn:active, - > .btn.active { - z-index: 1; - } -} - -// Optional: Group multiple button groups together for a toolbar -.btn-toolbar { - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - - .input-group { - width: auto; - } -} - -.btn-group { - // Prevent double borders when buttons are next to each other - > .btn:not(:first-child), - > .btn-group:not(:first-child) { - margin-left: -$btn-border-width; - } - - // Reset rounded corners - > .btn:not(:last-child):not(.dropdown-toggle), - > .btn-group:not(:last-child) > .btn { - @include border-end-radius(0); - } - - // The left radius should be 0 if the button is: - // - the "third or more" child - // - the second child and the previous element isn't `.btn-check` (making it the first child visually) - // - part of a btn-group which isn't the first child - > .btn:nth-child(n + 3), - > :not(.btn-check) + .btn, - > .btn-group:not(:first-child) > .btn { - @include border-start-radius(0); - } -} - -// Sizing -// -// Remix the default button sizing classes into new ones for easier manipulation. - -.btn-group-sm > .btn { @extend .btn-sm; } -.btn-group-lg > .btn { @extend .btn-lg; } - - -// -// Split button dropdowns -// - -.dropdown-toggle-split { - padding-right: $btn-padding-x * .75; - padding-left: $btn-padding-x * .75; - - &::after, - .dropup &::after, - .dropend &::after { - margin-left: 0; - } - - .dropstart &::before { - margin-right: 0; - } -} - -.btn-sm + .dropdown-toggle-split { - padding-right: $btn-padding-x-sm * .75; - padding-left: $btn-padding-x-sm * .75; -} - -.btn-lg + .dropdown-toggle-split { - padding-right: $btn-padding-x-lg * .75; - padding-left: $btn-padding-x-lg * .75; -} - - -// The clickable button for toggling the menu -// Set the same inset shadow as the :active state -.btn-group.show .dropdown-toggle { - @include box-shadow($btn-active-box-shadow); - - // Show no shadow for `.btn-link` since it has no other button styles. - &.btn-link { - @include box-shadow(none); - } -} - - -// -// Vertical button groups -// - -.btn-group-vertical { - flex-direction: column; - align-items: flex-start; - justify-content: center; - - > .btn, - > .btn-group { - width: 100%; - } - - > .btn:not(:first-child), - > .btn-group:not(:first-child) { - margin-top: -$btn-border-width; - } - - // Reset rounded corners - > .btn:not(:last-child):not(.dropdown-toggle), - > .btn-group:not(:last-child) > .btn { - @include border-bottom-radius(0); - } - - > .btn ~ .btn, - > .btn-group:not(:first-child) > .btn { - @include border-top-radius(0); - } -} diff --git a/tienda/static/scss/bootstrap/_buttons.scss b/tienda/static/scss/bootstrap/_buttons.scss deleted file mode 100644 index ee4287c..0000000 --- a/tienda/static/scss/bootstrap/_buttons.scss +++ /dev/null @@ -1,111 +0,0 @@ -// -// Base styles -// - -.btn { - display: inline-block; - font-family: $btn-font-family; - font-weight: $btn-font-weight; - line-height: $btn-line-height; - color: $body-color; - text-align: center; - text-decoration: if($link-decoration == none, null, none); - white-space: $btn-white-space; - vertical-align: middle; - cursor: if($enable-button-pointers, pointer, null); - user-select: none; - background-color: transparent; - border: $btn-border-width solid transparent; - @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-border-radius); - @include transition($btn-transition); - - &:hover { - color: $body-color; - text-decoration: if($link-hover-decoration == underline, none, null); - } - - .btn-check:focus + &, - &:focus { - outline: 0; - box-shadow: $btn-focus-box-shadow; - } - - .btn-check:checked + &, - .btn-check:active + &, - &:active, - &.active { - @include box-shadow($btn-active-box-shadow); - - &:focus { - @include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow); - } - } - - &:disabled, - &.disabled, - fieldset:disabled & { - pointer-events: none; - opacity: $btn-disabled-opacity; - @include box-shadow(none); - } -} - - -// -// Alternate buttons -// - -// scss-docs-start btn-variant-loops -@each $color, $value in $theme-colors { - .btn-#{$color} { - @include button-variant($value, $value); - } -} - -@each $color, $value in $theme-colors { - .btn-outline-#{$color} { - @include button-outline-variant($value); - } -} -// scss-docs-end btn-variant-loops - - -// -// Link buttons -// - -// Make a button look and behave like a link -.btn-link { - font-weight: $font-weight-normal; - color: $btn-link-color; - text-decoration: $link-decoration; - - &:hover { - color: $btn-link-hover-color; - text-decoration: $link-hover-decoration; - } - - &:focus { - text-decoration: $link-hover-decoration; - } - - &:disabled, - &.disabled { - color: $btn-link-disabled-color; - } - - // No need for an active state here -} - - -// -// Button Sizes -// - -.btn-lg { - @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg); -} - -.btn-sm { - @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm); -} diff --git a/tienda/static/scss/bootstrap/_card.scss b/tienda/static/scss/bootstrap/_card.scss deleted file mode 100644 index b077858..0000000 --- a/tienda/static/scss/bootstrap/_card.scss +++ /dev/null @@ -1,215 +0,0 @@ -// -// Base styles -// - -.card { - position: relative; - display: flex; - flex-direction: column; - min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 - height: $card-height; - word-wrap: break-word; - background-color: $card-bg; - background-clip: border-box; - border: $card-border-width solid $card-border-color; - @include border-radius($card-border-radius); - - > hr { - margin-right: 0; - margin-left: 0; - } - - > .list-group { - border-top: inherit; - border-bottom: inherit; - - &:first-child { - border-top-width: 0; - @include border-top-radius($card-inner-border-radius); - } - - &:last-child { - border-bottom-width: 0; - @include border-bottom-radius($card-inner-border-radius); - } - } - - // Due to specificity of the above selector (`.card > .list-group`), we must - // use a child selector here to prevent double borders. - > .card-header + .list-group, - > .list-group + .card-footer { - border-top: 0; - } -} - -.card-body { - // Enable `flex-grow: 1` for decks and groups so that card blocks take up - // as much space as possible, ensuring footers are aligned to the bottom. - flex: 1 1 auto; - padding: $card-spacer-y $card-spacer-x; - color: $card-color; -} - -.card-title { - margin-bottom: $card-title-spacer-y; -} - -.card-subtitle { - margin-top: -$card-title-spacer-y * .5; - margin-bottom: 0; -} - -.card-text:last-child { - margin-bottom: 0; -} - -.card-link { - &:hover { - text-decoration: none; - } - - + .card-link { - margin-left: $card-spacer-x; - } -} - -// -// Optional textual caps -// - -.card-header { - padding: $card-cap-padding-y $card-cap-padding-x; - margin-bottom: 0; // Removes the default margin-bottom of - color: $card-cap-color; - background-color: $card-cap-bg; - border-bottom: $card-border-width solid $card-border-color; - - &:first-child { - @include border-radius($card-inner-border-radius $card-inner-border-radius 0 0); - } -} - -.card-footer { - padding: $card-cap-padding-y $card-cap-padding-x; - color: $card-cap-color; - background-color: $card-cap-bg; - border-top: $card-border-width solid $card-border-color; - - &:last-child { - @include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius); - } -} - - -// -// Header navs -// - -.card-header-tabs { - margin-right: -$card-cap-padding-x * .5; - margin-bottom: -$card-cap-padding-y; - margin-left: -$card-cap-padding-x * .5; - border-bottom: 0; - - @if $nav-tabs-link-active-bg != $card-bg { - .nav-link.active { - background-color: $card-bg; - border-bottom-color: $card-bg; - } - } -} - -.card-header-pills { - margin-right: -$card-cap-padding-x * .5; - margin-left: -$card-cap-padding-x * .5; -} - -// Card image -.card-img-overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - padding: $card-img-overlay-padding; - @include border-radius($card-inner-border-radius); -} - -.card-img, -.card-img-top, -.card-img-bottom { - width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch -} - -.card-img, -.card-img-top { - @include border-top-radius($card-inner-border-radius); -} - -.card-img, -.card-img-bottom { - @include border-bottom-radius($card-inner-border-radius); -} - - -// -// Card groups -// - -.card-group { - // The child selector allows nested `.card` within `.card-group` - // to display properly. - > .card { - margin-bottom: $card-group-margin; - } - - @include media-breakpoint-up(sm) { - display: flex; - flex-flow: row wrap; - // The child selector allows nested `.card` within `.card-group` - // to display properly. - > .card { - // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 - flex: 1 0 0%; - margin-bottom: 0; - - + .card { - margin-left: 0; - border-left: 0; - } - - // Handle rounded corners - @if $enable-rounded { - &:not(:last-child) { - @include border-end-radius(0); - - .card-img-top, - .card-header { - // stylelint-disable-next-line property-disallowed-list - border-top-right-radius: 0; - } - .card-img-bottom, - .card-footer { - // stylelint-disable-next-line property-disallowed-list - border-bottom-right-radius: 0; - } - } - - &:not(:first-child) { - @include border-start-radius(0); - - .card-img-top, - .card-header { - // stylelint-disable-next-line property-disallowed-list - border-top-left-radius: 0; - } - .card-img-bottom, - .card-footer { - // stylelint-disable-next-line property-disallowed-list - border-bottom-left-radius: 0; - } - } - } - } - } -} diff --git a/tienda/static/scss/bootstrap/_carousel.scss b/tienda/static/scss/bootstrap/_carousel.scss deleted file mode 100644 index 3d8fb15..0000000 --- a/tienda/static/scss/bootstrap/_carousel.scss +++ /dev/null @@ -1,229 +0,0 @@ -// Notes on the classes: -// -// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically) -// even when their scroll action started on a carousel, but for compatibility (with Firefox) -// we're preventing all actions instead -// 2. The .carousel-item-start and .carousel-item-end is used to indicate where -// the active slide is heading. -// 3. .active.carousel-item is the current slide. -// 4. .active.carousel-item-start and .active.carousel-item-end is the current -// slide in its in-transition state. Only one of these occurs at a time. -// 5. .carousel-item-next.carousel-item-start and .carousel-item-prev.carousel-item-end -// is the upcoming slide in transition. - -.carousel { - position: relative; -} - -.carousel.pointer-event { - touch-action: pan-y; -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; - @include clearfix(); -} - -.carousel-item { - position: relative; - display: none; - float: left; - width: 100%; - margin-right: -100%; - backface-visibility: hidden; - @include transition($carousel-transition); -} - -.carousel-item.active, -.carousel-item-next, -.carousel-item-prev { - display: block; -} - -/* rtl:begin:ignore */ -.carousel-item-next:not(.carousel-item-start), -.active.carousel-item-end { - transform: translateX(100%); -} - -.carousel-item-prev:not(.carousel-item-end), -.active.carousel-item-start { - transform: translateX(-100%); -} - -/* rtl:end:ignore */ - - -// -// Alternate transitions -// - -.carousel-fade { - .carousel-item { - opacity: 0; - transition-property: opacity; - transform: none; - } - - .carousel-item.active, - .carousel-item-next.carousel-item-start, - .carousel-item-prev.carousel-item-end { - z-index: 1; - opacity: 1; - } - - .active.carousel-item-start, - .active.carousel-item-end { - z-index: 0; - opacity: 0; - @include transition(opacity 0s $carousel-transition-duration); - } -} - - -// -// Left/right controls for nav -// - -.carousel-control-prev, -.carousel-control-next { - position: absolute; - top: 0; - bottom: 0; - z-index: 1; - // Use flex for alignment (1-3) - display: flex; // 1. allow flex styles - align-items: center; // 2. vertically center contents - justify-content: center; // 3. horizontally center contents - width: $carousel-control-width; - padding: 0; - color: $carousel-control-color; - text-align: center; - background: none; - border: 0; - opacity: $carousel-control-opacity; - @include transition($carousel-control-transition); - - // Hover/focus state - &:hover, - &:focus { - color: $carousel-control-color; - text-decoration: none; - outline: 0; - opacity: $carousel-control-hover-opacity; - } -} -.carousel-control-prev { - left: 0; - background-image: if($enable-gradients, linear-gradient(90deg, rgba($black, .25), rgba($black, .001)), null); -} -.carousel-control-next { - right: 0; - background-image: if($enable-gradients, linear-gradient(270deg, rgba($black, .25), rgba($black, .001)), null); -} - -// Icons for within -.carousel-control-prev-icon, -.carousel-control-next-icon { - display: inline-block; - width: $carousel-control-icon-width; - height: $carousel-control-icon-width; - background-repeat: no-repeat; - background-position: 50%; - background-size: 100% 100%; -} - -/* rtl:options: { - "autoRename": true, - "stringMap":[ { - "name" : "prev-next", - "search" : "prev", - "replace" : "next" - } ] -} */ -.carousel-control-prev-icon { - background-image: escape-svg($carousel-control-prev-icon-bg); -} -.carousel-control-next-icon { - background-image: escape-svg($carousel-control-next-icon-bg); -} - -// Optional indicator pips/controls -// -// Add a container (such as a list) with the following class and add an item (ideally a focusable control, -// like a button) with data-bs-target for each slide your carousel holds. - -.carousel-indicators { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 2; - display: flex; - justify-content: center; - padding: 0; - // Use the .carousel-control's width as margin so we don't overlay those - margin-right: $carousel-control-width; - margin-bottom: 1rem; - margin-left: $carousel-control-width; - list-style: none; - - [data-bs-target] { - box-sizing: content-box; - flex: 0 1 auto; - width: $carousel-indicator-width; - height: $carousel-indicator-height; - padding: 0; - margin-right: $carousel-indicator-spacer; - margin-left: $carousel-indicator-spacer; - text-indent: -999px; - cursor: pointer; - background-color: $carousel-indicator-active-bg; - background-clip: padding-box; - border: 0; - // Use transparent borders to increase the hit area by 10px on top and bottom. - border-top: $carousel-indicator-hit-area-height solid transparent; - border-bottom: $carousel-indicator-hit-area-height solid transparent; - opacity: $carousel-indicator-opacity; - @include transition($carousel-indicator-transition); - } - - .active { - opacity: $carousel-indicator-active-opacity; - } -} - - -// Optional captions -// -// - -.carousel-caption { - position: absolute; - right: (100% - $carousel-caption-width) * .5; - bottom: $carousel-caption-spacer; - left: (100% - $carousel-caption-width) * .5; - padding-top: $carousel-caption-padding-y; - padding-bottom: $carousel-caption-padding-y; - color: $carousel-caption-color; - text-align: center; -} - -// Dark mode carousel - -.carousel-dark { - .carousel-control-prev-icon, - .carousel-control-next-icon { - filter: $carousel-dark-control-icon-filter; - } - - .carousel-indicators [data-bs-target] { - background-color: $carousel-dark-indicator-active-bg; - } - - .carousel-caption { - color: $carousel-dark-caption-color; - } -} diff --git a/tienda/static/scss/bootstrap/_close.scss b/tienda/static/scss/bootstrap/_close.scss deleted file mode 100644 index 32a0f68..0000000 --- a/tienda/static/scss/bootstrap/_close.scss +++ /dev/null @@ -1,40 +0,0 @@ -// transparent background and border properties included for button version. -// iOS requires the button element instead of an anchor tag. -// If you want the anchor version, it requires `href="#"`. -// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile - -.btn-close { - box-sizing: content-box; - width: $btn-close-width; - height: $btn-close-height; - padding: $btn-close-padding-y $btn-close-padding-x; - color: $btn-close-color; - background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements - border: 0; // for button elements - @include border-radius(); - opacity: $btn-close-opacity; - - // Override 's hover style - &:hover { - color: $btn-close-color; - text-decoration: none; - opacity: $btn-close-hover-opacity; - } - - &:focus { - outline: 0; - box-shadow: $btn-close-focus-shadow; - opacity: $btn-close-focus-opacity; - } - - &:disabled, - &.disabled { - pointer-events: none; - user-select: none; - opacity: $btn-close-disabled-opacity; - } -} - -.btn-close-white { - filter: $btn-close-white-filter; -} diff --git a/tienda/static/scss/bootstrap/_containers.scss b/tienda/static/scss/bootstrap/_containers.scss deleted file mode 100644 index f88f1e5..0000000 --- a/tienda/static/scss/bootstrap/_containers.scss +++ /dev/null @@ -1,41 +0,0 @@ -// Container widths -// -// Set the container width, and override it for fixed navbars in media queries. - -@if $enable-grid-classes { - // Single container class with breakpoint max-widths - .container, - // 100% wide container at all breakpoints - .container-fluid { - @include make-container(); - } - - // Responsive containers that are 100% wide until a breakpoint - @each $breakpoint, $container-max-width in $container-max-widths { - .container-#{$breakpoint} { - @extend .container-fluid; - } - - @include media-breakpoint-up($breakpoint, $grid-breakpoints) { - %responsive-container-#{$breakpoint} { - max-width: $container-max-width; - } - - // Extend each breakpoint which is smaller or equal to the current breakpoint - $extend-breakpoint: true; - - @each $name, $width in $grid-breakpoints { - @if ($extend-breakpoint) { - .container#{breakpoint-infix($name, $grid-breakpoints)} { - @extend %responsive-container-#{$breakpoint}; - } - - // Once the current breakpoint is reached, stop extending - @if ($breakpoint == $name) { - $extend-breakpoint: false; - } - } - } - } - } -} diff --git a/tienda/static/scss/bootstrap/_dropdown.scss b/tienda/static/scss/bootstrap/_dropdown.scss deleted file mode 100644 index adc1143..0000000 --- a/tienda/static/scss/bootstrap/_dropdown.scss +++ /dev/null @@ -1,240 +0,0 @@ -// The dropdown wrapper (`
`) -.dropup, -.dropend, -.dropdown, -.dropstart { - position: relative; -} - -.dropdown-toggle { - white-space: nowrap; - - // Generate the caret automatically - @include caret(); -} - -// The dropdown menu -.dropdown-menu { - position: absolute; - z-index: $zindex-dropdown; - display: none; // none by default, but block on "open" of the menu - min-width: $dropdown-min-width; - padding: $dropdown-padding-y $dropdown-padding-x; - margin: 0; // Override default margin of ul - @include font-size($dropdown-font-size); - color: $dropdown-color; - text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) - list-style: none; - background-color: $dropdown-bg; - background-clip: padding-box; - border: $dropdown-border-width solid $dropdown-border-color; - @include border-radius($dropdown-border-radius); - @include box-shadow($dropdown-box-shadow); - - &[data-bs-popper] { - top: 100%; - left: 0; - margin-top: $dropdown-spacer; - } -} - -// scss-docs-start responsive-breakpoints -// We deliberately hardcode the `bs-` prefix because we check -// this custom property in JS to determine Popper's positioning - -@each $breakpoint in map-keys($grid-breakpoints) { - @include media-breakpoint-up($breakpoint) { - $infix: breakpoint-infix($breakpoint, $grid-breakpoints); - - .dropdown-menu#{$infix}-start { - --bs-position: start; - - &[data-bs-popper] { - right: auto; - left: 0; - } - } - - .dropdown-menu#{$infix}-end { - --bs-position: end; - - &[data-bs-popper] { - right: 0; - left: auto; - } - } - } -} -// scss-docs-end responsive-breakpoints - -// Allow for dropdowns to go bottom up (aka, dropup-menu) -// Just add .dropup after the standard .dropdown class and you're set. -.dropup { - .dropdown-menu[data-bs-popper] { - top: auto; - bottom: 100%; - margin-top: 0; - margin-bottom: $dropdown-spacer; - } - - .dropdown-toggle { - @include caret(up); - } -} - -.dropend { - .dropdown-menu[data-bs-popper] { - top: 0; - right: auto; - left: 100%; - margin-top: 0; - margin-left: $dropdown-spacer; - } - - .dropdown-toggle { - @include caret(end); - &::after { - vertical-align: 0; - } - } -} - -.dropstart { - .dropdown-menu[data-bs-popper] { - top: 0; - right: 100%; - left: auto; - margin-top: 0; - margin-right: $dropdown-spacer; - } - - .dropdown-toggle { - @include caret(start); - &::before { - vertical-align: 0; - } - } -} - - -// Dividers (basically an `
`) within the dropdown -.dropdown-divider { - height: 0; - margin: $dropdown-divider-margin-y 0; - overflow: hidden; - border-top: 1px solid $dropdown-divider-bg; -} - -// Links, buttons, and more within the dropdown menu -// -// `